@life-palette/media 0.2.1

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/dist/index.cjs ADDED
@@ -0,0 +1,1297 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_rolldown_runtime = require("./rolldown-runtime-y5ARvAq5.cjs");
3
+ let metaprobe = require("metaprobe");
4
+ metaprobe = require_rolldown_runtime.__toESM(metaprobe, 1);
5
+ //#region ../../node_modules/arthash/wasm/pkg/arthash_wasm.js
6
+ /**
7
+ * Decode result for [`decode`]. JS sees `{ w, h, rgba }`.
8
+ */
9
+ var DecodeResult = class DecodeResult {
10
+ static __wrap(ptr) {
11
+ const obj = Object.create(DecodeResult.prototype);
12
+ obj.__wbg_ptr = ptr;
13
+ DecodeResultFinalization.register(obj, obj.__wbg_ptr, obj);
14
+ return obj;
15
+ }
16
+ __destroy_into_raw() {
17
+ const ptr = this.__wbg_ptr;
18
+ this.__wbg_ptr = 0;
19
+ DecodeResultFinalization.unregister(this);
20
+ return ptr;
21
+ }
22
+ free() {
23
+ const ptr = this.__destroy_into_raw();
24
+ wasm.__wbg_decoderesult_free(ptr, 0);
25
+ }
26
+ /**
27
+ * Consume the result and hand back its RGBA bytes (row-major, 4 B/px),
28
+ * moving the buffer into the returned Uint8Array instead of cloning it.
29
+ * This frees the `DecodeResult`, so read `w` / `h` BEFORE calling — any
30
+ * field access afterwards throws (the wasm pointer is taken).
31
+ * @returns {Uint8Array}
32
+ */
33
+ intoRgba() {
34
+ const ptr = this.__destroy_into_raw();
35
+ const ret = wasm.decoderesult_intoRgba(ptr);
36
+ var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
37
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
38
+ return v1;
39
+ }
40
+ /**
41
+ * @returns {number}
42
+ */
43
+ get h() {
44
+ return wasm.__wbg_get_decoderesult_h(this.__wbg_ptr) >>> 0;
45
+ }
46
+ /**
47
+ * @returns {number}
48
+ */
49
+ get w() {
50
+ return wasm.__wbg_get_decoderesult_w(this.__wbg_ptr) >>> 0;
51
+ }
52
+ };
53
+ if (Symbol.dispose) DecodeResult.prototype[Symbol.dispose] = DecodeResult.prototype.free;
54
+ /**
55
+ * Encode an RGB buffer (row-major, 3 bytes per pixel).
56
+ * @param {Uint8Array} rgb
57
+ * @param {number} w
58
+ * @param {number} h
59
+ * @param {any} codec
60
+ * @param {bigint} seed
61
+ * @param {any} search
62
+ * @returns {Uint8Array}
63
+ */
64
+ function encodeRgb(rgb, w, h, codec, seed, search) {
65
+ const ptr0 = passArray8ToWasm0(rgb, wasm.__wbindgen_malloc);
66
+ const len0 = WASM_VECTOR_LEN;
67
+ const ret = wasm.encodeRgb(ptr0, len0, w, h, codec, seed, search);
68
+ if (ret[3]) throw takeFromExternrefTable0(ret[2]);
69
+ var v2 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
70
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
71
+ return v2;
72
+ }
73
+ function __wbg_get_imports() {
74
+ return {
75
+ __proto__: null,
76
+ "./arthash_wasm_bg.js": {
77
+ __proto__: null,
78
+ __wbg_Error_bce6d499ff0a4aff: function(arg0, arg1) {
79
+ return Error(getStringFromWasm0(arg0, arg1));
80
+ },
81
+ __wbg___wbindgen_is_null_2042690d351e14f0: function(arg0) {
82
+ return arg0 === null;
83
+ },
84
+ __wbg___wbindgen_is_undefined_35bb9f4c7fd651d5: function(arg0) {
85
+ return arg0 === void 0;
86
+ },
87
+ __wbg___wbindgen_number_get_f73a1244370fcc2c: function(arg0, arg1) {
88
+ const obj = arg1;
89
+ const ret = typeof obj === "number" ? obj : void 0;
90
+ getDataViewMemory0().setFloat64(arg0 + 8, isLikeNone(ret) ? 0 : ret, true);
91
+ getDataViewMemory0().setInt32(arg0 + 0, !isLikeNone(ret), true);
92
+ },
93
+ __wbg___wbindgen_string_get_d109740c0d18f4d7: function(arg0, arg1) {
94
+ const obj = arg1;
95
+ const ret = typeof obj === "string" ? obj : void 0;
96
+ var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
97
+ var len1 = WASM_VECTOR_LEN;
98
+ getDataViewMemory0().setInt32(arg0 + 4, len1, true);
99
+ getDataViewMemory0().setInt32(arg0 + 0, ptr1, true);
100
+ },
101
+ __wbg___wbindgen_throw_9c31b086c2b26051: function(arg0, arg1) {
102
+ throw new Error(getStringFromWasm0(arg0, arg1));
103
+ },
104
+ __wbg_from_fa561fa561dc8031: function(arg0) {
105
+ return Array.from(arg0);
106
+ },
107
+ __wbg_get_98fdf51d029a75eb: function(arg0, arg1) {
108
+ return arg0[arg1 >>> 0];
109
+ },
110
+ __wbg_get_dcf82ab8aad1a593: function() {
111
+ return handleError(function(arg0, arg1) {
112
+ return Reflect.get(arg0, arg1);
113
+ }, arguments);
114
+ },
115
+ __wbg_instanceof_Uint8Array_abd07d4bd221d50b: function(arg0) {
116
+ let result;
117
+ try {
118
+ result = arg0 instanceof Uint8Array;
119
+ } catch (_) {
120
+ result = false;
121
+ }
122
+ return result;
123
+ },
124
+ __wbg_length_2591a0f4f659a55c: function(arg0) {
125
+ return arg0.length;
126
+ },
127
+ __wbg_length_56fcd3e2b7e0299d: function(arg0) {
128
+ return arg0.length;
129
+ },
130
+ __wbg_prototypesetcall_5f9bdc8d75e07276: function(arg0, arg1, arg2) {
131
+ Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), arg2);
132
+ },
133
+ __wbindgen_cast_0000000000000001: function(arg0, arg1) {
134
+ return getStringFromWasm0(arg0, arg1);
135
+ },
136
+ __wbindgen_init_externref_table: function() {
137
+ const table = wasm.__wbindgen_externrefs;
138
+ const offset = table.grow(4);
139
+ table.set(0, void 0);
140
+ table.set(offset + 0, void 0);
141
+ table.set(offset + 1, null);
142
+ table.set(offset + 2, true);
143
+ table.set(offset + 3, false);
144
+ }
145
+ }
146
+ };
147
+ }
148
+ const DecodeResultFinalization = typeof FinalizationRegistry === "undefined" ? {
149
+ register: () => {},
150
+ unregister: () => {}
151
+ } : new FinalizationRegistry((ptr) => wasm.__wbg_decoderesult_free(ptr, 1));
152
+ function addToExternrefTable0(obj) {
153
+ const idx = wasm.__externref_table_alloc();
154
+ wasm.__wbindgen_externrefs.set(idx, obj);
155
+ return idx;
156
+ }
157
+ function getArrayU8FromWasm0(ptr, len) {
158
+ ptr = ptr >>> 0;
159
+ return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);
160
+ }
161
+ let cachedDataViewMemory0 = null;
162
+ function getDataViewMemory0() {
163
+ if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || cachedDataViewMemory0.buffer.detached === void 0 && cachedDataViewMemory0.buffer !== wasm.memory.buffer) cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
164
+ return cachedDataViewMemory0;
165
+ }
166
+ function getStringFromWasm0(ptr, len) {
167
+ return decodeText(ptr >>> 0, len);
168
+ }
169
+ let cachedUint8ArrayMemory0 = null;
170
+ function getUint8ArrayMemory0() {
171
+ if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
172
+ return cachedUint8ArrayMemory0;
173
+ }
174
+ function handleError(f, args) {
175
+ try {
176
+ return f.apply(this, args);
177
+ } catch (e) {
178
+ const idx = addToExternrefTable0(e);
179
+ wasm.__wbindgen_exn_store(idx);
180
+ }
181
+ }
182
+ function isLikeNone(x) {
183
+ return x === void 0 || x === null;
184
+ }
185
+ function passArray8ToWasm0(arg, malloc) {
186
+ const ptr = malloc(arg.length * 1, 1) >>> 0;
187
+ getUint8ArrayMemory0().set(arg, ptr / 1);
188
+ WASM_VECTOR_LEN = arg.length;
189
+ return ptr;
190
+ }
191
+ function passStringToWasm0(arg, malloc, realloc) {
192
+ if (realloc === void 0) {
193
+ const buf = cachedTextEncoder.encode(arg);
194
+ const ptr = malloc(buf.length, 1) >>> 0;
195
+ getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
196
+ WASM_VECTOR_LEN = buf.length;
197
+ return ptr;
198
+ }
199
+ let len = arg.length;
200
+ let ptr = malloc(len, 1) >>> 0;
201
+ const mem = getUint8ArrayMemory0();
202
+ let offset = 0;
203
+ for (; offset < len; offset++) {
204
+ const code = arg.charCodeAt(offset);
205
+ if (code > 127) break;
206
+ mem[ptr + offset] = code;
207
+ }
208
+ if (offset !== len) {
209
+ if (offset !== 0) arg = arg.slice(offset);
210
+ ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
211
+ const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
212
+ const ret = cachedTextEncoder.encodeInto(arg, view);
213
+ offset += ret.written;
214
+ ptr = realloc(ptr, len, offset, 1) >>> 0;
215
+ }
216
+ WASM_VECTOR_LEN = offset;
217
+ return ptr;
218
+ }
219
+ function takeFromExternrefTable0(idx) {
220
+ const value = wasm.__wbindgen_externrefs.get(idx);
221
+ wasm.__externref_table_dealloc(idx);
222
+ return value;
223
+ }
224
+ let cachedTextDecoder = new TextDecoder("utf-8", {
225
+ ignoreBOM: true,
226
+ fatal: true
227
+ });
228
+ cachedTextDecoder.decode();
229
+ const MAX_SAFARI_DECODE_BYTES = 2146435072;
230
+ let numBytesDecoded = 0;
231
+ function decodeText(ptr, len) {
232
+ numBytesDecoded += len;
233
+ if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) {
234
+ cachedTextDecoder = new TextDecoder("utf-8", {
235
+ ignoreBOM: true,
236
+ fatal: true
237
+ });
238
+ cachedTextDecoder.decode();
239
+ numBytesDecoded = len;
240
+ }
241
+ return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
242
+ }
243
+ const cachedTextEncoder = new TextEncoder();
244
+ if (!("encodeInto" in cachedTextEncoder)) cachedTextEncoder.encodeInto = function(arg, view) {
245
+ const buf = cachedTextEncoder.encode(arg);
246
+ view.set(buf);
247
+ return {
248
+ read: arg.length,
249
+ written: buf.length
250
+ };
251
+ };
252
+ let WASM_VECTOR_LEN = 0;
253
+ let wasm;
254
+ function __wbg_finalize_init(instance, module) {
255
+ wasm = instance.exports;
256
+ cachedDataViewMemory0 = null;
257
+ cachedUint8ArrayMemory0 = null;
258
+ wasm.__wbindgen_start();
259
+ return wasm;
260
+ }
261
+ async function __wbg_load(module, imports) {
262
+ if (typeof Response === "function" && module instanceof Response) {
263
+ if (typeof WebAssembly.instantiateStreaming === "function") try {
264
+ return await WebAssembly.instantiateStreaming(module, imports);
265
+ } catch (e) {
266
+ if (module.ok && expectedResponseType(module.type) && module.headers.get("Content-Type") !== "application/wasm") console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
267
+ else throw e;
268
+ }
269
+ const bytes = await module.arrayBuffer();
270
+ return await WebAssembly.instantiate(bytes, imports);
271
+ } else {
272
+ const instance = await WebAssembly.instantiate(module, imports);
273
+ if (instance instanceof WebAssembly.Instance) return {
274
+ instance,
275
+ module
276
+ };
277
+ else return instance;
278
+ }
279
+ function expectedResponseType(type) {
280
+ switch (type) {
281
+ case "basic":
282
+ case "cors":
283
+ case "default": return true;
284
+ }
285
+ return false;
286
+ }
287
+ }
288
+ async function __wbg_init(module_or_path) {
289
+ if (wasm !== void 0) return wasm;
290
+ if (module_or_path !== void 0) {
291
+ if (Object.getPrototypeOf(module_or_path) === Object.prototype) ({module_or_path} = module_or_path);
292
+ else console.warn("using deprecated parameters for the initialization function; pass a single object instead");
293
+ }
294
+ if (module_or_path === void 0) module_or_path = new URL("arthash_wasm_bg.wasm", require("url").pathToFileURL(__filename).href);
295
+ const imports = __wbg_get_imports();
296
+ if (typeof module_or_path === "string" || typeof Request === "function" && module_or_path instanceof Request || typeof URL === "function" && module_or_path instanceof URL) module_or_path = fetch(module_or_path);
297
+ const { instance, module } = await __wbg_load(await module_or_path, imports);
298
+ return __wbg_finalize_init(instance, module);
299
+ }
300
+ //#endregion
301
+ //#region ../../node_modules/arthash/dist/palette.js
302
+ /**
303
+ * Palette type + construction helpers.
304
+ *
305
+ * Lives in its own module so both `index.ts` (which re-exports it on the
306
+ * public surface) and `palettes.ts` (which calls `palette.fromHex` at module
307
+ * top level to build the curated presets) can import it without forming a
308
+ * cycle. Putting `palette` in `index.ts` directly created a circular import:
309
+ * `index.ts → palettes.ts → index.ts`, where `palettes.ts`'s top-level
310
+ * `palette.fromHex(...)` calls executed before `index.ts` had reached its
311
+ * `export const palette = {...}` line, throwing TDZ.
312
+ */
313
+ const MIN_PALETTE_K = 2;
314
+ const MAX_PALETTE_K = 1024;
315
+ /** Palette construction helpers. */
316
+ const palette = {
317
+ /** Build a palette from an array of `[r, g, b]` triplets. */
318
+ fromRgb(colors) {
319
+ if (colors.length < MIN_PALETTE_K || colors.length > MAX_PALETTE_K) throw new Error(`palette K must be in [${MIN_PALETTE_K}, ${MAX_PALETTE_K}]; got ${colors.length}`);
320
+ const bytes = new Uint8Array(colors.length * 3);
321
+ for (let i = 0; i < colors.length; i++) {
322
+ const c = colors[i];
323
+ bytes[i * 3] = c[0];
324
+ bytes[i * 3 + 1] = c[1];
325
+ bytes[i * 3 + 2] = c[2];
326
+ }
327
+ return { bytes };
328
+ },
329
+ /** Build a palette from hex color strings (`"#aabbcc"` or `"aabbcc"`). */
330
+ fromHex(hexes) {
331
+ const rgb = hexes.map((h) => {
332
+ const s = h.startsWith("#") ? h.slice(1) : h;
333
+ if (s.length !== 6) throw new Error(`palette: expected 6-char hex, got ${h}`);
334
+ return [
335
+ parseInt(s.slice(0, 2), 16),
336
+ parseInt(s.slice(2, 4), 16),
337
+ parseInt(s.slice(4, 6), 16)
338
+ ];
339
+ });
340
+ return palette.fromRgb(rgb);
341
+ }
342
+ };
343
+ //#endregion
344
+ //#region ../../node_modules/arthash/dist/palettes.js
345
+ /**
346
+ * Hand-curated retro / pixel-game palettes for the placeholder hash.
347
+ *
348
+ * Mirrors `arthash.palettes` (Python). These are externally-designed color
349
+ * sets, NOT trained on any image corpus — they're meant to give placeholders
350
+ * a deliberate stylistic feel.
351
+ *
352
+ * References / origins:
353
+ * gbDmg Game Boy DMG (1989) — the iconic green-on-green LCD
354
+ * gbPocket Game Boy Pocket (1996) — grayscale LCD
355
+ * cgaMode4P1 IBM CGA mode 4 palette 1 — DOS-era 4-color
356
+ * pico8 PICO-8 fantasy console (Lexaloffle, 2014)
357
+ * sweetie16 GrafxKid's modern 16-color indie pixel-art palette
358
+ * endesga32 Endesga's modern 32-color indie palette (very popular)
359
+ * nes16 15-color subset commonly used by NES games
360
+ *
361
+ * See https://lospec.com for many more palettes.
362
+ */
363
+ const h$1 = (...hexes) => palette.fromHex(hexes);
364
+ const gbDmg = h$1("0F380F", "306230", "8BAC0F", "9BBC0F");
365
+ const gbPocket = h$1("000000", "545454", "A9A9A9", "FFFFFF");
366
+ const cgaMode4P1 = h$1("000000", "55FFFF", "FF55FF", "FFFFFF");
367
+ const c64Basic8 = h$1("000000", "FFFFFF", "880000", "AAFFEE", "CC44CC", "00CC55", "0000AA", "EEEE77");
368
+ const pico8 = h$1("000000", "1D2B53", "7E2553", "008751", "AB5236", "5F574F", "C2C3C7", "FFF1E8", "FF004D", "FFA300", "FFEC27", "00E436", "29ADFF", "83769C", "FF77A8", "FFCCAA");
369
+ const sweetie16 = h$1("1A1C2C", "5D275D", "B13E53", "EF7D57", "FFCD75", "A7F070", "38B764", "257179", "29366F", "3B5DC9", "41A6F6", "73EFF7", "F4F4F4", "94B0C2", "566C86", "333C57");
370
+ const nes16 = h$1("000000", "FCFCFC", "F8F8F8", "BCBCBC", "7C7C7C", "A4E4FC", "3CBCFC", "0078F8", "0000FC", "B8B8F8", "6888FC", "0058F8", "0000BC", "D8B8F8", "9878F8", "6844FC");
371
+ const c64 = h$1("000000", "FFFFFF", "880000", "AAFFEE", "CC44CC", "00CC55", "0000AA", "EEEE77", "DD8855", "664400", "FF7777", "333333", "777777", "AAFF66", "0088FF", "BBBBBB");
372
+ const endesga32 = h$1("BE4A2F", "D77643", "EAD4AA", "E4A672", "B86F50", "733E39", "3E2731", "A22633", "E43B44", "F77622", "FEAE34", "FEE761", "63C74D", "3E8948", "265C42", "193C3E", "124E89", "0099DB", "2CE8F5", "FFFFFF", "C0CBDC", "8B9BB4", "5A6988", "3A4466", "262B44", "181425", "FF0044", "68386C", "B55088", "F6757A", "E8B796", "C28569");
373
+ const aapSplendor32 = h$1("050403", "0E0C0C", "2D1B1E", "612721", "B9451D", "F1641F", "FCA570", "FFE0B7", "FFFFFF", "FFF089", "F8C53A", "E88A36", "B05B2C", "672D1F", "452326", "5D3B61", "AD5B91", "DF8BAD", "C2B0D1", "8E7DA5", "65517A", "39314C", "1A1932", "1E3E5E", "1A6291", "30A7D7", "53D2FF", "C3F4FF", "55B57F", "1E7541", "0E3E2C", "192418");
374
+ Object.freeze({
375
+ gb_dmg: gbDmg,
376
+ gb_pocket: gbPocket,
377
+ cga_mode4_p1: cgaMode4P1,
378
+ c64_basic8: c64Basic8,
379
+ pico8,
380
+ sweetie_16: sweetie16,
381
+ nes_16: nes16,
382
+ c64,
383
+ endesga_32: endesga32,
384
+ aap_splendor_32: aapSplendor32
385
+ });
386
+ //#endregion
387
+ //#region ../../node_modules/arthash/dist/index.js
388
+ /**
389
+ * arthash — TypeScript SDK.
390
+ *
391
+ * Backed by `arthash-rs` compiled to WebAssembly via wasm-bindgen.
392
+ * Works in browser and Node (>= 18, native `Uint8Array` + WebAssembly).
393
+ *
394
+ * Quick start:
395
+ * ```ts
396
+ * import { encode, decode, toSvg, codec, Preset, encodeImage } from "arthash";
397
+ *
398
+ * // Plain RGB buffer
399
+ * const hash = await encode(rgbBytes, width, height, codec.triangle({ n: 64 }));
400
+ * const { w, h, rgba } = await decode(hash, codec.triangle({ n: 64 }));
401
+ *
402
+ * // Named preset
403
+ * const hash2 = await encode(rgbBytes, w, h, codec.preset(Preset.LargeTriangle));
404
+ *
405
+ * // Browser convenience: load image, resize, encode in one call
406
+ * const hash3 = await encodeImage(imgUrl, codec.triangle({ n: 64 }));
407
+ * ```
408
+ *
409
+ * The wasm module loads automatically on first call. If you want to control
410
+ * timing (e.g. preload), call `await init()` explicitly.
411
+ */
412
+ const Shape = {
413
+ DCT: "dct",
414
+ CIRCLE: "circle",
415
+ TRIANGLE: "triangle",
416
+ PIXEL: "pixel",
417
+ SQUARE: "square",
418
+ RECT: "rect",
419
+ ROTATED_RECT: "rotrect"
420
+ };
421
+ /** Named codec recipes. Use `codec.preset(Preset.LargeTriangle)`.
422
+ *
423
+ * Two axes:
424
+ * - **size** — `Small*` (n=12, pixel n=16, ~50–80 B) → `Medium*` (n=24,
425
+ * ~100–150 B) → `Large*` (n=64, ~270–400 B).
426
+ * - **shape** — `Triangle` / `Circle` / `Pixel` / `Rect` / `Square`.
427
+ *
428
+ * Plus `Preset.Dct` — the frequency-domain placeholder (~21 B), outside the
429
+ * size axis. Actual byte counts vary ±1 B with image aspect.
430
+ *
431
+ * Pre-0.3 names (`TinyDct` / `Placeholder*` / `Detail*`) are kept as
432
+ * deprecated aliases for source compatibility; they will be removed in 1.0. */
433
+ const Preset = {
434
+ Dct: "dct",
435
+ SmallTriangle: "small_triangle",
436
+ SmallCircle: "small_circle",
437
+ SmallPixel: "small_pixel",
438
+ SmallRect: "small_rect",
439
+ SmallSquare: "small_square",
440
+ MediumTriangle: "medium_triangle",
441
+ MediumCircle: "medium_circle",
442
+ MediumPixel: "medium_pixel",
443
+ MediumRect: "medium_rect",
444
+ MediumSquare: "medium_square",
445
+ LargeTriangle: "large_triangle",
446
+ LargeCircle: "large_circle",
447
+ LargePixel: "large_pixel",
448
+ LargeRect: "large_rect",
449
+ LargeSquare: "large_square",
450
+ /** @deprecated Renamed to `Preset.Dct`. */
451
+ TinyDct: "tiny_dct",
452
+ /** @deprecated Renamed to `Preset.SmallTriangle`. */
453
+ PlaceholderTriangle: "placeholder_triangle",
454
+ /** @deprecated Renamed to `Preset.SmallCircle`. */
455
+ PlaceholderCircle: "placeholder_circle",
456
+ /** @deprecated Renamed to `Preset.SmallPixel`. */
457
+ PlaceholderPixel: "placeholder_pixel",
458
+ /** @deprecated Renamed to `Preset.LargeTriangle`. */
459
+ DetailTriangle: "detail_triangle",
460
+ /** @deprecated Renamed to `Preset.LargeCircle`. */
461
+ DetailCircle: "detail_circle",
462
+ /** @deprecated Renamed to `Preset.LargePixel`. */
463
+ DetailPixel: "detail_pixel"
464
+ };
465
+ /** Factory namespace — `codec.dct()`, `codec.triangle({ n: 64 })`, …
466
+ *
467
+ * Each factory's return type is inferred to its narrowed shape (e.g.
468
+ * `{ kind: 'rect', n: number, color?: ColorMode }`) rather than the broad
469
+ * `Codec` union. This narrowing lets `RenderStyle<C>`'s conditional type
470
+ * enforce `cornerRadius` is only allowed for rect-family codecs at call
471
+ * sites. Returns are still assignable to `Codec` since every narrow type
472
+ * is a member of the union. */
473
+ const codec = {
474
+ dct() {
475
+ return { kind: "dct" };
476
+ },
477
+ circle(opts = {}) {
478
+ return {
479
+ kind: "circle",
480
+ n: opts.n ?? 12,
481
+ color: opts.color
482
+ };
483
+ },
484
+ triangle(opts = {}) {
485
+ return {
486
+ kind: "triangle",
487
+ n: opts.n ?? 12,
488
+ color: opts.color
489
+ };
490
+ },
491
+ square(opts = {}) {
492
+ return {
493
+ kind: "square",
494
+ n: opts.n ?? 12,
495
+ color: opts.color
496
+ };
497
+ },
498
+ rect(opts = {}) {
499
+ return {
500
+ kind: "rect",
501
+ n: opts.n ?? 12,
502
+ color: opts.color
503
+ };
504
+ },
505
+ rotatedRect(opts = {}) {
506
+ return {
507
+ kind: "rotrect",
508
+ n: opts.n ?? 12,
509
+ thetaBits: opts.thetaBits,
510
+ color: opts.color
511
+ };
512
+ },
513
+ pixel(opts = {}) {
514
+ return {
515
+ kind: "pixel",
516
+ n: opts.n ?? 12,
517
+ gridAspect: opts.gridAspect,
518
+ color: opts.color
519
+ };
520
+ },
521
+ preset(p) {
522
+ switch (p) {
523
+ case Preset.Dct: return codec.dct();
524
+ case Preset.SmallTriangle: return codec.triangle({ n: 12 });
525
+ case Preset.SmallCircle: return codec.circle({ n: 12 });
526
+ case Preset.SmallPixel: return codec.pixel({ n: 16 });
527
+ case Preset.SmallRect: return codec.rect({ n: 12 });
528
+ case Preset.SmallSquare: return codec.square({ n: 12 });
529
+ case Preset.MediumTriangle: return codec.triangle({ n: 24 });
530
+ case Preset.MediumCircle: return codec.circle({ n: 24 });
531
+ case Preset.MediumPixel: return codec.pixel({ n: 24 });
532
+ case Preset.MediumRect: return codec.rect({ n: 24 });
533
+ case Preset.MediumSquare: return codec.square({ n: 24 });
534
+ case Preset.LargeTriangle: return codec.triangle({ n: 64 });
535
+ case Preset.LargeCircle: return codec.circle({ n: 64 });
536
+ case Preset.LargePixel: return codec.pixel({ n: 64 });
537
+ case Preset.LargeRect: return codec.rect({ n: 64 });
538
+ case Preset.LargeSquare: return codec.square({ n: 64 });
539
+ case Preset.TinyDct: return codec.dct();
540
+ case Preset.PlaceholderTriangle: return codec.triangle({ n: 12 });
541
+ case Preset.PlaceholderCircle: return codec.circle({ n: 12 });
542
+ case Preset.PlaceholderPixel: return codec.pixel({ n: 16 });
543
+ case Preset.DetailTriangle: return codec.triangle({ n: 64 });
544
+ case Preset.DetailCircle: return codec.circle({ n: 64 });
545
+ case Preset.DetailPixel: return codec.pixel({ n: 64 });
546
+ }
547
+ },
548
+ /** Convenience: switch a codec's color mode to palette indexing. */
549
+ withPalette(c, palette) {
550
+ if (c.kind === "dct" || c.kind === "raw") return c;
551
+ return {
552
+ ...c,
553
+ color: {
554
+ type: "palette",
555
+ palette
556
+ }
557
+ };
558
+ },
559
+ /** Low-level escape hatch — set any SPEC field. For conformance tests and
560
+ * advanced controls. Normal users should prefer the named factories. */
561
+ raw(spec) {
562
+ return {
563
+ kind: "raw",
564
+ spec
565
+ };
566
+ },
567
+ /** True if this codec stores per-shape palette indices instead of full color. */
568
+ isPaletteMode(c) {
569
+ if (c.kind === "raw") return c.spec.palette !== void 0;
570
+ if (c.kind === "dct") return false;
571
+ return c.color?.type === "palette";
572
+ },
573
+ /** Total byte length of a hash produced by this codec (header + body, rounded
574
+ * up to the byte). Matches Python's `Codec.bytes_total()` and Rust's
575
+ * `Codec::bytes_total()`. */
576
+ bytesTotal(c) {
577
+ return computeBytesTotal(codecToSpecFields(c));
578
+ }
579
+ };
580
+ const DEFAULT_SPEC = {
581
+ shape: Shape.DCT,
582
+ nShapes: 12,
583
+ cxBits: 5,
584
+ cyBits: 5,
585
+ rBits: 4,
586
+ alphaBits: 3,
587
+ colorBits: 16,
588
+ thetaBits: 5,
589
+ paletteK: 0,
590
+ hasPalette: false
591
+ };
592
+ function codecToSpecFields(c) {
593
+ if (c.kind === "raw") {
594
+ const s = c.spec;
595
+ const paletteLen = s.palette?.length ?? 0;
596
+ const k = s.paletteK ?? (paletteLen > 0 ? paletteLen / 3 : 0);
597
+ return {
598
+ ...DEFAULT_SPEC,
599
+ shape: s.shape,
600
+ nShapes: s.nShapes ?? DEFAULT_SPEC.nShapes,
601
+ cxBits: s.cxBits ?? DEFAULT_SPEC.cxBits,
602
+ cyBits: s.cyBits ?? DEFAULT_SPEC.cyBits,
603
+ rBits: s.rBits ?? DEFAULT_SPEC.rBits,
604
+ alphaBits: s.alphaBits ?? DEFAULT_SPEC.alphaBits,
605
+ colorBits: s.colorBits ?? DEFAULT_SPEC.colorBits,
606
+ thetaBits: s.thetaBits ?? DEFAULT_SPEC.thetaBits,
607
+ hasPalette: paletteLen > 0,
608
+ paletteK: k
609
+ };
610
+ }
611
+ if (c.kind === "dct") return {
612
+ ...DEFAULT_SPEC,
613
+ shape: Shape.DCT
614
+ };
615
+ const out = {
616
+ ...DEFAULT_SPEC,
617
+ shape: c.kind,
618
+ nShapes: c.n
619
+ };
620
+ if (c.kind === "rotrect" && c.thetaBits !== void 0) out.thetaBits = c.thetaBits;
621
+ const color = c.color;
622
+ if (color && color.type === "rgb888") out.colorBits = 24;
623
+ else if (color && color.type === "palette") {
624
+ out.hasPalette = true;
625
+ out.paletteK = color.palette.k ?? color.palette.bytes.length / 3;
626
+ }
627
+ return out;
628
+ }
629
+ function colorFieldBits(s) {
630
+ if (s.hasPalette) {
631
+ const k = Math.max(2, s.paletteK);
632
+ return 32 - Math.clz32(k - 1);
633
+ }
634
+ return s.colorBits;
635
+ }
636
+ function perShapeBits(s) {
637
+ const cx = s.cxBits, cy = s.cyBits, r = s.rBits;
638
+ const col = colorFieldBits(s), a = s.alphaBits;
639
+ switch (s.shape) {
640
+ case Shape.CIRCLE:
641
+ case Shape.SQUARE: return cx + cy + r + col + a;
642
+ case Shape.RECT: return cx + cy + 2 * r + col + a;
643
+ case Shape.ROTATED_RECT: return cx + cy + 2 * r + s.thetaBits + col + a;
644
+ case Shape.TRIANGLE: return 3 * (cx + cy) + col + a;
645
+ case Shape.PIXEL: return col;
646
+ case Shape.DCT: return 0;
647
+ }
648
+ }
649
+ function computeBytesTotal(s) {
650
+ if (s.shape === Shape.DCT) return Math.ceil(27);
651
+ const headerBits = s.shape === Shape.PIXEL ? 8 : 8 + colorFieldBits(s);
652
+ return Math.ceil((headerBits + s.nShapes * perShapeBits(s)) / 8);
653
+ }
654
+ let wasmReady = null;
655
+ async function ready() {
656
+ if (!wasmReady) wasmReady = __wbg_init().then(() => void 0);
657
+ await wasmReady;
658
+ }
659
+ /** camelCase → snake_case for FFI key names. `nShapes` → `n_shapes`, `rBits`
660
+ * → `r_bits`. Acronyms and existing underscores are preserved. */
661
+ function toSnakeCase(key) {
662
+ return key.replace(/[A-Z]/g, (m) => "_" + m.toLowerCase());
663
+ }
664
+ /** Copy own enumerable keys from `src` into `dst` with camelCase → snake_case
665
+ * renaming. Skips entries whose value (or transformed value) is `undefined`,
666
+ * so optional fields don't show up as `null` on the Rust side. Pass a
667
+ * `transform` map to override specific keys (return `undefined` to skip). */
668
+ function assignSnakeCase(dst, src, transform = {}) {
669
+ for (const [k, v] of Object.entries(src)) {
670
+ if (v === void 0) continue;
671
+ const fn = transform[k];
672
+ const transformed = fn ? fn(v) : v;
673
+ if (transformed === void 0) continue;
674
+ dst[toSnakeCase(k)] = transformed;
675
+ }
676
+ }
677
+ function codecToFfi(c) {
678
+ if (c.kind === "raw") {
679
+ const out = {};
680
+ assignSnakeCase(out, c.spec, { palette: (v) => {
681
+ const arr = v;
682
+ return arr.length > 0 ? arr : void 0;
683
+ } });
684
+ return out;
685
+ }
686
+ const out = { shape: c.kind };
687
+ if (c.kind === "dct") return out;
688
+ out.n_shapes = c.n;
689
+ if (c.kind === "rotrect" && c.thetaBits !== void 0) out.theta_bits = c.thetaBits;
690
+ if (c.kind === "pixel" && c.gridAspect !== void 0) out.grid_aspect = c.gridAspect;
691
+ const color = c.color;
692
+ if (color === void 0 || color.type === "rgb565") out.color_bits = 16;
693
+ else if (color.type === "rgb888") out.color_bits = 24;
694
+ else {
695
+ out.color_bits = 16;
696
+ const pal = color.palette;
697
+ out.palette = pal.bytes;
698
+ if (pal.k !== void 0) out.palette_k = pal.k;
699
+ }
700
+ return out;
701
+ }
702
+ function searchToFfi(s) {
703
+ if (!s) return void 0;
704
+ const out = {};
705
+ assignSnakeCase(out, s);
706
+ return out;
707
+ }
708
+ /** Encode RGB bytes into a placeholder hash. */
709
+ async function encode(rgb, width, height, c, opts = {}) {
710
+ await ready();
711
+ return encodeRgb(rgb, width, height, codecToFfi(c), BigInt(opts.seed ?? 0), searchToFfi(opts.search));
712
+ }
713
+ //#endregion
714
+ //#region ../../node_modules/blurhash/dist/index.mjs
715
+ var q = [
716
+ "0",
717
+ "1",
718
+ "2",
719
+ "3",
720
+ "4",
721
+ "5",
722
+ "6",
723
+ "7",
724
+ "8",
725
+ "9",
726
+ "A",
727
+ "B",
728
+ "C",
729
+ "D",
730
+ "E",
731
+ "F",
732
+ "G",
733
+ "H",
734
+ "I",
735
+ "J",
736
+ "K",
737
+ "L",
738
+ "M",
739
+ "N",
740
+ "O",
741
+ "P",
742
+ "Q",
743
+ "R",
744
+ "S",
745
+ "T",
746
+ "U",
747
+ "V",
748
+ "W",
749
+ "X",
750
+ "Y",
751
+ "Z",
752
+ "a",
753
+ "b",
754
+ "c",
755
+ "d",
756
+ "e",
757
+ "f",
758
+ "g",
759
+ "h",
760
+ "i",
761
+ "j",
762
+ "k",
763
+ "l",
764
+ "m",
765
+ "n",
766
+ "o",
767
+ "p",
768
+ "q",
769
+ "r",
770
+ "s",
771
+ "t",
772
+ "u",
773
+ "v",
774
+ "w",
775
+ "x",
776
+ "y",
777
+ "z",
778
+ "#",
779
+ "$",
780
+ "%",
781
+ "*",
782
+ "+",
783
+ ",",
784
+ "-",
785
+ ".",
786
+ ":",
787
+ ";",
788
+ "=",
789
+ "?",
790
+ "@",
791
+ "[",
792
+ "]",
793
+ "^",
794
+ "_",
795
+ "{",
796
+ "|",
797
+ "}",
798
+ "~"
799
+ ];
800
+ var p = (t, e) => {
801
+ var r = "";
802
+ for (let n = 1; n <= e; n++) {
803
+ let l = Math.floor(t) / Math.pow(83, e - n) % 83;
804
+ r += q[Math.floor(l)];
805
+ }
806
+ return r;
807
+ };
808
+ var f = (t) => {
809
+ let e = t / 255;
810
+ return e <= .04045 ? e / 12.92 : Math.pow((e + .055) / 1.055, 2.4);
811
+ };
812
+ var h = (t) => {
813
+ let e = Math.max(0, Math.min(1, t));
814
+ return e <= .0031308 ? Math.trunc(e * 12.92 * 255 + .5) : Math.trunc((1.055 * Math.pow(e, .4166666666666667) - .055) * 255 + .5);
815
+ };
816
+ var F = (t) => t < 0 ? -1 : 1;
817
+ var M = (t, e) => F(t) * Math.pow(Math.abs(t), e);
818
+ var d = class extends Error {
819
+ constructor(e) {
820
+ super(e), this.name = "ValidationError", this.message = e;
821
+ }
822
+ };
823
+ var A = 4;
824
+ var D = (t, e, r, n) => {
825
+ let l = 0, m = 0, b = 0, g = e * A;
826
+ for (let u = 0; u < e; u++) {
827
+ let c = A * u;
828
+ for (let s = 0; s < r; s++) {
829
+ let o = c + s * g, a = n(u, s);
830
+ l += a * f(t[o]), m += a * f(t[o + 1]), b += a * f(t[o + 2]);
831
+ }
832
+ }
833
+ let i = 1 / (e * r);
834
+ return [
835
+ l * i,
836
+ m * i,
837
+ b * i
838
+ ];
839
+ };
840
+ var $ = (t) => {
841
+ let e = h(t[0]), r = h(t[1]), n = h(t[2]);
842
+ return (e << 16) + (r << 8) + n;
843
+ };
844
+ var H = (t, e) => {
845
+ let r = Math.floor(Math.max(0, Math.min(18, Math.floor(M(t[0] / e, .5) * 9 + 9.5)))), n = Math.floor(Math.max(0, Math.min(18, Math.floor(M(t[1] / e, .5) * 9 + 9.5)))), l = Math.floor(Math.max(0, Math.min(18, Math.floor(M(t[2] / e, .5) * 9 + 9.5))));
846
+ return r * 19 * 19 + n * 19 + l;
847
+ };
848
+ var O = (t, e, r, n, l) => {
849
+ if (n < 1 || n > 9 || l < 1 || l > 9) throw new d("BlurHash must have between 1 and 9 components");
850
+ if (e * r * 4 !== t.length) throw new d("Width and height must match the pixels array");
851
+ let m = [];
852
+ for (let s = 0; s < l; s++) for (let o = 0; o < n; o++) {
853
+ let a = o == 0 && s == 0 ? 1 : 2, y = D(t, e, r, (B, R) => a * Math.cos(Math.PI * o * B / e) * Math.cos(Math.PI * s * R / r));
854
+ m.push(y);
855
+ }
856
+ let b = m[0], g = m.slice(1), i = "", u = n - 1 + (l - 1) * 9;
857
+ i += p(u, 1);
858
+ let c;
859
+ if (g.length > 0) {
860
+ let s = Math.max(...g.map((a) => Math.max(...a))), o = Math.floor(Math.max(0, Math.min(82, Math.floor(s * 166 - .5))));
861
+ c = (o + 1) / 166, i += p(o, 1);
862
+ } else c = 1, i += p(0, 1);
863
+ return i += p($(b), 4), g.forEach((s) => {
864
+ i += p(H(s, c), 2);
865
+ }), i;
866
+ };
867
+ var S = O;
868
+ //#endregion
869
+ //#region src/hash.ts
870
+ /**
871
+ * Browser-side MD5 hashing (lazy-loaded).
872
+ *
873
+ * spark-md5 是纯 JS 实现,体积小;动态导入让 bundler 不把它打进 dist,
874
+ * consumer 必须把它列为 `peerDependency` 或 `optionalDependencies`。
875
+ */
876
+ const CHUNK_SIZE = 2097152;
877
+ /**
878
+ * 计算 Blob/File 的 MD5 摘要(hex string)。
879
+ * 静态方法,便于在 Worker / OffscreenCanvas 等环境调用。
880
+ */
881
+ async function hashBlob(blob, onProgress) {
882
+ if (typeof blob === "undefined" || blob.size === void 0) throw new TypeError("hashBlob 需要传入 Blob 或 File");
883
+ const { default: SparkMD5 } = await Promise.resolve().then(() => /* @__PURE__ */ require_rolldown_runtime.__toESM(require("./spark-md5-BgTNfY_D.cjs").default, 1));
884
+ const hash = new SparkMD5.ArrayBuffer();
885
+ const total = Math.max(1, Math.ceil(blob.size / CHUNK_SIZE));
886
+ for (let index = 0; index < total; index += 1) {
887
+ const start = index * CHUNK_SIZE;
888
+ const buffer = await blob.slice(start, Math.min(start + CHUNK_SIZE, blob.size)).arrayBuffer();
889
+ hash.append(buffer);
890
+ onProgress?.({
891
+ loaded: index + 1,
892
+ percent: Math.round((index + 1) / total * 100),
893
+ total
894
+ });
895
+ }
896
+ return hash.end();
897
+ }
898
+ //#endregion
899
+ //#region src/video-metadata.ts
900
+ /** Video metadata extracted by the local metaprobe WASM package. */
901
+ const num = (v) => {
902
+ const n = typeof v === "number" ? v : Number(v);
903
+ return Number.isFinite(n) ? n : void 0;
904
+ };
905
+ const str = (v) => typeof v === "string" && v.trim() ? v.trim() : void 0;
906
+ const date = (v) => {
907
+ const s = str(v);
908
+ if (!s) return;
909
+ const normalized = s.replace(/^UTC\s+/i, "").replace(" ", "T") + (s.match(/^UTC\s+/i) ? "Z" : "");
910
+ const d = new Date(normalized);
911
+ return Number.isNaN(d.valueOf()) ? s : d.toISOString();
912
+ };
913
+ const clean = (v) => Object.fromEntries(Object.entries(v).filter(([, x]) => x !== void 0));
914
+ /** Compatibility mapper for callers that still provide the old MediaInfo shape. */
915
+ function mapMediaInfoResult(input) {
916
+ const tracks = input.media?.track ?? [];
917
+ if (!tracks.length) return {};
918
+ const general = tracks.find((t) => t["@type"] === "General") ?? {};
919
+ const video = tracks.find((t) => t["@type"] === "Video") ?? {};
920
+ const location = str(general.Recorded_Location)?.match(/([+-]?\d+(?:\.\d+)?)°?\s*([NS]).*?([+-]?\d+(?:\.\d+)?)°?\s*([EW]).*?(\d+(?:\.\d+)?)m/i);
921
+ return clean({
922
+ bitrate: num(video.BitRate),
923
+ codec: str(video.Format),
924
+ creation_time: date(general.Encoded_Date),
925
+ duration: num(general.Duration),
926
+ exif: location ? {
927
+ altitude: num(location[5]),
928
+ device_make: str(general.Encoded_Hardware_CompanyName),
929
+ device_model: str(general.Encoded_Hardware_Name),
930
+ lat: num(location[1]),
931
+ lng: num(location[3]),
932
+ taken_at: date(general.Encoded_Date)
933
+ } : void 0,
934
+ frame_rate: num(video.FrameRate),
935
+ height: num(video.Height),
936
+ metadata: {
937
+ general,
938
+ video
939
+ },
940
+ width: num(video.Width)
941
+ });
942
+ }
943
+ function mapExif(e) {
944
+ if (!e) return;
945
+ const v = clean({
946
+ altitude: num(e.altitude ?? e.GPSAltitude),
947
+ device_make: str(e.Make),
948
+ device_model: str(e.Model),
949
+ exposure_time: str(e.ExposureTime),
950
+ f_number: str(e.FNumber),
951
+ focal_length: str(e.FocalLength),
952
+ iso: num(e.ISO ?? e.PhotographicSensitivity),
953
+ lat: num(e.latitude ?? e.GPSLatitude),
954
+ lens_model: str(e.LensModel),
955
+ lng: num(e.longitude ?? e.GPSLongitude),
956
+ taken_at: date(e.DateTimeOriginalISO ?? e.DateTimeOriginal)
957
+ });
958
+ return Object.keys(v).length ? v : void 0;
959
+ }
960
+ async function loadMediaInfoVideoMetadata(file) {
961
+ await (0, metaprobe.default)();
962
+ const m = (0, metaprobe.extractMetaFastSized)(new Uint8Array(await file.arrayBuffer()), file.name ?? "", file.size);
963
+ return clean({
964
+ bitrate: num(m.overallBitrate),
965
+ codec: str(m.codec),
966
+ creation_time: date(m.creationTime ?? m.containerCreationTime),
967
+ duration: num(m.duration),
968
+ exif: mapExif(m.exif),
969
+ frame_rate: num(m.frameRate),
970
+ height: num(m.height),
971
+ metadata: m.metadata,
972
+ width: num(m.width)
973
+ });
974
+ }
975
+ //#endregion
976
+ //#region src/analyze.ts
977
+ /**
978
+ * Browser-side media analysis:
979
+ * - images: blurhash / arthash / dominant colors / EXIF
980
+ * - videos: mediainfo metadata + EXIF
981
+ */
982
+ const MEDIA_ANALYSIS_SCHEMA_VERSION = 1;
983
+ const ARTHASH_CODEC = "rect-v64";
984
+ const IMAGE_TYPES = ["image/"];
985
+ const IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([
986
+ "jpg",
987
+ "jpeg",
988
+ "png",
989
+ "gif",
990
+ "webp",
991
+ "avif",
992
+ "heic",
993
+ "heif",
994
+ "tif",
995
+ "tiff",
996
+ "bmp"
997
+ ]);
998
+ const VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([
999
+ "mov",
1000
+ "mp4",
1001
+ "avi",
1002
+ "mkv",
1003
+ "webm",
1004
+ "m4v"
1005
+ ]);
1006
+ function getExtension(fileName) {
1007
+ const dot = fileName.lastIndexOf(".");
1008
+ return dot >= 0 ? fileName.slice(dot).toLowerCase() : "";
1009
+ }
1010
+ function isVideoFile(file) {
1011
+ return file.type.startsWith("video/") || VIDEO_EXTENSIONS.has(getExtension(file.name ?? "").slice(1));
1012
+ }
1013
+ function isImageFile(file) {
1014
+ return IMAGE_TYPES.some((prefix) => file.type.startsWith(prefix)) || IMAGE_EXTENSIONS.has(getExtension(file.name ?? "").slice(1));
1015
+ }
1016
+ function createCanvas(width, height) {
1017
+ if (typeof OffscreenCanvas !== "undefined") return new OffscreenCanvas(width, height);
1018
+ if (typeof document !== "undefined") {
1019
+ const canvas = document.createElement("canvas");
1020
+ canvas.width = width;
1021
+ canvas.height = height;
1022
+ return canvas;
1023
+ }
1024
+ throw new Error("当前环境不支持 Canvas,无法分析图片");
1025
+ }
1026
+ function get2DContext(canvas) {
1027
+ const context = canvas.getContext("2d", { willReadFrequently: true });
1028
+ if (!context) throw new Error("无法创建 Canvas 2D 上下文");
1029
+ return context;
1030
+ }
1031
+ async function decodeImage(file, maxDimension) {
1032
+ if (typeof createImageBitmap === "function") {
1033
+ const bitmap = await createImageBitmap(file, { imageOrientation: "from-image" });
1034
+ try {
1035
+ return rasterize(bitmap, bitmap.width, bitmap.height, maxDimension);
1036
+ } finally {
1037
+ bitmap.close();
1038
+ }
1039
+ }
1040
+ if (typeof Image === "undefined" || typeof URL === "undefined") throw new Error("当前环境不支持图片解码");
1041
+ const url = URL.createObjectURL(file);
1042
+ try {
1043
+ const image = await new Promise((resolve, reject) => {
1044
+ const element = new Image();
1045
+ element.onload = () => resolve(element);
1046
+ element.onerror = () => reject(/* @__PURE__ */ new Error("图片解码失败"));
1047
+ element.src = url;
1048
+ });
1049
+ return rasterize(image, image.naturalWidth, image.naturalHeight, maxDimension);
1050
+ } finally {
1051
+ URL.revokeObjectURL(url);
1052
+ }
1053
+ }
1054
+ function rasterize(source, sourceWidth, sourceHeight, maxDimension) {
1055
+ const scale = Math.min(1, Math.max(1, maxDimension) / Math.max(sourceWidth, sourceHeight));
1056
+ const width = Math.max(1, Math.round(sourceWidth * scale));
1057
+ const height = Math.max(1, Math.round(sourceHeight * scale));
1058
+ const context = get2DContext(createCanvas(width, height));
1059
+ context.drawImage(source, 0, 0, width, height);
1060
+ return {
1061
+ height,
1062
+ rgba: context.getImageData(0, 0, width, height).data,
1063
+ sourceHeight,
1064
+ sourceWidth,
1065
+ width
1066
+ };
1067
+ }
1068
+ function rgbBytes(rgba) {
1069
+ const rgb = new Uint8Array(rgba.length / 4 * 3);
1070
+ for (let source = 0, target = 0; source < rgba.length; source += 4, target += 3) {
1071
+ rgb[target] = rgba[source] ?? 0;
1072
+ rgb[target + 1] = rgba[source + 1] ?? 0;
1073
+ rgb[target + 2] = rgba[source + 2] ?? 0;
1074
+ }
1075
+ return rgb;
1076
+ }
1077
+ function bytesToBase64(bytes) {
1078
+ if (typeof btoa !== "function") throw new Error("当前环境不支持 Base64 编码");
1079
+ let binary = "";
1080
+ const chunkSize = 32768;
1081
+ for (let index = 0; index < bytes.length; index += chunkSize) binary += String.fromCharCode(...bytes.subarray(index, index + chunkSize));
1082
+ return btoa(binary);
1083
+ }
1084
+ function toHex(value) {
1085
+ return value.toString(16).padStart(2, "0");
1086
+ }
1087
+ function extractColors(rgba, maxColors) {
1088
+ const colorMap = /* @__PURE__ */ new Map();
1089
+ const totalPixels = rgba.length / 4;
1090
+ const step = totalPixels > 1e5 ? 3 : totalPixels > 5e4 ? 2 : 1;
1091
+ let sampledPixels = 0;
1092
+ for (let pixel = 0; pixel < rgba.length; pixel += 4 * step) {
1093
+ if ((rgba[pixel + 3] ?? 0) < 128) continue;
1094
+ const r = Math.floor((rgba[pixel] ?? 0) / 16) * 16;
1095
+ const g = Math.floor((rgba[pixel + 1] ?? 0) / 16) * 16;
1096
+ const b = Math.floor((rgba[pixel + 2] ?? 0) / 16) * 16;
1097
+ const brightness = (r + g + b) / 3;
1098
+ if (brightness < 10 || brightness > 245) continue;
1099
+ const key = `${r},${g},${b}`;
1100
+ const current = colorMap.get(key);
1101
+ if (current) current.count += 1;
1102
+ else colorMap.set(key, {
1103
+ b,
1104
+ count: 1,
1105
+ g,
1106
+ r
1107
+ });
1108
+ sampledPixels += 1;
1109
+ }
1110
+ if (sampledPixels === 0) return [];
1111
+ return [...colorMap.values()].sort((left, right) => right.count - left.count).slice(0, Math.max(0, maxColors)).map((color, rank) => ({
1112
+ b: color.b,
1113
+ g: color.g,
1114
+ hex: `#${toHex(color.r)}${toHex(color.g)}${toHex(color.b)}`,
1115
+ is_primary: rank === 0,
1116
+ percentage: color.count / sampledPixels * 100,
1117
+ r: color.r,
1118
+ rank
1119
+ }));
1120
+ }
1121
+ function valueFrom(record, ...keys) {
1122
+ if (!record) return;
1123
+ for (const key of keys) if (record[key] !== void 0 && record[key] !== null) return record[key];
1124
+ }
1125
+ function numberFrom(value) {
1126
+ const number = typeof value === "number" ? value : Number(value);
1127
+ return Number.isFinite(number) ? number : void 0;
1128
+ }
1129
+ function stringFrom(value) {
1130
+ if (value instanceof Date) return value.toISOString();
1131
+ if (typeof value === "string" && value.trim()) return value.trim();
1132
+ if (typeof value === "number" && Number.isFinite(value)) return String(value);
1133
+ }
1134
+ function dateFrom(value) {
1135
+ if (value instanceof Date && !Number.isNaN(value.valueOf())) return value.toISOString();
1136
+ if (typeof value === "string" && value.trim()) {
1137
+ const date = new Date(value);
1138
+ return Number.isNaN(date.valueOf()) ? value : date.toISOString();
1139
+ }
1140
+ }
1141
+ async function readExif(file, includeRaw) {
1142
+ try {
1143
+ await (0, metaprobe.default)();
1144
+ const parsed = (0, metaprobe.extractMetaFastSized)(new Uint8Array(await file.arrayBuffer()), file.name ?? "", file.size).exif;
1145
+ const gps = parsed;
1146
+ const exif = {
1147
+ altitude: numberFrom(valueFrom(parsed, "GPSAltitude", "altitude")),
1148
+ device_make: stringFrom(valueFrom(parsed, "Make", "make", "DeviceManufacturer")),
1149
+ device_model: stringFrom(valueFrom(parsed, "Model", "model", "DeviceModel")),
1150
+ exposure_time: stringFrom(valueFrom(parsed, "ExposureTime", "exposure_time")),
1151
+ f_number: stringFrom(valueFrom(parsed, "FNumber", "f_number")),
1152
+ focal_length: stringFrom(valueFrom(parsed, "FocalLength", "focal_length")),
1153
+ iso: numberFrom(valueFrom(parsed, "ISO", "iso")),
1154
+ lat: numberFrom(valueFrom(gps, "latitude", "GPSLatitude", "lat") ?? valueFrom(parsed, "latitude", "GPSLatitude")),
1155
+ lens_model: stringFrom(valueFrom(parsed, "LensModel", "Lens", "lens_model")),
1156
+ lng: numberFrom(valueFrom(gps, "longitude", "GPSLongitude", "lng") ?? valueFrom(parsed, "longitude", "GPSLongitude")),
1157
+ orientation: numberFrom(valueFrom(parsed, "Orientation", "orientation")),
1158
+ taken_at: dateFrom(valueFrom(parsed, "DateTimeOriginal", "CreateDate", "CreationDate", "creation_time"))
1159
+ };
1160
+ if (includeRaw && parsed) exif.raw = parsed;
1161
+ return removeUndefined(exif);
1162
+ } catch (error) {
1163
+ console.error("metaprobe EXIF 解析失败:", error);
1164
+ return {};
1165
+ }
1166
+ }
1167
+ function removeUndefined(value) {
1168
+ return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== void 0));
1169
+ }
1170
+ async function analyzeImage(file, options) {
1171
+ const raster = await decodeImage(file, options.analysisMaxDimension ?? 100);
1172
+ const componentX = options.blurhashComponentX ?? 4;
1173
+ const componentY = options.blurhashComponentY ?? 3;
1174
+ const blurhash = S(raster.rgba, raster.width, raster.height, componentX, componentY);
1175
+ const hash = await encode(rgbBytes(raster.rgba), raster.width, raster.height, codec.rect({ n: 64 }));
1176
+ const exif = await readExif(file, options.includeRawExif ?? false);
1177
+ return {
1178
+ height: raster.sourceHeight,
1179
+ image: {
1180
+ arthash: bytesToBase64(hash),
1181
+ arthash_codec: ARTHASH_CODEC,
1182
+ blurhash,
1183
+ colors: extractColors(raster.rgba, options.colorCount ?? 5),
1184
+ exif
1185
+ },
1186
+ width: raster.sourceWidth
1187
+ };
1188
+ }
1189
+ async function analyzeVideo(file, includeRawExif) {
1190
+ const metadata = await loadVideoMetadata(file);
1191
+ const exif = removeUndefined({
1192
+ ...metadata.exif,
1193
+ ...await readExif(file, includeRawExif)
1194
+ });
1195
+ return { video: removeUndefined({
1196
+ bitrate: metadata.bitrate,
1197
+ codec: metadata.codec,
1198
+ creation_time: metadata.creation_time ?? exif.taken_at,
1199
+ duration: metadata.duration,
1200
+ exif,
1201
+ frame_rate: metadata.frame_rate,
1202
+ height: metadata.height,
1203
+ metadata: removeUndefined({
1204
+ ...metadata.metadata,
1205
+ exif: exif.raw
1206
+ }),
1207
+ width: metadata.width
1208
+ }) };
1209
+ }
1210
+ async function loadVideoElementMetadata(file) {
1211
+ if (typeof document === "undefined" || typeof URL === "undefined") return {};
1212
+ const url = URL.createObjectURL(file);
1213
+ try {
1214
+ const video = document.createElement("video");
1215
+ video.preload = "metadata";
1216
+ video.muted = true;
1217
+ await new Promise((resolve, reject) => {
1218
+ video.onloadedmetadata = () => resolve();
1219
+ video.onerror = () => reject(/* @__PURE__ */ new Error("视频元数据读取失败"));
1220
+ video.src = url;
1221
+ });
1222
+ return removeUndefined({
1223
+ duration: Number.isFinite(video.duration) ? video.duration : void 0,
1224
+ height: video.videoHeight || void 0,
1225
+ width: video.videoWidth || void 0
1226
+ });
1227
+ } finally {
1228
+ URL.revokeObjectURL(url);
1229
+ }
1230
+ }
1231
+ async function loadVideoMetadata(file) {
1232
+ const browserMetadata = await loadVideoElementMetadata(file).catch(() => ({}));
1233
+ try {
1234
+ const mediaInfoMetadata = await loadMediaInfoVideoMetadata(file);
1235
+ return removeUndefined({
1236
+ ...browserMetadata,
1237
+ ...mediaInfoMetadata,
1238
+ duration: mediaInfoMetadata.duration ?? browserMetadata.duration,
1239
+ height: mediaInfoMetadata.height ?? browserMetadata.height,
1240
+ width: mediaInfoMetadata.width ?? browserMetadata.width
1241
+ });
1242
+ } catch {
1243
+ return browserMetadata;
1244
+ }
1245
+ }
1246
+ async function analyzeMedia(file, options = {}) {
1247
+ if (!file || typeof file.arrayBuffer !== "function") throw new TypeError("analyzeMedia 需要传入 File 或 Blob");
1248
+ options.onProgress?.({
1249
+ percent: 0,
1250
+ stage: "md5"
1251
+ });
1252
+ const md5 = await hashBlob(file, ({ percent }) => options.onProgress?.({
1253
+ percent,
1254
+ stage: "md5"
1255
+ }));
1256
+ const result = {
1257
+ basic: {
1258
+ extension: getExtension(file.name ?? ""),
1259
+ md5,
1260
+ name: file.name ?? "",
1261
+ size: file.size,
1262
+ type: file.type || "application/octet-stream"
1263
+ },
1264
+ schema_version: 1
1265
+ };
1266
+ if (isImageFile(file)) {
1267
+ options.onProgress?.({
1268
+ percent: 0,
1269
+ stage: "decode"
1270
+ });
1271
+ const image = await analyzeImage(file, options);
1272
+ result.basic.width = image.width;
1273
+ result.basic.height = image.height;
1274
+ result.image = image.image;
1275
+ options.onProgress?.({
1276
+ percent: 100,
1277
+ stage: "analyze"
1278
+ });
1279
+ } else if (isVideoFile(file)) {
1280
+ const video = await analyzeVideo(file, options.includeRawExif ?? false);
1281
+ result.basic.width = video.video.width;
1282
+ result.basic.height = video.video.height;
1283
+ result.video = video.video;
1284
+ options.onProgress?.({
1285
+ percent: 100,
1286
+ stage: "analyze"
1287
+ });
1288
+ }
1289
+ return result;
1290
+ }
1291
+ //#endregion
1292
+ exports.ARTHASH_CODEC = ARTHASH_CODEC;
1293
+ exports.MEDIA_ANALYSIS_SCHEMA_VERSION = MEDIA_ANALYSIS_SCHEMA_VERSION;
1294
+ exports.analyzeMedia = analyzeMedia;
1295
+ exports.hashBlob = hashBlob;
1296
+ exports.loadMediaInfoVideoMetadata = loadMediaInfoVideoMetadata;
1297
+ exports.mapMediaInfoResult = mapMediaInfoResult;