watermarkDownload.ts
2.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import { saveAs } from "file-saver";
import JSZip from "jszip";
export async function fetchImageBlob(url: string): Promise<Blob> {
const r = await fetch(url, { credentials: "include" });
if (!r.ok) {
throw new Error(`加载图片失败(${r.status})`);
}
return r.blob();
}
/** 在图片右下角叠加白字描边(作者、日期) */
export async function blobWithWatermark(
blob: Blob,
line1: string,
line2: string
): Promise<Blob> {
const bmp = await createImageBitmap(blob);
const canvas = document.createElement("canvas");
const width = bmp.width;
const height = bmp.height;
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext("2d");
if (!ctx) {
throw new Error("canvas");
}
ctx.drawImage(bmp, 0, 0);
bmp.close();
const fs = Math.max(12, Math.round(width / 52));
const pad = Math.round(fs * 0.85);
ctx.font = `${fs}px "PingFang SC","Microsoft YaHei","Helvetica Neue",sans-serif`;
ctx.textBaseline = "bottom";
ctx.textAlign = "right";
const lines = [line1, line2].filter((s) => s.trim().length > 0);
const maxTextWidth = lines.reduce((m, t) => Math.max(m, Math.ceil(ctx.measureText(t).width)), 0);
const lineGap = Math.round(fs * 0.35);
const panelPadX = Math.round(fs * 0.7);
const panelPadY = Math.round(fs * 0.55);
const panelW = maxTextWidth + panelPadX * 2;
const panelH = lines.length * fs + (lines.length - 1) * lineGap + panelPadY * 2;
const panelX = width - pad - panelW;
const panelY = height - pad - panelH;
if (lines.length > 0) {
ctx.fillStyle = "rgba(30, 30, 30, 0.38)";
ctx.fillRect(panelX, panelY, panelW, panelH);
}
let y = height - pad;
const x = width - pad;
for (let i = lines.length - 1; i >= 0; i--) {
const t = lines[i];
ctx.strokeStyle = "rgba(0,0,0,0.52)";
ctx.lineWidth = Math.max(1.5, fs / 9);
ctx.fillStyle = "rgba(255,255,255,0.95)";
ctx.strokeText(t, x, y);
ctx.fillText(t, x, y);
y -= fs + lineGap;
}
return new Promise((resolve, reject) => {
canvas.toBlob(
(b) => (b ? resolve(b) : reject(new Error("导出失败"))),
"image/jpeg",
0.92
);
});
}
export function triggerSave(blob: Blob, filename: string) {
saveAs(blob, filename);
}
export async function zipBlobs(
entries: Array<{ path: string; blob: Blob }>
): Promise<Blob> {
const zip = new JSZip();
for (const { path, blob } of entries) {
zip.file(path, blob);
}
return zip.generateAsync({ type: "blob" });
}