js-watermark

JavaScript 图片文字水印、图片图片水印生成工具,生成 base64 编码图片。

Script này sẽ không được không được cài đặt trực tiếp. Nó là một thư viện cho các script khác để bao gồm các chỉ thị meta // @require https://update.greasyfork.org/scripts/452322/1470429/js-watermark.js

// @ts-nocheck
/**
 * @js-watermark.js
 * @author WhiteSev
 * @Created: 22-09-26
 * @repository: https://github.com/WhiteSevs/js-watermark
 * @forked by:https://github.com/gisonyeung/js-watermark
 * @description JavaScript 图片文字水印、图片图片水印生成工具,生成 base64 编码图片。
 */

(function (global, factory) {
	/**
	 * 不使用define
	 * typeof define === "function" && define.amd
	 * define(factory)
	 */
	if (typeof exports === "object" && typeof module !== "undefined") {
		/* 适用于NodeJs或typeScript */
		module.exports = factory();
	} else {
		global = typeof globalThis !== "undefined" ? globalThis : global || self;
		/* 适用于浏览器中,且this对象是window,如果this是其它,那么会在其它对象下注册对象 */
		global.Watermark = factory(global.Watermark);
	}
})(typeof window !== "undefined" ? window : this, function (AnotherWatermark) {
	"use strict";
	/**
	 * @class
	 */
	let Watermark = function () {};
	/**
	 * @author zhangxinxu(.com)
	 * @licence MIT
	 * @description http://www.zhangxinxu.com/wordpress/?p=7362
	 */
	/* api扩展-设置字符间距 */
	CanvasRenderingContext2D.prototype.letterSpacingText = function (
		text,
		x,
		y,
		letterSpacing
	) {
		var context = this;
		var canvas = context.canvas;

		if (!letterSpacing && canvas) {
			letterSpacing = parseFloat(window.getComputedStyle(canvas).letterSpacing);
		}
		if (!letterSpacing) {
			return this.fillText(text, x, y);
		}

		var arrText = text.split("");
		var align = context.textAlign || "left";

		/* 这里仅考虑水平排列 */
		var originWidth = context.measureText(text).width;
		/* 应用letterSpacing占据宽度 */
		var actualWidth = originWidth + letterSpacing * (arrText.length - 1);
		/* 根据水平对齐方式确定第一个字符的坐标	 */
		if (align == "center") {
			x = x - actualWidth / 2;
		} else if (align == "right") {
			x = x - actualWidth;
		}

		/* 临时修改为文本左对齐	 */
		context.textAlign = "left";
		/* 开始逐字绘制	 */
		arrText.forEach(function (letter) {
			var letterWidth = context.measureText(letter).width;
			context.fillText(letter, x, y);
			/* 确定下一个字符的横坐标	 */
			x = x + letterWidth + letterSpacing;
		});
		/* 对齐方式还原	 */
		context.textAlign = align;
	};

	/* api扩展-自动换行 */
	CanvasRenderingContext2D.prototype.wrapText = function (
		text,
		x,
		y,
		maxWidth,
		lineHeight,
		stroke
	) {
		if (
			typeof text != "string" ||
			typeof x != "number" ||
			typeof y != "number"
		) {
			return;
		}

		var context = this;
		var canvas = context.canvas;

		if (typeof maxWidth == "undefined") {
			maxWidth = (canvas && canvas.width) || 300;
		}
		if (typeof lineHeight == "undefined") {
			lineHeight =
				(canvas && parseInt(window.getComputedStyle(canvas).lineHeight)) ||
				parseInt(window.getComputedStyle(document.body).lineHeight);
		}

		/* 字符分隔为数组	 */
		var arrText = text.split("");
		var line = "";

		for (var n = 0; n < arrText.length; n++) {
			var testLine = line + arrText[n];
			var metrics = context.measureText(testLine);
			var testWidth = metrics.width;
			if (testWidth > maxWidth && n > 0) {
				if (stroke) {
					context.strokeText(line, x, y, canvas.width);
				} else {
					context.fillText(line, x, y);
				}
				line = arrText[n];
				y += lineHeight;
			} else {
				line = testLine;
			}
		}
		if (stroke) {
			context.strokeText(line, x, y, canvas.width);
		} else {
			context.fillText(line, x, y);
		}
	};

	/* api扩展-垂直排列 */
	CanvasRenderingContext2D.prototype.fillTextVertical = function (text, x, y) {
		var context = this;
		var canvas = context.canvas;

		var arrText = text.split("");
		var arrWidth = arrText.map(function (letter) {
			return context.measureText(letter).width;
		});

		var align = context.textAlign;
		var baseline = context.textBaseline;

		if (align == "left") {
			x = x + Math.max.apply(null, arrWidth) / 2;
		} else if (align == "right") {
			x = x - Math.max.apply(null, arrWidth) / 2;
		}
		if (
			baseline == "bottom" ||
			baseline == "alphabetic" ||
			baseline == "ideographic"
		) {
			y = y - arrWidth[0] / 2;
		} else if (baseline == "top" || baseline == "hanging") {
			y = y + arrWidth[0] / 2;
		}

		context.textAlign = "center";
		context.textBaseline = "middle";

		/* 开始逐字绘制 */
		arrText.forEach(function (letter, index) {
			/* 确定下一个字符的纵坐标位置 */
			var letterWidth = arrWidth[index];
			/* 是否需要旋转判断 */
			var code = letter.charCodeAt(0);
			if (code <= 256) {
				context.translate(x, y);
				/* 英文字符,旋转90° */
				context.rotate((90 * Math.PI) / 180);
				context.translate(-x, -y);
			} else if (index > 0 && text.charCodeAt(index - 1) < 256) {
				/* y修正 */
				y = y + arrWidth[index - 1] / 2;
			}
			context.fillText(letter, x, y);
			/* 旋转坐标系还原成初始态 */
			context.setTransform(1, 0, 0, 1, 0, 0);
			/* 确定下一个字符的纵坐标位置 */
			var letterWidth = arrWidth[index];
			y = y + letterWidth;
		});
		/* 水平垂直对齐方式还原 */
		context.textAlign = align;
		context.textBaseline = baseline;
	};

	/**
	 * 加载File对象
	 *
	 * @param {File} file
	 * @async
   * @returns {Promise<ProgressEvent<FileReader>>}
	 */
	function loadFile(file) {
		let fileReader = new FileReader();
		return new Promise((resolve) => {
			fileReader.onloadend = async function (event) {
				resolve(event);
			};
			fileReader.readAsDataURL(file);
		});
	}

	/**
	 * 加载Image对象
	 *
	 * @param {string} src
	 * @async
	 * @returns {Promise<HTMLImageElement>}
	 */
	function loadImage(src) {
		let image = new Image();

		return new Promise((resolve) => {
			image.onload = () => {
				resolve(image);
			};
			image.src = src;
		});
	}
	/**
	 * 检查坐标是否重复
	 * @param {any[]} arrayData
	 * @param {number} x
	 * @param {number} y
	 * @returns {boolean}
	 */
	function checkInArrayByPos(arrayData, x, y) {
		let flag = false;
		Array.from(arrayData).forEach((item) => {
			if (item["x"] == x && item["y"] == y) {
				flag = true;
				return;
			}
		});
		return flag;
	}
	/**
	 * 获取文字占据的宽度,高度
	 * @param {string} char
	 * @param {any} style
	 * @returns {{height:Number,width:Number}}
	 */
	function getCharSizeByCanvas(char, style = {}) {
		let textCanvas = document.createElement("canvas");
		textCanvas.style.positon = "ablsolute";
		let textCTX = textCanvas.getContext("2d");
		let { fontSize = 14, fontFamily = "Microsoft Yahei" } = style;
		document.body.appendChild(textCanvas);
		textCTX.font = `${fontSize}px ${fontFamily}`;
		document.body.removeChild(textCanvas);
		let text = textCTX.measureText(char); /* TextMetrics object */
		textCTX.fillText(char, 50, 50);
		let result = {
			height: parseInt(fontSize),
			width: parseInt(text.width),
		};
		return result;
	}
	/**
	 * 获取随机值
	 * @param {any[]} arr
	 */
	function getRandValue(arr) {
		if (arr instanceof Array) {
			return arr[Math.floor(Math.random() * arr.length)];
		} else {
			return arr;
		}
	}
	/**
	 * 通过 file 对象载入图片文件-异步
	 * @async
	 * @param {File} file
	 * @returns {Promise<boolean>}
	 */
	Watermark.prototype.setFile = function (file) {
		let that = this;
		return new Promise(async (resolve) => {
			try {
				var fileReader = await loadFile(file);
				await that.setImage(fileReader.target.result);
				resolve(true);
			} catch (error) {
				resolve(false);
			}
		});
	};
	/**
	 * 通过 base64 载入图片文件-异步
	 * @param {string} src
	 * @async
	 * @returns {Promise<boolean>}
	 */
	Watermark.prototype.setImage = function (src) {
		this.dataUrl = src;
		let that = this;
		return new Promise(async (res) => {
			var image = await loadImage(src);
			that.sizes = {
				width: image.width,
				height: image.height,
			};

			var canvas = document.createElement("canvas");

			canvas.width = that.sizes.width;
			canvas.height = that.sizes.height;
			var ctx = canvas.getContext("2d");

			ctx.drawImage(image, 0, 0);
			image = null;
			that.canvas = canvas;
			res(true);
		});
	};

	/**
	 * 获取是否存在图片对象
	 * @returns {boolean}
	 */
	Watermark.prototype.hasImage = function () {
		return !!this.dataUrl;
	};

	/**
	 * 获取当前图片尺寸
	 * @returns {number}
	 */
	Watermark.prototype.getSize = function () {
		return this.sizes;
	};

	/**
	 * 清空水印
	 */
	Watermark.prototype.clearMark = function () {
		let that = this;
		if (typeof that.canvas === "undefined") {
			return;
		}

		function _clearMark_() {
			var ctx = that.canvas.getContext("2d");
			/* 清空画布 */
			ctx.clearRect(0, 0, that.canvas.width, that.canvas.height);
			var w = that.canvas.width;
			var h = that.canvas.height;
			that.canvas.width = w;
			that.canvas.height = h;
			/* 清除path路径 */
			ctx.beginPath();
			/* 重绘 */
			var image = new Image();
			image.src = that.dataUrl;
			ctx.drawImage(image, 0, 0);
			image = null;
		}
		_clearMark_();
	};

	/**
	 * 添加文字水印(全屏)
	 * @param {object} opts
	 */
	Watermark.prototype.addText = function (opts) {
		var options = {
			text: ["Call By waterMark.addText"],
			fontSize: "6vw",
			fontFamily: "Microsoft Yahei",
			color: "#000000",
			textAlign: "center",
			/* 描边 */
			stroke: false,
			globalAlpha: 0.7,
			/* -360 ~ 360 */
			rotateAngle: 50,
			/* 必须大于0 */
			maxWidth: 100,
			/* 必须大于0 */
			xMoveDistance: 30,
			/* 必须大于0 */
			yMoveDistance: 30,
		};
		for (let key in options) {
			if (typeof opts[key] !== "undefined") {
				options[key] = opts[key];
			}
		}
		options.maxWidth = parseInt(options.maxWidth) > 0 ? options.maxWidth : 1;
		options.xMoveDistance =
			parseInt(options.xMoveDistance) > 0 ? options.xMoveDistance : 1;
		options.yMoveDistance =
			parseInt(options.yMoveDistance) > 0 ? options.yMoveDistance : 1;
		var ctx = this.canvas.getContext("2d");

		var fontSize = options.fontSize;
		fontSize = fontSize.toString();
		/* 转换 vw */
		if (~fontSize.indexOf("vw")) {
			fontSize = ((this.sizes.width / 100) * parseInt(fontSize)).toFixed(0);
		}
		fontSize = parseInt(fontSize);

		/* 绘制水印 */
		ctx.font = fontSize + "px " + options.fontFamily;
		ctx.fillStyle = options.color;
		ctx.textAlign = options.textAlign;
		ctx.globalAlpha = options.globalAlpha; /* 透明度 */

		let canvasWidth = this.sizes.width,
			/* 画布宽高 */
			canvasHeight = this.sizes.height;
		let rotateAngle = (options.rotateAngle * Math.PI) / 180;
		let xMoveDistance = options.xMoveDistance; /* 水平移动距离 */
		let yMoveDistance = options.yMoveDistance; /* 垂直移动距离 */
		let maxWidth = options.maxWidth; /* 文字最大宽度 */
		let lineHeight = fontSize; /* 文字占据高度 */
		let pos = [];
		for (var i = canvasWidth / 2; i < canvasWidth; i += xMoveDistance) {
			/* 右侧铺满 */
			for (var j = canvasHeight / 2; j < canvasHeight; j += yMoveDistance) {
				/* 右下 */
				if (!checkInArrayByPos(pos, i, j)) {
					pos = pos.concat({
						x: i,
						y: j,
					});
					ctx.setTransform(1, 0, 0, 1, 0, 0);
					ctx.translate(i, j);
					ctx.rotate(rotateAngle);
					ctx.wrapText(
						getRandValue(options.text),
						0,
						0,
						maxWidth,
						lineHeight,
						options.stroke
					);
				}
			}
			for (var k = canvasHeight / 2; k > 0; k -= yMoveDistance) {
				/* 右上 */
				if (!checkInArrayByPos(pos, i, k)) {
					pos = pos.concat({
						x: i,
						y: k,
					});
					ctx.setTransform(1, 0, 0, 1, 0, 0);
					ctx.translate(i, k);
					ctx.rotate(rotateAngle);
					ctx.wrapText(
						getRandValue(options.text),
						0,
						0,
						maxWidth,
						lineHeight,
						options.stroke
					);
				}
			}
		}

		for (var i = canvasWidth / 2; i > 0; i -= xMoveDistance) {
			/* 左侧铺满 */
			for (var j = canvasHeight / 2; j < canvasHeight; j += yMoveDistance) {
				/* 左下 */
				if (!checkInArrayByPos(pos, i, j)) {
					pos = pos.concat({
						x: i,
						y: j,
					});
					ctx.setTransform(1, 0, 0, 1, 0, 0);
					ctx.translate(i, j);
					ctx.rotate(rotateAngle);
					ctx.wrapText(
						getRandValue(options.text),
						0,
						0,
						maxWidth,
						lineHeight,
						options.stroke
					);
				}
			}
			for (var k = canvasHeight / 2; k > 0; k -= yMoveDistance) {
				/* 左上 */
				if (!checkInArrayByPos(pos, i, k)) {
					pos = pos.concat({
						x: i,
						y: k,
					});
					ctx.setTransform(1, 0, 0, 1, 0, 0);
					ctx.translate(i, k);
					ctx.rotate(rotateAngle);
					ctx.wrapText(
						getRandValue(options.text),
						0,
						0,
						maxWidth,
						lineHeight,
						options.stroke
					);
				}
			}
		}
	};

	/**
	 * 添加像素文字水印(单个)
	 * @param {object} opts
	 */
	Watermark.prototype.addPixelText = function (opts) {
		var options = {
			text: "像素文字水印",
			/* 像素文字 */
			big: {
				fontSize: 150,
				fontFamily: "微软雅黑",
				textAlign: "center",
				rotateAngle: 0,
				/* 描边 */
				stroke: false,
			},
			/* 绘制像素的文字 */
			small: {
				fontSize: 10,
				fontFamily: "微软雅黑",
				color: "#000",
				textAlign: "center",
				globalAlpha: 0.7,
			},
		};
		for (let key in options) {
			if (typeof opts[key] !== "undefined") {
				options[key] = opts[key];
			}
		}
		var ctx = this.canvas.getContext("2d");
		var tmpCanvas = document.createElement("canvas");
		var tmpctx = tmpCanvas.getContext("2d");
		tmpCanvas.width = this.sizes.width;
		tmpCanvas.height = this.sizes.height;
		tmpctx.font = options.big.fontSize + "px " + options.big.fontFamily;
		tmpctx.textAlign = options.big.textAlign;
		tmpctx.textBaseline = "middle";
		tmpctx.translate(tmpCanvas.width / 2, tmpCanvas.height / 2);
		tmpctx.rotate((options.big.rotateAngle * Math.PI) / 180);
		tmpctx.translate(-tmpCanvas.width / 2, -tmpCanvas.height / 2);
		if (options.big.stroke) {
			tmpctx.strokeText(
				options.text,
				tmpCanvas.width / 2,
				tmpCanvas.height / 2,
				tmpCanvas.width
			);
		} else {
			tmpctx.fillText(options.text, tmpCanvas.width / 2, tmpCanvas.height / 2);
		}

		var textArray = options.text.split("");
		var textPixleInfo = tmpctx.getImageData(
			0,
			0,
			tmpCanvas.width,
			tmpCanvas.height
		);
		var pixelArray = [];
		for (var i = 0; i < tmpCanvas.height; i += options.small.fontSize) {
			for (var j = 0; j < tmpCanvas.width; j += options.small.fontSize) {
				var index = j + i * tmpCanvas.width;
				var a = textPixleInfo.data[index * 4 + 3];
				if (a > 128) {
					//存入数组
					pixelArray.push({
						text: getRandValue(textArray),
						x: j,
						y: i,
					});
				}
			}
		}

		ctx.font = options.small.fontSize + "px " + options.small.fontFamily;
		ctx.fillStyle = options.small.color;
		ctx.textAlign = options.small.textAlign;
		ctx.textBaseline = "middle";
		ctx.globalAlpha = options.small.globalAlpha;
		pixelArray.forEach((item) => {
			ctx.fillText(item.text, item.x, item.y);
		});
	};

	/**
	 * 添加图片水印(全屏)
	 * @param {object} opts
	 * @returns
	 */
	Watermark.prototype.addImage = function (opts) {
		if (opts.imageArray == null) {
			alert("参数缺少imageArray");
			return false;
		}
		if (opts.imageArray.length === 0) {
			alert("参数imageArray不能为空");
			return false;
		}
		let options = {
			imageArray: [],
			/* 里面为水印Image对象 */
			width: 50,
			/* 必须大于0 */
			height: 50,
			/* 必须大于0 */
			globalAlpha: 0.5,
			rotateAngle: 0,
			xMoveDistance: 70,
			/* 必须大于0 */
			yMoveDistance: 70,
			/* 必须大于0 */
		};
		for (let key in options) {
			if (typeof opts[key] !== "undefined") {
				options[key] = opts[key];
			}
		}
		options.width = parseInt(options.width) > 0 ? options.width : 1;
		options.height = parseInt(options.height) > 0 ? options.height : 1;
		options.xMoveDistance =
			parseInt(options.xMoveDistance) > 0 ? options.xMoveDistance : 1;
		options.yMoveDistance =
			parseInt(options.yMoveDistance) > 0 ? options.yMoveDistance : 1;
		let ctx = this.canvas.getContext("2d");

		let waterImageCanvasArray = [];
		let waterImageCanvasDiagonal = parseInt(
			Math.sqrt(options.width * options.width + options.height * options.height)
		); /* 水印对角线 */

		let canvasWidth = this.sizes.width,
			/* 画布宽高 */
			canvasHeight = this.sizes.height;
		let rotateAngle = (options.rotateAngle * Math.PI) / 180; /* 旋转角度 */
		let xMoveDistance = options.xMoveDistance; /* 水平移动距离 */
		let yMoveDistance = options.yMoveDistance; /* 垂直移动距离 */

		let centerDrawLeftPosX =
			canvasWidth / 2 -
			waterImageCanvasDiagonal / 2; /* 中心的绘制水印的左上角坐标x */
		let centerDrawLeftPosY =
			canvasHeight / 2 -
			waterImageCanvasDiagonal / 2; /* 绘制水印的左上角坐标y */
		let waterDrawPosX =
			(waterImageCanvasDiagonal - options.width) / 2; /* 水印里图片坐标x */
		let waterDrawPosY =
			(waterImageCanvasDiagonal - options.height) / 2; /* 水印里图片坐标y */

		Array.from(options.imageArray).forEach((item) => {
			/* 先把水印绘制好 */
			var waterImageCanvas = document.createElement("canvas");
			var waterctx = waterImageCanvas.getContext("2d");

			waterImageCanvas.width = waterImageCanvasDiagonal;
			waterImageCanvas.height = waterImageCanvasDiagonal;
			waterctx.globalAlpha = options.globalAlpha; /* 透明度 */
			waterctx.translate(
				waterImageCanvasDiagonal / 2,
				waterImageCanvasDiagonal / 2
			);
			waterctx.rotate(rotateAngle);
			waterctx.translate(
				-waterImageCanvasDiagonal / 2,
				-waterImageCanvasDiagonal / 2
			);
			waterctx.drawImage(
				item,
				waterDrawPosX,
				waterDrawPosY,
				options.width,
				options.height
			);

			waterImageCanvasArray = waterImageCanvasArray.concat(waterImageCanvas);
		});

		function randomArrayData(array_data) {
			/* 随机项 */
			return array_data[Math.floor(Math.random() * array_data.length)];
		}
		ctx.setTransform(1, 0, 0, 1, 0, 0);
		let pos = [];
		for (let i = centerDrawLeftPosX; i < canvasWidth; i += xMoveDistance) {
			/* 右侧铺满 */
			for (let j = centerDrawLeftPosY; j < canvasHeight; j += yMoveDistance) {
				/* 右下 */
				if (!checkInArrayByPos(pos, i, j)) {
					pos = pos.concat({
						x: i,
						y: j,
					});
					ctx.drawImage(
						randomArrayData(waterImageCanvasArray),
						i,
						j
					); /* 绘制水印 */
				}
			}
			for (
				let k = centerDrawLeftPosY;
				k > -Math.abs(waterImageCanvasDiagonal);
				k -= yMoveDistance
			) {
				/* 右上 */
				if (!checkInArrayByPos(pos, i, k)) {
					pos = pos.concat({
						x: i,
						y: k,
					});
					ctx.drawImage(randomArrayData(waterImageCanvasArray), i, k);
				}
			}
		}
		for (
			let i = centerDrawLeftPosX;
			i > -Math.abs(waterImageCanvasDiagonal);
			i -= xMoveDistance
		) {
			/* 左侧铺满 */
			for (let j = centerDrawLeftPosY; j < canvasHeight; j += yMoveDistance) {
				/* 左下 */
				if (!checkInArrayByPos(pos, i, j)) {
					pos = pos.concat({
						x: i,
						y: j,
					});
					ctx.drawImage(randomArrayData(waterImageCanvasArray), i, j);
				}
			}
			for (
				let k = centerDrawLeftPosY;
				k > -Math.abs(waterImageCanvasDiagonal);
				k -= yMoveDistance
			) {
				/* 左上 */
				if (!checkInArrayByPos(pos, i, k)) {
					pos = pos.concat({
						x: i,
						y: k,
					});
					ctx.drawImage(randomArrayData(waterImageCanvasArray), i, k);
				}
			}
		}
	};

	/**
	 * 获得原图
	 * @returns {?string}
	 */
	Watermark.prototype.getPreview = function () {
		return this.dataUrl;
	};

	/**
	 * 绘制图片
	 * @param {string} type png|jpeg
	 * @returns
	 */
	Watermark.prototype.render = function (type) {
		type = type === "png" ? "png" : "jpeg";
		return this.canvas.toDataURL("image/" + type);
	};

	/**
	 * 绘制图片Blob Url-异步
	 * @async
	 */
	Watermark.prototype.renderBlob = function () {
		let that = this;
		return new Promise((res) => {
			that.canvas.toBlob(function (blob) {
				res(window.URL.createObjectURL(blob));
			});
		});
	};

	/**
	 * 释放控制权
	 * @returns {Watermark}
	 */
	Watermark.prototype.noConflict = function () {
		if (window.Watermark) {
			delete window.Watermark;
		}
		if (AnotherWatermark) {
			window.Watermark = AnotherWatermark;
		}
		return Watermark;
	};
	return Watermark;
});