Back

Technologies:

javascriptjavascript
avatar
Tolerim
a month ago

How can I delete the drawings without removing the image templates in my drawing program?

My drawing program allows users to choose an image, which is then drawn onto the canvas. Afterwards, they can use either a brush or a pencil to draw on top of the image. The drawings created by the brush or pencil are drawn below the image, which is the intended behavior. However, after selecting an image, I set the canvas to:

ctx.globalCompositeOperation = "destination-over"; This causes each new layer to be drawn under the previous. I want the eraser tool to erase only the brush and pencil drawings, but not the chosen image. Here is the code:

const canvas = document.getElementById("canvas1");
const ctx = canvas.getContext("2d");
let selectedImage = null;

let imageElements = document.querySelectorAll(".carousel img");
for (let i = 0; i < imageElements.length; i++) {
  imageElements[i].addEventListener("click", function() {
    selectedImage = imageElements[i];
    drawSelectedImage();
  });
}

function drawSelectedImage() {
        if (selectedImage) {
          ctx.clearRect(0, 0, canvas.width, canvas.height);
          let image = new Image();
          image.onload = function () {
            let x = (canvas.width - image.width) / 2;
            let y = (canvas.height - image.height) / 2;
            ctx.drawImage(image, x, y);
          };
          image.src = selectedImage.src;
        }
      }

const handleClick = (e) => {
    if (e.target.tagName === "IMG") { // Image is clicked
      const img = e.target;
      const imgWidth = img.clientWidth;
      const imgHeight = img.clientHeight;
      const canvasWidth = canvas.width;
      const canvasHeight = canvas.height;
      const x = (canvasWidth - imgWidth) / 4.5;
      const y = (canvasHeight - imgHeight) / 65;
      const newImgWidth = imgWidth * 2.8;
      const newImgHeight = imgHeight * 2.8;

      const ctx = canvas.getContext("2d");

      ctx.globalCompositeOperation = "destination-over";

      canvas.addEventListener('mousedown', startDrawing);
      canvas.addEventListener('mousemove', draw);
      canvas.addEventListener('mouseup', stopDrawing);

      let isDrawing = false;
      
      function startDrawing(e) {
        isDrawing = true;
        if (selectedTool === "eraser") {
          ctx.strokeStyle = "#123333";
        }
      }
      
      function draw(e) {
        if (!isDrawing) return;
        
        ctx.lineTo(e.offsetX, e.offsetY);
        ctx.stroke();
        
        lastX = e.offsetX;
        lastY = e.offsetY;
      }
      
      function stopDrawing() {
        ctx.closePath();
        isDrawing = false;
        if (e.type != "mousedown") {
          paths_array.push(ctx.getImageData(0, 0, canvas.width, canvas.height));
          paths_index += 1;
        }
      }
    }
  };
I attempted to use ctx.globalCompositeOperation to create an eraser tool that would only erase the brush and pencil drawings, but it either erased everything or caused unexpected behavior.

Answers(2)
avatar
Vogi
a month ago
Verified Answer
You can use the globalCompositeOperation property to set how new shapes are drawn onto an existing image. The default value is source-over, which means that new shapes are drawn on top of the existing canvas content. By setting it to destination-out, you can erase parts of the canvas content. However, this will erase everything on the canvas, including the image you want to keep. To erase only the brush and pencil drawings, you can create a new canvas element and draw the brush and pencil drawings on it. Then, you can use this new canvas element as a mask for the original canvas element. Here is an example code snippet that demonstrates this approach:
const brushCanvas = document.createElement('canvas');
brushCanvas.width = canvas.width;
brushCanvas.height = canvas.height;
const brushCtx = brushCanvas.getContext('2d');

// Draw brush strokes on brushCanvas
// ...

// Use brushCanvas as a mask for the original canvas
ctx.save();
ctx.globalCompositeOperation = 'destination-out';
ctx.drawImage(brushCanvas, 0, 0);
ctx.restore();
This code creates a new canvas element called brushCanvas and sets its dimensions to match the original canvas element. Then, it gets the 2D context of this new canvas element and draws the brush strokes on it. Finally, it saves the current state of the original canvas context using ctx.save(), sets the globalCompositeOperation property to destination-out, which will erase parts of the original canvas content that overlap with the brush strokes on brushCanvas, draws brushCanvas onto the original canvas using ctx.drawImage(), and restores the saved state of the original canvas context using ctx.restore(). I hope this helps! Let me know if you have any other questions.

Sources

