hexo 图片添加水印(png, jpeg, jpg, gif)
文章同步发布:https://blog.jijian.link/2020-04-21/hexo-watermark/
本文折腾 hexo 图片添加水印功能,大部分代码沿用: nodejs 图片添加水印(png, jpeg, jpg, gif)
方案一
使用现有插件:https://github.com/SpiritLing/hexo-images-watermark
问题:依赖 sharp 安装困难
方案二
使用 jimp 造个轮子
本文仅处理图片水印,文字水印请参考后文介绍
步骤
1. 安装依赖
npm install jimp gifwrap --save
2. 新建文件 themes/landscape/scripts/image_watermark.js
const { deepMerge } = require('hexo-util');
const watermark = require('../../../component/watermark/index'); const defaultOptions = {
// 保存的图片质量
quality: 80,
// 图片宽度小于 100 时不加水印
minWidth: 100,
// 图片高度小于 100 时不加水印
minHeight: 100,
// 旋转
rotate: 0,
// 水印 logo 图片
logo: '', // 需要添加的图片类型
include: ['*.jpg', '*.jpeg', '*.png', '*.gif'],
// 文件名为 .watermark.png 禁止添加水印图片
exclude: ['*.watermark.*'],
// 文章链接,非文章链接不加水印
articlePath: /^\d{4}-\d{2}-\d{2}/,
}; hexo.config.watermark = deepMerge(defaultOptions, hexo.config.watermark);
hexo.extend.filter.register('after_generate', watermark);
3. 新建文件 component/watermark/index.js
const fs = require('fs');
const { isMatch } = require('micromatch');
const { extname } = require('path');
const Promise = require('bluebird');
const { img, gif } = require('./watermark'); const getBuffer = (hexo, path) => {
return new Promise((resolve) => {
const stream = hexo.route.get(path);
const arr = [];
stream.on('data', chunk => arr.push(chunk));
stream.on('end', () => resolve(Buffer.concat(arr)));
});
} const getExtname = str => {
if (typeof str !== 'string') return ''; const ext = extname(str) || str;
return ext[0] === '.' ? ext.slice(1) : ext;
}; module.exports = function () {
const hexo = this;
const config = hexo.config.watermark; if (!fs.existsSync(config.logo)) {
// 带颜色的输出: https://www.jianshu.com/p/cca3e72c3ba7
return console.log('\033[41;30m ERROR \033[40;31m Add watermark no logo image found \033[0m');
} const route = hexo.route; const { include, exclude, articlePath } = config; // exclude image
const routes = route.list().filter((path) => {
// 如果文件没修改,则不再加水印
if (!route.isModified(path)) {
return false;
}
if (!articlePath.test(path)) {
return false;
}
if (isMatch(path, exclude, { basename: true })) {
return false;
}
return isMatch(path, include, {
basename: true
});
});
// 用 Promise 延迟执行,否则 build 命令水印在图片生成前执行会被覆盖
return Promise.map(routes, async (path) => {
const ext = getExtname(path);
const buffer = await getBuffer(hexo, path);
const arg = {
input: buffer,
logo: config.logo,
quality: config.quality,
rotate: config.rotate,
minWidth: config.minWidth,
minHeight: config.minHeight,
};
const newBuffer = ext === 'gif' ? await gif(arg) : await img(arg);
if (!newBuffer) {
return;
}
route.set(path, newBuffer);
});
}
4. 新建文件 component/watermark/watermark.js
const Jimp = require('jimp');
const { GifUtil, GifCodec } = require('gifwrap');
const trueTo256 = require('./trueTo256'); // 水印距离右下角百分比
const LOGO_MARGIN_PERCENTAGE = 5 / 100; function getXY (img, logoImage) {
// 如果logo小于图片 8/10 ,取 img.width * (8 / 10) 与图片宽度的最小值缩放
logoImage.resize(Math.min(logoImage.bitmap.width, img.width * (8 / 10)), Jimp.AUTO); const margin = Math.min(img.width * LOGO_MARGIN_PERCENTAGE, img.height * LOGO_MARGIN_PERCENTAGE, 20); const X = img.width - logoImage.bitmap.width - margin;
const Y = img.height - logoImage.bitmap.height - margin; return {
X,
Y,
};
} async function gif({
input = '',
logo = '',
quality = 80,
rotate = 0,
} = {}) {
const inputGif = await GifUtil.read(input);
const logoImage = await Jimp.read(logo); logoImage.rotate(rotate); const { X, Y } = getXY({
width: inputGif.width,
height: inputGif.height,
}, logoImage); // 给每一帧都打上水印
inputGif.frames.forEach((frame, i) => {
const jimpCopied = GifUtil.copyAsJimp(Jimp, frame); // 计算获得的坐标再减去每一帧偏移位置,为实际添加水印坐标
jimpCopied.composite(logoImage, X - frame.xOffset, Y - frame.yOffset, [{
mode: Jimp.BLEND_SOURCE_OVER,
opacitySource: 0.1,
opacityDest: 1
}]); // 压缩图片
jimpCopied.quality(quality); frame.bitmap = jimpCopied.bitmap; // 真彩色转 256 色
frame.bitmap = trueTo256(frame.bitmap);
}); // 不使用 trueTo256 也可以使用自带的 quantizeWu 进行颜色转换,不过自带的算法运行需要更多的时间,没有 trueTo256 快
// GifUtil.quantizeWu(inputGif.frames); const codec = new GifCodec();
return (await codec.encodeGif(inputGif.frames)).buffer;
}; async function img({
input = '',
logo = '',
quality = 80,
rotate = 0,
minWidth = 0,
minHeight = 0,
} = {}) {
const image = await Jimp.read(input); if (image.getWidth() < minWidth || image.getHeight() < minHeight) {
return;
} const logoImage = await Jimp.read(logo); logoImage.rotate(rotate); const { X, Y } = getXY({
width: image.getWidth(),
height: image.getHeight(),
}, logoImage); image.composite(logoImage, X, Y, [{
mode: Jimp.BLEND_SOURCE_OVER,
opacitySource: 0.1,
opacityDest: 1
}]); // 压缩图片
image.quality(quality); return await image.getBufferAsync(Jimp.AUTO);
}; module.exports = {
gif,
img
};
5. 新建文件 component/watermark/trueTo256.js
/**
* 真彩色转 256 色
* https://www.jianshu.com/p/9188b4639a83
*/ function colorTransfer(rgb) {
var r = (rgb & 0x0F00000) >> 12;
var g = (rgb & 0x000F000) >> 8;
var b = (rgb & 0x00000F0) >> 4;
return (r | g | b);
}; function colorRevert(rgb) {
var r = (rgb & 0x0F00) << 12;
var g = (rgb & 0x000F0) << 8;
var b = (rgb & 0x00000F) << 4;
return (r | g | b);
} function getDouble(a, b) {
var red = ((a & 0x0F00) >> 8) - ((b & 0x0F00) >> 8);
var grn = ((a & 0x00F0) >> 4) - ((b & 0x00F0) >> 4);
var blu = (a & 0x000F) - (b & 0x000F);
return red * red + blu * blu + grn * grn;
} function getSimulatorColor(rgb, rgbs, m) {
var r = 0;
var lest = getDouble(rgb, rgbs[r]);
for (var i = 1; i < m; i++) {
var d2 = getDouble(rgb, rgbs[i]);
if (lest > d2) {
lest = d2;
r = i;
}
}
return rgbs[r];
} function transferTo256(rgbs) {
var n = 4096;
var m = 256;
var colorV = new Array(n);
var colorIndex = new Array(n); //初始化
for (var i = 0; i < n; i++) {
colorV[i] = 0;
colorIndex[i] = i;
} //颜色转换
for (var x = 0; x < rgbs.length; x++) {
for (var y = 0; y < rgbs[x].length; y++) {
rgbs[x][y] = colorTransfer(rgbs[x][y]);
colorV[rgbs[x][y]]++;
}
} //出现频率排序
var exchange;
var r;
for (var i = 0; i < n; i++) {
exchange = false;
for (var j = n - 2; j >= i; j--) {
if (colorV[colorIndex[j + 1]] > colorV[colorIndex[j]]) {
r = colorIndex[j];
colorIndex[j] = colorIndex[j + 1];
colorIndex[j + 1] = r;
exchange = true;
}
}
if (!exchange) break;
} //颜色排序位置
for (var i = 0; i < n; i++) {
colorV[colorIndex[i]] = i;
} for (var x = 0; x < rgbs.length; x++) {
for (var y = 0; y < rgbs[x].length; y++) {
if (colorV[rgbs[x][y]] >= m) {
rgbs[x][y] = colorRevert(getSimulatorColor(rgbs[x][y], colorIndex, m));
} else {
rgbs[x][y] = colorRevert(rgbs[x][y]);
}
}
}
return rgbs;
} // 获取 rgba int 值
function getRgbaInt(bitmap, x, y) {
const bi = (y * bitmap.width + x) * 4;
return bitmap.data.readUInt32BE(bi, true);
} // 设置 rgba int 值
function setRgbaInt(bitmap, x, y, rgbaInt) {
const bi = (y * bitmap.width + x) * 4;
return bitmap.data.writeUInt32BE(rgbaInt, bi);
} // int 值转为 rgba
function intToRGBA (i) {
let rgba = {}; rgba.r = Math.floor(i / Math.pow(256, 3));
rgba.g = Math.floor((i - rgba.r * Math.pow(256, 3)) / Math.pow(256, 2));
rgba.b = Math.floor(
(i - rgba.r * Math.pow(256, 3) - rgba.g * Math.pow(256, 2)) /
Math.pow(256, 1)
);
rgba.a = Math.floor(
(i -
rgba.r * Math.pow(256, 3) -
rgba.g * Math.pow(256, 2) -
rgba.b * Math.pow(256, 1)) /
Math.pow(256, 0)
);
return rgba;
}; // rgba int 转为 rgb int
function rgbaIntToRgbInt (i) {
const r = Math.floor(i / Math.pow(256, 3));
const g = Math.floor((i - r * Math.pow(256, 3)) / Math.pow(256, 2));
const b = Math.floor(
(i - r * Math.pow(256, 3) - g * Math.pow(256, 2)) /
Math.pow(256, 1)
); return r * Math.pow(256, 2) +
g * Math.pow(256, 1) +
b * Math.pow(256, 0);
}; // rgb int 转为 rgba int
function rgbIntToRgbaInt (i, a) {
const r = Math.floor(i / Math.pow(256, 2));
const g = Math.floor((i - r * Math.pow(256, 2)) / Math.pow(256, 1));
const b = Math.floor(
(i - r * Math.pow(256, 2) - g * Math.pow(256, 1)) /
Math.pow(256, 0)
);
return r * Math.pow(256, 3) +
g * Math.pow(256, 2) +
b * Math.pow(256, 1) +
a * Math.pow(256, 0);
}; /**
* @interface Bitmap { data: Buffer; width: number; height: number;}
* @param {Bitmap} bitmap
*/
module.exports = function (bitmap) {
const width = bitmap.width;
const height = bitmap.height; let rgbs = new Array();
let alphas = new Array(); for (let x = 0; x < width; x++) {
rgbs[x] = rgbs[x] || [];
alphas[x] = alphas[x] || [];
for (let y = 0; y < height; y++) {
// 由于真彩色转 256色 算法是使用 int rgb 计算,所以需要把获取到的 int rgba 转为 int rgb
const rgbaInt = getRgbaInt(bitmap, x, y);
rgbs[x][y] = rgbaIntToRgbInt(rgbaInt);
alphas[x][y] = intToRGBA(rgbaInt).a;
}
} // 颜色转换
const color = transferTo256(rgbs); for (let x = 0; x < width; x++) {
for (let y = 0; y < height; y++) {
// 写入转换后的颜色
setRgbaInt(bitmap, x, y, rgbIntToRgbaInt(color[x][y], alphas[x][y]));
}
} return bitmap;
};
6. 添加配置 _config.yml
# 水印
watermark:
# 此处需要改成你的 logo 文件地址
logo: ./component/watermark/logo.png
7. 重新运行项目即可。
文字水印
使用 jimp.loadFont 绘制文字水印。
问题:不能设置文字颜色大小等样式。
参考 hexo-images-watermark 方案,逻辑是先用 text-to-svg 将文本转为 svg ,在用 svg2png 将 svg 转为 png 图片获得 buffer 数据,再拿 buffer 绘制水印。
问题:安装困难,svg2png 需要用到 PhantomJS。
其他文字转图片的方案也有各自安装问题,比如使用 node-canvas 转换文字,安装
node-pre-gyp
困难。
hexo 改造系列文章推荐阅读 https://blog.jijian.link/categories/hexo/
hexo 图片添加水印(png, jpeg, jpg, gif)的更多相关文章
- Hexo 文章图片添加水印,不用云处理
由于网上找到的都是借用第三方云处理添加水印,但是我不太想用,所以自己开发了一个插件 Hexo 图片添加水印Github地址 目前插件可以直接在 hexo 官网上搜索到 下面内容都是在 Github 上 ...
- java实现给图片添加水印
package michael.io.image; import java.awt.AlphaComposite; import java.awt.Graphics2D; import java.aw ...
- java.imageIo给图片添加水印
最近项目在做一个商城项目, 项目上的图片要添加水印①,添加图片水印;②:添加文字水印; 一下提供下个方法,希望大家可以用得着: package com.blogs.image; import java ...
- 【Python】给图片添加水印的Python及Golang实现
前言 不知道大家有没有这样的习惯,一篇比较得意的博客在发表一段时间之后会特别关注,前段时间一篇写到凌晨的博客被 码迷 这个网关爬取之后发表了,因为搜索引擎先爬取码迷的,所以我的博客无法被搜索到,即使直 ...
- .net为图片添加水印(转) jpg png和gif格式
.net为图片添加水印(转) jpg png和gif格式 .net为图片添加水印(转) jpg png和gif格式,转自csdn的hyde82,现在跟大家一起来分享下: 利 用.net中System. ...
- 完美png图片添加水印类
完美png图片添加水印类 被添加水印图片和水印图片都可以是png,保证透明无色背景,可调节透明度 <?phpclass Imgshuiyin{ /* 缩略图相关常量定义 */ const THU ...
- Python Windows 快捷键自动给剪贴板(复制)图片添加水印
编写一个能在windows上使用的按下快捷键自动给剪贴板(复制)的图片添加水印的小工具.plyer.PIL.pyinstaller.pynput.win32clipboard库.记录自己踩过的坑,部分 ...
- Android 图片添加水印图片或者文字
给图片添加水印的基本思路都是载入原图,添加文字或者载入水印图片,保存图片这三个部分 添加水印图片: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ...
- ASP.NET -- WebForm -- 给图片添加水印标记
ASP.NET -- WebForm: 给图片添加水印标记 ASP.NET:使用 WebForm(C#) 制作一个简单的为图片添加水印的页面. 1. Test2.aspx文件 <%@ Page ...
- JAVA给图片添加水印
package com.test; import org.junit.Test; import javax.imageio.ImageIO; import java.awt.*; import jav ...
随机推荐
- Qt编写可视化大屏电子看板系统23-模块1产量汇总
一.前言 大屏系统采用结构模块化的分层设计思路,一个表对应一个最小模块比如模具产量.零件产量,数据库采集的时候采集对应的表,拿到数据后按照对应的数据规则传给控件绘制,其中模具产量.零件产量两个模块采用 ...
- 创建用于预测序列的人工智能模型,用Keras Tuner探索模型的超参数。
上一篇:<创建用于预测序列的人工智能模型(五),调整模型的超参数> 序言:在完成初步的模型研发后,接下来的重点是探索和优化超参数.通过合理调整超参数(如学习率.动量参数.神经元数量等),可 ...
- IM开发者的零基础通信技术入门(十一):为什么WiFi信号差?一文即懂!
一.本文内容概述 WiFi对于现在的家庭来说,属于司空见惯的上网方式,但很多情况下,家里房间多.空间大.杂物乱的情况下,WiFi的信号就受影响.为什么WiFi信号会受影响?什么情况下该使用何种方式组网 ...
- torque提交作业
PBS(Protable Batch System)是功能最为齐全,历史最悠久,支持最广泛的本地集群调度器之一. PBS的目前包括openPBS,PBS Pro和Torque三个主要分支.其中Open ...
- 记录socket的使用
今天记录一下socket的基本使用方法,直接上代码 initWebSocket() { //初始化weosocket const wsuri = "socket地址";//地址以w ...
- nvim及插件安装配置
1. install neovim 1 sudo apt install neovim After installing neovim, we can delete old vi. 3. instal ...
- Matplotlab显示OpenCV读取到的图像
Matplotlab显示OpenCV读取到的图像 一. 确认图像的数组类型 在使用 OpenCV 的 cv2.imread() 函数读取图像时,第二个参数(标志)决定了图像的读取方式.具体来说,0.1 ...
- 解决使用yarn安装依赖出现“The engine "node" is incompatible with this module. Expected version "^14.18.0 || ^16.14.0 || >=18.0.0". Got "17.9.0"”的问题
1.问题描述 某天在使用yarn安装依赖的时候,突然出现如下错误导致安装依赖终止: The engine "node" is incompatible with this modu ...
- Git 忽略文件配置全解析
Git 忽略文件配置全解析 在Git版本控制系统中,.gitignore文件扮演着至关重要的角色.它允许我们指定哪些文件或目录应该被Git忽略,即不被纳入版本控制之中.这对于避免提交敏感信息.构建产物 ...
- FLink14--核心窗口--TumblingWindiwApp
一.依赖 https://www.cnblogs.com/robots2/p/16048648.html 二. 代码 前言:window用作有keyBy情况,前面没有使用keyBy的话用windowA ...