背景
在很多地方,我们都可以看到,上传图片的时候,图片都会被加上默认的水印,水印的作用主要体现在以下几个方面:
- 1.版权保护:在商业用途的照片中添加水印可以帮助保护作者的版权,防止他人未经授权使用照片。
- 2.品牌推广:将商业品牌、商标或公司标志添加到照片中,可以帮助提高品牌知名度和曝光率。
- 3.防止盗版:添加水印可以防止盗版和未经授权的使用,因为水印会明显表明该照片的版权归原作者所有。
- 4.标识来源:在社交媒体平台上分享照片时,添加水印可以帮助其他用户识别出照片的来源和作者。
- 5.识别真伪:对于一些重要的照片或证件,如证书或合同等,加上水印可以帮助识别真伪,防止伪造和篡改。
因此,我们在个人网站进行图片操作时,也可以给它加上自己独特的水印,那么作为一名前端开发,我们该如何实现给图片加上水印呢?
实现
对图片进行处理,我们的首选当然是canvas啦,使用canvas我们可以便捷地对图片进行操作,我们需要操作的图片主要分为以下两种:
- 1、本地上传的图片
- 2、线上链接图片
file 转 base64
对于本地上传的图片,我们需要先将其转换成 base64 再进行后续处理:
我们可以通过FileReader来获取图片的 base64,FileReader 是一种异步读取文件机制。
FileReader 提供了如下方法:
- readAsArrayBuffer(file):按字节读取文件内容,结果用 ArrayBuffer 对象表示
- readAsBinaryString(file):按字节读取文件内容,结果为文件的二进制串
- readAsDataURL(file):读取文件内容,结果用 data:url 的字符串形式表示
- readAsText(file,encoding):按字符读取文件内容,结果用字符串形式表示
- abort():终止文件读取操作
readAsDataURL 方法会读取指定的 Blob 或 File 对象。并生成 data URl(base64 编码)。这里我们可以使用readAsDataURL来获取上传图片的 base64 编码:
function fileToBase64Async(file) { return new Promise((resolve, reject) => { let reader = new FileReader(); reader.readAsDataURL(file); reader.onload = (e) => { resolve(e.target.result); }; }); }
使用 canvas 给图片加水印
使用在线图片链接的时候需要注意给图片设置crossOrigin
属性(img.setAttribute("crossOrigin",'Anonymous')
),不然会出现以下错误:
Uncaught DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
大概意思是 canvas 无法执行 toDataURL 方法:污染的画布无法输出。受限于 CORS 策略,会存在跨域问题,虽然可以使用图像(比如 append 到页面上)但是绘制到画布上会污染画布,一旦一个画布被污染,就无法提取画布的数据,比如无法使用使用画布 toBlob(),toDataURL(),或 getImageData()方法;当使用这些方法的时候 会抛出一个安全错误。
我们这里可以分为文字水印和图片水印两种:
文字水印
添加文字水印的大致步骤如下:
- 1、生成一个新的 canvas 画布;
const canvas = document.createElement("canvas"); canvas.width = img.width; canvas.height = img.height;
- 2、将现有需要添加水印的图片绘制到画布上;
const ctx = canvas.getContext("2d"); ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
- 3、绘制需要添加的水印文本并设置样式。
我们可以使用fillText
和strokeText
这两个方法来绘制文字,fillText
绘制的是默认的普通实线文本,strokeText
绘制的是描边文本,这里我使用了strokeText
来进行水印文本绘制。
完整代码如下:
const remFontSize = canvas.width / 35; ctx.font = "bolder " + remFontSize + "px Verdana"; ctx.textAlign = "center"; ctx.strokeStyle = "#fff"; const name = "@JYeontu"; const spaceH = remFontSize * 0.3; ctx.strokeText(name, canvas.width / 2, canvas.height - remFontSize - spaceH); • 1 • 2 • 3 • 4 • 5 • 6 • 7
function fillTextToImg(base64) { const img = new Image(); img.src = base64; img.setAttribute("crossOrigin", "Anonymous"); return new Promise((resolve, reject) => { img.onload = () => { const canvas = document.createElement("canvas"); canvas.width = img.width; canvas.height = img.height; const ctx = canvas.getContext("2d"); ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0, canvas.width, canvas.height); const remFontSize = canvas.width / 35; ctx.font = "bolder " + remFontSize + "px Verdana"; ctx.textAlign = "center"; /** ctx.textAlign = "center|end|left|right|start"; start:默认,文本在指定的位置开始。 end:文本在指定的位置结束。 center:文本的中心在指定的位置。 left:文本左对齐。 right:文本右对齐。 **/ ctx.strokeStyle = "#fff"; const name = "@JYeontu"; const spaceH = remFontSize * 0.3; ctx.strokeText( name, canvas.width / 2, canvas.height - remFontSize - spaceH ); resolve(canvas.toDataURL("image/jpeg")); }; });
效果如下图,左边为原图,右边为加了文字水印的图片:
图片水印
//图片转为base64 async function getImgBase64(base64, width = 50) { const img = new Image(); img.src = base64; img.setAttribute("crossOrigin", "Anonymous"); return new Promise((resolve, reject) => { img.onload = () => { const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); canvas.width = width; canvas.height = (img.height * width) / img.width; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0, canvas.width, canvas.height); resolve(canvas.toDataURL("image/jpeg")); }; }); } function fillImgToImg(base64, waterMark = imgLink) { waterMark = "https://img2.baidu.com/it/u=2243573419,589412055&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1684861200&t=7bf0a17ca21ae8ec8aa77b0f98cb4c7e"; const img = new Image(); img.src = base64; img.setAttribute("crossOrigin", "Anonymous"); return new Promise((resolve, reject) => { img.onload = async () => { const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); canvas.width = img.width; canvas.height = img.height; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0, canvas.width, canvas.height); const waterMarkSrc = await getImgBase64(waterMark, 100); const waterMarkImg = new Image(); waterMarkImg.src = waterMarkSrc; waterMarkImg.setAttribute("crossOrigin", "Anonymous"); waterMarkImg.onload = () => { ctx.drawImage( waterMarkImg, canvas.width / 2 - waterMarkImg.width / 2, canvas.height - waterMarkImg.height - 10, waterMarkImg.width, waterMarkImg.height ); resolve(canvas.toDataURL("image/jpeg")); }; }; }); }
效果如下图,左边为原图,右边为加了图片水印的图片:
完整代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <style> img { width: 500px; } </style> </head> <body> <input type="file" id="fileUplodBox" /> <img alt="原图" id="originPic" /> <img alt="水印图" id="waterMark" /> </body> <script> const imgLink = "https://img2.baidu.com/it/u=2048195462,703560066&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1684861200&t=a0c977f68632303e7dac2196e8ad2866"; document.getElementById("originPic").setAttribute("src", imgLink); const fileUplodBox = document.getElementById("fileUplodBox"); fileUplodBox.addEventListener("change", (e) => { const file = e.target.files[0]; dealFile(file); }); test(); async function test() { const img = await fillImgToImg(imgLink); document.getElementById("waterMark").setAttribute("src", img); } async function dealFile(file) { const base64 = await fileToBase64Async(file); document.getElementById("originPic").setAttribute("src", base64); const img = await fillTextToImg(base64); document.getElementById("waterMark").setAttribute("src", img); } function fileToBase64Async(file) { return new Promise((resolve, reject) => { let reader = new FileReader(); reader.readAsDataURL(file); reader.onload = (e) => { resolve(e.target.result); }; }); } // 给图片加文字水印 function fillTextToImg(base64) { const img = new Image(); img.src = base64; img.setAttribute("crossOrigin", "Anonymous"); return new Promise((resolve, reject) => { img.onload = () => { const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); canvas.width = img.width; canvas.height = img.height; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0, canvas.width, canvas.height); const remFontSize = canvas.width / 35; ctx.font = "bolder " + remFontSize + "px Verdana"; ctx.textAlign = "center"; ctx.strokeStyle = "#fff"; const uploadTime = new Date(); const name = "@JYeontu"; const spaceH = remFontSize * 0.3; ctx.strokeText( name, canvas.width / 2, canvas.height - remFontSize - spaceH ); resolve(canvas.toDataURL("image/jpeg")); }; }); } async function getImgBase64(base64, width = 50) { const img = new Image(); img.src = base64; img.setAttribute("crossOrigin", "Anonymous"); return new Promise((resolve, reject) => { img.onload = () => { const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); canvas.width = width; canvas.height = (img.height * width) / img.width; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0, canvas.width, canvas.height); resolve(canvas.toDataURL("image/jpeg")); }; }); } // 给图片加图片水印 function fillImgToImg(base64, waterMark = imgLink) { waterMark = "https://img2.baidu.com/it/u=2243573419,589412055&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1684861200&t=7bf0a17ca21ae8ec8aa77b0f98cb4c7e"; const img = new Image(); img.src = base64; img.setAttribute("crossOrigin", "Anonymous"); return new Promise((resolve, reject) => { img.onload = async () => { const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); canvas.width = img.width; canvas.height = img.height; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0, canvas.width, canvas.height); const waterMarkSrc = await getImgBase64(waterMark, 100); const waterMarkImg = new Image(); waterMarkImg.src = waterMarkSrc; waterMarkImg.setAttribute("crossOrigin", "Anonymous"); waterMarkImg.onload = () => { ctx.drawImage( waterMarkImg, canvas.width / 2 - waterMarkImg.width / 2, canvas.height - waterMarkImg.height - 10, waterMarkImg.width, waterMarkImg.height ); resolve(canvas.toDataURL("image/jpeg")); }; }; }); } </script> </html>
说在后面
🎉这里是JYeontu,喜欢算法,GDCPC打过卡;热爱羽毛球,大运会打过酱油。毕业两年,三年前端开发经验,目前担任H5前端开发,算法业余爱好者,有空会刷刷算法题,平时喜欢打打羽毛球🏸 ,也喜欢写些东西,既为自己记录📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解🙇,写错的地方望指出,定会认真改进😊,在此谢谢大家的支持,我们下文再见🙌。