-1

I am very nerd in this topic. But still I got some solution from this answer. I need covered area by non-transparent part of image on canvas. Its possible to draw outline for sprite image using globalCompositeOperation . Now Is it possible to get that area covered by outline for that non-transparent part of image? Is it possible to get covered area in x,y position that I can save it for further use ? OR Is there any way to restrict touch event on source-out area from globalCompositeOperation ?

Currently using code from this answer to draw outline :

var ctx = canvas.getContext('2d'),
    img = new Image;

img.onload = draw;
img.src = "https://i.stack.imgur.com/UFBxY.png";

function draw() {

  var dArr = [-1,-1, 0,-1, 1,-1, -1,0, 1,0, -1,1, 0,1, 1,1], // offset array
      s = 2,  // scale
      i = 0,  // iterator
      x = 5,  // final position
      y = 5;

  // draw images at offsets from the array scaled by s
  for(; i < dArr.length; i += 2)
    ctx.drawImage(img, x + dArr[i]*s, y + dArr[i+1]*s);

  // fill with color
  ctx.globalCompositeOperation = "source-in";
  ctx.fillStyle = "red";
  ctx.fillRect(0,0,canvas.width, canvas.height);

  // draw original image in normal mode
  ctx.globalCompositeOperation = "source-over";
  ctx.drawImage(img, x, y);
}

EDIT: Using @Kaiido solution.Its taking pixels from non-transparent + outline. I need only non transparent area.

var ctx = canvas.getContext('2d'),
img = new Image;
img.onload = draw;
img.crossOrigin = 'anonymous';
img.src = "drawing/templates/drawing-pic4.png";
var outline, origOutline,
    outlineCtx;

function draw(color) {
  ctx.clearRect(0,0,canvas.width,canvas.height);
  // onload
  if(typeof color !== 'string') color = 'white';

  var dArr = [-1,-1, 0,-1, 1,-1, -1,0, 1,0, -1,1, 0,1, 1,1], // offset array
      s = 5,  // scale
      i = 0,  // iterator
      x = 5,  // final position
      y = 5;

  // draw images at offsets from the array scaled by s
  for(; i < dArr.length; i += 2)
    ctx.drawImage(img, x + dArr[i]*s, y + dArr[i+1]*s);

  // fill with color
  ctx.globalCompositeOperation = "source-in";
  ctx.fillStyle = color;
  ctx.fillRect(0,0,canvas.width, canvas.height);

  // keep only the outline
  ctx.globalCompositeOperation = "destination-out";
  ctx.drawImage(img, x, y);
  origOutline = ctx.getImageData(0,0,canvas.width, canvas.height).data;

  // store the imageData in a new Canvas
  outline = canvas.cloneNode(true);
  outlineCtx = outline.getContext('2d')
  outlineCtx.drawImage(canvas,0,0);

  // draw image in original mode
  ctx.globalCompositeOperation = "source-over";
  ctx.drawImage(img, x, y);
}

var w= 10;
canvas.onclick = function(e){
   var rect = canvas.getBoundingClientRect();
   var x = e.clientX-rect.left,
       y = e.clientY-rect.top;

   var pixels = ((y*canvas.width)+x)*4;
   showLog("pixels: "+pixels);
   if(origOutline[pixels+3]!==0)
       {
       showLog("in out line: "+origOutline[pixels+3]);
       }
  // not transparent ?
    if(outlineCtx.getImageData(x,y,1,1).data[3]!==0){

      ctx.strokeStyle = "#0000ff";
      ctx.lineWidth = w;
      ctx.lineJoin = ctx.lineCap = 'round';
      ctx.beginPath();
      ctx.moveTo(x,y);
      ctx.lineTo(x,y);

      ctx.stroke();
      ctx.closePath();

    }
  else{
      showLog("else");
  }

  }
Community
  • 1
  • 1
