I wrote an article before recorded the history of optimization of time to upload pictures, a history of vant image upload component optimization Or the last project, now has a new demand, is to embed the watermark to upload pictures, watermarking image is the content of the descendants of time + + system Logo + name of the system This requirement is difficult to handle, Adding watermark has always been back-end processing before, now I hope to try front-end processing, and now the project uses the OSS direct upload method, in order to reduce the pressure of the server, the upload operation is not through their own server transfer, since there is no way to be transferred to back-end processing, so let’s do it. It’s exciting to think about trying something you’ve never done before

Let’s start with the first version, which uses canvas to fill text

/** * Add watermark *@param {file} Uploaded image file */
async function addWaterMarker(file) {
  // Convert the file to an IMG tag
  let img = await blobToImg(file)
  return new Promise((resolve, reject) = > {
    // Create canvas canvas
    let canvas = document.createElement('canvas')
    canvas.width = img.width
    canvas.height = img.height
    let ctx = canvas.getContext('2d')
    ctx.drawImage(img, 0.0)

    // Set the fill size, font and style. Here, set the font size to scale in proportion to the width of the canvas to prevent the problem of small watermark generated by large pictures
    ctx.font = `${canvas.width * 0.05}Px tahoma `
    ctx.fillStyle = "red"
    // Set the right alignment
    ctx.textAlign = 'right'
    // Draw text at the specified position
    ctx.fillText('I'm Watermark 1', canvas.width - 100, canvas.height - 100)
    ctx.fillText('I am Watermark 2', canvas.width - 100, canvas.height - 50)

    // Return canvas as blob file
    canvas.toBlob(blob= > resolve(blob))
  })
}

/** * blob to img tag */
function blobToImg(blob) {
  return new Promise((resolve, reject) = > {
    let reader = new FileReader()
    reader.addEventListener('load'.() = > {
      let img = new Image()
      img.src = reader.result
      img.addEventListener('load'.() = > resolve(img))
    })
    reader.readAsDataURL(blob)
  })
}
Copy the code

The watermark is indeed added, if the watermark is just a simple layout of text, this can also be used, but after thinking about it always feel not elegant, add watermark content is more complex, and pictures, rely on this way to achieve it is still enough

After much effort, a second version was produced, in which the watermark content was converted from HTML to images, which were then combined into uploaded images

/** * Add watermark *@param {file} Uploaded picture file *@param {el} Watermark content HTML */
async function addWaterMarker(file, el = '#markImg') {
  // Convert the file to an IMG tag
  let img = await blobToImg(file)
  return new Promise(async (resolve, reject) => {
    try {
      // Create canvas canvas
      let canvas = document.createElement('canvas')
      canvas.width = img.width
      canvas.height = img.height
      let ctx = canvas.getContext('2d')
      ctx.drawImage(img, 0.0)

      // Create a watermark image canvas
      let markCanvas = document.createElement('canvas')
      // Create a watermark image canvas
      const markImg = await createMarkImg(document.querySelector(el))
      // Make the watermark scale equally according to the image size, default width is set to 1000
      let zoom = canvas.width / 1000
      let markCtx = markCanvas.getContext('2d')
      // Scale the watermark image canvas
      markCtx.scale(zoom, zoom)

      // Set the width and height of the watermark canvas to the width and height of the scaled watermark canvas
      markCanvas.width = markImg.width
      markCanvas.height = markImg.height
      // Fill the watermark canvas with the watermark canvas
      markCtx.drawImage(markImg, 0.0)

      // Fill the watermark canvas canvas to canvas canvas
      ctx.drawImage(markCanvas, canvas.width - markCanvas.width, canvas.height - markCanvas.height, markCanvas.width, markCanvas.height)
      canvas.toBlob(blob= > resolve(blob))
    } catch (error) {
      reject(error)
    }
  })
}

/** * blob to img tag */
function blobToImg(blob) {
  return new Promise((resolve, reject) = > {
    let reader = new FileReader()
    reader.addEventListener('load'.() = > {
      let img = new Image()
      img.src = reader.result
      img.addEventListener('load'.() = > resolve(img))
    })
    reader.readAsDataURL(blob)
  })
}

/** * To create a watermark canvas, install html2Canvas.js plugin */
function createMarkImg(el) {
  return new Promise(async (resolve, reject) => {
    try {
      const markImg = await html2canvas(el, {
        allowTaint: false.// Allow pollution
        useCORS: true.backgroundColor: null//'transparent' // background color
      })
      resolve(markImg)
    } catch (error) {
      reject(error)
    }
  })
}
Copy the code

Although the writing is a little complicated, but it is very easy to use, the watermark content using HTML + CSS first draw, and then directly synthesized to the designated position of the picture, save a lot of worry, but people sometimes love to torture, the first day to finish the code the next day is very awkward, or feel not elegant, self-summary at least the following two problems

  1. The watermark part uses two canvas layers to achieve scaling, which has certain performance consumption and is inappropriate
  2. The volume of the uploaded image is two or three times larger after watermarking, which is under the premise of compression before uploading the image, which will undoubtedly greatly increase the speed of uploading the image

Again, the tentative final version is finally out, and the code is as follows

/** * add watermark */
export async function addWaterMarker(file, el = '#markImg') {
  // Convert file blobs to images
  let img = await blobToImg(file)
  return new Promise(async (resolve, reject) => {
    try {
      // Create canvas canvas
      let canvas = document.createElement('canvas')
      // Adjust the canvas width and height proportionally to reduce the image size
      let imgRatio = img.naturalWidth / img.naturalHeight // Image scale
      canvas.width = 750  // The default setting is 750
      canvas.height = canvas.width / imgRatio

      let ctx = canvas.getContext('2d')

      // Fill in the uploaded image
      ctx.drawImage(img, 0.0, canvas.width, canvas.height)

      // Generate a watermark image
      const markWidth = document.querySelector(el).clientWidth
      let zoom = canvas.width * 0.3 / markWidth
      let markEle = document.querySelector(el)
      // Scale the watermarked HTML before converting it to an image
      markEle.style.transform = `scale(${zoom}) `
      const markImg = await htmlToCanvas(markEle)

      // Fill the watermark
      ctx.drawImage(markImg, canvas.width - markImg.width - 15 * zoom, canvas.height - markImg.height - 15 * zoom, markImg.width, markImg.height)

      // Convert canvas to blob
      canvas.toBlob(blob= > resolve(blob))
    } catch (error) {
      reject(error)
    }

  })
}

/** * blob to img tag */
function blobToImg(blob) {
  return new Promise((resolve, reject) = > {
    let reader = new FileReader()
    reader.addEventListener('load'.() = > {
      let img = new Image()
      img.src = reader.result
      img.addEventListener('load'.() = > resolve(img))
    })
    reader.readAsDataURL(blob)
  })
}

/** * To convert HTML to canvas, html2Canvas.js plug-in */ is required
export function htmlToCanvas(el, backgroundColor = 'rgba(0,0,0,.1)') {
  return new Promise(async (resolve, reject) => {
    try {
      const markImg = await html2canvas(el, {
        allowTaint: false.// Allow pollution
        useCORS: true,
        backgroundColor //'transparent' // background color
      })
      resolve(markImg)
    } catch (error) {
      reject(error)
    }
  })
}
Copy the code

So far, the above two problems can be solved perfectly. If the image volume is still too large, you can adjust the width of canvas appropriately. Now let’s look at the effect of the image with watermark