0

I'm trying to have a user manually color in certain parts of an image. As an example, here's a cat https://techflourish.com/images/3-cat-clipart-9.png. The user should be able to color in the foot of the cat if they choose to. I want them to only color inside the cat body image portion of the canvas (not the background portion of the image or whitespace of canvas, but I guess I could just manually trim the image).

What I've attempted so far is below. Basically I check the color of the pixel at my position and draw only if it isn't that background color. This sort of works, but I'm able to bleed out really easily because something is off. I was wondering if it was possibly to set a specific clip area, but wasn't able to figure it out.

`

    var canvas = document.getElementById("space");
    var ctx = canvas.getContext("2d");

    var pos = { x: 0, y: 0 };

    // new position from mouse events
    function setPosition(e) {
        pos.x = e.clientX;
        pos.y = e.clientY;
    }

    function rgbToHex(r, g, b) {
        if (r > 255 || g > 255 || b > 255)
            throw "Invalid color component";
        return ((r << 16) | (g << 8) | b).toString(16);
    }

    function draw(e) {
        if (e.buttons !== 1) return; // if mouse is pressed.....

        var color = "#cb3594";

        ctx.beginPath(); // begin the drawing path

        ctx.lineWidth = 5; // width of line
        ctx.lineCap = "round"; // rounded end cap
        ctx.strokeStyle = color; // hex color of line

        var p = ctx.getImageData(pos.x, pos.y, 1, 1).data;
        var sourceColor = rgbToHex(p[0], p[1], p[2]);
        if(sourceColor != "BACKGROUNDHEX" && sourceColor != color) {
            ctx.moveTo(pos.x, pos.y); // from position
            setPosition(e);
            p = ctx.getImageData(pos.x, pos.y, 1, 1).data;
            targetColor = rgbToHex(p[0], p[1], p[2]);

            if(targetColor != "BACKGROUNDHEX" && targetColor != color) {
                ctx.lineTo(pos.x, pos.y); // to position
                ctx.stroke(); // draw it!
            }
        }

    }

    var outlineImage = new Image();
    outlineImage.onload = function() {
        ctx.drawImage(outlineImage, 0, 0, 704, 720);
    }
    outlineImage.src = "IMAGE.png";

    space.addEventListener("mousemove", draw);
    space.addEventListener("mousedown", setPosition);
    space.addEventListener("mouseenter", setPosition);


</script>

`

(related edit: the bleeding is caused by my "sourceColor != color" being wrong, but the question is still relevant as this still doesn't feel like a great solution)

2perdo
  • 13
  • 4
  • Well, one way to approach this would be to use image-maps. Here's something I played with to highlight `` elements within a `` element. You could simply prevent any marking operations if the cursor is not inside the currently allowed polygon. https://stackoverflow.com/questions/12661124/how-to-apply-hovering-on-html-area-tag?noredirect=1&lq=1 – enhzflep Sep 25 '18 at 20:54
  • interesting, will take a look – 2perdo Sep 25 '18 at 21:25

1 Answers1

2

Since the parts of the image you don't want to color are transparent, you can set the context's globalCompositeOperation to 'source-atop'. After that, any pixels you draw to the canvas will automatically take on the overwritten pixels' opacity, and you don't have to mess with getImageData:

var canvas = document.getElementById("space");
var ctx = canvas.getContext("2d");

var pos = {
  x: 0,
  y: 0
};

// new position from mouse events
function setPosition(e) {
  // offsetX/Y gives the correct coordinates within the canvas
  // assuming it has no padding
  pos.x = e.offsetX;
  pos.y = e.offsetY;
}

function draw(e) {
  if (e.buttons !== 1) return; // if mouse is pressed.....

  var color = "#cb3594";

  ctx.beginPath(); // begin the drawing path

  ctx.lineWidth = 5; // width of line
  ctx.lineCap = "round"; // rounded end cap
  ctx.strokeStyle = color; // hex color of line

  ctx.moveTo(pos.x, pos.y); // from position
  setPosition(e);
  ctx.lineTo(pos.x, pos.y); // to position
  ctx.stroke(); // draw it!
}

var outlineImage = new Image();
outlineImage.onload = function() {
  // the default, set explicitly because we're changing it elsewhere
  ctx.globalCompositeOperation = 'source-over';
  
  ctx.drawImage(outlineImage, 0, 0);

  // don't draw over the transparent parts of the canvas
  ctx.globalCompositeOperation = 'source-atop';
  
  // wait until the stencil is loaded before handing out crayons
  space.addEventListener("mousemove", draw);
  space.addEventListener("mousedown", setPosition);
  space.addEventListener("mouseenter", setPosition);
}
outlineImage.src = "https://i.stack.imgur.com/so095.png";
<canvas id="space" width="610" height="733"></canvas>
AuxTaco
  • 4,883
  • 1
  • 12
  • 27