Bhoomika Brahmbhatt
  • 7,404
  • 3
  • 29
  • 44
  • it seems you're looking for a flood fill algo : http://stackoverflow.com/questions/2106995/how-can-i-perform-flood-fill-with-html-canvas – Kaiido Sep 23 '15 at 04:21
  • @Kaiido I need all x,y points area which is covered by that outline. flood fill will not depart non-transparent part of image like globalCompositeOperation is doing. – Bhoomika Brahmbhatt Sep 23 '15 at 04:29
  • you mean if tehre are some transparent pixels inside the shape drawn? Then your only option is to sneak the whole image and store those transparent pixels, but of course, you would lose all advantages you got from the `source-in`.`source-out` solution. Maybe if you tell us why you need to store those... (wouldn't storing only the outline as an image be easier?) – Kaiido Sep 23 '15 at 04:33
  • @Kaiido I wanna fill up some color on that non transparent part of image. As globalCompositeOperation is giving me outline for that nontransparent part then there must be some x,y points for that outline. So I need that covered area by outline. – Bhoomika Brahmbhatt Sep 23 '15 at 04:38
  • But the solution you are using (pretty convenient btw) is that you first draw your image, with gCO making its non transparent pixels appear in some color, and then your redraw it at normal scale, so you hide the rest of the non-transparent pixels, with their normal value. Their is no calculation nor x,y anything, just two images. But, it may be possible to use some other options of gCO to keep only the outline. Then you could store only this, as an image or a array of pixels or even convert it to an array of boolean. – Kaiido Sep 23 '15 at 04:45
  • @Kaiido Is there any way to restrict touch event on `source-out` area from `globalCompositeOperation` ? – Bhoomika Brahmbhatt Sep 23 '15 at 05:09
  • no, gCO is just an information for the context on how he should deal with new painted pixels over old ones. It doesn't store anything, and there is no area. The whole canvas is `source-out`, your best bet is to check if the pixel where your touchevent occured is the same color as your outline color. – Kaiido Sep 23 '15 at 05:14
  • One method to create a "sticker effect" on a sprite is to use the Marching Squares algorithm to get the outline path of your sprite. Then draw a thick stroke on the path. Since a stroke is drawn half-outside the path, the outside part of the stroke will give you the sticker effect. Finally, redraw the original sprite back onto the canvas. – markE Sep 23 '15 at 05:23
  • @markE already checked "sticker effect", but it does nt help me. – Bhoomika Brahmbhatt Sep 23 '15 at 07:02
  • @Kaiido checked current pixel color of image & then I have prevent it,its working on some part of image but on the border of image it will draw line with width of 10 so almost half of that cap of line will be drawn on outside of that non transparent image.so next time it will get color on that transparent part & drawing on that part continuously. so its not working for border. – Bhoomika Brahmbhatt Sep 23 '15 at 07:05
  • @geet, I'm not sure I got your last comment, so I posted an answer of what I thought. – Kaiido Sep 23 '15 at 15:55

1 Answers1

1

Here is one way :

Use Ken's method to draw the shape, but for the last drawing, instead of setting the globalCompositeOperation property to 'source-out', set it to 'destination-out'. This way you will have only the outline :

var ctx = canvas.getContext('2d'),
    img = new Image;

img.onload = draw;
img.src = "https://dl.dropboxusercontent.com/s/1alt1303g9zpemd/UFBxY.png";

function draw() {

  var dArr = [-1,-1, 0,-1, 1,-1, -1,0, 1,0, -1,1, 0,1, 1,1], // offset array
      s = 2,  // scale
      i = 0,  // iterator
      x = 5,  // final position
      y = 5;
  
  // draw images at offsets from the array scaled by s
  for(; i < dArr.length; i += 2)
    ctx.drawImage(img, x + dArr[i]*s, y + dArr[i+1]*s);
  
  // fill with color
  ctx.globalCompositeOperation = "source-in";
  ctx.fillStyle = "red";
  ctx.fillRect(0,0,canvas.width, canvas.height);
  
  // draw original image in dest-out mode to keep only the outline
  ctx.globalCompositeOperation = "destination-out";
  ctx.drawImage(img, x, y);
}
<canvas id=canvas width=500 height=500></canvas>

Now, you can store this outline in a new canvas, and every time you click on your canvas, compare the click event's position to the pixel at same position in your stored canvas :

var ctx = canvas.getContext('2d'),
    img = new Image;
img.onload = draw;
img.crossOrigin = 'anonymous';
img.src = "https://dl.dropboxusercontent.com/s/1alt1303g9zpemd/UFBxY.png";
var outline, 
    outlineCtx;

function draw(color) {
  ctx.clearRect(0,0,canvas.width,canvas.height);
  // onload
  if(typeof color !== 'string') color = 'red';

  var dArr = [-1,-1, 0,-1, 1,-1, -1,0, 1,0, -1,1, 0,1, 1,1], // offset array
      s = 5,  // scale
      i = 0,  // iterator
      x = 5,  // final position
      y = 5;
  
  // draw images at offsets from the array scaled by s
  for(; i < dArr.length; i += 2)
    ctx.drawImage(img, x + dArr[i]*s, y + dArr[i+1]*s);
  
  // fill with color
  ctx.globalCompositeOperation = "source-in";
  ctx.fillStyle = color;
  ctx.fillRect(0,0,canvas.width, canvas.height);
  
  // keep only the outline
  ctx.globalCompositeOperation = "destination-out";
  ctx.drawImage(img, x, y);

  // store the imageData in a new Canvas
  outline = canvas.cloneNode(true);
  outlineCtx = outline.getContext('2d')
  outlineCtx.drawImage(canvas,0,0);

  // draw image in original mode
  ctx.globalCompositeOperation = "source-over";
  ctx.drawImage(img, x, y);
}

canvas.onclick = function(e){
   var rect = canvas.getBoundingClientRect();
   var x = e.clientX-rect.left,
       y = e.clientY-rect.top;
  // not transparent ?
  if(outlineCtx.getImageData(x,y,1,1).data[3]===255){
    draw('green');
    }
  else
    draw('red');
  }
<canvas id=canvas width=500 height=500></canvas>

If your outline is unlikely to change very often, it may be interesting to store the imageData instead of calling getImageData every click.

// in the draw function
ctx.globalCompositeOperation = "destination-out";
ctx.drawImage(img, x, y);
outline = ctx.getImageData(0,0,canvas.width, canvas.height).data;

// in the click event
var pixels = ((y*canvas.width)+x)*4;
if(outline[pixels+3]===255)
    // in the outline
else
    // out
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • Thanks for your concern. My goal is to fill the color on pixel of Image when user touch on that image, its simple! But the tricky part is that color should be drawn on only non transparent part of the image.I tried above code. Its filling on outline,too. How can I avoid it. Please check my edited question where m using your code. – Bhoomika Brahmbhatt Sep 24 '15 at 03:19
  • I still don't get it, you only want the non-transparent part of the original image? Then simply apply the same method, but instead of drawing the outline on an outer canvas, draw only the original image on it and perform the same check. – Kaiido Sep 24 '15 at 03:26
  • In my edited code, its always showing log as its `in the outline` in first if condition of [if(outline[pixels+3]!==0)]. So how can I detect that position is on outline or on out side or in side ? – Bhoomika Brahmbhatt Sep 24 '15 at 03:27
  • the checking I'm doing is to know if you are inside the outline or outside it (be it in the inner or the outer of the shape it draws). If you want to get both inner and inside outline, use only one canvas, and check if your touched point is transparent. If you want only the inner, but not the outline, then draw only the original image onto the hidden canvas – Kaiido Sep 24 '15 at 03:31
  • Ps : the imageData solution was an alternative to the canvas one : either you use one or the other but not both... – Kaiido Sep 24 '15 at 03:34
  • Thank you for your quick reply. I am using ` if(ctx.getImageData(x,y,1,1).data[3]!==0){` now. But still its same. user can fill color on outline,too. If user touch on very near by outline then it will draw circle of 10 width on that pixel in that scenario half of that circle will be drawn to the out side of non transparent part of image. – Bhoomika Brahmbhatt Sep 24 '15 at 03:35
  • check image http://prek-8.com/preschool/images/shapebook1s.png . I want to fill the color on only rabbit body. I have tried to minus offset from x & y. still its the same. – Bhoomika Brahmbhatt Sep 24 '15 at 03:41
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/90487/discussion-between-geet-and-kaiido). – Bhoomika Brahmbhatt Sep 24 '15 at 03:42
  • This is a good example. but I want to fill color on png image, not at the outline. Is it possible? – Amit Kumar Aug 16 '18 at 10:26