@lacspace/image 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +51 -0
- package/README.md +136 -0
- package/dist/index.cjs +1598 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +229 -0
- package/dist/index.d.ts +229 -0
- package/dist/index.js +1582 -0
- package/dist/index.js.map +1 -0
- package/package.json +74 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1582 @@
|
|
|
1
|
+
// src/color.ts
|
|
2
|
+
var NAMED = {
|
|
3
|
+
transparent: [0, 0, 0, 0],
|
|
4
|
+
black: [0, 0, 0, 255],
|
|
5
|
+
white: [255, 255, 255, 255],
|
|
6
|
+
red: [255, 0, 0, 255],
|
|
7
|
+
green: [0, 128, 0, 255],
|
|
8
|
+
blue: [0, 0, 255, 255]
|
|
9
|
+
};
|
|
10
|
+
function clampByte(n) {
|
|
11
|
+
n = Math.round(n);
|
|
12
|
+
return n < 0 ? 0 : n > 255 ? 255 : n;
|
|
13
|
+
}
|
|
14
|
+
function parseColor(input) {
|
|
15
|
+
const s = String(input).trim().toLowerCase();
|
|
16
|
+
if (s in NAMED) return [...NAMED[s]];
|
|
17
|
+
if (s[0] === "#") {
|
|
18
|
+
const hex = s.slice(1);
|
|
19
|
+
if (hex.length === 3 || hex.length === 4) {
|
|
20
|
+
const r = parseInt(hex[0] + hex[0], 16);
|
|
21
|
+
const g = parseInt(hex[1] + hex[1], 16);
|
|
22
|
+
const b = parseInt(hex[2] + hex[2], 16);
|
|
23
|
+
const a = hex.length === 4 ? parseInt(hex[3] + hex[3], 16) : 255;
|
|
24
|
+
return [r, g, b, a];
|
|
25
|
+
}
|
|
26
|
+
if (hex.length === 6 || hex.length === 8) {
|
|
27
|
+
const r = parseInt(hex.slice(0, 2), 16);
|
|
28
|
+
const g = parseInt(hex.slice(2, 4), 16);
|
|
29
|
+
const b = parseInt(hex.slice(4, 6), 16);
|
|
30
|
+
const a = hex.length === 8 ? parseInt(hex.slice(6, 8), 16) : 255;
|
|
31
|
+
return [r, g, b, a];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const m = s.match(/^rgba?\(([^)]+)\)$/);
|
|
35
|
+
if (m) {
|
|
36
|
+
const parts = m[1].split(/[,/\s]+/).filter(Boolean);
|
|
37
|
+
const r = clampByte(parseFloat(parts[0] ?? "0"));
|
|
38
|
+
const g = clampByte(parseFloat(parts[1] ?? "0"));
|
|
39
|
+
const b = clampByte(parseFloat(parts[2] ?? "0"));
|
|
40
|
+
let a = 255;
|
|
41
|
+
if (parts[3] != null) {
|
|
42
|
+
const av = parseFloat(parts[3]);
|
|
43
|
+
a = parts[3].includes("%") ? clampByte(av / 100 * 255) : clampByte(av <= 1 ? av * 255 : av);
|
|
44
|
+
}
|
|
45
|
+
return [r, g, b, a];
|
|
46
|
+
}
|
|
47
|
+
return [0, 0, 0, 255];
|
|
48
|
+
}
|
|
49
|
+
function blendOver(dst, src) {
|
|
50
|
+
const sa = src[3] / 255;
|
|
51
|
+
const da = dst[3] / 255;
|
|
52
|
+
const outA = sa + da * (1 - sa);
|
|
53
|
+
if (outA === 0) return [0, 0, 0, 0];
|
|
54
|
+
const r = (src[0] * sa + dst[0] * da * (1 - sa)) / outA;
|
|
55
|
+
const g = (src[1] * sa + dst[1] * da * (1 - sa)) / outA;
|
|
56
|
+
const b = (src[2] * sa + dst[2] * da * (1 - sa)) / outA;
|
|
57
|
+
return [clampByte(r), clampByte(g), clampByte(b), clampByte(outA * 255)];
|
|
58
|
+
}
|
|
59
|
+
function mix(a, b, t) {
|
|
60
|
+
return [
|
|
61
|
+
clampByte(a[0] + (b[0] - a[0]) * t),
|
|
62
|
+
clampByte(a[1] + (b[1] - a[1]) * t),
|
|
63
|
+
clampByte(a[2] + (b[2] - a[2]) * t),
|
|
64
|
+
clampByte(a[3] + (b[3] - a[3]) * t)
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/surface.ts
|
|
69
|
+
function sortStops(stops) {
|
|
70
|
+
return stops.map((s) => ({ offset: Math.max(0, Math.min(1, s.offset)), rgba: parseColor(s.color) })).sort((a, b) => a.offset - b.offset);
|
|
71
|
+
}
|
|
72
|
+
function sampleStops(stops, t) {
|
|
73
|
+
if (stops.length === 0) return [0, 0, 0, 0];
|
|
74
|
+
if (t <= stops[0].offset) return stops[0].rgba;
|
|
75
|
+
const last = stops[stops.length - 1];
|
|
76
|
+
if (t >= last.offset) return last.rgba;
|
|
77
|
+
for (let i = 1; i < stops.length; i++) {
|
|
78
|
+
const a = stops[i - 1];
|
|
79
|
+
const b = stops[i];
|
|
80
|
+
if (t <= b.offset) {
|
|
81
|
+
const span = b.offset - a.offset || 1;
|
|
82
|
+
return mix(a.rgba, b.rgba, (t - a.offset) / span);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return last.rgba;
|
|
86
|
+
}
|
|
87
|
+
function rng(seed) {
|
|
88
|
+
let s = seed >>> 0 || 1;
|
|
89
|
+
return () => {
|
|
90
|
+
s |= 0;
|
|
91
|
+
s = s + 1831565813 | 0;
|
|
92
|
+
let t = Math.imul(s ^ s >>> 15, 1 | s);
|
|
93
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
94
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
var Surface = class _Surface {
|
|
98
|
+
constructor(width, height, data) {
|
|
99
|
+
this.width = Math.max(1, Math.floor(width));
|
|
100
|
+
this.height = Math.max(1, Math.floor(height));
|
|
101
|
+
this.data = data ?? new Uint8ClampedArray(this.width * this.height * 4);
|
|
102
|
+
}
|
|
103
|
+
/** Wrap an existing RGBA source (e.g. a Canvas ImageData) without copying. */
|
|
104
|
+
static from(src) {
|
|
105
|
+
return new _Surface(src.width, src.height, src.data);
|
|
106
|
+
}
|
|
107
|
+
idx(x, y) {
|
|
108
|
+
return (y * this.width + x) * 4;
|
|
109
|
+
}
|
|
110
|
+
set(x, y, c) {
|
|
111
|
+
const i = this.idx(x, y);
|
|
112
|
+
const d = this.data;
|
|
113
|
+
if (c[3] === 255) {
|
|
114
|
+
d[i] = c[0];
|
|
115
|
+
d[i + 1] = c[1];
|
|
116
|
+
d[i + 2] = c[2];
|
|
117
|
+
d[i + 3] = 255;
|
|
118
|
+
} else {
|
|
119
|
+
const dst = [d[i], d[i + 1], d[i + 2], d[i + 3]];
|
|
120
|
+
const out = blendOver(dst, c);
|
|
121
|
+
d[i] = out[0];
|
|
122
|
+
d[i + 1] = out[1];
|
|
123
|
+
d[i + 2] = out[2];
|
|
124
|
+
d[i + 3] = out[3];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** Paint the whole surface a solid colour (replaces, does not blend). */
|
|
128
|
+
fill(color) {
|
|
129
|
+
const [r, g, b, a] = parseColor(color);
|
|
130
|
+
const d = this.data;
|
|
131
|
+
for (let i = 0; i < d.length; i += 4) {
|
|
132
|
+
d[i] = r;
|
|
133
|
+
d[i + 1] = g;
|
|
134
|
+
d[i + 2] = b;
|
|
135
|
+
d[i + 3] = a;
|
|
136
|
+
}
|
|
137
|
+
return this;
|
|
138
|
+
}
|
|
139
|
+
/** Blend a rectangle of colour over the surface. */
|
|
140
|
+
rect(x, y, w, h, color) {
|
|
141
|
+
const c = parseColor(color);
|
|
142
|
+
const x0 = Math.max(0, Math.floor(x));
|
|
143
|
+
const y0 = Math.max(0, Math.floor(y));
|
|
144
|
+
const x1 = Math.min(this.width, Math.floor(x + w));
|
|
145
|
+
const y1 = Math.min(this.height, Math.floor(y + h));
|
|
146
|
+
for (let py = y0; py < y1; py++) for (let px = x0; px < x1; px++) this.set(px, py, c);
|
|
147
|
+
return this;
|
|
148
|
+
}
|
|
149
|
+
linearGradient(opts) {
|
|
150
|
+
const stops = sortStops(opts.stops);
|
|
151
|
+
const angle = (opts.angle ?? 0) * Math.PI / 180;
|
|
152
|
+
const dx = Math.cos(angle);
|
|
153
|
+
const dy = Math.sin(angle);
|
|
154
|
+
const w = this.width;
|
|
155
|
+
const h = this.height;
|
|
156
|
+
const projs = [0, w * dx, h * dy, w * dx + h * dy];
|
|
157
|
+
const min = Math.min(...projs);
|
|
158
|
+
const span = Math.max(...projs) - min || 1;
|
|
159
|
+
for (let py = 0; py < h; py++) {
|
|
160
|
+
for (let px = 0; px < w; px++) {
|
|
161
|
+
const t = ((px + 0.5) * dx + (py + 0.5) * dy - min) / span;
|
|
162
|
+
this.set(px, py, sampleStops(stops, t));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return this;
|
|
166
|
+
}
|
|
167
|
+
radialGradient(opts) {
|
|
168
|
+
const stops = sortStops(opts.stops);
|
|
169
|
+
const w = this.width;
|
|
170
|
+
const h = this.height;
|
|
171
|
+
const cx = (opts.cx ?? 0.5) * w;
|
|
172
|
+
const cy = (opts.cy ?? 0.5) * h;
|
|
173
|
+
const radius = (opts.radius ?? 0.75) * Math.max(w, h);
|
|
174
|
+
for (let py = 0; py < h; py++) {
|
|
175
|
+
for (let px = 0; px < w; px++) {
|
|
176
|
+
const dx = px + 0.5 - cx;
|
|
177
|
+
const dy = py + 0.5 - cy;
|
|
178
|
+
const t = Math.sqrt(dx * dx + dy * dy) / (radius || 1);
|
|
179
|
+
this.set(px, py, sampleStops(stops, t));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return this;
|
|
183
|
+
}
|
|
184
|
+
pattern(kind, opts = {}) {
|
|
185
|
+
const size = Math.max(1, opts.size ?? 24);
|
|
186
|
+
const color = parseColor(opts.color ?? "rgba(255,255,255,0.13)");
|
|
187
|
+
const w = this.width;
|
|
188
|
+
const h = this.height;
|
|
189
|
+
if (kind === "noise") {
|
|
190
|
+
const rand = rng(opts.seed ?? 1);
|
|
191
|
+
const strength = opts.strength ?? 0.06;
|
|
192
|
+
const d = this.data;
|
|
193
|
+
for (let i = 0; i < d.length; i += 4) {
|
|
194
|
+
const n = (rand() - 0.5) * 2 * strength * 255;
|
|
195
|
+
d[i] = d[i] + n;
|
|
196
|
+
d[i + 1] = d[i + 1] + n;
|
|
197
|
+
d[i + 2] = d[i + 2] + n;
|
|
198
|
+
}
|
|
199
|
+
return this;
|
|
200
|
+
}
|
|
201
|
+
if (kind === "dots") {
|
|
202
|
+
const r = Math.max(1, size / 6);
|
|
203
|
+
for (let cy = size / 2; cy < h; cy += size)
|
|
204
|
+
for (let cx = size / 2; cx < w; cx += size)
|
|
205
|
+
for (let py = -r; py <= r; py++)
|
|
206
|
+
for (let px = -r; px <= r; px++)
|
|
207
|
+
if (px * px + py * py <= r * r) this.set(Math.round(cx + px), Math.round(cy + py), color);
|
|
208
|
+
return this;
|
|
209
|
+
}
|
|
210
|
+
if (kind === "grid") {
|
|
211
|
+
for (let x = 0; x < w; x += size) this.rect(x, 0, 1, h, opts.color ?? "rgba(255,255,255,0.13)");
|
|
212
|
+
for (let y = 0; y < h; y += size) this.rect(0, y, w, 1, opts.color ?? "rgba(255,255,255,0.13)");
|
|
213
|
+
return this;
|
|
214
|
+
}
|
|
215
|
+
if (kind === "stripes") {
|
|
216
|
+
const angle = opts.angle ?? 45;
|
|
217
|
+
for (let py = 0; py < h; py++) {
|
|
218
|
+
for (let px = 0; px < w; px++) {
|
|
219
|
+
let coord;
|
|
220
|
+
if (angle === 0) coord = px;
|
|
221
|
+
else if (angle === 90) coord = py;
|
|
222
|
+
else coord = px + py;
|
|
223
|
+
if (Math.floor(coord / size) % 2 === 0) this.set(px, py, color);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return this;
|
|
227
|
+
}
|
|
228
|
+
for (let py = 0; py < h; py++) {
|
|
229
|
+
for (let px = 0; px < w; px++) {
|
|
230
|
+
const on = (Math.floor(px / size) + Math.floor(py / size)) % 2 === 0;
|
|
231
|
+
if (on) this.set(px, py, color);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return this;
|
|
235
|
+
}
|
|
236
|
+
/** Bilinear sample of a source at fractional (u,v) in source pixel space. */
|
|
237
|
+
static sampleBilinear(src, u, v) {
|
|
238
|
+
const x = Math.max(0, Math.min(src.width - 1, u));
|
|
239
|
+
const y = Math.max(0, Math.min(src.height - 1, v));
|
|
240
|
+
const x0 = Math.floor(x);
|
|
241
|
+
const y0 = Math.floor(y);
|
|
242
|
+
const x1 = Math.min(src.width - 1, x0 + 1);
|
|
243
|
+
const y1 = Math.min(src.height - 1, y0 + 1);
|
|
244
|
+
const fx = x - x0;
|
|
245
|
+
const fy = y - y0;
|
|
246
|
+
const at = (px, py) => {
|
|
247
|
+
const i = (py * src.width + px) * 4;
|
|
248
|
+
return [src.data[i], src.data[i + 1], src.data[i + 2], src.data[i + 3]];
|
|
249
|
+
};
|
|
250
|
+
const top = mix(at(x0, y0), at(x1, y0), fx);
|
|
251
|
+
const bot = mix(at(x0, y1), at(x1, y1), fx);
|
|
252
|
+
return mix(top, bot, fy);
|
|
253
|
+
}
|
|
254
|
+
/** Place another image/surface onto this one, with fit + resampling. */
|
|
255
|
+
drawImage(src, o = {}) {
|
|
256
|
+
const dx = o.x ?? 0;
|
|
257
|
+
const dy = o.y ?? 0;
|
|
258
|
+
const dw = o.width ?? src.width;
|
|
259
|
+
const dh = o.height ?? src.height;
|
|
260
|
+
const fit2 = o.fit ?? "cover";
|
|
261
|
+
const nearest = o.resample === "nearest";
|
|
262
|
+
let scaleX = src.width / dw;
|
|
263
|
+
let scaleY = src.height / dh;
|
|
264
|
+
let offU = 0;
|
|
265
|
+
let offV = 0;
|
|
266
|
+
if (fit2 === "cover" || fit2 === "contain") {
|
|
267
|
+
const s = fit2 === "cover" ? Math.min(scaleX, scaleY) : Math.max(scaleX, scaleY);
|
|
268
|
+
scaleX = scaleY = s;
|
|
269
|
+
offU = (src.width - dw * s) / 2;
|
|
270
|
+
offV = (src.height - dh * s) / 2;
|
|
271
|
+
}
|
|
272
|
+
const x0 = Math.max(0, Math.floor(dx));
|
|
273
|
+
const y0 = Math.max(0, Math.floor(dy));
|
|
274
|
+
const x1 = Math.min(this.width, Math.floor(dx + dw));
|
|
275
|
+
const y1 = Math.min(this.height, Math.floor(dy + dh));
|
|
276
|
+
for (let py = y0; py < y1; py++) {
|
|
277
|
+
for (let px = x0; px < x1; px++) {
|
|
278
|
+
const u = (px - dx) * scaleX + offU;
|
|
279
|
+
const v = (py - dy) * scaleY + offV;
|
|
280
|
+
if (fit2 === "contain" && (u < 0 || v < 0 || u >= src.width || v >= src.height)) continue;
|
|
281
|
+
const c = nearest ? (() => {
|
|
282
|
+
const i = (Math.min(src.height - 1, Math.max(0, Math.round(v))) * src.width + Math.min(src.width - 1, Math.max(0, Math.round(u)))) * 4;
|
|
283
|
+
return [src.data[i], src.data[i + 1], src.data[i + 2], src.data[i + 3]];
|
|
284
|
+
})() : _Surface.sampleBilinear(src, u, v);
|
|
285
|
+
this.set(px, py, c);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return this;
|
|
289
|
+
}
|
|
290
|
+
/** Return a NEW surface resampled to the given dimensions (bilinear). */
|
|
291
|
+
resize(width, height) {
|
|
292
|
+
const out = new _Surface(width, height);
|
|
293
|
+
const sx = this.width / out.width;
|
|
294
|
+
const sy = this.height / out.height;
|
|
295
|
+
for (let py = 0; py < out.height; py++) {
|
|
296
|
+
for (let px = 0; px < out.width; px++) {
|
|
297
|
+
const c = _Surface.sampleBilinear(this, (px + 0.5) * sx - 0.5, (py + 0.5) * sy - 0.5);
|
|
298
|
+
const i = (py * out.width + px) * 4;
|
|
299
|
+
out.data[i] = c[0];
|
|
300
|
+
out.data[i + 1] = c[1];
|
|
301
|
+
out.data[i + 2] = c[2];
|
|
302
|
+
out.data[i + 3] = c[3];
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return out;
|
|
306
|
+
}
|
|
307
|
+
/** Return a NEW surface cropped to the given rectangle. */
|
|
308
|
+
crop(x, y, w, h) {
|
|
309
|
+
const out = new _Surface(w, h);
|
|
310
|
+
for (let py = 0; py < out.height; py++) {
|
|
311
|
+
for (let px = 0; px < out.width; px++) {
|
|
312
|
+
const srcX = Math.min(this.width - 1, Math.max(0, x + px));
|
|
313
|
+
const srcY = Math.min(this.height - 1, Math.max(0, y + py));
|
|
314
|
+
const si = (srcY * this.width + srcX) * 4;
|
|
315
|
+
const di = (py * out.width + px) * 4;
|
|
316
|
+
out.data[di] = this.data[si];
|
|
317
|
+
out.data[di + 1] = this.data[si + 1];
|
|
318
|
+
out.data[di + 2] = this.data[si + 2];
|
|
319
|
+
out.data[di + 3] = this.data[si + 3];
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return out;
|
|
323
|
+
}
|
|
324
|
+
/** Flatten transparency onto a solid background, returning a NEW opaque surface. */
|
|
325
|
+
flatten(background = "#ffffff") {
|
|
326
|
+
const bg = parseColor(background);
|
|
327
|
+
const out = new _Surface(this.width, this.height);
|
|
328
|
+
for (let i = 0; i < this.data.length; i += 4) {
|
|
329
|
+
const src = [this.data[i], this.data[i + 1], this.data[i + 2], this.data[i + 3]];
|
|
330
|
+
const c = blendOver([bg[0], bg[1], bg[2], 255], src);
|
|
331
|
+
out.data[i] = c[0];
|
|
332
|
+
out.data[i + 1] = c[1];
|
|
333
|
+
out.data[i + 2] = c[2];
|
|
334
|
+
out.data[i + 3] = 255;
|
|
335
|
+
}
|
|
336
|
+
return out;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Return a NEW surface with each channel reduced to `bits` bits (2..8).
|
|
340
|
+
* Fewer distinct values compress far better as lossless PNG — the main lever
|
|
341
|
+
* for hitting a PNG size budget on noisy/photographic content.
|
|
342
|
+
*/
|
|
343
|
+
posterize(bits) {
|
|
344
|
+
const b = Math.max(1, Math.min(8, Math.floor(bits)));
|
|
345
|
+
if (b >= 8) return this.clone();
|
|
346
|
+
const levels = (1 << b) - 1;
|
|
347
|
+
const out = new _Surface(this.width, this.height, new Uint8ClampedArray(this.data));
|
|
348
|
+
const d = out.data;
|
|
349
|
+
for (let i = 0; i < d.length; i += 4) {
|
|
350
|
+
d[i] = Math.round(Math.round(d[i] / 255 * levels) / levels * 255);
|
|
351
|
+
d[i + 1] = Math.round(Math.round(d[i + 1] / 255 * levels) / levels * 255);
|
|
352
|
+
d[i + 2] = Math.round(Math.round(d[i + 2] / 255 * levels) / levels * 255);
|
|
353
|
+
}
|
|
354
|
+
return out;
|
|
355
|
+
}
|
|
356
|
+
clone() {
|
|
357
|
+
return new _Surface(this.width, this.height, new Uint8ClampedArray(this.data));
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
function gradient(width, height, opts) {
|
|
361
|
+
return new Surface(width, height).linearGradient(opts);
|
|
362
|
+
}
|
|
363
|
+
function radial(width, height, opts) {
|
|
364
|
+
return new Surface(width, height).radialGradient(opts);
|
|
365
|
+
}
|
|
366
|
+
function pattern(width, height, base, kind, opts) {
|
|
367
|
+
return new Surface(width, height).fill(base).pattern(kind, opts);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// src/env.ts
|
|
371
|
+
function hasCanvas() {
|
|
372
|
+
if (typeof OffscreenCanvas !== "undefined") return true;
|
|
373
|
+
return typeof document !== "undefined" && typeof document.createElement === "function";
|
|
374
|
+
}
|
|
375
|
+
function makeCanvas(width, height) {
|
|
376
|
+
if (typeof OffscreenCanvas !== "undefined") return new OffscreenCanvas(width, height);
|
|
377
|
+
const el = document.createElement("canvas");
|
|
378
|
+
el.width = width;
|
|
379
|
+
el.height = height;
|
|
380
|
+
return el;
|
|
381
|
+
}
|
|
382
|
+
var MIME = {
|
|
383
|
+
png: "image/png",
|
|
384
|
+
jpeg: "image/jpeg",
|
|
385
|
+
webp: "image/webp"
|
|
386
|
+
};
|
|
387
|
+
async function canvasToBytes(canvas, format, quality) {
|
|
388
|
+
const type = MIME[format];
|
|
389
|
+
const q = quality != null ? Math.max(0, Math.min(1, quality / 100)) : void 0;
|
|
390
|
+
let blob;
|
|
391
|
+
if (canvas.convertToBlob) {
|
|
392
|
+
blob = await canvas.convertToBlob({ type, quality: q });
|
|
393
|
+
} else if (canvas.toBlob) {
|
|
394
|
+
blob = await new Promise((res) => canvas.toBlob((b) => res(b), type, q));
|
|
395
|
+
} else {
|
|
396
|
+
throw new Error("Canvas has no toBlob/convertToBlob");
|
|
397
|
+
}
|
|
398
|
+
if (!blob) throw new Error(`Canvas failed to encode ${format}`);
|
|
399
|
+
return new Uint8Array(await blob.arrayBuffer());
|
|
400
|
+
}
|
|
401
|
+
async function encodeViaCanvas(source, format, quality) {
|
|
402
|
+
if (!hasCanvas()) throw new Error("No canvas available in this runtime");
|
|
403
|
+
const canvas = makeCanvas(source.width, source.height);
|
|
404
|
+
const ctx = canvas.getContext("2d");
|
|
405
|
+
if (!ctx) throw new Error("Could not get a 2D context");
|
|
406
|
+
const img = new ImageData(new Uint8ClampedArray(source.data), source.width, source.height);
|
|
407
|
+
ctx.putImageData(img, 0, 0);
|
|
408
|
+
return canvasToBytes(canvas, format, quality);
|
|
409
|
+
}
|
|
410
|
+
async function canvasSupports(format) {
|
|
411
|
+
if (!hasCanvas()) return false;
|
|
412
|
+
try {
|
|
413
|
+
const test = makeCanvas(2, 2);
|
|
414
|
+
const bytes = await canvasToBytes(test, format, 80);
|
|
415
|
+
if (format === "webp") return bytes[0] !== 137;
|
|
416
|
+
return bytes.length > 0;
|
|
417
|
+
} catch {
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// src/png.ts
|
|
423
|
+
var SIGNATURE = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
424
|
+
var CRC_TABLE = (() => {
|
|
425
|
+
const t = new Uint32Array(256);
|
|
426
|
+
for (let n = 0; n < 256; n++) {
|
|
427
|
+
let c = n;
|
|
428
|
+
for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
429
|
+
t[n] = c >>> 0;
|
|
430
|
+
}
|
|
431
|
+
return t;
|
|
432
|
+
})();
|
|
433
|
+
function crc32(bytes) {
|
|
434
|
+
let c = 4294967295;
|
|
435
|
+
for (let i = 0; i < bytes.length; i++) c = CRC_TABLE[(c ^ bytes[i]) & 255] ^ c >>> 8;
|
|
436
|
+
return (c ^ 4294967295) >>> 0;
|
|
437
|
+
}
|
|
438
|
+
function adler32(bytes) {
|
|
439
|
+
let a = 1;
|
|
440
|
+
let b = 0;
|
|
441
|
+
const MOD = 65521;
|
|
442
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
443
|
+
a = (a + bytes[i]) % MOD;
|
|
444
|
+
b = (b + a) % MOD;
|
|
445
|
+
}
|
|
446
|
+
return (b << 16 | a) >>> 0;
|
|
447
|
+
}
|
|
448
|
+
function u32(n) {
|
|
449
|
+
return new Uint8Array([n >>> 24 & 255, n >>> 16 & 255, n >>> 8 & 255, n & 255]);
|
|
450
|
+
}
|
|
451
|
+
function chunk(type, data) {
|
|
452
|
+
const typeBytes = new Uint8Array([type.charCodeAt(0), type.charCodeAt(1), type.charCodeAt(2), type.charCodeAt(3)]);
|
|
453
|
+
const body = new Uint8Array(typeBytes.length + data.length);
|
|
454
|
+
body.set(typeBytes, 0);
|
|
455
|
+
body.set(data, typeBytes.length);
|
|
456
|
+
const out = new Uint8Array(4 + body.length + 4);
|
|
457
|
+
out.set(u32(data.length), 0);
|
|
458
|
+
out.set(body, 4);
|
|
459
|
+
out.set(u32(crc32(body)), 4 + body.length);
|
|
460
|
+
return out;
|
|
461
|
+
}
|
|
462
|
+
function storedZlib(data) {
|
|
463
|
+
const MAX = 65535;
|
|
464
|
+
const blocks = [];
|
|
465
|
+
let offset = 0;
|
|
466
|
+
if (data.length === 0) {
|
|
467
|
+
blocks.push(new Uint8Array([1, 0, 0, 255, 255]));
|
|
468
|
+
}
|
|
469
|
+
while (offset < data.length) {
|
|
470
|
+
const len = Math.min(MAX, data.length - offset);
|
|
471
|
+
const final = offset + len >= data.length ? 1 : 0;
|
|
472
|
+
const header = new Uint8Array(5);
|
|
473
|
+
header[0] = final;
|
|
474
|
+
header[1] = len & 255;
|
|
475
|
+
header[2] = len >>> 8 & 255;
|
|
476
|
+
header[3] = ~len & 255;
|
|
477
|
+
header[4] = ~len >>> 8 & 255;
|
|
478
|
+
blocks.push(header, data.subarray(offset, offset + len));
|
|
479
|
+
offset += len;
|
|
480
|
+
}
|
|
481
|
+
const bodyLen = blocks.reduce((n, b) => n + b.length, 0);
|
|
482
|
+
const out = new Uint8Array(2 + bodyLen + 4);
|
|
483
|
+
out[0] = 120;
|
|
484
|
+
out[1] = 1;
|
|
485
|
+
let p = 2;
|
|
486
|
+
for (const b of blocks) {
|
|
487
|
+
out.set(b, p);
|
|
488
|
+
p += b.length;
|
|
489
|
+
}
|
|
490
|
+
out.set(u32(adler32(data)), p);
|
|
491
|
+
return out;
|
|
492
|
+
}
|
|
493
|
+
function isNode() {
|
|
494
|
+
return typeof process !== "undefined" && !!process.versions?.node;
|
|
495
|
+
}
|
|
496
|
+
async function deflate(data, level) {
|
|
497
|
+
if (isNode()) {
|
|
498
|
+
try {
|
|
499
|
+
const z = await import(
|
|
500
|
+
/* @vite-ignore */
|
|
501
|
+
'zlib'
|
|
502
|
+
);
|
|
503
|
+
return new Uint8Array(z.deflateSync(data, { level }));
|
|
504
|
+
} catch {
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return storedZlib(data);
|
|
508
|
+
}
|
|
509
|
+
function filterScanlines(src) {
|
|
510
|
+
const { width, height, data } = src;
|
|
511
|
+
const rowBytes = width * 4;
|
|
512
|
+
const out = new Uint8Array(height * (rowBytes + 1));
|
|
513
|
+
for (let y = 0; y < height; y++) {
|
|
514
|
+
const o = y * (rowBytes + 1);
|
|
515
|
+
out[o] = 0;
|
|
516
|
+
out.set(data.subarray(y * rowBytes, y * rowBytes + rowBytes), o + 1);
|
|
517
|
+
}
|
|
518
|
+
return out;
|
|
519
|
+
}
|
|
520
|
+
function assemble(source, idat) {
|
|
521
|
+
const ihdrData = new Uint8Array(13);
|
|
522
|
+
ihdrData.set(u32(source.width), 0);
|
|
523
|
+
ihdrData.set(u32(source.height), 4);
|
|
524
|
+
ihdrData[8] = 8;
|
|
525
|
+
ihdrData[9] = 6;
|
|
526
|
+
ihdrData[10] = 0;
|
|
527
|
+
ihdrData[11] = 0;
|
|
528
|
+
ihdrData[12] = 0;
|
|
529
|
+
const ihdr = chunk("IHDR", ihdrData);
|
|
530
|
+
const idatChunk = chunk("IDAT", idat);
|
|
531
|
+
const iend = chunk("IEND", new Uint8Array(0));
|
|
532
|
+
const out = new Uint8Array(SIGNATURE.length + ihdr.length + idatChunk.length + iend.length);
|
|
533
|
+
let p = 0;
|
|
534
|
+
for (const part of [SIGNATURE, ihdr, idatChunk, iend]) {
|
|
535
|
+
out.set(part, p);
|
|
536
|
+
p += part.length;
|
|
537
|
+
}
|
|
538
|
+
return out;
|
|
539
|
+
}
|
|
540
|
+
async function encodePng(source, level = 9) {
|
|
541
|
+
const idat = await deflate(filterScanlines(source), level);
|
|
542
|
+
return assemble(source, idat);
|
|
543
|
+
}
|
|
544
|
+
function encodePngSync(source) {
|
|
545
|
+
return assemble(source, storedZlib(filterScanlines(source)));
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// src/jpeg.ts
|
|
549
|
+
var ZIGZAG = Int32Array.of(
|
|
550
|
+
0,
|
|
551
|
+
1,
|
|
552
|
+
5,
|
|
553
|
+
6,
|
|
554
|
+
14,
|
|
555
|
+
15,
|
|
556
|
+
27,
|
|
557
|
+
28,
|
|
558
|
+
2,
|
|
559
|
+
4,
|
|
560
|
+
7,
|
|
561
|
+
13,
|
|
562
|
+
16,
|
|
563
|
+
26,
|
|
564
|
+
29,
|
|
565
|
+
42,
|
|
566
|
+
3,
|
|
567
|
+
8,
|
|
568
|
+
12,
|
|
569
|
+
17,
|
|
570
|
+
25,
|
|
571
|
+
30,
|
|
572
|
+
41,
|
|
573
|
+
43,
|
|
574
|
+
9,
|
|
575
|
+
11,
|
|
576
|
+
18,
|
|
577
|
+
24,
|
|
578
|
+
31,
|
|
579
|
+
40,
|
|
580
|
+
44,
|
|
581
|
+
53,
|
|
582
|
+
10,
|
|
583
|
+
19,
|
|
584
|
+
23,
|
|
585
|
+
32,
|
|
586
|
+
39,
|
|
587
|
+
45,
|
|
588
|
+
52,
|
|
589
|
+
54,
|
|
590
|
+
20,
|
|
591
|
+
22,
|
|
592
|
+
33,
|
|
593
|
+
38,
|
|
594
|
+
46,
|
|
595
|
+
51,
|
|
596
|
+
55,
|
|
597
|
+
60,
|
|
598
|
+
21,
|
|
599
|
+
34,
|
|
600
|
+
37,
|
|
601
|
+
47,
|
|
602
|
+
50,
|
|
603
|
+
56,
|
|
604
|
+
59,
|
|
605
|
+
61,
|
|
606
|
+
35,
|
|
607
|
+
36,
|
|
608
|
+
48,
|
|
609
|
+
49,
|
|
610
|
+
57,
|
|
611
|
+
58,
|
|
612
|
+
62,
|
|
613
|
+
63
|
|
614
|
+
);
|
|
615
|
+
var STD_DC_LUM_CODES = Int32Array.of(0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0);
|
|
616
|
+
var STD_DC_LUM_VALUES = Int32Array.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
|
|
617
|
+
var STD_AC_LUM_CODES = Int32Array.of(0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125);
|
|
618
|
+
var STD_AC_LUM_VALUES = Int32Array.of(
|
|
619
|
+
1,
|
|
620
|
+
2,
|
|
621
|
+
3,
|
|
622
|
+
0,
|
|
623
|
+
4,
|
|
624
|
+
17,
|
|
625
|
+
5,
|
|
626
|
+
18,
|
|
627
|
+
33,
|
|
628
|
+
49,
|
|
629
|
+
65,
|
|
630
|
+
6,
|
|
631
|
+
19,
|
|
632
|
+
81,
|
|
633
|
+
97,
|
|
634
|
+
7,
|
|
635
|
+
34,
|
|
636
|
+
113,
|
|
637
|
+
20,
|
|
638
|
+
50,
|
|
639
|
+
129,
|
|
640
|
+
145,
|
|
641
|
+
161,
|
|
642
|
+
8,
|
|
643
|
+
35,
|
|
644
|
+
66,
|
|
645
|
+
177,
|
|
646
|
+
193,
|
|
647
|
+
21,
|
|
648
|
+
82,
|
|
649
|
+
209,
|
|
650
|
+
240,
|
|
651
|
+
36,
|
|
652
|
+
51,
|
|
653
|
+
98,
|
|
654
|
+
114,
|
|
655
|
+
130,
|
|
656
|
+
9,
|
|
657
|
+
10,
|
|
658
|
+
22,
|
|
659
|
+
23,
|
|
660
|
+
24,
|
|
661
|
+
25,
|
|
662
|
+
26,
|
|
663
|
+
37,
|
|
664
|
+
38,
|
|
665
|
+
39,
|
|
666
|
+
40,
|
|
667
|
+
41,
|
|
668
|
+
42,
|
|
669
|
+
52,
|
|
670
|
+
53,
|
|
671
|
+
54,
|
|
672
|
+
55,
|
|
673
|
+
56,
|
|
674
|
+
57,
|
|
675
|
+
58,
|
|
676
|
+
67,
|
|
677
|
+
68,
|
|
678
|
+
69,
|
|
679
|
+
70,
|
|
680
|
+
71,
|
|
681
|
+
72,
|
|
682
|
+
73,
|
|
683
|
+
74,
|
|
684
|
+
83,
|
|
685
|
+
84,
|
|
686
|
+
85,
|
|
687
|
+
86,
|
|
688
|
+
87,
|
|
689
|
+
88,
|
|
690
|
+
89,
|
|
691
|
+
90,
|
|
692
|
+
99,
|
|
693
|
+
100,
|
|
694
|
+
101,
|
|
695
|
+
102,
|
|
696
|
+
103,
|
|
697
|
+
104,
|
|
698
|
+
105,
|
|
699
|
+
106,
|
|
700
|
+
115,
|
|
701
|
+
116,
|
|
702
|
+
117,
|
|
703
|
+
118,
|
|
704
|
+
119,
|
|
705
|
+
120,
|
|
706
|
+
121,
|
|
707
|
+
122,
|
|
708
|
+
131,
|
|
709
|
+
132,
|
|
710
|
+
133,
|
|
711
|
+
134,
|
|
712
|
+
135,
|
|
713
|
+
136,
|
|
714
|
+
137,
|
|
715
|
+
138,
|
|
716
|
+
146,
|
|
717
|
+
147,
|
|
718
|
+
148,
|
|
719
|
+
149,
|
|
720
|
+
150,
|
|
721
|
+
151,
|
|
722
|
+
152,
|
|
723
|
+
153,
|
|
724
|
+
154,
|
|
725
|
+
162,
|
|
726
|
+
163,
|
|
727
|
+
164,
|
|
728
|
+
165,
|
|
729
|
+
166,
|
|
730
|
+
167,
|
|
731
|
+
168,
|
|
732
|
+
169,
|
|
733
|
+
170,
|
|
734
|
+
178,
|
|
735
|
+
179,
|
|
736
|
+
180,
|
|
737
|
+
181,
|
|
738
|
+
182,
|
|
739
|
+
183,
|
|
740
|
+
184,
|
|
741
|
+
185,
|
|
742
|
+
186,
|
|
743
|
+
194,
|
|
744
|
+
195,
|
|
745
|
+
196,
|
|
746
|
+
197,
|
|
747
|
+
198,
|
|
748
|
+
199,
|
|
749
|
+
200,
|
|
750
|
+
201,
|
|
751
|
+
202,
|
|
752
|
+
210,
|
|
753
|
+
211,
|
|
754
|
+
212,
|
|
755
|
+
213,
|
|
756
|
+
214,
|
|
757
|
+
215,
|
|
758
|
+
216,
|
|
759
|
+
217,
|
|
760
|
+
218,
|
|
761
|
+
225,
|
|
762
|
+
226,
|
|
763
|
+
227,
|
|
764
|
+
228,
|
|
765
|
+
229,
|
|
766
|
+
230,
|
|
767
|
+
231,
|
|
768
|
+
232,
|
|
769
|
+
233,
|
|
770
|
+
234,
|
|
771
|
+
241,
|
|
772
|
+
242,
|
|
773
|
+
243,
|
|
774
|
+
244,
|
|
775
|
+
245,
|
|
776
|
+
246,
|
|
777
|
+
247,
|
|
778
|
+
248,
|
|
779
|
+
249,
|
|
780
|
+
250
|
|
781
|
+
);
|
|
782
|
+
var STD_DC_CHR_CODES = Int32Array.of(0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0);
|
|
783
|
+
var STD_DC_CHR_VALUES = Int32Array.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
|
|
784
|
+
var STD_AC_CHR_CODES = Int32Array.of(0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119);
|
|
785
|
+
var STD_AC_CHR_VALUES = Int32Array.of(
|
|
786
|
+
0,
|
|
787
|
+
1,
|
|
788
|
+
2,
|
|
789
|
+
3,
|
|
790
|
+
17,
|
|
791
|
+
4,
|
|
792
|
+
5,
|
|
793
|
+
33,
|
|
794
|
+
49,
|
|
795
|
+
6,
|
|
796
|
+
18,
|
|
797
|
+
65,
|
|
798
|
+
81,
|
|
799
|
+
7,
|
|
800
|
+
97,
|
|
801
|
+
113,
|
|
802
|
+
19,
|
|
803
|
+
34,
|
|
804
|
+
50,
|
|
805
|
+
129,
|
|
806
|
+
8,
|
|
807
|
+
20,
|
|
808
|
+
66,
|
|
809
|
+
145,
|
|
810
|
+
161,
|
|
811
|
+
177,
|
|
812
|
+
193,
|
|
813
|
+
9,
|
|
814
|
+
35,
|
|
815
|
+
51,
|
|
816
|
+
82,
|
|
817
|
+
240,
|
|
818
|
+
21,
|
|
819
|
+
98,
|
|
820
|
+
114,
|
|
821
|
+
209,
|
|
822
|
+
10,
|
|
823
|
+
22,
|
|
824
|
+
36,
|
|
825
|
+
52,
|
|
826
|
+
225,
|
|
827
|
+
37,
|
|
828
|
+
241,
|
|
829
|
+
23,
|
|
830
|
+
24,
|
|
831
|
+
25,
|
|
832
|
+
26,
|
|
833
|
+
38,
|
|
834
|
+
39,
|
|
835
|
+
40,
|
|
836
|
+
41,
|
|
837
|
+
42,
|
|
838
|
+
53,
|
|
839
|
+
54,
|
|
840
|
+
55,
|
|
841
|
+
56,
|
|
842
|
+
57,
|
|
843
|
+
58,
|
|
844
|
+
67,
|
|
845
|
+
68,
|
|
846
|
+
69,
|
|
847
|
+
70,
|
|
848
|
+
71,
|
|
849
|
+
72,
|
|
850
|
+
73,
|
|
851
|
+
74,
|
|
852
|
+
83,
|
|
853
|
+
84,
|
|
854
|
+
85,
|
|
855
|
+
86,
|
|
856
|
+
87,
|
|
857
|
+
88,
|
|
858
|
+
89,
|
|
859
|
+
90,
|
|
860
|
+
99,
|
|
861
|
+
100,
|
|
862
|
+
101,
|
|
863
|
+
102,
|
|
864
|
+
103,
|
|
865
|
+
104,
|
|
866
|
+
105,
|
|
867
|
+
106,
|
|
868
|
+
115,
|
|
869
|
+
116,
|
|
870
|
+
117,
|
|
871
|
+
118,
|
|
872
|
+
119,
|
|
873
|
+
120,
|
|
874
|
+
121,
|
|
875
|
+
122,
|
|
876
|
+
130,
|
|
877
|
+
131,
|
|
878
|
+
132,
|
|
879
|
+
133,
|
|
880
|
+
134,
|
|
881
|
+
135,
|
|
882
|
+
136,
|
|
883
|
+
137,
|
|
884
|
+
138,
|
|
885
|
+
146,
|
|
886
|
+
147,
|
|
887
|
+
148,
|
|
888
|
+
149,
|
|
889
|
+
150,
|
|
890
|
+
151,
|
|
891
|
+
152,
|
|
892
|
+
153,
|
|
893
|
+
154,
|
|
894
|
+
162,
|
|
895
|
+
163,
|
|
896
|
+
164,
|
|
897
|
+
165,
|
|
898
|
+
166,
|
|
899
|
+
167,
|
|
900
|
+
168,
|
|
901
|
+
169,
|
|
902
|
+
170,
|
|
903
|
+
178,
|
|
904
|
+
179,
|
|
905
|
+
180,
|
|
906
|
+
181,
|
|
907
|
+
182,
|
|
908
|
+
183,
|
|
909
|
+
184,
|
|
910
|
+
185,
|
|
911
|
+
186,
|
|
912
|
+
194,
|
|
913
|
+
195,
|
|
914
|
+
196,
|
|
915
|
+
197,
|
|
916
|
+
198,
|
|
917
|
+
199,
|
|
918
|
+
200,
|
|
919
|
+
201,
|
|
920
|
+
202,
|
|
921
|
+
210,
|
|
922
|
+
211,
|
|
923
|
+
212,
|
|
924
|
+
213,
|
|
925
|
+
214,
|
|
926
|
+
215,
|
|
927
|
+
216,
|
|
928
|
+
217,
|
|
929
|
+
218,
|
|
930
|
+
226,
|
|
931
|
+
227,
|
|
932
|
+
228,
|
|
933
|
+
229,
|
|
934
|
+
230,
|
|
935
|
+
231,
|
|
936
|
+
232,
|
|
937
|
+
233,
|
|
938
|
+
234,
|
|
939
|
+
242,
|
|
940
|
+
243,
|
|
941
|
+
244,
|
|
942
|
+
245,
|
|
943
|
+
246,
|
|
944
|
+
247,
|
|
945
|
+
248,
|
|
946
|
+
249,
|
|
947
|
+
250
|
|
948
|
+
);
|
|
949
|
+
var YQT = Int32Array.of(
|
|
950
|
+
16,
|
|
951
|
+
11,
|
|
952
|
+
10,
|
|
953
|
+
16,
|
|
954
|
+
24,
|
|
955
|
+
40,
|
|
956
|
+
51,
|
|
957
|
+
61,
|
|
958
|
+
12,
|
|
959
|
+
12,
|
|
960
|
+
14,
|
|
961
|
+
19,
|
|
962
|
+
26,
|
|
963
|
+
58,
|
|
964
|
+
60,
|
|
965
|
+
55,
|
|
966
|
+
14,
|
|
967
|
+
13,
|
|
968
|
+
16,
|
|
969
|
+
24,
|
|
970
|
+
40,
|
|
971
|
+
57,
|
|
972
|
+
69,
|
|
973
|
+
56,
|
|
974
|
+
14,
|
|
975
|
+
17,
|
|
976
|
+
22,
|
|
977
|
+
29,
|
|
978
|
+
51,
|
|
979
|
+
87,
|
|
980
|
+
80,
|
|
981
|
+
62,
|
|
982
|
+
18,
|
|
983
|
+
22,
|
|
984
|
+
37,
|
|
985
|
+
56,
|
|
986
|
+
68,
|
|
987
|
+
109,
|
|
988
|
+
103,
|
|
989
|
+
77,
|
|
990
|
+
24,
|
|
991
|
+
35,
|
|
992
|
+
55,
|
|
993
|
+
64,
|
|
994
|
+
81,
|
|
995
|
+
104,
|
|
996
|
+
113,
|
|
997
|
+
92,
|
|
998
|
+
49,
|
|
999
|
+
64,
|
|
1000
|
+
78,
|
|
1001
|
+
87,
|
|
1002
|
+
103,
|
|
1003
|
+
121,
|
|
1004
|
+
120,
|
|
1005
|
+
101,
|
|
1006
|
+
72,
|
|
1007
|
+
92,
|
|
1008
|
+
95,
|
|
1009
|
+
98,
|
|
1010
|
+
112,
|
|
1011
|
+
100,
|
|
1012
|
+
103,
|
|
1013
|
+
99
|
|
1014
|
+
);
|
|
1015
|
+
var UVQT = Int32Array.of(
|
|
1016
|
+
17,
|
|
1017
|
+
18,
|
|
1018
|
+
24,
|
|
1019
|
+
47,
|
|
1020
|
+
99,
|
|
1021
|
+
99,
|
|
1022
|
+
99,
|
|
1023
|
+
99,
|
|
1024
|
+
18,
|
|
1025
|
+
21,
|
|
1026
|
+
26,
|
|
1027
|
+
66,
|
|
1028
|
+
99,
|
|
1029
|
+
99,
|
|
1030
|
+
99,
|
|
1031
|
+
99,
|
|
1032
|
+
24,
|
|
1033
|
+
26,
|
|
1034
|
+
56,
|
|
1035
|
+
99,
|
|
1036
|
+
99,
|
|
1037
|
+
99,
|
|
1038
|
+
99,
|
|
1039
|
+
99,
|
|
1040
|
+
47,
|
|
1041
|
+
66,
|
|
1042
|
+
99,
|
|
1043
|
+
99,
|
|
1044
|
+
99,
|
|
1045
|
+
99,
|
|
1046
|
+
99,
|
|
1047
|
+
99,
|
|
1048
|
+
99,
|
|
1049
|
+
99,
|
|
1050
|
+
99,
|
|
1051
|
+
99,
|
|
1052
|
+
99,
|
|
1053
|
+
99,
|
|
1054
|
+
99,
|
|
1055
|
+
99,
|
|
1056
|
+
99,
|
|
1057
|
+
99,
|
|
1058
|
+
99,
|
|
1059
|
+
99,
|
|
1060
|
+
99,
|
|
1061
|
+
99,
|
|
1062
|
+
99,
|
|
1063
|
+
99,
|
|
1064
|
+
99,
|
|
1065
|
+
99,
|
|
1066
|
+
99,
|
|
1067
|
+
99,
|
|
1068
|
+
99,
|
|
1069
|
+
99,
|
|
1070
|
+
99,
|
|
1071
|
+
99,
|
|
1072
|
+
99,
|
|
1073
|
+
99,
|
|
1074
|
+
99,
|
|
1075
|
+
99,
|
|
1076
|
+
99,
|
|
1077
|
+
99,
|
|
1078
|
+
99,
|
|
1079
|
+
99
|
|
1080
|
+
);
|
|
1081
|
+
var AASF = Float64Array.of(1, 1.387039845, 1.306562965, 1.175875602, 1, 0.785694958, 0.5411961, 0.275899379);
|
|
1082
|
+
function computeHuffTable(nrcodes, values) {
|
|
1083
|
+
const code = new Int32Array(256);
|
|
1084
|
+
const len = new Int32Array(256);
|
|
1085
|
+
let codeValue = 0;
|
|
1086
|
+
let pos = 0;
|
|
1087
|
+
for (let k = 1; k <= 16; k++) {
|
|
1088
|
+
for (let j = 1; j <= nrcodes[k]; j++) {
|
|
1089
|
+
const v = values[pos];
|
|
1090
|
+
code[v] = codeValue;
|
|
1091
|
+
len[v] = k;
|
|
1092
|
+
pos++;
|
|
1093
|
+
codeValue++;
|
|
1094
|
+
}
|
|
1095
|
+
codeValue *= 2;
|
|
1096
|
+
}
|
|
1097
|
+
return { code, len };
|
|
1098
|
+
}
|
|
1099
|
+
function encodeJpeg(source, quality = 82) {
|
|
1100
|
+
const q = Math.max(1, Math.min(100, Math.round(quality)));
|
|
1101
|
+
const sf = q < 50 ? Math.floor(5e3 / q) : 200 - q * 2;
|
|
1102
|
+
const YTable = new Int32Array(64);
|
|
1103
|
+
const UVTable = new Int32Array(64);
|
|
1104
|
+
const fdtblY = new Float64Array(64);
|
|
1105
|
+
const fdtblUV = new Float64Array(64);
|
|
1106
|
+
for (let i = 0; i < 64; i++) {
|
|
1107
|
+
YTable[ZIGZAG[i]] = Math.min(255, Math.max(1, Math.floor((YQT[i] * sf + 50) / 100)));
|
|
1108
|
+
UVTable[ZIGZAG[i]] = Math.min(255, Math.max(1, Math.floor((UVQT[i] * sf + 50) / 100)));
|
|
1109
|
+
}
|
|
1110
|
+
{
|
|
1111
|
+
let i = 0;
|
|
1112
|
+
for (let row = 0; row < 8; row++) {
|
|
1113
|
+
for (let col = 0; col < 8; col++) {
|
|
1114
|
+
fdtblY[i] = 1 / (YTable[ZIGZAG[i]] * AASF[row] * AASF[col] * 8);
|
|
1115
|
+
fdtblUV[i] = 1 / (UVTable[ZIGZAG[i]] * AASF[row] * AASF[col] * 8);
|
|
1116
|
+
i++;
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
const YDC = computeHuffTable(STD_DC_LUM_CODES, STD_DC_LUM_VALUES);
|
|
1121
|
+
const YAC = computeHuffTable(STD_AC_LUM_CODES, STD_AC_LUM_VALUES);
|
|
1122
|
+
const UVDC = computeHuffTable(STD_DC_CHR_CODES, STD_DC_CHR_VALUES);
|
|
1123
|
+
const UVAC = computeHuffTable(STD_AC_CHR_CODES, STD_AC_CHR_VALUES);
|
|
1124
|
+
const category = new Int32Array(65536);
|
|
1125
|
+
const bitCode = new Int32Array(65536);
|
|
1126
|
+
const bitLen = new Int32Array(65536);
|
|
1127
|
+
{
|
|
1128
|
+
let nrlower = 1;
|
|
1129
|
+
let nrupper = 2;
|
|
1130
|
+
for (let cat = 1; cat <= 15; cat++) {
|
|
1131
|
+
for (let nr = nrlower; nr < nrupper; nr++) {
|
|
1132
|
+
category[32767 + nr] = cat;
|
|
1133
|
+
bitLen[32767 + nr] = cat;
|
|
1134
|
+
bitCode[32767 + nr] = nr;
|
|
1135
|
+
}
|
|
1136
|
+
for (let nrneg = -(nrupper - 1); nrneg <= -nrlower; nrneg++) {
|
|
1137
|
+
category[32767 + nrneg] = cat;
|
|
1138
|
+
bitLen[32767 + nrneg] = cat;
|
|
1139
|
+
bitCode[32767 + nrneg] = nrupper - 1 + nrneg;
|
|
1140
|
+
}
|
|
1141
|
+
nrlower <<= 1;
|
|
1142
|
+
nrupper <<= 1;
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
const out = [];
|
|
1146
|
+
let byteNew = 0;
|
|
1147
|
+
let bytePos = 7;
|
|
1148
|
+
const writeByte = (v) => out.push(v & 255);
|
|
1149
|
+
const writeWord = (v) => {
|
|
1150
|
+
out.push(v >>> 8 & 255);
|
|
1151
|
+
out.push(v & 255);
|
|
1152
|
+
};
|
|
1153
|
+
const writeBits = (value, length) => {
|
|
1154
|
+
let posval = length - 1;
|
|
1155
|
+
while (posval >= 0) {
|
|
1156
|
+
if (value & 1 << posval) byteNew |= 1 << bytePos;
|
|
1157
|
+
posval--;
|
|
1158
|
+
bytePos--;
|
|
1159
|
+
if (bytePos < 0) {
|
|
1160
|
+
if (byteNew === 255) {
|
|
1161
|
+
writeByte(255);
|
|
1162
|
+
writeByte(0);
|
|
1163
|
+
} else {
|
|
1164
|
+
writeByte(byteNew);
|
|
1165
|
+
}
|
|
1166
|
+
bytePos = 7;
|
|
1167
|
+
byteNew = 0;
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
};
|
|
1171
|
+
const data = new Float64Array(64);
|
|
1172
|
+
const outDU = new Int32Array(64);
|
|
1173
|
+
const DU = new Int32Array(64);
|
|
1174
|
+
const fDCTQuant = (fdtbl) => {
|
|
1175
|
+
let off = 0;
|
|
1176
|
+
for (let i = 0; i < 8; i++) {
|
|
1177
|
+
const d0 = data[off], d1 = data[off + 1], d2 = data[off + 2], d3 = data[off + 3];
|
|
1178
|
+
const d4 = data[off + 4], d5 = data[off + 5], d6 = data[off + 6], d7 = data[off + 7];
|
|
1179
|
+
const t0 = d0 + d7, t7 = d0 - d7, t1 = d1 + d6, t6 = d1 - d6;
|
|
1180
|
+
const t2 = d2 + d5, t5 = d2 - d5, t3 = d3 + d4, t4 = d3 - d4;
|
|
1181
|
+
let t10 = t0 + t3;
|
|
1182
|
+
const t13 = t0 - t3;
|
|
1183
|
+
let t11 = t1 + t2;
|
|
1184
|
+
const t12 = t1 - t2;
|
|
1185
|
+
data[off] = t10 + t11;
|
|
1186
|
+
data[off + 4] = t10 - t11;
|
|
1187
|
+
const z1 = (t12 + t13) * 0.707106781;
|
|
1188
|
+
data[off + 2] = t13 + z1;
|
|
1189
|
+
data[off + 6] = t13 - z1;
|
|
1190
|
+
t10 = t4 + t5;
|
|
1191
|
+
t11 = t5 + t6;
|
|
1192
|
+
const t12b = t6 + t7;
|
|
1193
|
+
const z5 = (t10 - t12b) * 0.382683433;
|
|
1194
|
+
const z2 = 0.5411961 * t10 + z5;
|
|
1195
|
+
const z4 = 1.306562965 * t12b + z5;
|
|
1196
|
+
const z3 = t11 * 0.707106781;
|
|
1197
|
+
const z11 = t7 + z3;
|
|
1198
|
+
const z13 = t7 - z3;
|
|
1199
|
+
data[off + 5] = z13 + z2;
|
|
1200
|
+
data[off + 3] = z13 - z2;
|
|
1201
|
+
data[off + 1] = z11 + z4;
|
|
1202
|
+
data[off + 7] = z11 - z4;
|
|
1203
|
+
off += 8;
|
|
1204
|
+
}
|
|
1205
|
+
off = 0;
|
|
1206
|
+
for (let i = 0; i < 8; i++) {
|
|
1207
|
+
const d0 = data[off], d1 = data[off + 8], d2 = data[off + 16], d3 = data[off + 24];
|
|
1208
|
+
const d4 = data[off + 32], d5 = data[off + 40], d6 = data[off + 48], d7 = data[off + 56];
|
|
1209
|
+
const t0 = d0 + d7, t7 = d0 - d7, t1 = d1 + d6, t6 = d1 - d6;
|
|
1210
|
+
const t2 = d2 + d5, t5 = d2 - d5, t3 = d3 + d4, t4 = d3 - d4;
|
|
1211
|
+
let t10 = t0 + t3;
|
|
1212
|
+
const t13 = t0 - t3;
|
|
1213
|
+
let t11 = t1 + t2;
|
|
1214
|
+
const t12 = t1 - t2;
|
|
1215
|
+
data[off] = t10 + t11;
|
|
1216
|
+
data[off + 32] = t10 - t11;
|
|
1217
|
+
const z1 = (t12 + t13) * 0.707106781;
|
|
1218
|
+
data[off + 16] = t13 + z1;
|
|
1219
|
+
data[off + 48] = t13 - z1;
|
|
1220
|
+
t10 = t4 + t5;
|
|
1221
|
+
t11 = t5 + t6;
|
|
1222
|
+
const t12b = t6 + t7;
|
|
1223
|
+
const z5 = (t10 - t12b) * 0.382683433;
|
|
1224
|
+
const z2 = 0.5411961 * t10 + z5;
|
|
1225
|
+
const z4 = 1.306562965 * t12b + z5;
|
|
1226
|
+
const z3 = t11 * 0.707106781;
|
|
1227
|
+
const z11 = t7 + z3;
|
|
1228
|
+
const z13 = t7 - z3;
|
|
1229
|
+
data[off + 40] = z13 + z2;
|
|
1230
|
+
data[off + 24] = z13 - z2;
|
|
1231
|
+
data[off + 8] = z11 + z4;
|
|
1232
|
+
data[off + 56] = z11 - z4;
|
|
1233
|
+
off++;
|
|
1234
|
+
}
|
|
1235
|
+
for (let i = 0; i < 64; i++) {
|
|
1236
|
+
const v = data[i] * fdtbl[i];
|
|
1237
|
+
outDU[i] = v > 0 ? v + 0.5 | 0 : v - 0.5 | 0;
|
|
1238
|
+
}
|
|
1239
|
+
return outDU;
|
|
1240
|
+
};
|
|
1241
|
+
const processDU = (fdtbl, dc, HTDC, HTAC) => {
|
|
1242
|
+
const duDct = fDCTQuant(fdtbl);
|
|
1243
|
+
for (let j = 0; j < 64; j++) DU[ZIGZAG[j]] = duDct[j];
|
|
1244
|
+
const diff = DU[0] - dc;
|
|
1245
|
+
const newDc = DU[0];
|
|
1246
|
+
if (diff === 0) {
|
|
1247
|
+
writeBits(HTDC.code[0], HTDC.len[0]);
|
|
1248
|
+
} else {
|
|
1249
|
+
const pos = 32767 + diff;
|
|
1250
|
+
writeBits(HTDC.code[category[pos]], HTDC.len[category[pos]]);
|
|
1251
|
+
writeBits(bitCode[pos], bitLen[pos]);
|
|
1252
|
+
}
|
|
1253
|
+
let end0 = 63;
|
|
1254
|
+
while (end0 > 0 && DU[end0] === 0) end0--;
|
|
1255
|
+
if (end0 === 0) {
|
|
1256
|
+
writeBits(HTAC.code[0], HTAC.len[0]);
|
|
1257
|
+
return newDc;
|
|
1258
|
+
}
|
|
1259
|
+
let i = 1;
|
|
1260
|
+
while (i <= end0) {
|
|
1261
|
+
const start = i;
|
|
1262
|
+
while (DU[i] === 0 && i <= end0) i++;
|
|
1263
|
+
let nrzeroes = i - start;
|
|
1264
|
+
if (nrzeroes >= 16) {
|
|
1265
|
+
const lng = nrzeroes >> 4;
|
|
1266
|
+
for (let m = 1; m <= lng; m++) writeBits(HTAC.code[240], HTAC.len[240]);
|
|
1267
|
+
nrzeroes &= 15;
|
|
1268
|
+
}
|
|
1269
|
+
const pos = 32767 + DU[i];
|
|
1270
|
+
const sym = (nrzeroes << 4) + category[pos];
|
|
1271
|
+
writeBits(HTAC.code[sym], HTAC.len[sym]);
|
|
1272
|
+
writeBits(bitCode[pos], bitLen[pos]);
|
|
1273
|
+
i++;
|
|
1274
|
+
}
|
|
1275
|
+
if (end0 !== 63) writeBits(HTAC.code[0], HTAC.len[0]);
|
|
1276
|
+
return newDc;
|
|
1277
|
+
};
|
|
1278
|
+
const { width, height, data: px } = source;
|
|
1279
|
+
writeWord(65496);
|
|
1280
|
+
writeWord(65504);
|
|
1281
|
+
writeWord(16);
|
|
1282
|
+
for (const c of [74, 70, 73, 70, 0]) writeByte(c);
|
|
1283
|
+
writeByte(1);
|
|
1284
|
+
writeByte(1);
|
|
1285
|
+
writeByte(0);
|
|
1286
|
+
writeWord(1);
|
|
1287
|
+
writeWord(1);
|
|
1288
|
+
writeByte(0);
|
|
1289
|
+
writeByte(0);
|
|
1290
|
+
writeWord(65499);
|
|
1291
|
+
writeWord(132);
|
|
1292
|
+
writeByte(0);
|
|
1293
|
+
for (let i = 0; i < 64; i++) writeByte(YTable[i]);
|
|
1294
|
+
writeByte(1);
|
|
1295
|
+
for (let i = 0; i < 64; i++) writeByte(UVTable[i]);
|
|
1296
|
+
writeWord(65472);
|
|
1297
|
+
writeWord(17);
|
|
1298
|
+
writeByte(8);
|
|
1299
|
+
writeWord(height);
|
|
1300
|
+
writeWord(width);
|
|
1301
|
+
writeByte(3);
|
|
1302
|
+
writeByte(1);
|
|
1303
|
+
writeByte(17);
|
|
1304
|
+
writeByte(0);
|
|
1305
|
+
writeByte(2);
|
|
1306
|
+
writeByte(17);
|
|
1307
|
+
writeByte(1);
|
|
1308
|
+
writeByte(3);
|
|
1309
|
+
writeByte(17);
|
|
1310
|
+
writeByte(1);
|
|
1311
|
+
writeWord(65476);
|
|
1312
|
+
writeWord(418);
|
|
1313
|
+
const writeHuffSpec = (cls, codes, values) => {
|
|
1314
|
+
writeByte(cls);
|
|
1315
|
+
for (let i = 1; i <= 16; i++) writeByte(codes[i]);
|
|
1316
|
+
for (let i = 0; i < values.length; i++) writeByte(values[i]);
|
|
1317
|
+
};
|
|
1318
|
+
writeHuffSpec(0, STD_DC_LUM_CODES, STD_DC_LUM_VALUES);
|
|
1319
|
+
writeHuffSpec(16, STD_AC_LUM_CODES, STD_AC_LUM_VALUES);
|
|
1320
|
+
writeHuffSpec(1, STD_DC_CHR_CODES, STD_DC_CHR_VALUES);
|
|
1321
|
+
writeHuffSpec(17, STD_AC_CHR_CODES, STD_AC_CHR_VALUES);
|
|
1322
|
+
writeWord(65498);
|
|
1323
|
+
writeWord(12);
|
|
1324
|
+
writeByte(3);
|
|
1325
|
+
writeByte(1);
|
|
1326
|
+
writeByte(0);
|
|
1327
|
+
writeByte(2);
|
|
1328
|
+
writeByte(17);
|
|
1329
|
+
writeByte(3);
|
|
1330
|
+
writeByte(17);
|
|
1331
|
+
writeByte(0);
|
|
1332
|
+
writeByte(63);
|
|
1333
|
+
writeByte(0);
|
|
1334
|
+
let dcY = 0;
|
|
1335
|
+
let dcU = 0;
|
|
1336
|
+
let dcV = 0;
|
|
1337
|
+
byteNew = 0;
|
|
1338
|
+
bytePos = 7;
|
|
1339
|
+
const clampX = (x) => x < 0 ? 0 : x >= width ? width - 1 : x;
|
|
1340
|
+
const clampY = (y) => y < 0 ? 0 : y >= height ? height - 1 : y;
|
|
1341
|
+
for (let by = 0; by < height; by += 8) {
|
|
1342
|
+
for (let bx = 0; bx < width; bx += 8) {
|
|
1343
|
+
let k = 0;
|
|
1344
|
+
for (let r = 0; r < 8; r++) {
|
|
1345
|
+
for (let c = 0; c < 8; c++) {
|
|
1346
|
+
const i = (clampY(by + r) * width + clampX(bx + c)) * 4;
|
|
1347
|
+
const R = px[i], G = px[i + 1], B = px[i + 2];
|
|
1348
|
+
data[k++] = 0.299 * R + 0.587 * G + 0.114 * B - 128;
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
dcY = processDU(fdtblY, dcY, YDC, YAC);
|
|
1352
|
+
k = 0;
|
|
1353
|
+
for (let r = 0; r < 8; r++) {
|
|
1354
|
+
for (let c = 0; c < 8; c++) {
|
|
1355
|
+
const i = (clampY(by + r) * width + clampX(bx + c)) * 4;
|
|
1356
|
+
const R = px[i], G = px[i + 1], B = px[i + 2];
|
|
1357
|
+
data[k++] = -0.16874 * R - 0.33126 * G + 0.5 * B;
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
dcU = processDU(fdtblUV, dcU, UVDC, UVAC);
|
|
1361
|
+
k = 0;
|
|
1362
|
+
for (let r = 0; r < 8; r++) {
|
|
1363
|
+
for (let c = 0; c < 8; c++) {
|
|
1364
|
+
const i = (clampY(by + r) * width + clampX(bx + c)) * 4;
|
|
1365
|
+
const R = px[i], G = px[i + 1], B = px[i + 2];
|
|
1366
|
+
data[k++] = 0.5 * R - 0.41869 * G - 0.08131 * B;
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
dcV = processDU(fdtblUV, dcV, UVDC, UVAC);
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
if (bytePos !== 7) writeBits(127, 7);
|
|
1373
|
+
writeWord(65497);
|
|
1374
|
+
return Uint8Array.from(out);
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
// src/encode.ts
|
|
1378
|
+
function hasAlpha(src) {
|
|
1379
|
+
const d = src.data;
|
|
1380
|
+
for (let i = 3; i < d.length; i += 4) if (d[i] < 255) return true;
|
|
1381
|
+
return false;
|
|
1382
|
+
}
|
|
1383
|
+
async function encode(source, opts) {
|
|
1384
|
+
const format = opts.format;
|
|
1385
|
+
const quality = opts.quality ?? 82;
|
|
1386
|
+
let bytes;
|
|
1387
|
+
if (format === "jpeg") {
|
|
1388
|
+
const flat = hasAlpha(source) ? Surface.from(source).flatten(opts.background ?? "#ffffff") : source;
|
|
1389
|
+
if (hasCanvas()) {
|
|
1390
|
+
try {
|
|
1391
|
+
bytes = await encodeViaCanvas(flat, "jpeg", quality);
|
|
1392
|
+
} catch {
|
|
1393
|
+
bytes = encodeJpeg(flat, quality);
|
|
1394
|
+
}
|
|
1395
|
+
} else {
|
|
1396
|
+
bytes = encodeJpeg(flat, quality);
|
|
1397
|
+
}
|
|
1398
|
+
} else if (format === "png") {
|
|
1399
|
+
if (hasCanvas()) {
|
|
1400
|
+
try {
|
|
1401
|
+
bytes = await encodeViaCanvas(source, "png");
|
|
1402
|
+
} catch {
|
|
1403
|
+
bytes = await encodePng(source);
|
|
1404
|
+
}
|
|
1405
|
+
} else {
|
|
1406
|
+
bytes = await encodePng(source);
|
|
1407
|
+
}
|
|
1408
|
+
} else if (format === "webp") {
|
|
1409
|
+
if (!hasCanvas()) {
|
|
1410
|
+
throw new Error(
|
|
1411
|
+
"WebP encoding needs a Canvas runtime (browser or a Worker with OffscreenCanvas). In Node, export png or jpeg instead."
|
|
1412
|
+
);
|
|
1413
|
+
}
|
|
1414
|
+
bytes = await encodeViaCanvas(source, "webp", quality);
|
|
1415
|
+
} else {
|
|
1416
|
+
throw new Error(`Unsupported format: ${String(format)}`);
|
|
1417
|
+
}
|
|
1418
|
+
return {
|
|
1419
|
+
bytes,
|
|
1420
|
+
format,
|
|
1421
|
+
width: source.width,
|
|
1422
|
+
height: source.height,
|
|
1423
|
+
quality: format === "png" ? void 0 : quality,
|
|
1424
|
+
size: bytes.length
|
|
1425
|
+
};
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
// src/bytes.ts
|
|
1429
|
+
var UNITS = {
|
|
1430
|
+
b: 1,
|
|
1431
|
+
kb: 1024,
|
|
1432
|
+
k: 1024,
|
|
1433
|
+
kib: 1024,
|
|
1434
|
+
mb: 1024 * 1024,
|
|
1435
|
+
m: 1024 * 1024,
|
|
1436
|
+
mib: 1024 * 1024,
|
|
1437
|
+
gb: 1024 * 1024 * 1024,
|
|
1438
|
+
g: 1024 * 1024 * 1024,
|
|
1439
|
+
gib: 1024 * 1024 * 1024
|
|
1440
|
+
};
|
|
1441
|
+
function parseSize(input) {
|
|
1442
|
+
if (typeof input === "number") {
|
|
1443
|
+
if (!Number.isFinite(input) || input <= 0) throw new Error(`Invalid size: ${input}`);
|
|
1444
|
+
return Math.floor(input);
|
|
1445
|
+
}
|
|
1446
|
+
const m = String(input).trim().toLowerCase().match(/^([0-9]*\.?[0-9]+)\s*([a-z]*)$/);
|
|
1447
|
+
if (!m) throw new Error(`Cannot parse size: "${input}"`);
|
|
1448
|
+
const value = parseFloat(m[1]);
|
|
1449
|
+
const unit = m[2] || "b";
|
|
1450
|
+
const mult = UNITS[unit];
|
|
1451
|
+
if (mult == null) throw new Error(`Unknown size unit: "${unit}"`);
|
|
1452
|
+
return Math.floor(value * mult);
|
|
1453
|
+
}
|
|
1454
|
+
function formatBytes(bytes, decimals = 1) {
|
|
1455
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
1456
|
+
const units = ["KB", "MB", "GB", "TB"];
|
|
1457
|
+
let n = bytes / 1024;
|
|
1458
|
+
let u = 0;
|
|
1459
|
+
while (n >= 1024 && u < units.length - 1) {
|
|
1460
|
+
n /= 1024;
|
|
1461
|
+
u++;
|
|
1462
|
+
}
|
|
1463
|
+
return `${n.toFixed(decimals).replace(/\.0$/, "")} ${units[u]}`;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
// src/fit.ts
|
|
1467
|
+
function toSurface(src) {
|
|
1468
|
+
return src instanceof Surface ? src : Surface.from(src);
|
|
1469
|
+
}
|
|
1470
|
+
async function fit(source, opts) {
|
|
1471
|
+
const maxBytes = opts.maxBytes ?? (opts.maxSize != null ? parseSize(opts.maxSize) : void 0);
|
|
1472
|
+
if (!maxBytes || maxBytes <= 0) throw new Error("fit() needs a positive `maxBytes` or `maxSize`.");
|
|
1473
|
+
const { format } = opts;
|
|
1474
|
+
const minQuality = opts.minQuality ?? 30;
|
|
1475
|
+
const maxQuality = opts.maxQuality ?? 92;
|
|
1476
|
+
const allowResize = opts.allowResize ?? true;
|
|
1477
|
+
const minScale = opts.minScale ?? 0.35;
|
|
1478
|
+
const background = opts.background;
|
|
1479
|
+
const base = toSurface(source);
|
|
1480
|
+
if (format === "png") {
|
|
1481
|
+
let best = await encode(base, { format, background });
|
|
1482
|
+
if (best.size <= maxBytes) return best;
|
|
1483
|
+
if (!allowResize) return best;
|
|
1484
|
+
const scales = [1, 0.85, 0.7, 0.55, minScale];
|
|
1485
|
+
const bitsSteps = [8, 6, 5, 4, 3, 2];
|
|
1486
|
+
for (const scale of scales) {
|
|
1487
|
+
const w = Math.max(1, Math.round(base.width * scale));
|
|
1488
|
+
const h = Math.max(1, Math.round(base.height * scale));
|
|
1489
|
+
const scaled = scale === 1 ? base : base.resize(w, h);
|
|
1490
|
+
for (const bits of bitsSteps) {
|
|
1491
|
+
const surf = bits >= 8 ? scaled : scaled.posterize(bits);
|
|
1492
|
+
const r = await encode(surf, { format, background });
|
|
1493
|
+
if (r.size < best.size) best = r;
|
|
1494
|
+
if (r.size <= maxBytes) return r;
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
return best;
|
|
1498
|
+
}
|
|
1499
|
+
const qualitySearch = async (surface) => {
|
|
1500
|
+
let lo = minQuality;
|
|
1501
|
+
let hi = maxQuality;
|
|
1502
|
+
let best = null;
|
|
1503
|
+
while (lo <= hi) {
|
|
1504
|
+
const mid = lo + hi >> 1;
|
|
1505
|
+
const r = await encode(surface, { format, quality: mid, background });
|
|
1506
|
+
if (r.size <= maxBytes) {
|
|
1507
|
+
best = r;
|
|
1508
|
+
lo = mid + 1;
|
|
1509
|
+
} else {
|
|
1510
|
+
hi = mid - 1;
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
return best;
|
|
1514
|
+
};
|
|
1515
|
+
const atFull = await qualitySearch(base);
|
|
1516
|
+
if (atFull) return atFull;
|
|
1517
|
+
if (allowResize) {
|
|
1518
|
+
for (let scale = 0.85; scale >= minScale; scale -= 0.12) {
|
|
1519
|
+
const w2 = Math.max(1, Math.round(base.width * scale));
|
|
1520
|
+
const h2 = Math.max(1, Math.round(base.height * scale));
|
|
1521
|
+
const found = await qualitySearch(base.resize(w2, h2));
|
|
1522
|
+
if (found) return found;
|
|
1523
|
+
}
|
|
1524
|
+
const w = Math.max(1, Math.round(base.width * minScale));
|
|
1525
|
+
const h = Math.max(1, Math.round(base.height * minScale));
|
|
1526
|
+
return encode(base.resize(w, h), { format, quality: minQuality, background });
|
|
1527
|
+
}
|
|
1528
|
+
return encode(base, { format, quality: minQuality, background });
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
// src/svg.ts
|
|
1532
|
+
async function svgToSurfaceBrowser(svg, opts) {
|
|
1533
|
+
const blob = new Blob([svg], { type: "image/svg+xml;charset=utf-8" });
|
|
1534
|
+
const url = URL.createObjectURL(blob);
|
|
1535
|
+
try {
|
|
1536
|
+
const img = await new Promise((resolve, reject) => {
|
|
1537
|
+
const el = new Image();
|
|
1538
|
+
el.onload = () => resolve(el);
|
|
1539
|
+
el.onerror = () => reject(new Error("Failed to load SVG"));
|
|
1540
|
+
el.src = url;
|
|
1541
|
+
});
|
|
1542
|
+
const w = opts.width ?? img.naturalWidth ?? 512;
|
|
1543
|
+
const h = opts.height ?? img.naturalHeight ?? 512;
|
|
1544
|
+
const canvas = typeof OffscreenCanvas !== "undefined" ? new OffscreenCanvas(w, h) : Object.assign(document.createElement("canvas"), { width: w, height: h });
|
|
1545
|
+
const ctx = canvas.getContext("2d");
|
|
1546
|
+
if (!ctx) throw new Error("No 2D context for SVG rasterization");
|
|
1547
|
+
if (opts.background) {
|
|
1548
|
+
ctx.fillStyle = opts.background;
|
|
1549
|
+
ctx.fillRect(0, 0, w, h);
|
|
1550
|
+
}
|
|
1551
|
+
ctx.drawImage(img, 0, 0, w, h);
|
|
1552
|
+
const data = ctx.getImageData(0, 0, w, h);
|
|
1553
|
+
return new Surface(w, h, data.data);
|
|
1554
|
+
} finally {
|
|
1555
|
+
URL.revokeObjectURL(url);
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
async function svgToSurfaceNode(svg, opts) {
|
|
1559
|
+
let sharp;
|
|
1560
|
+
try {
|
|
1561
|
+
const mod = await import('sharp');
|
|
1562
|
+
sharp = mod.default;
|
|
1563
|
+
} catch {
|
|
1564
|
+
throw new Error(
|
|
1565
|
+
"SVG rasterization in Node requires the optional peer `sharp`. Install it (npm i sharp) or run rasterizeSvg() in the browser, where it uses the native Canvas with no extra deps."
|
|
1566
|
+
);
|
|
1567
|
+
}
|
|
1568
|
+
let pipe = sharp(Buffer.from(svg));
|
|
1569
|
+
if (opts.width || opts.height) pipe = pipe.resize(opts.width, opts.height, { fit: "fill" });
|
|
1570
|
+
if (opts.background) pipe = pipe.flatten({ background: opts.background });
|
|
1571
|
+
const { data, info } = await pipe.ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
1572
|
+
return new Surface(info.width, info.height, new Uint8ClampedArray(data));
|
|
1573
|
+
}
|
|
1574
|
+
async function rasterizeSvg(svg, opts = {}) {
|
|
1575
|
+
const isNode2 = typeof process !== "undefined" && !!process.versions?.node;
|
|
1576
|
+
if (!isNode2 && typeof Image !== "undefined") return svgToSurfaceBrowser(svg, opts);
|
|
1577
|
+
return svgToSurfaceNode(svg, opts);
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
export { Surface, canvasSupports, encode, encodeJpeg, encodePng, encodePngSync, fit, formatBytes, gradient, hasCanvas, parseColor, parseSize, pattern, radial, rasterizeSvg };
|
|
1581
|
+
//# sourceMappingURL=index.js.map
|
|
1582
|
+
//# sourceMappingURL=index.js.map
|