Rodrigo
Rodrigo

Reputation: 5129

html5 how to stroke with the inverted background color?

I want to do a selector tool in HTML5. As the user click and moves the mouse, a rectangle should be drawn from the starting point to the mouse pointer. This is usually done choosing a logical operator for the color of the stroke (so the lines are drawn as the inverse of the background), but it looks like I can only do this in HTML5 manipulating the image pixel by pixel. Wouldn't it be too slow? Is there a direct way to do that?

Upvotes: 0

Views: 1107

Answers (2)

user1693593
user1693593

Reputation:

You can do this two ways:

  1. Inverse region in-place and reset by re-drawing image
  2. Keep an inverted version of image in a second canvas and draw clipped version

Option 1

For option 1 you would need to iterate the area each time using JavaScript which will be a relative slow operation compared to option 2.

Example of option 1:

// inside the mouse move code (please see live demo below)
ctx.drawImage(img, 0, 0);                        // update image (clear prev inv)

//invert the region
if (w !== 0 && h !== 0) {
    var idata = ctx.getImageData(x1, y1, w, h),  // get buffer
        buffer = idata.data,
        len = buffer.length,
        i = 0;

    for(; i < len; i += 4) {                     // inverse it
        buffer[i] = 255 - buffer[i];
        buffer[i+1] = 255 - buffer[i+1];
        buffer[i+2] = 255 - buffer[i+2];
    }
    ctx.putImageData(idata, x1, y1);
}    

As you can see the performance is not bad at all using just JavaScript and the CPU. The performance threshold is dependent on size of region though.

Live demo of option 1

Option 2

In option 2 you pre-invert the image in the second canvas and draw a clipped version of it to main canvas. This means from that point on you will get the benefit from using the GPU (if available) and performance will be much better.

Example of option 2:

// have a second canvas setup
var ocanvas = document.createElement('canvas'),
    octx = ocanvas.getContext('2d'),

//invert the off-screen canvas
var idata = octx.getImageData(0, 0, ocanvas.width, ocanvas.height),
    buffer = idata.data,
    len = buffer.length,
    i = 0;
for(; i < len; i += 4) {
    buffer[i] = 255 - buffer[i];
    buffer[i+1] = 255 - buffer[i+1];
    buffer[i+2] = 255 - buffer[i+2];
}
octx.putImageData(idata, 0, 0);

Now it's a matter of getting the region of that area and update with a clipped version:

if (w !== 0 && h !== 0)
    ctx.drawImage(ocanvas, x1, y1, w, h,  x1, y1, w, h);

Live demo of option 2

Upvotes: 1

markE
markE

Reputation: 105015

You can optimize your "inverted stroke" to be fast enough for "live" strokes.

  1. Layer 2 canvases, one on top of another.

  2. Draw the inverted image on the bottom canvas.

  3. Draw the normal image on the top canvas.

  4. Draw your strokes on the top canvas with context.globalCompositeOperation="destination-out".

"destination-out" compositing causes the top canvas pixels to be made transparent where the stroking occurs.

This "reveals" the inverted image below only where the strokes are.

This reveal is very fast since the GPU assists in compositing.

Upvotes: 1

Related Questions