Cut to the chase

After a request, the image processing new Mosaic, can undo the previous step with undo

Item address can be pulled down and used directly

Technical requirements

JS, canvas,

Design ideas

  • Canvas Draw a picture with the same proportion and no blank space on the canvas
  • Gets the mouse position as the mouse moves over the canvas
  • Gets the pixel average of mouse positioning range size(size per Mosaic)
  • Set the RGBA of all pixels in this region to the average of what you just calculated
  • Cycle steps 2-4. Before drawing, judge whether the current drawing point is too close to the last drawing point
  • Export the url of the full size Mosaic picture
  • Undo: Save the picture data after each Mosaic
  • Recovery: Cache undo data

Code implementation

Define a function that accepts the container ID and canvasID

//html <div id="content"><canvas id="myCanvas" width="1200" height="0"></canvas></div> //js function CreateMosaic(contentId, canvasId){// All code inside this function}Copy the code

Define an initialization function that returns the required variables

// Initialize parameters
    const init = () = >{
        const content = document.getElementById(contentId)
        const canvas = document.getElementById(canvasId);
        const painting = canvas.getContext('2d');
        const size = 20 // Each time the Mosaic ranges, pixels
        let lastP = [0.0] // Draw the center point one last time
        let isPrint = false // Start drawing, true means drawable, false means not drawable
        let brushFollow = false // The brush starts
        let brush = null / / brush
        let handleData = [] // Each operation step
        let cacheDeleteData = [] // Cache undo data for recovery
        let imgWH = [] // Original width and height of the image
        return { content, canvas, painting, size, lastP, isPrint, brushFollow, brush, handleData, cacheDeleteData, imgWH }
    }
let { content, canvas, painting, size, lastP, isPrint, brushFollow, brush, handleData, cacheDeleteData, imgWH } = init()

Copy the code

The picture is scaled in equal proportion to the canvas width, and the last height of the canvas is set to be the same as the height of the picture after scaling

// Calculate the width and height of the image scale
const calculate = (cw, pw, ph) = > {
    ph = ph * ( cw / pw )
    pw = cw
    return {pw, ph}
}

const img = new Image();
    img.src = 'Picture address'
    img.crossOrigin = ' ';/ / across domains
    img.onload = () = > {
        const {pw, ph} = calculate(canvas.width, img.width, img.height)
        imgWH = [img.width, img.height]
        painting.drawImage(img, 0.0, pw, ph);
        canvas.height = ph
        handleData.push(painting.getImageData(0.0, canvas.width, canvas.height))
    }
Copy the code

Define the pixel average value of mouse positioning range size(size per Mosaic)

// Get the mean value of pixels in the range
    const getPxAVG = (imgDate) = > {
        const data = imgDate.data
        let r = 0, g = 0, b = 0, a = 0
        const sumPx = data.length / 4
        for (let i = 0; i < sumPx; i ++){
            r = r + data[i * 4]
            g = g + data[i * 4 + 1]
            b = b + data[i * 4 + 2]
            a = a + data[i * 4 + 3]}return [r / sumPx, g / sumPx, b / sumPx, a / sumPx]
    }
Copy the code

Sets the RGBA value for all pixels in an area

// Set the pixel values in the range
const setPxColor = (imgDate, color) = > {
    for (let i = 0; i < imgDate.data.length; i ++){
        imgDate.data[i] = color[i % 4]}return imgDate
}
Copy the code

Defines a function to draw a Mosaic

// Create a Mosaic
    const printMosaic = (x, y) = > {
        // Get the pixel data of mouse location range size
        const imgData = painting.getImageData(x, y, size, size)
        // Get the average value of range pixel data
        const pxAVG = getPxAVG(imgData)
        // Create a Mosaic image data for the region
        let mosaic = setPxColor(painting.createImageData(size, size), pxAVG)
        // Redraw
        painting.putImageData(mosaic, x, y)
    }
Copy the code

Define the onMousedown, onMousemove, onMouseup functions

// Determine if the range of the last Mosaic is exceeded
    const judgeD = (currentP, lastP, r) = > {
        const [x1, y1] = currentP, [x2, y2] = lastP
        return Math.sqrt(Math.pow(Math.abs(x1 - x2), 2) + Math.pow(Math.abs(y1 - y2), 2)) <= r
    }
    canvas.onmousedown = e= > {
        if(! brushFollow)return false
        isPrint = true
    }
    canvas.onmousemove = e= > {
        const { offsetX, offsetY } = e
        if(! brushFollow)return false
        brush.style.transform = `translate(${offsetX - size / 2}px, ${offsetY - size / 2}px)`
        if(! isPrint)return false
        if(! judgeD([offsetX, offsetY], lastP, size /2)){
            lastP = [offsetX, offsetY]
            printMosaic(offsetX - (size / 2), offsetY - (size / 2))
        }
    }
    canvas.onmouseup = e= > {
        isPrint = false
        handleData.push(painting.getImageData(0.0, canvas.width, canvas.height))
        cacheDeleteData = []
    }
Copy the code

Defines the brush creation and startup functions

    // Create a brush
    const createBrush = () = > {
        const brush = document.createElement('div')
        brush.style.position = 'absolute'
        brush.style.zIndex = '99'
        brush.style.width = size + 'px'
        brush.style.height = size + 'px'
        brush.style.background = 'rgba (233233233,0.5)'
        brush.style.pointerEvents = 'none'
        brush.style.left = '0'
        brush.style.right = '0'
        brush.style.transform = `translate(-${size}px, -${size}px)`
        content.appendChild(brush)
        return brush
    }
    / / start
    const start = () = > {
        !brush && (brush = createBrush())
        brushFollow = true
    }
    / / stop
    const stop = () = > {
        brush && brush.remove()
        brushFollow = false
        brush = null
    }
    canvas.onmouseenter = start
    canvas.onmouseleave = stop
Copy the code

Undo and Restore

    / / cancel
    const revocation = () = > {
        if (handleData.length <= 1) return false
        cacheDeleteData.push(handleData.pop())
        painting.putImageData(handleData[handleData.length - 1].0.0)}/ / recovery
    const recover = () = > {
        if (cacheDeleteData.length === 0) return false
        handleData.push(cacheDeleteData.pop())
        painting.putImageData(handleData[handleData.length - 1].0.0)}// Createmaic exposes the two functions
    return {
        revocation,
        recover,
    }
Copy the code

Returns the original size Mosaic picture

// Do not write the actual code
// Create a canvas with the width and height of the imgWH cache. Display: None, put into the DOM.
// Assign the result of canvas.todataURL ('image/ PNG ') to a new new image ()
// Then in the new canvas.getContext('2d'). DrawImage (0, 0,... [imgWH])
// The new canvas.toDataURL('image/ PNG ') is the result we want
Copy the code

The effect