vue3-image-compressor 1.0.4

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.
Files changed (36) hide show
  1. package/README.md +174 -0
  2. package/dist/assets/imageWorker-DyeUTFOy.js +67 -0
  3. package/dist/components/ImageCompressor.vue.d.ts +38 -0
  4. package/dist/composables/useCompression.d.ts +170 -0
  5. package/dist/composables/useEncoderRegistry.d.ts +17 -0
  6. package/dist/composables/useWorker.d.ts +11 -0
  7. package/dist/constants/encoders.d.ts +5 -0
  8. package/dist/constants/resizeMethods.d.ts +39 -0
  9. package/dist/index.d.ts +15 -0
  10. package/dist/style.css +1 -0
  11. package/dist/types/compression.d.ts +35 -0
  12. package/dist/types/encoder.d.ts +100 -0
  13. package/dist/types/processor.d.ts +30 -0
  14. package/dist/types/worker.d.ts +21 -0
  15. package/dist/utils/file.d.ts +23 -0
  16. package/dist/utils/image.d.ts +31 -0
  17. package/dist/vue-image-compressor.js +660 -0
  18. package/dist/vue-image-compressor.umd.cjs +1 -0
  19. package/dist/workers/imageWorker.d.ts +4 -0
  20. package/dist/workers/utils/emscripten.d.ts +6 -0
  21. package/package.json +49 -0
  22. package/src/components/ImageCompressor.vue +304 -0
  23. package/src/composables/useCompression.ts +314 -0
  24. package/src/composables/useEncoderRegistry.ts +70 -0
  25. package/src/composables/useWorker.ts +132 -0
  26. package/src/constants/encoders.ts +137 -0
  27. package/src/constants/resizeMethods.ts +23 -0
  28. package/src/index.ts +63 -0
  29. package/src/types/compression.ts +38 -0
  30. package/src/types/encoder.ts +144 -0
  31. package/src/types/processor.ts +36 -0
  32. package/src/types/worker.ts +29 -0
  33. package/src/utils/file.ts +48 -0
  34. package/src/utils/image.ts +90 -0
  35. package/src/workers/imageWorker.ts +107 -0
  36. package/src/workers/utils/emscripten.ts +16 -0
