modern-pdf-lib 0.14.1 → 0.15.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/README.md +46 -5
- package/dist/batchOptimize-ClXizv19.mjs +306 -0
- package/dist/batchOptimize-DYQOX1-7.cjs +329 -0
- package/dist/bridge-DN7BOHRW.cjs +132 -0
- package/dist/bridge-DpzMOnHd.mjs +103 -0
- package/dist/cli/index.cjs +225 -0
- package/dist/cli/index.d.cts +1 -0
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.mjs +226 -0
- package/dist/deduplicateImages-BfpjHY9b.mjs +102 -0
- package/dist/deduplicateImages-BtJ5tlrr.cjs +113 -0
- package/dist/{fflateAdapter-DX0VqT5k.mjs → fflateAdapter-D2mv_ttM.mjs} +1 -1
- package/dist/{fflateAdapter-AHC_S3cb.cjs → fflateAdapter-cT4YeY_h.cjs} +1 -1
- package/dist/{fontSubset-pFc8Dueu.cjs → fontSubset-BxsF9Tu5.cjs} +1 -1
- package/dist/{fontSubset-ZpLoOZ2e.mjs → fontSubset-ClyTXlhY.mjs} +1 -1
- package/dist/imageExtract-BC7TMY98.cjs +4770 -0
- package/dist/imageExtract-vjyQyFcT.mjs +4747 -0
- package/dist/index.cjs +846 -5164
- package/dist/index.d.cts +486 -12
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +486 -12
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +298 -4629
- package/dist/{libdeflateWasm-OkNoqBnO.cjs → libdeflateWasm-Cg7cWHOq.cjs} +2 -2
- package/dist/{libdeflateWasm-DlHgU5oy.mjs → libdeflateWasm-Cmxa-yiS.mjs} +2 -2
- package/dist/{loader-CQfoGFp9.mjs → loader-B6VIrZOJ.mjs} +3 -2
- package/dist/{loader-_fqS-TmT.cjs → loader-DdB5Xo5D.cjs} +3 -2
- package/dist/pdfCatalog-BcOL6QF-.cjs +173 -0
- package/dist/pdfCatalog-CnJRovvm.mjs +138 -0
- package/dist/{pdfCatalog-COKoYQ8C.cjs → pdfObjects-BrU4Xd0V.cjs} +1 -171
- package/dist/{pdfCatalog-BB2Wnmud.mjs → pdfObjects-DZZ2GPRW.mjs} +2 -137
- package/dist/{pdfPage-N1K2U3jI.mjs → pdfPage-Dm5XC_g_.mjs} +3 -2
- package/dist/{pdfPage-DBfdinTR.cjs → pdfPage-Dz_SVKUS.cjs} +105 -104
- package/dist/{pngEmbed-OYyOe_W0.cjs → pngEmbed-C6M1eX6b.cjs} +2 -2
- package/dist/{pngEmbed-DTOqgEUC.mjs → pngEmbed-I1hU3Y6m.mjs} +2 -2
- package/package.json +4 -1
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('./rolldown-runtime-CKhH4XqG.cjs');
|
|
2
|
+
const require_pdfObjects = require('./pdfObjects-BrU4Xd0V.cjs');
|
|
3
|
+
const require_imageExtract = require('./imageExtract-BC7TMY98.cjs');
|
|
4
|
+
|
|
5
|
+
//#region src/assets/image/grayscaleDetect.ts
|
|
6
|
+
/**
|
|
7
|
+
* @module assets/image/grayscaleDetect
|
|
8
|
+
*
|
|
9
|
+
* Grayscale detection and conversion for image optimization.
|
|
10
|
+
*
|
|
11
|
+
* Detects RGB images where all pixels are effectively grayscale
|
|
12
|
+
* (R ≈ G ≈ B) and converts them to single-channel grayscale,
|
|
13
|
+
* reducing data size by ~66%.
|
|
14
|
+
*
|
|
15
|
+
* No Buffer — uses Uint8Array exclusively.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Check whether an RGB/RGBA image is effectively grayscale.
|
|
19
|
+
*
|
|
20
|
+
* Scans all pixels and checks if R, G, and B channels are within
|
|
21
|
+
* `tolerance` of each other. If ≥99% of pixels pass, the image
|
|
22
|
+
* is considered grayscale.
|
|
23
|
+
*
|
|
24
|
+
* @param pixels - Raw pixel data (row-major, channel-interleaved).
|
|
25
|
+
* @param width - Image width in pixels.
|
|
26
|
+
* @param height - Image height in pixels.
|
|
27
|
+
* @param channels - Number of channels: 3 (RGB) or 4 (RGBA).
|
|
28
|
+
* @param tolerance - Maximum allowed difference between R, G, and B
|
|
29
|
+
* values for a pixel to be considered gray.
|
|
30
|
+
* Default: `2`.
|
|
31
|
+
* @returns `true` if the image is effectively grayscale.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* import { isGrayscaleImage, convertToGrayscale } from 'modern-pdf-lib';
|
|
36
|
+
*
|
|
37
|
+
* if (isGrayscaleImage(pixels, width, height, 3)) {
|
|
38
|
+
* const grayPixels = convertToGrayscale(pixels, width, height, 3);
|
|
39
|
+
* // grayPixels has 1 byte per pixel instead of 3
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
function isGrayscaleImage(pixels, width, height, channels, tolerance = 2) {
|
|
44
|
+
const pixelCount = width * height;
|
|
45
|
+
const maxNonGray = Math.floor(pixelCount * .01);
|
|
46
|
+
let nonGrayCount = 0;
|
|
47
|
+
for (let i = 0; i < pixelCount; i++) {
|
|
48
|
+
const r = pixels[i * channels];
|
|
49
|
+
const g = pixels[i * channels + 1];
|
|
50
|
+
const b = pixels[i * channels + 2];
|
|
51
|
+
if (Math.max(r, g, b) - Math.min(r, g, b) > tolerance) {
|
|
52
|
+
nonGrayCount++;
|
|
53
|
+
if (nonGrayCount > maxNonGray) return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Convert an RGB/RGBA image to single-channel grayscale.
|
|
60
|
+
*
|
|
61
|
+
* Uses the ITU-R BT.601 luma formula:
|
|
62
|
+
* ```
|
|
63
|
+
* gray = 0.299 × R + 0.587 × G + 0.114 × B
|
|
64
|
+
* ```
|
|
65
|
+
*
|
|
66
|
+
* The alpha channel (if present) is discarded.
|
|
67
|
+
*
|
|
68
|
+
* @param pixels - Raw pixel data (row-major, channel-interleaved).
|
|
69
|
+
* @param width - Image width in pixels.
|
|
70
|
+
* @param height - Image height in pixels.
|
|
71
|
+
* @param channels - Number of channels: 3 (RGB) or 4 (RGBA).
|
|
72
|
+
* @returns Grayscale pixel data (1 byte per pixel).
|
|
73
|
+
*/
|
|
74
|
+
function convertToGrayscale(pixels, width, height, channels) {
|
|
75
|
+
const pixelCount = width * height;
|
|
76
|
+
const gray = new Uint8Array(pixelCount);
|
|
77
|
+
for (let i = 0; i < pixelCount; i++) {
|
|
78
|
+
const r = pixels[i * channels];
|
|
79
|
+
const g = pixels[i * channels + 1];
|
|
80
|
+
const b = pixels[i * channels + 2];
|
|
81
|
+
gray[i] = Math.round(.299 * r + .587 * g + .114 * b);
|
|
82
|
+
}
|
|
83
|
+
return gray;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
//#endregion
|
|
87
|
+
//#region src/assets/image/batchOptimize.ts
|
|
88
|
+
var batchOptimize_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({ optimizeAllImages: () => optimizeAllImages });
|
|
89
|
+
/** Minimum image size to bother optimizing (10 KB). */
|
|
90
|
+
const SMALL_IMAGE_THRESHOLD = 10240;
|
|
91
|
+
/**
|
|
92
|
+
* Optimize all images in a PDF document by recompressing them as JPEG.
|
|
93
|
+
*
|
|
94
|
+
* Walks every image XObject in the document, decodes its pixel data,
|
|
95
|
+
* recompresses it as JPEG using the WASM encoder (if available), and
|
|
96
|
+
* replaces the stream data in-place when the result is smaller.
|
|
97
|
+
*
|
|
98
|
+
* **Requires the JPEG WASM module to be initialized** via
|
|
99
|
+
* `initJpegWasm()` or `initWasm({ jpeg: true })`. Without it,
|
|
100
|
+
* no images will be optimized (all will be skipped).
|
|
101
|
+
*
|
|
102
|
+
* @param doc - A parsed `PdfDocument` (from `loadPdf()`).
|
|
103
|
+
* @param options - Optimization settings.
|
|
104
|
+
* @returns A report summarizing the optimization results.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```ts
|
|
108
|
+
* import { loadPdf, initWasm, optimizeAllImages } from 'modern-pdf-lib';
|
|
109
|
+
*
|
|
110
|
+
* await initWasm({ jpeg: true });
|
|
111
|
+
*
|
|
112
|
+
* const doc = await loadPdf(pdfBytes);
|
|
113
|
+
* const report = await optimizeAllImages(doc);
|
|
114
|
+
*
|
|
115
|
+
* console.log(`Optimized ${report.optimizedImages} of ${report.totalImages} images`);
|
|
116
|
+
* console.log(`Savings: ${report.savings.toFixed(1)}%`);
|
|
117
|
+
*
|
|
118
|
+
* const optimizedBytes = await doc.save();
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
async function optimizeAllImages(doc, options = {}) {
|
|
122
|
+
const quality = options.quality ?? 80;
|
|
123
|
+
const minSavingsPercent = options.minSavingsPercent ?? 10;
|
|
124
|
+
const skipSmall = options.skipSmallImages ?? false;
|
|
125
|
+
const progressive = options.progressive ?? false;
|
|
126
|
+
const chromaSubsampling = options.chromaSubsampling ?? "4:2:0";
|
|
127
|
+
const { encodeJpegWasm, isJpegWasmReady } = await Promise.resolve().then(() => require("./bridge-DN7BOHRW.cjs")).then((n) => n.bridge_exports);
|
|
128
|
+
const { decodeJpegWasm } = await Promise.resolve().then(() => require("./bridge-DN7BOHRW.cjs")).then((n) => n.bridge_exports);
|
|
129
|
+
const images = require_imageExtract.extractImages(doc);
|
|
130
|
+
const perImage = [];
|
|
131
|
+
let totalOriginal = 0;
|
|
132
|
+
let totalNew = 0;
|
|
133
|
+
let optimizedCount = 0;
|
|
134
|
+
for (const img of images) {
|
|
135
|
+
totalOriginal += img.compressedSize;
|
|
136
|
+
if (!isJpegWasmReady()) {
|
|
137
|
+
perImage.push({
|
|
138
|
+
name: img.name,
|
|
139
|
+
pageIndex: img.pageIndex,
|
|
140
|
+
originalSize: img.compressedSize,
|
|
141
|
+
newSize: img.compressedSize,
|
|
142
|
+
skipped: true,
|
|
143
|
+
reason: "JPEG WASM encoder not initialized"
|
|
144
|
+
});
|
|
145
|
+
totalNew += img.compressedSize;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (skipSmall && img.compressedSize < SMALL_IMAGE_THRESHOLD) {
|
|
149
|
+
perImage.push({
|
|
150
|
+
name: img.name,
|
|
151
|
+
pageIndex: img.pageIndex,
|
|
152
|
+
originalSize: img.compressedSize,
|
|
153
|
+
newSize: img.compressedSize,
|
|
154
|
+
skipped: true,
|
|
155
|
+
reason: `Below size threshold (${SMALL_IMAGE_THRESHOLD} bytes)`
|
|
156
|
+
});
|
|
157
|
+
totalNew += img.compressedSize;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (img.bitsPerComponent !== 8) {
|
|
161
|
+
perImage.push({
|
|
162
|
+
name: img.name,
|
|
163
|
+
pageIndex: img.pageIndex,
|
|
164
|
+
originalSize: img.compressedSize,
|
|
165
|
+
newSize: img.compressedSize,
|
|
166
|
+
skipped: true,
|
|
167
|
+
reason: `Unsupported bits per component: ${img.bitsPerComponent}`
|
|
168
|
+
});
|
|
169
|
+
totalNew += img.compressedSize;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (img.colorSpace === "Indexed") {
|
|
173
|
+
perImage.push({
|
|
174
|
+
name: img.name,
|
|
175
|
+
pageIndex: img.pageIndex,
|
|
176
|
+
originalSize: img.compressedSize,
|
|
177
|
+
newSize: img.compressedSize,
|
|
178
|
+
skipped: true,
|
|
179
|
+
reason: "Indexed color space not suitable for JPEG"
|
|
180
|
+
});
|
|
181
|
+
totalNew += img.compressedSize;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
let pixels;
|
|
185
|
+
let channels = img.channels;
|
|
186
|
+
try {
|
|
187
|
+
if (img.filters[0] === "DCTDecode") {
|
|
188
|
+
const decoded = decodeJpegWasm(img.stream.data);
|
|
189
|
+
if (!decoded) {
|
|
190
|
+
perImage.push({
|
|
191
|
+
name: img.name,
|
|
192
|
+
pageIndex: img.pageIndex,
|
|
193
|
+
originalSize: img.compressedSize,
|
|
194
|
+
newSize: img.compressedSize,
|
|
195
|
+
skipped: true,
|
|
196
|
+
reason: "Failed to decode existing JPEG"
|
|
197
|
+
});
|
|
198
|
+
totalNew += img.compressedSize;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
pixels = decoded.pixels;
|
|
202
|
+
channels = decoded.channels;
|
|
203
|
+
} else pixels = require_imageExtract.decodeImageStream(img);
|
|
204
|
+
} catch {
|
|
205
|
+
perImage.push({
|
|
206
|
+
name: img.name,
|
|
207
|
+
pageIndex: img.pageIndex,
|
|
208
|
+
originalSize: img.compressedSize,
|
|
209
|
+
newSize: img.compressedSize,
|
|
210
|
+
skipped: true,
|
|
211
|
+
reason: "Failed to decode image stream"
|
|
212
|
+
});
|
|
213
|
+
totalNew += img.compressedSize;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
const expectedLen = img.width * img.height * channels;
|
|
217
|
+
if (pixels.length !== expectedLen) {
|
|
218
|
+
perImage.push({
|
|
219
|
+
name: img.name,
|
|
220
|
+
pageIndex: img.pageIndex,
|
|
221
|
+
originalSize: img.compressedSize,
|
|
222
|
+
newSize: img.compressedSize,
|
|
223
|
+
skipped: true,
|
|
224
|
+
reason: `Pixel data length mismatch: got ${pixels.length}, expected ${expectedLen}`
|
|
225
|
+
});
|
|
226
|
+
totalNew += img.compressedSize;
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (channels === 4 && img.colorSpace === "DeviceCMYK") {
|
|
230
|
+
const rgb = new Uint8Array(img.width * img.height * 3);
|
|
231
|
+
for (let i = 0; i < img.width * img.height; i++) {
|
|
232
|
+
const c = pixels[i * 4] / 255;
|
|
233
|
+
const m = pixels[i * 4 + 1] / 255;
|
|
234
|
+
const y = pixels[i * 4 + 2] / 255;
|
|
235
|
+
const k = pixels[i * 4 + 3] / 255;
|
|
236
|
+
rgb[i * 3] = Math.round(255 * (1 - c) * (1 - k));
|
|
237
|
+
rgb[i * 3 + 1] = Math.round(255 * (1 - m) * (1 - k));
|
|
238
|
+
rgb[i * 3 + 2] = Math.round(255 * (1 - y) * (1 - k));
|
|
239
|
+
}
|
|
240
|
+
pixels = rgb;
|
|
241
|
+
channels = 3;
|
|
242
|
+
}
|
|
243
|
+
if (options.autoGrayscale && (channels === 3 || channels === 4)) {
|
|
244
|
+
if (isGrayscaleImage(pixels, img.width, img.height, channels)) {
|
|
245
|
+
pixels = convertToGrayscale(pixels, img.width, img.height, channels);
|
|
246
|
+
channels = 1;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const jpegBytes = encodeJpegWasm(pixels, img.width, img.height, channels, quality, progressive, chromaSubsampling);
|
|
250
|
+
if (!jpegBytes) {
|
|
251
|
+
perImage.push({
|
|
252
|
+
name: img.name,
|
|
253
|
+
pageIndex: img.pageIndex,
|
|
254
|
+
originalSize: img.compressedSize,
|
|
255
|
+
newSize: img.compressedSize,
|
|
256
|
+
skipped: true,
|
|
257
|
+
reason: "JPEG encoding failed"
|
|
258
|
+
});
|
|
259
|
+
totalNew += img.compressedSize;
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
const savingsPercent = (img.compressedSize - jpegBytes.length) / img.compressedSize * 100;
|
|
263
|
+
if (savingsPercent < minSavingsPercent) {
|
|
264
|
+
perImage.push({
|
|
265
|
+
name: img.name,
|
|
266
|
+
pageIndex: img.pageIndex,
|
|
267
|
+
originalSize: img.compressedSize,
|
|
268
|
+
newSize: img.compressedSize,
|
|
269
|
+
skipped: true,
|
|
270
|
+
reason: `Savings ${savingsPercent.toFixed(1)}% below threshold ${minSavingsPercent}%`
|
|
271
|
+
});
|
|
272
|
+
totalNew += img.compressedSize;
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
img.stream.data = jpegBytes;
|
|
276
|
+
img.stream.syncLength();
|
|
277
|
+
const dict = img.stream.dict;
|
|
278
|
+
dict.set("/Filter", require_pdfObjects.PdfName.of("/DCTDecode"));
|
|
279
|
+
if (img.colorSpace === "DeviceCMYK" && channels === 3) dict.set("/ColorSpace", require_pdfObjects.PdfName.of("/DeviceRGB"));
|
|
280
|
+
if (channels === 1) dict.set("/ColorSpace", require_pdfObjects.PdfName.of("/DeviceGray"));
|
|
281
|
+
dict.delete("/DecodeParms");
|
|
282
|
+
if (img.colorSpace === "DeviceCMYK") dict.delete("/Decode");
|
|
283
|
+
optimizedCount++;
|
|
284
|
+
perImage.push({
|
|
285
|
+
name: img.name,
|
|
286
|
+
pageIndex: img.pageIndex,
|
|
287
|
+
originalSize: img.compressedSize,
|
|
288
|
+
newSize: jpegBytes.length,
|
|
289
|
+
skipped: false
|
|
290
|
+
});
|
|
291
|
+
totalNew += jpegBytes.length;
|
|
292
|
+
}
|
|
293
|
+
const overallSavings = totalOriginal > 0 ? (totalOriginal - totalNew) / totalOriginal * 100 : 0;
|
|
294
|
+
return {
|
|
295
|
+
totalImages: images.length,
|
|
296
|
+
optimizedImages: optimizedCount,
|
|
297
|
+
originalTotalBytes: totalOriginal,
|
|
298
|
+
optimizedTotalBytes: totalNew,
|
|
299
|
+
savings: overallSavings,
|
|
300
|
+
perImage
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
//#endregion
|
|
305
|
+
Object.defineProperty(exports, 'batchOptimize_exports', {
|
|
306
|
+
enumerable: true,
|
|
307
|
+
get: function () {
|
|
308
|
+
return batchOptimize_exports;
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
Object.defineProperty(exports, 'convertToGrayscale', {
|
|
312
|
+
enumerable: true,
|
|
313
|
+
get: function () {
|
|
314
|
+
return convertToGrayscale;
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
Object.defineProperty(exports, 'isGrayscaleImage', {
|
|
318
|
+
enumerable: true,
|
|
319
|
+
get: function () {
|
|
320
|
+
return isGrayscaleImage;
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
Object.defineProperty(exports, 'optimizeAllImages', {
|
|
324
|
+
enumerable: true,
|
|
325
|
+
get: function () {
|
|
326
|
+
return optimizeAllImages;
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
//# sourceMappingURL=batchOptimize-DYQOX1-7.cjs.map
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('./rolldown-runtime-CKhH4XqG.cjs');
|
|
2
|
+
|
|
3
|
+
//#region src/wasm/jpeg/bridge.ts
|
|
4
|
+
var bridge_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
|
|
5
|
+
decodeJpegWasm: () => decodeJpegWasm,
|
|
6
|
+
encodeJpegWasm: () => encodeJpegWasm,
|
|
7
|
+
initJpegWasm: () => initJpegWasm,
|
|
8
|
+
isJpegWasmReady: () => isJpegWasmReady
|
|
9
|
+
});
|
|
10
|
+
let wasmModule;
|
|
11
|
+
/**
|
|
12
|
+
* Initialize the JPEG WASM module.
|
|
13
|
+
*
|
|
14
|
+
* @param wasmSource - The WASM binary as `Uint8Array`, URL, `Response`,
|
|
15
|
+
* or a pre-built wasm-bindgen module. When omitted,
|
|
16
|
+
* the function uses the universal WASM loader.
|
|
17
|
+
*/
|
|
18
|
+
async function initJpegWasm(wasmSource) {
|
|
19
|
+
if (wasmModule) return;
|
|
20
|
+
try {
|
|
21
|
+
if (wasmSource && typeof wasmSource.encode_jpeg === "function") {
|
|
22
|
+
wasmModule = wasmSource;
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const { loadWasmModule: loadWasm } = await Promise.resolve().then(() => require("./loader-DdB5Xo5D.cjs"));
|
|
26
|
+
const wasmBytes = await loadWasm("jpeg");
|
|
27
|
+
wasmModule = (await WebAssembly.instantiate(wasmBytes.buffer, { env: {} })).instance.exports;
|
|
28
|
+
} catch {
|
|
29
|
+
wasmModule = void 0;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Check whether the JPEG WASM module has been initialized.
|
|
34
|
+
*
|
|
35
|
+
* @returns `true` if {@link initJpegWasm} completed successfully.
|
|
36
|
+
*/
|
|
37
|
+
function isJpegWasmReady() {
|
|
38
|
+
return wasmModule !== void 0;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Map a `ChromaSubsampling` string to the numeric code expected by WASM.
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
function chromaToCode(chroma) {
|
|
45
|
+
switch (chroma) {
|
|
46
|
+
case "4:4:4": return 0;
|
|
47
|
+
case "4:2:2": return 1;
|
|
48
|
+
default: return 2;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Encode raw pixel data to JPEG using the WASM encoder.
|
|
53
|
+
*
|
|
54
|
+
* @param pixels - Raw pixel data (row-major, channel-interleaved).
|
|
55
|
+
* @param width - Image width in pixels.
|
|
56
|
+
* @param height - Image height in pixels.
|
|
57
|
+
* @param channels - Number of channels: 1 (grayscale), 3 (RGB), or 4 (RGBA).
|
|
58
|
+
* @param quality - JPEG quality 1–100.
|
|
59
|
+
* @param progressive - Encode as progressive JPEG (default: false).
|
|
60
|
+
* @param chroma - Chroma subsampling mode (default: '4:2:0').
|
|
61
|
+
* @returns JPEG-encoded bytes, or `undefined` if WASM is not available.
|
|
62
|
+
*/
|
|
63
|
+
function encodeJpegWasm(pixels, width, height, channels, quality, progressive = false, chroma = "4:2:0") {
|
|
64
|
+
if (!wasmModule) return void 0;
|
|
65
|
+
try {
|
|
66
|
+
return wasmModule.encode_jpeg(pixels, width, height, channels, Math.max(1, Math.min(100, Math.round(quality))), progressive, chromaToCode(chroma));
|
|
67
|
+
} catch {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Decode JPEG bytes to raw pixel data using the WASM decoder.
|
|
73
|
+
*
|
|
74
|
+
* The WASM module returns a flat byte array with layout:
|
|
75
|
+
* `[width_u32_le, height_u32_le, channels_u8, ...pixels]`.
|
|
76
|
+
*
|
|
77
|
+
* @param jpegBytes - JPEG-encoded image data.
|
|
78
|
+
* @returns Decoded pixel data with metadata, or `undefined` if WASM is not
|
|
79
|
+
* available or decoding failed.
|
|
80
|
+
*/
|
|
81
|
+
function decodeJpegWasm(jpegBytes) {
|
|
82
|
+
if (!wasmModule) return void 0;
|
|
83
|
+
try {
|
|
84
|
+
const raw = wasmModule.decode_jpeg(jpegBytes);
|
|
85
|
+
if (raw.length < 9) return void 0;
|
|
86
|
+
const view = new DataView(raw.buffer, raw.byteOffset, raw.byteLength);
|
|
87
|
+
const width = view.getUint32(0, true);
|
|
88
|
+
const height = view.getUint32(4, true);
|
|
89
|
+
const channels = raw[8];
|
|
90
|
+
return {
|
|
91
|
+
pixels: raw.slice(9),
|
|
92
|
+
width,
|
|
93
|
+
height,
|
|
94
|
+
channels
|
|
95
|
+
};
|
|
96
|
+
} catch {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
//#endregion
|
|
102
|
+
Object.defineProperty(exports, 'bridge_exports', {
|
|
103
|
+
enumerable: true,
|
|
104
|
+
get: function () {
|
|
105
|
+
return bridge_exports;
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
Object.defineProperty(exports, 'decodeJpegWasm', {
|
|
109
|
+
enumerable: true,
|
|
110
|
+
get: function () {
|
|
111
|
+
return decodeJpegWasm;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
Object.defineProperty(exports, 'encodeJpegWasm', {
|
|
115
|
+
enumerable: true,
|
|
116
|
+
get: function () {
|
|
117
|
+
return encodeJpegWasm;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
Object.defineProperty(exports, 'initJpegWasm', {
|
|
121
|
+
enumerable: true,
|
|
122
|
+
get: function () {
|
|
123
|
+
return initJpegWasm;
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
Object.defineProperty(exports, 'isJpegWasmReady', {
|
|
127
|
+
enumerable: true,
|
|
128
|
+
get: function () {
|
|
129
|
+
return isJpegWasmReady;
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
//# sourceMappingURL=bridge-DN7BOHRW.cjs.map
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-95iHPtFO.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/wasm/jpeg/bridge.ts
|
|
4
|
+
var bridge_exports = /* @__PURE__ */ __exportAll({
|
|
5
|
+
decodeJpegWasm: () => decodeJpegWasm,
|
|
6
|
+
encodeJpegWasm: () => encodeJpegWasm,
|
|
7
|
+
initJpegWasm: () => initJpegWasm,
|
|
8
|
+
isJpegWasmReady: () => isJpegWasmReady
|
|
9
|
+
});
|
|
10
|
+
let wasmModule;
|
|
11
|
+
/**
|
|
12
|
+
* Initialize the JPEG WASM module.
|
|
13
|
+
*
|
|
14
|
+
* @param wasmSource - The WASM binary as `Uint8Array`, URL, `Response`,
|
|
15
|
+
* or a pre-built wasm-bindgen module. When omitted,
|
|
16
|
+
* the function uses the universal WASM loader.
|
|
17
|
+
*/
|
|
18
|
+
async function initJpegWasm(wasmSource) {
|
|
19
|
+
if (wasmModule) return;
|
|
20
|
+
try {
|
|
21
|
+
if (wasmSource && typeof wasmSource.encode_jpeg === "function") {
|
|
22
|
+
wasmModule = wasmSource;
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const { loadWasmModule: loadWasm } = await import("./loader-B6VIrZOJ.mjs");
|
|
26
|
+
const wasmBytes = await loadWasm("jpeg");
|
|
27
|
+
wasmModule = (await WebAssembly.instantiate(wasmBytes.buffer, { env: {} })).instance.exports;
|
|
28
|
+
} catch {
|
|
29
|
+
wasmModule = void 0;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Check whether the JPEG WASM module has been initialized.
|
|
34
|
+
*
|
|
35
|
+
* @returns `true` if {@link initJpegWasm} completed successfully.
|
|
36
|
+
*/
|
|
37
|
+
function isJpegWasmReady() {
|
|
38
|
+
return wasmModule !== void 0;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Map a `ChromaSubsampling` string to the numeric code expected by WASM.
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
function chromaToCode(chroma) {
|
|
45
|
+
switch (chroma) {
|
|
46
|
+
case "4:4:4": return 0;
|
|
47
|
+
case "4:2:2": return 1;
|
|
48
|
+
default: return 2;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Encode raw pixel data to JPEG using the WASM encoder.
|
|
53
|
+
*
|
|
54
|
+
* @param pixels - Raw pixel data (row-major, channel-interleaved).
|
|
55
|
+
* @param width - Image width in pixels.
|
|
56
|
+
* @param height - Image height in pixels.
|
|
57
|
+
* @param channels - Number of channels: 1 (grayscale), 3 (RGB), or 4 (RGBA).
|
|
58
|
+
* @param quality - JPEG quality 1–100.
|
|
59
|
+
* @param progressive - Encode as progressive JPEG (default: false).
|
|
60
|
+
* @param chroma - Chroma subsampling mode (default: '4:2:0').
|
|
61
|
+
* @returns JPEG-encoded bytes, or `undefined` if WASM is not available.
|
|
62
|
+
*/
|
|
63
|
+
function encodeJpegWasm(pixels, width, height, channels, quality, progressive = false, chroma = "4:2:0") {
|
|
64
|
+
if (!wasmModule) return void 0;
|
|
65
|
+
try {
|
|
66
|
+
return wasmModule.encode_jpeg(pixels, width, height, channels, Math.max(1, Math.min(100, Math.round(quality))), progressive, chromaToCode(chroma));
|
|
67
|
+
} catch {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Decode JPEG bytes to raw pixel data using the WASM decoder.
|
|
73
|
+
*
|
|
74
|
+
* The WASM module returns a flat byte array with layout:
|
|
75
|
+
* `[width_u32_le, height_u32_le, channels_u8, ...pixels]`.
|
|
76
|
+
*
|
|
77
|
+
* @param jpegBytes - JPEG-encoded image data.
|
|
78
|
+
* @returns Decoded pixel data with metadata, or `undefined` if WASM is not
|
|
79
|
+
* available or decoding failed.
|
|
80
|
+
*/
|
|
81
|
+
function decodeJpegWasm(jpegBytes) {
|
|
82
|
+
if (!wasmModule) return void 0;
|
|
83
|
+
try {
|
|
84
|
+
const raw = wasmModule.decode_jpeg(jpegBytes);
|
|
85
|
+
if (raw.length < 9) return void 0;
|
|
86
|
+
const view = new DataView(raw.buffer, raw.byteOffset, raw.byteLength);
|
|
87
|
+
const width = view.getUint32(0, true);
|
|
88
|
+
const height = view.getUint32(4, true);
|
|
89
|
+
const channels = raw[8];
|
|
90
|
+
return {
|
|
91
|
+
pixels: raw.slice(9),
|
|
92
|
+
width,
|
|
93
|
+
height,
|
|
94
|
+
channels
|
|
95
|
+
};
|
|
96
|
+
} catch {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
//#endregion
|
|
102
|
+
export { isJpegWasmReady as a, initJpegWasm as i, decodeJpegWasm as n, encodeJpegWasm as r, bridge_exports as t };
|
|
103
|
+
//# sourceMappingURL=bridge-DpzMOnHd.mjs.map
|