Greasy Fork is available in English.
JavaScript 图片文字水印、图片图片水印生成工具,生成 base64 编码图片。
此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @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; });