js-watermark

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

目前为 2022-10-01 提交的版本。查看 最新版本

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.gf.qytechs.cn/scripts/452322/1099860/js-watermark.js

/**
 * @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) {
    if (typeof define === 'function' && (define.amd || define.cmd)) {
        define(factory);
    } else {
        global.Watermark = factory();
    }
}(this, function () {
    'use strict';
    /**	
     * @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) {
        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) {
                context.fillText(line, x, y);
                line = arrText[n];
                y += lineHeight;
            } else {
                line = testLine;
            }
        }
        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对象 */
    function loadFile(file) {
        let fileReader = new FileReader();
        return new Promise(res => {
            fileReader.onloadend = async function (e) {
                res(e);
            };
            fileReader.readAsDataURL(file);
        })
    }
    /* 加载Image对象 */
    function loadImage(src) {
        let image = new Image();

        return new Promise(res => {
            image.onload = () => {
                res(image);
            };
            image.src = src;
        })
    }
    /* 检查坐标是否重复 */
    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;
    }
    /* 获取文字占据的宽度,高度 */
    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
    }

    var Watermark = function () {};

    /* 通过 file 对象载入图片文件-异步 */
    Watermark.prototype.setFile = function (file) {
        let self = this;
        return new Promise(async res => {
            var fileReader = await loadFile(file);
            await self.setImage(fileReader.target.result);
            res(true);
        })
    }

    /* 通过 base64 载入图片文件-异步 */
    Watermark.prototype.setImage = function (src) {
        this.dataUrl = src;
        let self = this;
        return new Promise(async res => {
            var image = await loadImage(src);
            self.sizes = {
                width: image.width,
                height: image.height
            };

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

            canvas.width = self.sizes.width;
            canvas.height = self.sizes.height;
            var ctx = canvas.getContext('2d');

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

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

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

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

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

        }
        _clearMark_();
    }

    /* 增加文字水印(全屏) */
    Watermark.prototype.addText = function (opts) {
        var options = {
            text: ['Call By waterMark.addText'],
            fontSize: '6vw',
            fontFamily: 'Microsoft Yahei',
            color: "#000000",
            textAlign: "center",
            globalAlpha: 0.7,
            rotateAngle: 50,
            /* -360 ~ 360 */
            maxWidth: 100,/* 必须大于0 */
            xMoveDistance: 30,/* 必须大于0 */
            yMoveDistance: 30/* 必须大于0 */
        };
        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(options.text[Math.floor(Math.random() * options.text.length)], 0, 0, maxWidth, lineHeight);
                }

            }
            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(options.text[Math.floor(Math.random() * options.text.length)], 0, 0, maxWidth, lineHeight);
                }

            }
        }

        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(options.text[Math.floor(Math.random() * options.text.length)], 0, 0, maxWidth, lineHeight);
                }

            }
            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(options.text[Math.floor(Math.random() * options.text.length)], 0, 0, maxWidth, lineHeight);
                }
            }
        }

    }

    /* 添加图片水印(全屏) */
    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);
                }
            }
        }


    }

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

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

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

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址