@@ -0,0 +1,660 @@
1
+ import { ref as b, onUnmounted as Q, computed as q, defineComponent as H, watch as K, openBlock as _, createElementBlock as k, createElementVNode as g, withModifiers as N, renderSlot as S, toDisplayString as T, unref as m, createCommentVNode as z, withDirectives as X, isRef as Y, Fragment as Z, renderList as ee, vModelSelect as te, createTextVNode as ne } from "vue";
2
+ const L = {
3
+ mozJPEG: {
4
+ label: "MozJPEG",
5
+ mimeType: "image/jpeg",
6
+ extension: "jpg"
7
+ },
8
+ webP: {
9
+ label: "WebP",
10
+ mimeType: "image/webp",
11
+ extension: "webp"
12
+ },
13
+ avif: {
14
+ label: "AVIF",
15
+ mimeType: "image/avif",
16
+ extension: "avif"
17
+ },
18
+ jxl: {
19
+ label: "JPEG XL",
20
+ mimeType: "image/jxl",
21
+ extension: "jxl"
22
+ },
23
+ oxiPNG: {
24
+ label: "OxiPNG",
25
+ mimeType: "image/png",
26
+ extension: "png"
27
+ },
28
+ browserJPEG: {
29
+ label: "Browser JPEG",
30
+ mimeType: "image/jpeg",
31
+ extension: "jpg"
32
+ },
33
+ browserPNG: {
34
+ label: "Browser PNG",
35
+ mimeType: "image/png",
36
+ extension: "png"
37
+ },
38
+ browserGIF: {
39
+ label: "Browser GIF",
40
+ mimeType: "image/gif",
41
+ extension: "gif"
42
+ },
43
+ qoi: {
44
+ label: "QOI",
45
+ mimeType: "image/qoi",
46
+ extension: "qoi"
47
+ },
48
+ wp2: {
49
+ label: "WebP2",
50
+ mimeType: "image/webp2",
51
+ extension: "wp2"
52
+ }
53
+ }, W = {
54
+ mozJPEG: {
55
+ quality: 75,
56
+ baseline: !1,
57
+ arithmetic: !1,
58
+ progressive: !0,
59
+ optimize_coding: !0,
60
+ smoothing: 0,
61
+ color_space: 3,
62
+ quant_table: 3,
63
+ trellis_multipass: !1,
64
+ trellis_opt_zero: !1,
65
+ trellis_opt_table: !1,
66
+ trellis_loops: 1,
67
+ auto_subsample: !0,
68
+ chroma_subsample: 2,
69
+ separate_chroma_quality: !1,
70
+ chroma_quality: 75
71
+ },
72
+ webP: {
73
+ quality: 75,
74
+ target_size: 0,
75
+ target_PSNR: 0,
76
+ method: 4,
77
+ sns_strength: 50,
78
+ filter_strength: 60,
79
+ filter_sharpness: 0,
80
+ filter_type: 1,
81
+ partitions: 0,
82
+ segments: 4,
83
+ pass: 1,
84
+ show_compressed: 0,
85
+ preprocessing: 0,
86
+ autofilter: 0,
87
+ partition_limit: 0,
88
+ alpha_compression: 1,
89
+ alpha_filtering: 1,
90
+ alpha_quality: 100,
91
+ lossless: 0,
92
+ exact: 0,
93
+ use_delta_palette: 0,
94
+ vlnr: 0,
95
+ near_lossless: 60
96
+ },
97
+ avif: {
98
+ cqLevel: 33,
99
+ denoiseLevel: 0,
100
+ cqAlphaLevel: -1,
101
+ tileRows: 0,
102
+ tileCols: 0,
103
+ speed: 6,
104
+ subsample: 1,
105
+ chromaDeltaQ: !1,
106
+ sharpness: 0,
107
+ tune: 0
108
+ },
109
+ jxl: {
110
+ effort: 7,
111
+ quality: 75,
112
+ progressive: !1,
113
+ targetPsize: 0
114
+ },
115
+ oxiPNG: {
116
+ level: 2
117
+ },
118
+ browserJPEG: {
119
+ quality: 0.75
120
+ },
121
+ browserPNG: {},
122
+ browserGIF: {},
123
+ qoi: {},
124
+ wp2: {
125
+ quality: 75
126
+ }
127
+ }, re = Object.keys(L);
128
+ async function oe(t) {
129
+ const e = await createImageBitmap(t), r = document.createElement("canvas");
130
+ r.width = e.width, r.height = e.height;
131
+ const n = r.getContext("2d");
132
+ return n.drawImage(e, 0, 0), n.getImageData(0, 0, r.width, r.height);
133
+ }
134
+ async function J(t, e, r) {
135
+ const n = document.createElement("canvas");
136
+ return n.width = t.width, n.height = t.height, n.getContext("2d").putImageData(t, 0, 0), new Promise((p) => {
137
+ n.toBlob(
138
+ (c) => p(c),
139
+ e,
140
+ r
141
+ );
142
+ });
143
+ }
144
+ function Ge(t) {
145
+ return new Promise((e, r) => {
146
+ const n = new Image();
147
+ n.onload = () => {
148
+ e({ width: n.width, height: n.height }), URL.revokeObjectURL(n.src);
149
+ }, n.onerror = r, n.src = URL.createObjectURL(t);
150
+ });
151
+ }
152
+ async function se(t) {
153
+ const e = new Uint8Array(await t.slice(0, 4).arrayBuffer());
154
+ return e[0] === 137 && e[1] === 80 ? "image/png" : e[0] === 255 && e[1] === 216 ? "image/jpeg" : e[0] === 71 && e[1] === 73 ? "image/gif" : e[0] === 82 && e[1] === 73 ? "image/webp" : e[0] === 0 && e[1] === 0 ? "image/avif" : t.type || "image/jpeg";
155
+ }
156
+ function ae(t) {
157
+ return new Promise((e) => {
158
+ const r = new Image();
159
+ r.onload = () => e(!0), r.onerror = () => e(!1), r.src = `data:${t};base64,`;
160
+ });
161
+ }
162
+ function ie(t, e, r) {
163
+ return new File([t], e, { type: r });
164
+ }
165
+ function $(t, e = 2) {
166
+ if (t === 0) return "0 Bytes";
167
+ const r = 1024, n = e < 0 ? 0 : e, u = ["Bytes", "KB", "MB", "GB"], p = Math.floor(Math.log(t) / Math.log(r));
168
+ return parseFloat((t / Math.pow(r, p)).toFixed(n)) + " " + u[p];
169
+ }
170
+ function le(t, e) {
171
+ return t === 0 ? 0 : Math.round((t - e) / t * 100);
172
+ }
173
+ function ce(t, e) {
174
+ return `${t.replace(/\.[^.]+$/, "")}_compressed.${e}`;
175
+ }
176
+ function V(t) {
177
+ return URL.createObjectURL(t);
178
+ }
179
+ function ue(t) {
180
+ URL.revokeObjectURL(t);
181
+ }
182
+ const de = 1e4;
183
+ function me() {
184
+ const t = b(!1), e = b(!1), r = b(null);
185
+ let n = null, u = null;
186
+ function p() {
187
+ if (!n)
188
+ try {
189
+ e.value = !0, n = new Worker(
190
+ new URL(
191
+ /* @vite-ignore */
192
+ "/assets/imageWorker-DyeUTFOy.js",
193
+ import.meta.url
194
+ ),
195
+ { type: "module" }
196
+ ), t.value = !0, r.value = null;
197
+ } catch (x) {
198
+ r.value = x instanceof Error ? x : new Error("Failed to start worker"), console.error("Worker start failed:", x);
199
+ } finally {
200
+ e.value = !1;
201
+ }
202
+ }
203
+ function c() {
204
+ u && (clearTimeout(u), u = null), n && (n.terminate(), n = null, t.value = !1);
205
+ }
206
+ function h() {
207
+ u && clearTimeout(u), u = window.setTimeout(() => {
208
+ c();
209
+ }, de);
210
+ }
211
+ async function w() {
212
+ if (n || p(), !n)
213
+ throw new Error("Worker failed to initialize");
214
+ return n;
215
+ }
216
+ async function P(x, C) {
217
+ if (x.aborted)
218
+ throw new DOMException("AbortError", "AbortError");
219
+ return clearTimeout(u), new Promise((G, R) => {
220
+ const O = () => {
221
+ c(), R(new DOMException("AbortError", "AbortError"));
222
+ };
223
+ x.addEventListener("abort", O), C().then(G).catch(R).finally(() => {
224
+ x.removeEventListener("abort", O), h();
225
+ });
226
+ });
227
+ }
228
+ return Q(() => {
229
+ c();
230
+ }), {
231
+ isReady: t,
232
+ isLoading: e,
233
+ error: r,
234
+ worker: n,
235
+ getWorkerApi: w,
236
+ executeTask: P,
237
+ terminateWorker: c
238
+ };
239
+ }
240
+ function pe() {
241
+ const t = b(!1), e = b(0), r = b(null), n = b(null), u = b(null), { getWorkerApi: p, executeTask: c } = me(), h = q(() => t.value && !!u.value);
242
+ async function w(d, o) {
243
+ const a = await se(o), i = await ae(a), f = await p();
244
+ if (!i) {
245
+ if (a === "image/avif")
246
+ return c(d, () => f.avifDecode(o));
247
+ if (a === "image/webp")
248
+ return c(d, () => f.webpDecode(o));
249
+ if (a === "image/jxl")
250
+ return c(d, () => f.jxlDecode(o));
251
+ if (a === "image/webp2")
252
+ return c(d, () => f.wp2Decode(o));
253
+ if (a === "image/qoi")
254
+ return c(d, () => f.qoiDecode(o));
255
+ }
256
+ return oe(o);
257
+ }
258
+ async function P(d, o, a) {
259
+ if (a === 0) return o;
260
+ const i = await p();
261
+ return c(
262
+ d,
263
+ () => i.rotate(o, { rotate: a })
264
+ );
265
+ }
266
+ async function x(d, o, a) {
267
+ var v, y;
268
+ let i = o;
269
+ const f = await p();
270
+ return (v = a.resize) != null && v.enabled && (i = await c(
271
+ d,
272
+ () => f.resize(i, {
273
+ width: a.resize.width || i.width,
274
+ height: a.resize.height || i.height,
275
+ method: a.resize.method || "lanczos3",
276
+ fitMethod: a.resize.fitMethod || "stretch",
277
+ premultiply: !0,
278
+ linearRGB: !0
279
+ })
280
+ )), (y = a.quantize) != null && y.enabled && (i = await c(
281
+ d,
282
+ () => f.quantize(i, {
283
+ numColors: a.quantize.numColors || 256,
284
+ dither: a.quantize.dither || 1
285
+ })
286
+ )), i;
287
+ }
288
+ async function C(d, o, a) {
289
+ const i = await p(), f = a.encoder;
290
+ if (f === "browserJPEG")
291
+ return (await J(
292
+ o,
293
+ "image/jpeg",
294
+ a.encoderOptions.quality
295
+ )).arrayBuffer();
296
+ if (f === "browserPNG")
297
+ return (await J(o, "image/png")).arrayBuffer();
298
+ if (f === "browserGIF")
299
+ throw new Error("Browser GIF encoding not supported directly");
300
+ const y = {
301
+ mozJPEG: i.mozjpegEncode,
302
+ webP: i.webpEncode,
303
+ avif: i.avifEncode,
304
+ jxl: i.jxlEncode,
305
+ oxiPNG: i.oxipngEncode,
306
+ qoi: i.qoiEncode,
307
+ wp2: i.wp2Encode
308
+ }[f];
309
+ if (!y)
310
+ throw new Error(`Unknown encoder: ${f}`);
311
+ return (await c(
312
+ d,
313
+ () => y.call(i, o, a.encoderOptions)
314
+ )).buffer;
315
+ }
316
+ async function G(d, o) {
317
+ var f;
318
+ u.value = new AbortController();
319
+ const a = u.value.signal;
320
+ t.value = !0, e.value = 0, r.value = null;
321
+ const i = n.value;
322
+ n.value = null, (f = i == null ? void 0 : i.compressed) != null && f.blobUrl && ue(i.compressed.blobUrl);
323
+ try {
324
+ e.value = 10;
325
+ const v = {
326
+ file: d,
327
+ size: d.size,
328
+ width: 0,
329
+ height: 0,
330
+ blobUrl: V(d)
331
+ }, y = await w(a, d);
332
+ v.width = y.width, v.height = y.height, e.value = 25;
333
+ const j = await P(
334
+ a,
335
+ y,
336
+ o.rotate || 0
337
+ );
338
+ e.value = 35;
339
+ const U = await x(a, j, o);
340
+ e.value = 50;
341
+ const F = await C(a, U, o);
342
+ e.value = 80;
343
+ const I = L[o.encoder], M = ce(
344
+ d.name,
345
+ (I == null ? void 0 : I.extension) || "bin"
346
+ ), D = ie(
347
+ F,
348
+ M,
349
+ (I == null ? void 0 : I.mimeType) || "application/octet-stream"
350
+ ), B = {
351
+ file: D,
352
+ size: D.size,
353
+ width: U.width,
354
+ height: U.height,
355
+ blobUrl: V(D)
356
+ }, l = {
357
+ original: v,
358
+ compressed: B,
359
+ savingsBytes: v.size - B.size,
360
+ savingsPercent: le(v.size, B.size),
361
+ encoderType: o.encoder,
362
+ encoderOptions: o.encoderOptions
363
+ };
364
+ return n.value = l, e.value = 100, l;
365
+ } catch (v) {
366
+ if (v instanceof DOMException && v.name === "AbortError")
367
+ throw r.value = "已取消", v;
368
+ const y = v instanceof Error ? v.message : "压缩失败";
369
+ throw r.value = y, console.error("Compression error:", v), v;
370
+ } finally {
371
+ t.value = !1, u.value = null;
372
+ }
373
+ }
374
+ function R() {
375
+ u.value && u.value.abort();
376
+ }
377
+ function O() {
378
+ if (!n.value) return;
379
+ const { compressed: d } = n.value, o = document.createElement("a");
380
+ o.href = d.blobUrl, o.download = d.file.name, document.body.appendChild(o), o.click(), document.body.removeChild(o);
381
+ }
382
+ return {
383
+ compress: G,
384
+ cancel: R,
385
+ downloadResult: O,
386
+ isCompressing: t,
387
+ progress: e,
388
+ error: r,
389
+ result: n,
390
+ canCancel: h
391
+ };
392
+ }
393
+ function fe() {
394
+ const t = b("mozJPEG"), e = b({}), r = q(() => L[t.value]), n = q(
395
+ () => W[t.value]
396
+ ), u = q(
397
+ () => re.map((w) => ({
398
+ type: w,
399
+ label: L[w].label,
400
+ mimeType: L[w].mimeType,
401
+ extension: L[w].extension
402
+ }))
403
+ );
404
+ function p(w) {
405
+ t.value = w, e.value = { ...W[w] };
406
+ }
407
+ function c(w, P) {
408
+ e.value = {
409
+ ...e.value,
410
+ [w]: P
411
+ };
412
+ }
413
+ function h() {
414
+ e.value = { ...n.value };
415
+ }
416
+ return h(), {
417
+ selectedEncoder: t,
418
+ encoderOptions: e,
419
+ currentMeta: r,
420
+ currentDefaults: n,
421
+ availableEncoders: u,
422
+ selectEncoder: p,
423
+ updateOption: c,
424
+ resetOptions: h
425
+ };
426
+ }
427
+ const ve = { class: "image-compressor" }, ge = {
428
+ key: 0,
429
+ class: "preview-area"
430
+ }, we = { class: "preview-item" }, be = ["src"], he = { class: "preview-item" }, ye = ["src"], Ee = {
431
+ key: 1,
432
+ class: "compressing"
433
+ }, _e = ["value"], ke = { key: 2 }, xe = {
434
+ key: 1,
435
+ class: "encoder-settings"
436
+ }, Oe = ["value"], Te = {
437
+ key: 0,
438
+ class: "quality-control"
439
+ }, ze = ["value"], Pe = {
440
+ key: 2,
441
+ class: "actions"
442
+ }, Ie = ["disabled"], Ce = {
443
+ key: 3,
444
+ class: "error"
445
+ }, Re = /* @__PURE__ */ H({
446
+ __name: "ImageCompressor",
447
+ props: {
448
+ defaultEncoder: {},
449
+ defaultOptions: {}
450
+ },
451
+ emits: ["success", "error", "cancel"],
452
+ setup(t, { emit: e }) {
453
+ const r = t, n = e, u = b(null), p = b(null), c = b(""), h = b(""), {
454
+ compress: w,
455
+ cancel: P,
456
+ downloadResult: x,
457
+ isCompressing: C,
458
+ progress: G,
459
+ error: R,
460
+ result: O,
461
+ canCancel: d
462
+ } = pe(), {
463
+ selectedEncoder: o,
464
+ encoderOptions: a,
465
+ availableEncoders: i,
466
+ selectEncoder: f,
467
+ updateOption: v
468
+ } = fe();
469
+ r.defaultEncoder && f(r.defaultEncoder), r.defaultOptions && Object.entries(r.defaultOptions).forEach(([l, s]) => v(l, s));
470
+ const y = q(
471
+ () => p.value ? $(p.value.size) : ""
472
+ ), j = q(
473
+ () => O.value ? $(O.value.compressed.size) : ""
474
+ ), U = q(
475
+ () => ["mozJPEG", "webP", "jxl", "browserJPEG", "wp2"].includes(o.value)
476
+ );
477
+ function F(l) {
478
+ const s = l.target;
479
+ s.files && s.files[0] && M(s.files[0]);
480
+ }
481
+ function I(l) {
482
+ var s;
483
+ (s = l.dataTransfer) != null && s.files[0] && M(l.dataTransfer.files[0]);
484
+ }
485
+ function M(l) {
486
+ l.type.startsWith("image/") && (p.value = l, c.value = URL.createObjectURL(l), h.value && (URL.revokeObjectURL(h.value), h.value = ""));
487
+ }
488
+ function D() {
489
+ f(o.value);
490
+ }
491
+ async function B() {
492
+ if (p.value)
493
+ try {
494
+ const l = {
495
+ encoder: o.value,
496
+ encoderOptions: { ...a.value }
497
+ }, s = await w(p.value, l);
498
+ h.value = s.compressed.blobUrl, n("success", s);
499
+ } catch (l) {
500
+ if (l instanceof DOMException && l.name === "AbortError") {
501
+ n("cancel");
502
+ return;
503
+ }
504
+ n("error", l instanceof Error ? l : new Error(String(l)));
505
+ }
506
+ }
507
+ return K(
508
+ () => p.value,
509
+ (l, s) => {
510
+ l && l !== s && c.value && URL.revokeObjectURL(c.value);
511
+ }
512
+ ), (l, s) => (_(), k("div", ve, [
513
+ g("div", {
514
+ class: "upload-zone",
515
+ onDrop: N(I, ["prevent"]),
516
+ onDragover: s[1] || (s[1] = N(() => {
517
+ }, ["prevent"]))
518
+ }, [
519
+ g("input", {
520
+ type: "file",
521
+ accept: "image/*",
522
+ onChange: F,
523
+ ref_key: "fileInput",
524
+ ref: u,
525
+ class: "file-input"
526
+ }, null, 544),
527
+ g("div", {
528
+ class: "upload-prompt",
529
+ onClick: s[0] || (s[0] = (E) => {
530
+ var A;
531
+ return (A = u.value) == null ? void 0 : A.click();
532
+ })
533
+ }, [
534
+ S(l.$slots, "upload-prompt", {}, () => [
535
+ s[6] || (s[6] = g("p", null, "拖拽图片到此处,或点击上传", -1))
536
+ ], !0)
537
+ ])
538
+ ], 32),
539
+ c.value ? (_(), k("div", ge, [
540
+ g("div", we, [
541
+ s[7] || (s[7] = g("h4", null, "原始图片", -1)),
542
+ g("img", {
543
+ src: c.value,
544
+ alt: "Original"
545
+ }, null, 8, be),
546
+ g("p", null, T(y.value), 1)
547
+ ]),
548
+ g("div", he, [
549
+ s[8] || (s[8] = g("h4", null, "压缩后", -1)),
550
+ h.value ? (_(), k("img", {
551
+ key: 0,
552
+ src: h.value,
553
+ alt: "Compressed"
554
+ }, null, 8, ye)) : m(C) ? (_(), k("div", Ee, [
555
+ g("progress", {
556
+ value: m(G),
557
+ max: "100"
558
+ }, null, 8, _e),
559
+ g("p", null, "压缩中... " + T(m(G)) + "%", 1)
560
+ ])) : z("", !0),
561
+ j.value ? (_(), k("p", ke, T(j.value), 1)) : z("", !0)
562
+ ])
563
+ ])) : z("", !0),
564
+ c.value ? (_(), k("div", xe, [
565
+ s[9] || (s[9] = g("label", null, "编码器:", -1)),
566
+ X(g("select", {
567
+ "onUpdate:modelValue": s[2] || (s[2] = (E) => Y(o) ? o.value = E : null),
568
+ onChange: D
569
+ }, [
570
+ (_(!0), k(Z, null, ee(m(i), (E) => (_(), k("option", {
571
+ key: E.type,
572
+ value: E.type
573
+ }, T(E.label) + " (" + T(E.extension) + ") ", 9, Oe))), 128))
574
+ ], 544), [
575
+ [te, m(o)]
576
+ ]),
577
+ U.value ? (_(), k("div", Te, [
578
+ g("label", null, [
579
+ ne(" 质量: " + T(m(a).quality ?? 75) + " ", 1),
580
+ g("input", {
581
+ type: "range",
582
+ min: "0",
583
+ max: "100",
584
+ value: m(a).quality ?? 75,
585
+ onInput: s[3] || (s[3] = (E) => m(v)("quality", Number(E.target.value))),
586
+ "-------": "",
587
+ REPLACE: ""
588
+ }, null, 40, ze)
589
+ ])
590
+ ])) : z("", !0),
591
+ S(l.$slots, "encoder-settings", {
592
+ encoder: m(o),
593
+ options: m(a)
594
+ }, void 0, !0)
595
+ ])) : z("", !0),
596
+ c.value ? (_(), k("div", Pe, [
597
+ g("button", {
598
+ onClick: B,
599
+ disabled: m(C),
600
+ class: "btn-primary"
601
+ }, T(m(C) ? "压缩中..." : "开始压缩"), 9, Ie),
602
+ m(d) ? (_(), k("button", {
603
+ key: 0,
604
+ onClick: s[4] || (s[4] = //@ts-ignore
605
+ (...E) => m(P) && m(P)(...E)),
606
+ class: "btn-secondary"
607
+ }, " 取消 ")) : z("", !0),
608
+ m(O) ? (_(), k("button", {
609
+ key: 1,
610
+ onClick: s[5] || (s[5] = //@ts-ignore
611
+ (...E) => m(x) && m(x)(...E)),
612
+ class: "btn-success"
613
+ }, " 下载 (节省 " + T(m(O).savingsPercent) + "%) ", 1)) : z("", !0)
614
+ ])) : z("", !0),
615
+ m(R) ? (_(), k("div", Ce, T(m(R)), 1)) : z("", !0)
616
+ ]));
617
+ }
618
+ }), Ue = (t, e) => {
619
+ const r = t.__vccOpts || t;
620
+ for (const [n, u] of e)
621
+ r[n] = u;
622
+ return r;
623
+ }, Le = /* @__PURE__ */ Ue(Re, [["__scopeId", "data-v-86f2b482"]]), je = [
624
+ { value: "lanczos3", label: "Lanczos3" },
625
+ { value: "catrom", label: "Catrom" },
626
+ { value: "mitchell", label: "Mitchell" },
627
+ { value: "triangle", label: "Triangle" },
628
+ { value: "vector", label: "Vector" }
629
+ ], De = [
630
+ { value: "stretch", label: "拉伸" },
631
+ { value: "contain", label: "适应" }
632
+ ], Be = [
633
+ { value: 0, label: "不旋转" },
634
+ { value: 90, label: "顺时针 90°" },
635
+ { value: 180, label: "旋转 180°" },
636
+ { value: 270, label: "逆时针 90°" }
637
+ ];
638
+ export {
639
+ W as DEFAULT_ENCODER_OPTIONS,
640
+ re as ENCODER_LIST,
641
+ L as ENCODER_REGISTRY,
642
+ De as FIT_METHODS,
643
+ Le as ImageCompressor,
644
+ je as RESIZE_METHODS,
645
+ Be as ROTATE_OPTIONS,
646
+ ie as arrayBufferToFile,
647
+ oe as blobToImageData,
648
+ le as calculateSavings,
649
+ ae as canDecodeImageType,
650
+ V as createBlobUrl,
651
+ $ as formatBytes,
652
+ ce as generateCompressedFilename,
653
+ Ge as getImageDimensions,
654
+ J as imageDataToBlob,
655
+ ue as revokeBlobUrl,
656
+ se as sniffMimeType,
657
+ pe as useCompression,
658
+ fe as useEncoderRegistry,
659
+ me as useWorker
660
+ };
@@ -0,0 +1 @@
1
+ (function(s,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],e):(s=typeof globalThis<"u"?globalThis:s||self,e(s.VueImageCompressor={},s.Vue))})(this,function(s,e){"use strict";var v=typeof document<"u"?document.currentScript:null;const B={mozJPEG:{label:"MozJPEG",mimeType:"image/jpeg",extension:"jpg"},webP:{label:"WebP",mimeType:"image/webp",extension:"webp"},avif:{label:"AVIF",mimeType:"image/avif",extension:"avif"},jxl:{label:"JPEG XL",mimeType:"image/jxl",extension:"jxl"},oxiPNG:{label:"OxiPNG",mimeType:"image/png",extension:"png"},browserJPEG:{label:"Browser JPEG",mimeType:"image/jpeg",extension:"jpg"},browserPNG:{label:"Browser PNG",mimeType:"image/png",extension:"png"},browserGIF:{label:"Browser GIF",mimeType:"image/gif",extension:"gif"},qoi:{label:"QOI",mimeType:"image/qoi",extension:"qoi"},wp2:{label:"WebP2",mimeType:"image/webp2",extension:"wp2"}},S={mozJPEG:{quality:75,baseline:!1,arithmetic:!1,progressive:!0,optimize_coding:!0,smoothing:0,color_space:3,quant_table:3,trellis_multipass:!1,trellis_opt_zero:!1,trellis_opt_table:!1,trellis_loops:1,auto_subsample:!0,chroma_subsample:2,separate_chroma_quality:!1,chroma_quality:75},webP:{quality:75,target_size:0,target_PSNR:0,method:4,sns_strength:50,filter_strength:60,filter_sharpness:0,filter_type:1,partitions:0,segments:4,pass:1,show_compressed:0,preprocessing:0,autofilter:0,partition_limit:0,alpha_compression:1,alpha_filtering:1,alpha_quality:100,lossless:0,exact:0,use_delta_palette:0,vlnr:0,near_lossless:60},avif:{cqLevel:33,denoiseLevel:0,cqAlphaLevel:-1,tileRows:0,tileCols:0,speed:6,subsample:1,chromaDeltaQ:!1,sharpness:0,tune:0},jxl:{effort:7,quality:75,progressive:!1,targetPsize:0},oxiPNG:{level:2},browserJPEG:{quality:.75},browserPNG:{},browserGIF:{},qoi:{},wp2:{quality:75}},G=Object.keys(B);async function M(n){const t=await createImageBitmap(n),r=document.createElement("canvas");r.width=t.width,r.height=t.height;const o=r.getContext("2d");return o.drawImage(t,0,0),o.getImageData(0,0,r.width,r.height)}async function L(n,t,r){const o=document.createElement("canvas");return o.width=n.width,o.height=n.height,o.getContext("2d").putImageData(n,0,0),new Promise(p=>{o.toBlob(m=>p(m),t,r)})}function Z(n){return new Promise((t,r)=>{const o=new Image;o.onload=()=>{t({width:o.width,height:o.height}),URL.revokeObjectURL(o.src)},o.onerror=r,o.src=URL.createObjectURL(n)})}async function x(n){const t=new Uint8Array(await n.slice(0,4).arrayBuffer());return t[0]===137&&t[1]===80?"image/png":t[0]===255&&t[1]===216?"image/jpeg":t[0]===71&&t[1]===73?"image/gif":t[0]===82&&t[1]===73?"image/webp":t[0]===0&&t[1]===0?"image/avif":n.type||"image/jpeg"}function F(n){return new Promise(t=>{const r=new Image;r.onload=()=>t(!0),r.onerror=()=>t(!1),r.src=`data:${n};base64,`})}function A(n,t,r){return new File([n],t,{type:r})}function V(n,t=2){if(n===0)return"0 Bytes";const r=1024,o=t<0?0:t,u=["Bytes","KB","MB","GB"],p=Math.floor(Math.log(n)/Math.log(r));return parseFloat((n/Math.pow(r,p)).toFixed(o))+" "+u[p]}function W(n,t){return n===0?0:Math.round((n-t)/n*100)}function J(n,t){return`${n.replace(/\.[^.]+$/,"")}_compressed.${t}`}function q(n){return URL.createObjectURL(n)}function $(n){URL.revokeObjectURL(n)}const X=1e4;function H(){const n=e.ref(!1),t=e.ref(!1),r=e.ref(null);let o=null,u=null;function p(){if(!o)try{t.value=!0,o=new Worker(new URL("/assets/imageWorker-DyeUTFOy.js",typeof document>"u"&&typeof location>"u"?require("url").pathToFileURL(__filename).href:typeof document>"u"?location.href:v&&v.tagName.toUpperCase()==="SCRIPT"&&v.src||new URL("vue-image-compressor.umd.cjs",document.baseURI).href),{type:"module"}),n.value=!0,r.value=null}catch(_){r.value=_ instanceof Error?_:new Error("Failed to start worker"),console.error("Worker start failed:",_)}finally{t.value=!1}}function m(){u&&(clearTimeout(u),u=null),o&&(o.terminate(),o=null,n.value=!1)}function w(){u&&clearTimeout(u),u=window.setTimeout(()=>{m()},X)}async function E(){if(o||p(),!o)throw new Error("Worker failed to initialize");return o}async function T(_,C){if(_.aborted)throw new DOMException("AbortError","AbortError");return clearTimeout(u),new Promise((R,N)=>{const k=()=>{m(),N(new DOMException("AbortError","AbortError"))};_.addEventListener("abort",k),C().then(R).catch(N).finally(()=>{_.removeEventListener("abort",k),w()})})}return e.onUnmounted(()=>{m()}),{isReady:n,isLoading:t,error:r,worker:o,getWorkerApi:E,executeTask:T,terminateWorker:m}}function Q(){const n=e.ref(!1),t=e.ref(0),r=e.ref(null),o=e.ref(null),u=e.ref(null),{getWorkerApi:p,executeTask:m}=H(),w=e.computed(()=>n.value&&!!u.value);async function E(f,a){const i=await x(a),c=await F(i),g=await p();if(!c){if(i==="image/avif")return m(f,()=>g.avifDecode(a));if(i==="image/webp")return m(f,()=>g.webpDecode(a));if(i==="image/jxl")return m(f,()=>g.jxlDecode(a));if(i==="image/webp2")return m(f,()=>g.wp2Decode(a));if(i==="image/qoi")return m(f,()=>g.qoiDecode(a))}return M(a)}async function T(f,a,i){if(i===0)return a;const c=await p();return m(f,()=>c.rotate(a,{rotate:i}))}async function _(f,a,i){var b,h;let c=a;const g=await p();return(b=i.resize)!=null&&b.enabled&&(c=await m(f,()=>g.resize(c,{width:i.resize.width||c.width,height:i.resize.height||c.height,method:i.resize.method||"lanczos3",fitMethod:i.resize.fitMethod||"stretch",premultiply:!0,linearRGB:!0}))),(h=i.quantize)!=null&&h.enabled&&(c=await m(f,()=>g.quantize(c,{numColors:i.quantize.numColors||256,dither:i.quantize.dither||1}))),c}async function C(f,a,i){const c=await p(),g=i.encoder;if(g==="browserJPEG")return(await L(a,"image/jpeg",i.encoderOptions.quality)).arrayBuffer();if(g==="browserPNG")return(await L(a,"image/png")).arrayBuffer();if(g==="browserGIF")throw new Error("Browser GIF encoding not supported directly");const h={mozJPEG:c.mozjpegEncode,webP:c.webpEncode,avif:c.avifEncode,jxl:c.jxlEncode,oxiPNG:c.oxipngEncode,qoi:c.qoiEncode,wp2:c.wp2Encode}[g];if(!h)throw new Error(`Unknown encoder: ${g}`);return(await m(f,()=>h.call(c,a,i.encoderOptions))).buffer}async function R(f,a){var g;u.value=new AbortController;const i=u.value.signal;n.value=!0,t.value=0,r.value=null;const c=o.value;o.value=null,(g=c==null?void 0:c.compressed)!=null&&g.blobUrl&&$(c.compressed.blobUrl);try{t.value=10;const b={file:f,size:f.size,width:0,height:0,blobUrl:q(f)},h=await E(i,f);b.width=h.width,b.height=h.height,t.value=25;const D=await T(i,h,a.rotate||0);t.value=35;const I=await _(i,D,a);t.value=50;const j=await C(i,I,a);t.value=80;const O=B[a.encoder],z=J(f.name,(O==null?void 0:O.extension)||"bin"),P=A(j,z,(O==null?void 0:O.mimeType)||"application/octet-stream"),U={file:P,size:P.size,width:I.width,height:I.height,blobUrl:q(P)},d={original:b,compressed:U,savingsBytes:b.size-U.size,savingsPercent:W(b.size,U.size),encoderType:a.encoder,encoderOptions:a.encoderOptions};return o.value=d,t.value=100,d}catch(b){if(b instanceof DOMException&&b.name==="AbortError")throw r.value="已取消",b;const h=b instanceof Error?b.message:"压缩失败";throw r.value=h,console.error("Compression error:",b),b}finally{n.value=!1,u.value=null}}function N(){u.value&&u.value.abort()}function k(){if(!o.value)return;const{compressed:f}=o.value,a=document.createElement("a");a.href=f.blobUrl,a.download=f.file.name,document.body.appendChild(a),a.click(),document.body.removeChild(a)}return{compress:R,cancel:N,downloadResult:k,isCompressing:n,progress:t,error:r,result:o,canCancel:w}}function K(){const n=e.ref("mozJPEG"),t=e.ref({}),r=e.computed(()=>B[n.value]),o=e.computed(()=>S[n.value]),u=e.computed(()=>G.map(E=>({type:E,label:B[E].label,mimeType:B[E].mimeType,extension:B[E].extension})));function p(E){n.value=E,t.value={...S[E]}}function m(E,T){t.value={...t.value,[E]:T}}function w(){t.value={...o.value}}return w(),{selectedEncoder:n,encoderOptions:t,currentMeta:r,currentDefaults:o,availableEncoders:u,selectEncoder:p,updateOption:m,resetOptions:w}}const ee={class:"image-compressor"},te={key:0,class:"preview-area"},ne={class:"preview-item"},oe=["src"],re={class:"preview-item"},ae=["src"],le={key:1,class:"compressing"},ie=["value"],se={key:2},ce={key:1,class:"encoder-settings"},de=["value"],me={key:0,class:"quality-control"},ue=["value"],fe={key:2,class:"actions"},pe=["disabled"],ge={key:3,class:"error"},be=((n,t)=>{const r=n.__vccOpts||n;for(const[o,u]of t)r[o]=u;return r})(e.defineComponent({__name:"ImageCompressor",props:{defaultEncoder:{},defaultOptions:{}},emits:["success","error","cancel"],setup(n,{emit:t}){const r=n,o=t,u=e.ref(null),p=e.ref(null),m=e.ref(""),w=e.ref(""),{compress:E,cancel:T,downloadResult:_,isCompressing:C,progress:R,error:N,result:k,canCancel:f}=Q(),{selectedEncoder:a,encoderOptions:i,availableEncoders:c,selectEncoder:g,updateOption:b}=K();r.defaultEncoder&&g(r.defaultEncoder),r.defaultOptions&&Object.entries(r.defaultOptions).forEach(([d,l])=>b(d,l));const h=e.computed(()=>p.value?V(p.value.size):""),D=e.computed(()=>k.value?V(k.value.compressed.size):""),I=e.computed(()=>["mozJPEG","webP","jxl","browserJPEG","wp2"].includes(a.value));function j(d){const l=d.target;l.files&&l.files[0]&&z(l.files[0])}function O(d){var l;(l=d.dataTransfer)!=null&&l.files[0]&&z(d.dataTransfer.files[0])}function z(d){d.type.startsWith("image/")&&(p.value=d,m.value=URL.createObjectURL(d),w.value&&(URL.revokeObjectURL(w.value),w.value=""))}function P(){g(a.value)}async function U(){if(p.value)try{const d={encoder:a.value,encoderOptions:{...i.value}},l=await E(p.value,d);w.value=l.compressed.blobUrl,o("success",l)}catch(d){if(d instanceof DOMException&&d.name==="AbortError"){o("cancel");return}o("error",d instanceof Error?d:new Error(String(d)))}}return e.watch(()=>p.value,(d,l)=>{d&&d!==l&&m.value&&URL.revokeObjectURL(m.value)}),(d,l)=>(e.openBlock(),e.createElementBlock("div",ee,[e.createElementVNode("div",{class:"upload-zone",onDrop:e.withModifiers(O,["prevent"]),onDragover:l[1]||(l[1]=e.withModifiers(()=>{},["prevent"]))},[e.createElementVNode("input",{type:"file",accept:"image/*",onChange:j,ref_key:"fileInput",ref:u,class:"file-input"},null,544),e.createElementVNode("div",{class:"upload-prompt",onClick:l[0]||(l[0]=y=>{var Y;return(Y=u.value)==null?void 0:Y.click()})},[e.renderSlot(d.$slots,"upload-prompt",{},()=>[l[6]||(l[6]=e.createElementVNode("p",null,"拖拽图片到此处,或点击上传",-1))],!0)])],32),m.value?(e.openBlock(),e.createElementBlock("div",te,[e.createElementVNode("div",ne,[l[7]||(l[7]=e.createElementVNode("h4",null,"原始图片",-1)),e.createElementVNode("img",{src:m.value,alt:"Original"},null,8,oe),e.createElementVNode("p",null,e.toDisplayString(h.value),1)]),e.createElementVNode("div",re,[l[8]||(l[8]=e.createElementVNode("h4",null,"压缩后",-1)),w.value?(e.openBlock(),e.createElementBlock("img",{key:0,src:w.value,alt:"Compressed"},null,8,ae)):e.unref(C)?(e.openBlock(),e.createElementBlock("div",le,[e.createElementVNode("progress",{value:e.unref(R),max:"100"},null,8,ie),e.createElementVNode("p",null,"压缩中... "+e.toDisplayString(e.unref(R))+"%",1)])):e.createCommentVNode("",!0),D.value?(e.openBlock(),e.createElementBlock("p",se,e.toDisplayString(D.value),1)):e.createCommentVNode("",!0)])])):e.createCommentVNode("",!0),m.value?(e.openBlock(),e.createElementBlock("div",ce,[l[9]||(l[9]=e.createElementVNode("label",null,"编码器:",-1)),e.withDirectives(e.createElementVNode("select",{"onUpdate:modelValue":l[2]||(l[2]=y=>e.isRef(a)?a.value=y:null),onChange:P},[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(e.unref(c),y=>(e.openBlock(),e.createElementBlock("option",{key:y.type,value:y.type},e.toDisplayString(y.label)+" ("+e.toDisplayString(y.extension)+") ",9,de))),128))],544),[[e.vModelSelect,e.unref(a)]]),I.value?(e.openBlock(),e.createElementBlock("div",me,[e.createElementVNode("label",null,[e.createTextVNode(" 质量: "+e.toDisplayString(e.unref(i).quality??75)+" ",1),e.createElementVNode("input",{type:"range",min:"0",max:"100",value:e.unref(i).quality??75,onInput:l[3]||(l[3]=y=>e.unref(b)("quality",Number(y.target.value))),"-------":"",REPLACE:""},null,40,ue)])])):e.createCommentVNode("",!0),e.renderSlot(d.$slots,"encoder-settings",{encoder:e.unref(a),options:e.unref(i)},void 0,!0)])):e.createCommentVNode("",!0),m.value?(e.openBlock(),e.createElementBlock("div",fe,[e.createElementVNode("button",{onClick:U,disabled:e.unref(C),class:"btn-primary"},e.toDisplayString(e.unref(C)?"压缩中...":"开始压缩"),9,pe),e.unref(f)?(e.openBlock(),e.createElementBlock("button",{key:0,onClick:l[4]||(l[4]=(...y)=>e.unref(T)&&e.unref(T)(...y)),class:"btn-secondary"}," 取消 ")):e.createCommentVNode("",!0),e.unref(k)?(e.openBlock(),e.createElementBlock("button",{key:1,onClick:l[5]||(l[5]=(...y)=>e.unref(_)&&e.unref(_)(...y)),class:"btn-success"}," 下载 (节省 "+e.toDisplayString(e.unref(k).savingsPercent)+"%) ",1)):e.createCommentVNode("",!0)])):e.createCommentVNode("",!0),e.unref(N)?(e.openBlock(),e.createElementBlock("div",ge,e.toDisplayString(e.unref(N)),1)):e.createCommentVNode("",!0)]))}}),[["__scopeId","data-v-86f2b482"]]),Ee=[{value:"lanczos3",label:"Lanczos3"},{value:"catrom",label:"Catrom"},{value:"mitchell",label:"Mitchell"},{value:"triangle",label:"Triangle"},{value:"vector",label:"Vector"}],we=[{value:"stretch",label:"拉伸"},{value:"contain",label:"适应"}],he=[{value:0,label:"不旋转"},{value:90,label:"顺时针 90°"},{value:180,label:"旋转 180°"},{value:270,label:"逆时针 90°"}];s.DEFAULT_ENCODER_OPTIONS=S,s.ENCODER_LIST=G,s.ENCODER_REGISTRY=B,s.FIT_METHODS=we,s.ImageCompressor=be,s.RESIZE_METHODS=Ee,s.ROTATE_OPTIONS=he,s.arrayBufferToFile=A,s.blobToImageData=M,s.calculateSavings=W,s.canDecodeImageType=F,s.createBlobUrl=q,s.formatBytes=V,s.generateCompressedFilename=J,s.getImageDimensions=Z,s.imageDataToBlob=L,s.revokeBlobUrl=$,s.sniffMimeType=x,s.useCompression=Q,s.useEncoderRegistry=K,s.useWorker=H,Object.defineProperty(s,Symbol.toStringTag,{value:"Module"})});
@@ -0,0 +1,4 @@
1
+ import { WorkerApi } from '../types/worker';
2
+
3
+ declare const workerApi: WorkerApi;
4
+ export default workerApi;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Emscripten WASM 模块初始化工具
3
+ * 对标 Squoosh: src/features/worker-utils/index.ts
4
+ */
5
+ export declare function initEmscriptenModule<T extends EmscriptenWasm.Module>(moduleFactory: EmscriptenWasm.ModuleFactory<T>): Promise<T>;
6
+ export declare function blobToArrayBuffer(blob: Blob): Promise<ArrayBuffer>;