How to do graffiti/smear on a picture, and support exporting related trajectory maps (includes online demonstration address)

How to make graffiti on a picture (feature continues to be updated)

  • 1. Supported functions
  • 2. Implementation ideas
    • Q1: How to implement graffiti function on pictures?
    • Q2: How to change the brush color and width function?
    • Q3: How to implement the withdrawal function?
    • Q4: How to implement the function of clearing the canvas?
    • Q5: How to convert the user’s graffiti into a picture with white tracks on a black background without changing the content on the current canvas?
  • 3. Complete code
  • 4. Online demonstration

1. Supported functions

  1. Doodle on pictures
  2. Supports recalling the latest graffiti operation
  3. Support clearing canvas
  4. Supports converting user graffiti into a white track with a black background

2. Implementation ideas

Q1: How to implement graffiti function on pictures?

You can overlay a element on top of the element. When the source of the image is determined, set the width and height of to be consistent with the image. Then use absolute positioning to overlap on .
Then, doodle by listening for mouse events on the element. When the mousedown event is triggered, set a flag to start drawing; then, trigger the mousemove event to draw lines according to the movement of the mouse; and trigger mouseup event, stop drawing.
At this time, we draw content on the canvas, and the user visually draws the content on the picture.

Q2: How to change the brush color and width?

Create a constant named ctx to represent the 2D drawing context of the Canvas (obtained through the getContext('2d') method). This context object allows us to draw on the Canvas.
The color and width of the brush are controlled by ctx.strokeStyle and ctx.lineWidth respectively.

Q3: How to implement the withdrawal function?

You need to create an array to store the history of drawing.
For example, we create a new drawingHistory array to store the state of Canvas. Whenever the mouse is raised, the current Canvas data URL is stored in the array.
When the user clicks the “Undo” button, we remove the last step from the history array, then clear the Canvas and fetch the most recent record in the history. Draw.

Q4: How to implement the function of clearing the canvas?

You need to create an array to store the history of drawing.
For example, we create a new drawingHistory array to store the state of Canvas. Whenever the mouse is raised, the current Canvas data URL is stored in the array.
When the user clicks the “Clear Canvas” button, we clear the history array and then clear the Canvas .

Q5: How to convert the user’s graffiti into a picture with white tracks on a black background without changing the content on the current canvas?

Create a new canvas element, setting the new canvas’s width and height to be the same as the original canvas. Draw a black background on the new canvas, and draw the graphics on the original canvas onto the new canvas.

3. Complete code

<template>
  <p>You can do whatever you want in the picture below:</p>
  <div class="main">
    <div class="image" @contextmenu.prevent>
      <img ref="$imgRef" :src="src" />
      <canvas
        ref="$canvasRef"
        @mousedown="startDrawing"
        @mousemove="draw"
        @mouseup="stopDrawing"
      />
    </div>
  </div>
  <div class="actions">
    <el-button @click="undoLastDraw">Withdraw</el-button>
    <el-button @click="clearAll">Clear canvas</el-button>
    <el-button @click="extraction">Extract the content of graffiti</el-button>
  </div>
  <div v-if="resultSrc" class="result">
    <p>Extraction results:</p>
    <img :src="resultSrc" />
  </div>
</template>

<script setup lang="ts">
import {<!-- --> nextTick, onMounted, ref } from 'vue'

/**
 * URL of the image
 */
const src = ref('https://images.pexels.com/photos/2187304/pexels-photo-2187304.jpeg?auto=compress & amp;cs=tinysrgb & amp;w=1260 & amp;h=750 & amp;dpr=1')

/**
 * URL to extract the result
 */
const resultSrc = ref('')

/**
 * Examples of pictures
 */
const $imgRef = ref()

/**
 *canvas instance
 */
const $canvasRef = ref()

/**
 * Whether it is being drawn
 */
const drawing = ref(false)

/**
 * Drawn history
 */
const drawingHistory = ref([])

/**
 * Set the width and height of canvas
 */
async function setCanvas () {<!-- -->
  await nextTick()
  if (!$canvasRef.value) {<!-- -->
    return
  }

  //Set the width and height of the Canvas to be consistent with the image
  $canvasRef.value.width = $imgRef.value.width;
  $canvasRef.value.height = $imgRef.value.height;
}

/**
 * Start drawing
 */
