update image watermark
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
|
.idea/
|
||||||
node_modules/
|
node_modules/
|
||||||
# ---> macOS
|
# ---> macOS
|
||||||
# General
|
# General
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
},
|
},
|
||||||
"nodeModulesDir": "auto",
|
"nodeModulesDir": "auto",
|
||||||
"imports": {
|
"imports": {
|
||||||
|
"@std/cli/parse-args": "jsr:@std/cli/parse-args",
|
||||||
"@std/assert": "jsr:@std/assert@1",
|
"@std/assert": "jsr:@std/assert@1",
|
||||||
"sharp": "npm:sharp@^0.33.0"
|
"sharp": "npm:sharp@^0.33.0"
|
||||||
}
|
}
|
||||||
|
|||||||
1
image-watermark/deno.lock
generated
1
image-watermark/deno.lock
generated
@@ -254,6 +254,7 @@
|
|||||||
"workspace": {
|
"workspace": {
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"jsr:@std/assert@1",
|
"jsr:@std/assert@1",
|
||||||
|
"jsr:@std/cli@*",
|
||||||
"npm:sharp@0.33"
|
"npm:sharp@0.33"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/env -S deno run -A
|
#!/usr/bin/env -S deno run -A
|
||||||
|
|
||||||
import sharp from "sharp";
|
import sharp from "sharp";
|
||||||
import { Buffer } from "node:buffer";
|
import {Buffer} from "node:buffer";
|
||||||
import {parseArgs} from "jsr:@std/cli/parse-args";
|
import {parseArgs} from "@std/cli/parse-args";
|
||||||
|
|
||||||
export interface WatermarkOptions {
|
export interface WatermarkOptions {
|
||||||
/** 水印文字 */
|
/** 水印文字 */
|
||||||
@@ -23,6 +23,13 @@ export interface WatermarkOptions {
|
|||||||
outputPath?: string;
|
outputPath?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDefaultOutputPath(inputPath: string): string {
|
||||||
|
const lastDot = inputPath.lastIndexOf(".");
|
||||||
|
return lastDot !== -1
|
||||||
|
? `${inputPath.slice(0, lastDot)}_watermarked${inputPath.slice(lastDot)}`
|
||||||
|
: `${inputPath}_watermarked`;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在图片右下角添加文字水印
|
* 在图片右下角添加文字水印
|
||||||
* @param inputPath 输入图片路径
|
* @param inputPath 输入图片路径
|
||||||
@@ -44,10 +51,7 @@ export async function addWatermark(
|
|||||||
|
|
||||||
let outputPath = options.outputPath;
|
let outputPath = options.outputPath;
|
||||||
if (!outputPath) {
|
if (!outputPath) {
|
||||||
const lastDot = inputPath.lastIndexOf(".");
|
outputPath = getDefaultOutputPath(inputPath);
|
||||||
outputPath = lastDot !== -1
|
|
||||||
? `${inputPath.slice(0, lastDot)}_watermarked${inputPath.slice(lastDot)}`
|
|
||||||
: `${inputPath}_watermarked`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取原图信息
|
// 获取原图信息
|
||||||
@@ -68,30 +72,40 @@ export async function addWatermark(
|
|||||||
// text-anchor="end" 意味着文字的右边缘在指定的 x 坐标
|
// text-anchor="end" 意味着文字的右边缘在指定的 x 坐标
|
||||||
const textX = width - marginRight - 20;
|
const textX = width - marginRight - 20;
|
||||||
const textY = height - marginBottom;
|
const textY = height - marginBottom;
|
||||||
|
|
||||||
// 背景矩形的左上角坐标
|
// 背景矩形的左上角坐标
|
||||||
// 背景右边缘与文字右边缘对齐
|
// 背景右边缘与文字右边缘对齐
|
||||||
const rectX = textX - bgWidth + 20;
|
const rectX = textX - bgWidth + 20;
|
||||||
const rectY = textY - bgHeight + backgroundPadding + 2;
|
const rectY = textY - bgHeight + backgroundPadding + 2;
|
||||||
|
|
||||||
// 创建最终 SVG 水印
|
// 创建最终 SVG 水印
|
||||||
const svg = `<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
|
const svg =
|
||||||
${backgroundColor ? `<rect x="${rectX}" y="${rectY}" width="${bgWidth}" height="${bgHeight}" fill="${backgroundColor}" rx="4"/>` : ""}
|
`<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
|
||||||
<text x="${textX}" y="${textY}" font-family="sans-serif" font-size="${fontSize}" fill="${color}" text-anchor="end" dominant-baseline="alphabetic">${escapeXml(text)}</text>
|
${
|
||||||
|
backgroundColor
|
||||||
|
? `<rect x="${rectX}" y="${rectY}" width="${bgWidth}" height="${bgHeight}" fill="${backgroundColor}" rx="4"/>`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
<text x="${textX}" y="${textY}" font-family="sans-serif" font-size="${fontSize}" fill="${color}" text-anchor="end" dominant-baseline="alphabetic">${
|
||||||
|
escapeXml(text)
|
||||||
|
}</text>
|
||||||
</svg>`;
|
</svg>`;
|
||||||
|
|
||||||
// 将 SVG 水印叠加到原图
|
// 将 SVG 水印叠加到原图
|
||||||
await sharp(inputPath)
|
await sharp(inputPath).composite([
|
||||||
.composite([
|
{
|
||||||
{
|
input: Buffer.from(svg),
|
||||||
input: Buffer.from(svg),
|
top: 0,
|
||||||
top: 0,
|
left: 0,
|
||||||
left: 0,
|
},
|
||||||
},
|
])
|
||||||
])
|
|
||||||
.toFile(outputPath);
|
.toFile(outputPath);
|
||||||
|
|
||||||
console.log(`Added watermark: ${outputPath}`);
|
if (inputPath == outputPath) {
|
||||||
|
console.log(`Added watermark: ${inputPath}`);
|
||||||
|
} else {
|
||||||
|
console.log(`Added watermark: ${inputPath} -> ${outputPath}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -108,8 +122,8 @@ function escapeXml(str: string): string {
|
|||||||
|
|
||||||
if (import.meta.main) {
|
if (import.meta.main) {
|
||||||
const flags = parseArgs(Deno.args, {
|
const flags = parseArgs(Deno.args, {
|
||||||
boolean: ["help"],
|
boolean: ["help", "unsafe-replace-file"],
|
||||||
string: ["watermark"],
|
string: ["watermark"],
|
||||||
});
|
});
|
||||||
|
|
||||||
const watermarkText = flags.watermark;
|
const watermarkText = flags.watermark;
|
||||||
@@ -117,17 +131,26 @@ if (import.meta.main) {
|
|||||||
|
|
||||||
if (!watermarkText || imagePaths.length == 0) {
|
if (!watermarkText || imagePaths.length == 0) {
|
||||||
console.log("Usage: deno run -A main.ts --watermark 'watermark' <FILES>");
|
console.log("Usage: deno run -A main.ts --watermark 'watermark' <FILES>");
|
||||||
console.log("Example: deno run -A main.ts --watermark '© 2026 MyName' image.jpg");
|
console.log(
|
||||||
|
"Example: deno run -A main.ts --watermark '© 2026 MyName' image.jpg",
|
||||||
|
);
|
||||||
Deno.exit(1);
|
Deno.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const imagePath of imagePaths) {
|
for (const imagePath of imagePaths) {
|
||||||
await addWatermark(imagePath+'', {
|
const imagePathStr = imagePath as string;
|
||||||
|
|
||||||
|
const options = {
|
||||||
fontSize: 32,
|
fontSize: 32,
|
||||||
text: watermarkText,
|
text: watermarkText,
|
||||||
color: 'black',
|
color: "black",
|
||||||
backgroundPadding: 2,
|
backgroundPadding: 2,
|
||||||
backgroundColor: 'white'
|
backgroundColor: "white",
|
||||||
});
|
} as WatermarkOptions;
|
||||||
|
if (flags["unsafe-replace-file"]) {
|
||||||
|
options.outputPath = imagePathStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
await addWatermark(imagePathStr, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user