背景
在很多地方,我们都可以看到,上传图片的时候,图片都会被加上默认的水印,水印的作用主要体现在以下几个方面:
- 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);
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前端开发,算法业余爱好者,有空会刷刷算法题,平时喜欢打打羽毛球🏸 ,也喜欢写些东西,既为自己记录📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解🙇,写错的地方望指出,定会认真改进😊,在此谢谢大家的支持,我们下文再见🙌。