function startDrawing (event) {<!-- -->
  console.log('startDrawing')
  drawing.value = true
  
  const ctx = $canvasRef.value.getContext('2d');
  ctx.beginPath(); // Clear the subpath list and start a new path

  const {<!-- --> offsetX, offsetY } = event;
  ctx.moveTo(offsetX, offsetY); // Move the starting point of a new subpath to the (x, y) coordinates
}

/**
 * Draw lines based on mouse movement
 */
function draw (event) {<!-- -->
  if (!drawing.value) return;
  
  const ctx = $canvasRef.value.getContext('2d');
  
  ctx.lineWidth = 5 // define stroke width
  ctx.strokeStyle = 'white' // define stroke color
  
  const {<!-- --> offsetX, offsetY } = event;
  ctx.lineTo(offsetX, offsetY); // Method of using a straight line to connect the end point of the subpath to the x, y coordinates (not actually drawn)
  ctx.stroke(); // Method to draw the current or existing path according to the current line drawing style
}

/**
 * Stop drawing
 */
function stopDrawing() {<!-- -->
  drawing.value = false

  const ctx = $canvasRef.value.getContext('2d');
  
  ctx.closePath(); // Method to return the pen point to the starting point of the current subpath
  drawingHistory.value.push($canvasRef.value.toDataURL()); // Store the current Canvas state
  // The toDataURL method returns a data URI containing the image display
  console.log('$canvasRef.value.toDataURL()', $canvasRef.value.toDataURL())
}

/**
 * canvas to image
 */
function canvasToImage() {<!-- -->
  //Create a new Canvas element
  const newCanvas = document.createElement('canvas');
  const newContext = newCanvas.getContext('2d');

  //Set the width and height of the new Canvas to be the same as the original Canvas
  newCanvas.width = $canvasRef.value.width;
  newCanvas.height = $canvasRef.value.height;

  //Draw a black background on the new Canvas
  newContext.fillStyle = 'black'; // Set the background color to black
  newContext.fillRect(0, 0, newCanvas.width, newCanvas.height); // The fillRect method draws fill directly on the canvas

  // Draw the graphics on the original Canvas to the new Canvas
  newContext.drawImage($canvasRef.value, 0, 0);
  // The drawImage method provides multiple ways to draw images on the canvas. The first parameter allows any canvas image source.

  // Convert the content on the Canvas to DataURL
  const dataURL = newCanvas.toDataURL('image/png'); // Specify the image format to be generated
  return dataURL;
}

/**
 * Undo the last graffiti operation
 */
function undoLastDraw() {<!-- -->
  if (drawingHistory.value.length > 0) {<!-- -->
    drawingHistory.value.pop(); // Remove last step
    clearCanvas()
    redrawHistory()
  }
}

/**
 * Clear the entire Canvas
 */
function clearCanvas() {<!-- -->
  const ctx = $canvasRef.value.getContext('2d');
  ctx.clearRect(0, 0, $canvasRef.value.width, $canvasRef.value.height);
  // The clearRect method achieves the purpose of erasing a rectangular area by setting pixels to transparent
}

/**
 * Redraw canvas
 */
function redrawHistory() {<!-- -->
  const ctx = $canvasRef.value.getContext('2d');
  const dataURL = drawingHistory.value[drawingHistory.value.length - 1] // Just take the last step and redraw it
  const img = new Image();
  img.src = dataURL;
  img.onload = () => {<!-- -->
    ctx.drawImage(img, 0, 0);
  };
}

/**
 * Clear all drawing content
 */
function clearAll () {<!-- -->
  drawingHistory.value = []
  clearCanvas()
}

/**
 * Confirm withdrawal
 */
function extraction() {<!-- -->
  resultSrc.value = canvasToImage()
}

onMounted(() => {<!-- -->
  setCanvas()
})

</script>

<style>
.main {<!-- -->
  width: 100%;
  height: 300px;
}
.actions {<!-- -->
  margin-top: 12px;
}
.image {<!-- -->
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  background-color: rgba(0, 0, 0, 0.015);
  border-radius: 8px;
}
.image img {<!-- -->
  max-width: 100%;
  max-height: 100%;
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  margin: 0 auto;
  object-fit: contain;
  border-radius: 8px;
  user-select: none;
}

.image canvas {<!-- -->
  position: absolute;
  top: 0;
}

</style>

4. Online demonstration

Copy the above code to Element Plus Playground for debugging.