How to Erase Pencil on Canvas | Top 10 Instructions (2023) - DIY QuicklyHow to remove pencil marks from canvas /How to erase on canvasWhat to Use for Sketching on Canvas - My Sketch Journal
avatar
Tolerim
a month ago
To make the eraser tool erase only the brush and pencil drawings, you can create a separate layer for the image and set the layer with the brush and pencil drawings to "destination-over". Then, when the eraser is used, you can set its globalCompositeOperation to "destination-out" so that it only affects the topmost layer (i.e. the brush and pencil drawings layer) and not the bottom layer with the image. Here's an updated code snippet that implements this:
const canvas = document.getElementById("canvas1");
const ctx =canvas.getContext("2d");
let selectedImage = null;

// keep track of layers
const layers = [];
let currentLayer = null;

let imageElements = document.querySelectorAll(".carousel img");
for (let i = 0; i < imageElements.length; i++) {
  imageElements[i].addEventListener("click", function() {
    selectedImage = imageElements[i];
    drawSelectedImage();
  });
}

function drawSelectedImage() {
  if (selectedImage) {
    // clear all layers and create new bottom layer with image
    layers.length = 0;
    layers.push(ctx.getImageData(0, 0, canvas.width, canvas.height));
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    let image = new Image();
    image.onload = function () {
      let x = (canvas.width - image.width) / 2;
      let y = (canvas.height - image.height) / 2;
      ctx.drawImage(image, x, y);

      // create new layer for brush/pencil drawings
      currentLayer = ctx.getImageData(0, 0, canvas.width, canvas.height);
      layers.push(currentLayer);
      // set current layer to topmost layer
      ctx.putImageData(currentLayer, 0, 0);
      // set globalCompositeOperation to "destination-over" for brush/pencil drawings layer
      ctx.globalCompositeOperation = "destination-over";
    };
    image.src = selectedImage.src;
  }
}

let selectedTool = "pencil";

const handleClick = (e) => {
  if (e.target.tagName === "IMG") {
    const img = e.target;
    const imgWidth = img.clientWidth;
    const imgHeight = img.clientHeight;
    const canvasWidth = canvas.width;
    const canvasHeight = canvas.height;
    const x = (canvasWidth - imgWidth) / 4.5; // Calculate the x-coordinate to center the image
    const y = (canvasHeight - imgHeight) / 65; // Calculate the y-coordinate to center the image
    const newImgWidth = imgWidth * 2.8; // Set the new image width to be half of the original size
    const newImgHeight = imgHeight * 2.8; // Set the new image height to be half of the original size

    // Get the canvas context
    const ctx = canvas.getContext("2d");

    // Draw the image on the canvas at the center
    //ctx.drawImage(img, x, y, newImgWidth, newImgHeight);

    // set selected tool
    selectedTool = e.shiftKey ? "eraser" : "pencil";

    // set stroke style depending on selected tool
    if (selectedTool === "eraser") {
      ctx.strokeStyle = "#123333";
      // set globalCompositeOperation to "destination-out" for eraser
      ctx.globalCompositeOperation = "destination-out";
    } else {
      ctx.strokeStyle = "#000000";
      // set globalCompositeOperation to "source-over" for pencil
      ctx.globalCompositeOperation = "source-over";
    }

    canvas.addEventListener('mousedown', startDrawing);
    canvas.addEventListener('mousemove', draw);
    canvas.addEventListener('mouseup', stopDrawing);
  }
};

let isDrawing = false;
let lastX, lastY;

function startDrawing(e) {
  isDrawing = true;
  lastX = e.offsetX;
  lastY = e.offsetY;
}

function draw(e) {
  if (!isDrawing) return;

  ctx.beginPath();
  ctx.moveTo(lastX, lastY);
  ctx.lineTo(e.offsetX, e.offsetY);
  ctx.stroke();

  lastX = e.offsetX;
  lastY = e.offsetY;
}

function stopDrawing() {
  isDrawing = false;
  // save current layer to paths array
  layers.push(ctx.getImageData(0, 0, canvas.width, canvas.height));
  // set current layer to topmost layer
  currentLayer = layers[layers.length - 1];
  ctx.putImageData(currentLayer, 0, 0);
}

canvas.addEventListener('click', handleClick);
As you can see, the code now keeps track of layers using an array. When a new image is selected, it clears all layers and creates a new bottom layer containing the image. When the eraser tool is selected, it sets the globalCompositeOperation to "destination-out" for the brush and pencil drawings layer so that it only erases those drawings. Finally, when a new stroke is drawn, it saves the current layer to the layers array and sets the current layer to the topmost layer.
;