@huh-david/bmp-js 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1025 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/sharp/index.ts
21
+ var sharp_exports = {};
22
+ __export(sharp_exports, {
23
+ InvalidSharpRawInputError: () => InvalidSharpRawInputError,
24
+ NotBmpInputError: () => NotBmpInputError,
25
+ SharpAdapterError: () => SharpAdapterError,
26
+ SharpModuleLoadError: () => SharpModuleLoadError,
27
+ decodeForSharp: () => decodeForSharp,
28
+ encodeFromSharp: () => encodeFromSharp,
29
+ isBmp: () => isBmp,
30
+ sharpFromBmp: () => sharpFromBmp,
31
+ toSharpInput: () => toSharpInput
32
+ });
33
+ module.exports = __toCommonJS(sharp_exports);
34
+ var import_node_module = require("module");
35
+
36
+ // src/binary.ts
37
+ function toUint8Array(input) {
38
+ if (input instanceof ArrayBuffer) {
39
+ return new Uint8Array(input);
40
+ }
41
+ return new Uint8Array(input.buffer, input.byteOffset, input.byteLength);
42
+ }
43
+ function assertInteger(name, value) {
44
+ if (!Number.isInteger(value) || value <= 0) {
45
+ throw new Error(`${name} must be a positive integer`);
46
+ }
47
+ }
48
+
49
+ // src/decoder.ts
50
+ var FILE_HEADER_SIZE = 14;
51
+ var INFO_HEADER_MIN = 40;
52
+ var CORE_HEADER_SIZE = 12;
53
+ function rowStride(width, bitPP) {
54
+ return Math.floor((bitPP * width + 31) / 32) * 4;
55
+ }
56
+ var BmpDecoder = class {
57
+ pos = 0;
58
+ bytes;
59
+ view;
60
+ options;
61
+ bottomUp = true;
62
+ dibStart = FILE_HEADER_SIZE;
63
+ paletteEntrySize = 4;
64
+ externalMaskOffset = 0;
65
+ maskRed = 0;
66
+ maskGreen = 0;
67
+ maskBlue = 0;
68
+ maskAlpha = 0;
69
+ fileSize;
70
+ reserved;
71
+ offset;
72
+ headerSize;
73
+ width;
74
+ height;
75
+ planes;
76
+ bitPP;
77
+ compress;
78
+ rawSize;
79
+ hr;
80
+ vr;
81
+ colors;
82
+ importantColors;
83
+ palette;
84
+ data;
85
+ constructor(input, options = {}) {
86
+ this.bytes = toUint8Array(input);
87
+ this.view = new DataView(this.bytes.buffer, this.bytes.byteOffset, this.bytes.byteLength);
88
+ this.options = {
89
+ treat16BitAs15BitAlpha: options.treat16BitAs15BitAlpha ?? false,
90
+ toRGBA: options.toRGBA ?? false
91
+ };
92
+ this.parseFileHeader();
93
+ this.parseDibHeader();
94
+ this.parsePalette();
95
+ this.pos = this.offset;
96
+ this.parseRGBA();
97
+ this.transformToRgbaIfNeeded();
98
+ }
99
+ ensureReadable(offset, size, context) {
100
+ if (offset < 0 || size < 0 || offset + size > this.bytes.length) {
101
+ throw new Error(`BMP decode out-of-range while reading ${context}`);
102
+ }
103
+ }
104
+ readUInt8(offset = this.pos) {
105
+ this.ensureReadable(offset, 1, "uint8");
106
+ if (offset === this.pos) this.pos += 1;
107
+ return this.view.getUint8(offset);
108
+ }
109
+ readUInt16LE(offset = this.pos) {
110
+ this.ensureReadable(offset, 2, "uint16");
111
+ if (offset === this.pos) this.pos += 2;
112
+ return this.view.getUint16(offset, true);
113
+ }
114
+ readInt16LE(offset = this.pos) {
115
+ this.ensureReadable(offset, 2, "int16");
116
+ if (offset === this.pos) this.pos += 2;
117
+ return this.view.getInt16(offset, true);
118
+ }
119
+ readUInt32LE(offset = this.pos) {
120
+ this.ensureReadable(offset, 4, "uint32");
121
+ if (offset === this.pos) this.pos += 4;
122
+ return this.view.getUint32(offset, true);
123
+ }
124
+ readInt32LE(offset = this.pos) {
125
+ this.ensureReadable(offset, 4, "int32");
126
+ if (offset === this.pos) this.pos += 4;
127
+ return this.view.getInt32(offset, true);
128
+ }
129
+ parseFileHeader() {
130
+ this.ensureReadable(0, FILE_HEADER_SIZE, "file header");
131
+ if (this.bytes[0] !== 66 || this.bytes[1] !== 77) {
132
+ throw new Error("Invalid BMP file signature");
133
+ }
134
+ this.pos = 2;
135
+ this.fileSize = this.readUInt32LE();
136
+ this.reserved = this.readUInt32LE();
137
+ this.offset = this.readUInt32LE();
138
+ if (this.offset < FILE_HEADER_SIZE || this.offset > this.bytes.length) {
139
+ throw new Error(`Invalid pixel data offset: ${this.offset}`);
140
+ }
141
+ }
142
+ parseDibHeader() {
143
+ this.pos = this.dibStart;
144
+ this.headerSize = this.readUInt32LE();
145
+ if (this.headerSize < CORE_HEADER_SIZE) {
146
+ throw new Error(`Unsupported DIB header size: ${this.headerSize}`);
147
+ }
148
+ this.ensureReadable(this.dibStart, this.headerSize, "DIB header");
149
+ if (this.headerSize === CORE_HEADER_SIZE) {
150
+ this.parseCoreHeader();
151
+ return;
152
+ }
153
+ if (this.headerSize < INFO_HEADER_MIN) {
154
+ throw new Error(`Unsupported DIB header size: ${this.headerSize}`);
155
+ }
156
+ this.parseInfoHeader();
157
+ }
158
+ parseCoreHeader() {
159
+ const width = this.readUInt16LE(this.dibStart + 4);
160
+ const height = this.readUInt16LE(this.dibStart + 6);
161
+ this.width = width;
162
+ this.height = height;
163
+ this.planes = this.readUInt16LE(this.dibStart + 8);
164
+ this.bitPP = this.readUInt16LE(this.dibStart + 10);
165
+ this.compress = 0;
166
+ this.rawSize = 0;
167
+ this.hr = 0;
168
+ this.vr = 0;
169
+ this.colors = 0;
170
+ this.importantColors = 0;
171
+ this.bottomUp = true;
172
+ this.paletteEntrySize = 3;
173
+ this.externalMaskOffset = this.dibStart + this.headerSize;
174
+ this.validateDimensions();
175
+ }
176
+ parseInfoHeader() {
177
+ const rawWidth = this.readInt32LE(this.dibStart + 4);
178
+ const rawHeight = this.readInt32LE(this.dibStart + 8);
179
+ this.width = rawWidth;
180
+ this.height = rawHeight;
181
+ this.planes = this.readUInt16LE(this.dibStart + 12);
182
+ this.bitPP = this.readUInt16LE(this.dibStart + 14);
183
+ this.compress = this.readUInt32LE(this.dibStart + 16);
184
+ this.rawSize = this.readUInt32LE(this.dibStart + 20);
185
+ this.hr = this.readUInt32LE(this.dibStart + 24);
186
+ this.vr = this.readUInt32LE(this.dibStart + 28);
187
+ this.colors = this.readUInt32LE(this.dibStart + 32);
188
+ this.importantColors = this.readUInt32LE(this.dibStart + 36);
189
+ this.paletteEntrySize = 4;
190
+ this.externalMaskOffset = this.dibStart + this.headerSize;
191
+ if (this.height < 0) {
192
+ this.height *= -1;
193
+ this.bottomUp = false;
194
+ }
195
+ if (this.width < 0) {
196
+ this.width *= -1;
197
+ }
198
+ if (this.bitPP === 16 && this.options.treat16BitAs15BitAlpha) {
199
+ this.bitPP = 15;
200
+ }
201
+ this.validateDimensions();
202
+ this.parseBitMasks();
203
+ }
204
+ validateDimensions() {
205
+ if (!Number.isInteger(this.width) || !Number.isInteger(this.height) || this.width <= 0 || this.height <= 0) {
206
+ throw new Error(`Invalid BMP dimensions: ${this.width}x${this.height}`);
207
+ }
208
+ }
209
+ parseBitMasks() {
210
+ if (!(this.bitPP === 16 || this.bitPP === 32) || !(this.compress === 3 || this.compress === 6)) {
211
+ return;
212
+ }
213
+ const inHeaderMaskStart = this.dibStart + 40;
214
+ const hasMasksInHeader = this.headerSize >= 52;
215
+ const maskStart = hasMasksInHeader ? inHeaderMaskStart : this.externalMaskOffset;
216
+ const maskCount = this.compress === 6 || this.headerSize >= 56 ? 4 : 3;
217
+ this.ensureReadable(maskStart, maskCount * 4, "bit masks");
218
+ this.maskRed = this.readUInt32LE(maskStart);
219
+ this.maskGreen = this.readUInt32LE(maskStart + 4);
220
+ this.maskBlue = this.readUInt32LE(maskStart + 8);
221
+ this.maskAlpha = maskCount >= 4 ? this.readUInt32LE(maskStart + 12) : 0;
222
+ if (!hasMasksInHeader) {
223
+ this.externalMaskOffset += maskCount * 4;
224
+ }
225
+ }
226
+ parsePalette() {
227
+ if (this.bitPP >= 16) {
228
+ return;
229
+ }
230
+ const colorCount = this.colors === 0 ? 1 << this.bitPP : this.colors;
231
+ if (colorCount <= 0) {
232
+ return;
233
+ }
234
+ const paletteStart = this.externalMaskOffset;
235
+ const paletteSize = colorCount * this.paletteEntrySize;
236
+ if (paletteStart + paletteSize > this.offset) {
237
+ throw new Error("Palette data overlaps or exceeds pixel data offset");
238
+ }
239
+ this.palette = new Array(colorCount);
240
+ for (let i = 0; i < colorCount; i += 1) {
241
+ const base = paletteStart + i * this.paletteEntrySize;
242
+ const blue = this.readUInt8(base);
243
+ const green = this.readUInt8(base + 1);
244
+ const red = this.readUInt8(base + 2);
245
+ const quad = this.paletteEntrySize === 4 ? this.readUInt8(base + 3) : 0;
246
+ this.palette[i] = { red, green, blue, quad };
247
+ }
248
+ }
249
+ parseRGBA() {
250
+ const pixelCount = this.width * this.height;
251
+ const len = pixelCount * 4;
252
+ this.data = new Uint8Array(len);
253
+ switch (this.bitPP) {
254
+ case 1:
255
+ this.bit1();
256
+ return;
257
+ case 4:
258
+ this.bit4();
259
+ return;
260
+ case 8:
261
+ this.bit8();
262
+ return;
263
+ case 15:
264
+ this.bit15();
265
+ return;
266
+ case 16:
267
+ this.bit16();
268
+ return;
269
+ case 24:
270
+ this.bit24();
271
+ return;
272
+ case 32:
273
+ this.bit32();
274
+ return;
275
+ default:
276
+ throw new Error(`Unsupported BMP bit depth: ${this.bitPP}`);
277
+ }
278
+ }
279
+ transformToRgbaIfNeeded() {
280
+ if (!this.options.toRGBA) {
281
+ return;
282
+ }
283
+ for (let i = 0; i < this.data.length; i += 4) {
284
+ const alpha = this.data[i] ?? 0;
285
+ const blue = this.data[i + 1] ?? 0;
286
+ const green = this.data[i + 2] ?? 0;
287
+ const red = this.data[i + 3] ?? 0;
288
+ this.data[i] = red;
289
+ this.data[i + 1] = green;
290
+ this.data[i + 2] = blue;
291
+ this.data[i + 3] = alpha;
292
+ }
293
+ }
294
+ getPaletteColor(index) {
295
+ const color = this.palette?.[index];
296
+ if (color) {
297
+ return color;
298
+ }
299
+ return { red: 255, green: 255, blue: 255, quad: 0 };
300
+ }
301
+ setPixel(destY, x, alpha, blue, green, red) {
302
+ const base = (destY * this.width + x) * 4;
303
+ this.data[base] = alpha;
304
+ this.data[base + 1] = blue;
305
+ this.data[base + 2] = green;
306
+ this.data[base + 3] = red;
307
+ }
308
+ bit1() {
309
+ const stride = rowStride(this.width, 1);
310
+ const bytesPerRow = Math.ceil(this.width / 8);
311
+ for (let srcRow = 0; srcRow < this.height; srcRow += 1) {
312
+ const rowStart = this.offset + srcRow * stride;
313
+ this.ensureReadable(rowStart, bytesPerRow, "1-bit row");
314
+ const destY = this.bottomUp ? this.height - 1 - srcRow : srcRow;
315
+ for (let x = 0; x < this.width; x += 1) {
316
+ const packed = this.readUInt8(rowStart + Math.floor(x / 8));
317
+ const bit = packed >> 7 - x % 8 & 1;
318
+ const rgb = this.getPaletteColor(bit);
319
+ this.setPixel(destY, x, 255, rgb.blue, rgb.green, rgb.red);
320
+ }
321
+ }
322
+ }
323
+ bit4() {
324
+ if (this.compress === 2) {
325
+ this.bit4Rle();
326
+ return;
327
+ }
328
+ const stride = rowStride(this.width, 4);
329
+ const bytesPerRow = Math.ceil(this.width / 2);
330
+ for (let srcRow = 0; srcRow < this.height; srcRow += 1) {
331
+ const rowStart = this.offset + srcRow * stride;
332
+ this.ensureReadable(rowStart, bytesPerRow, "4-bit row");
333
+ const destY = this.bottomUp ? this.height - 1 - srcRow : srcRow;
334
+ for (let x = 0; x < this.width; x += 1) {
335
+ const packed = this.readUInt8(rowStart + Math.floor(x / 2));
336
+ const idx = x % 2 === 0 ? (packed & 240) >> 4 : packed & 15;
337
+ const rgb = this.getPaletteColor(idx);
338
+ this.setPixel(destY, x, 255, rgb.blue, rgb.green, rgb.red);
339
+ }
340
+ }
341
+ }
342
+ bit8() {
343
+ if (this.compress === 1) {
344
+ this.bit8Rle();
345
+ return;
346
+ }
347
+ const stride = rowStride(this.width, 8);
348
+ const bytesPerRow = this.width;
349
+ for (let srcRow = 0; srcRow < this.height; srcRow += 1) {
350
+ const rowStart = this.offset + srcRow * stride;
351
+ this.ensureReadable(rowStart, bytesPerRow, "8-bit row");
352
+ const destY = this.bottomUp ? this.height - 1 - srcRow : srcRow;
353
+ for (let x = 0; x < this.width; x += 1) {
354
+ const idx = this.readUInt8(rowStart + x);
355
+ const rgb = this.getPaletteColor(idx);
356
+ this.setPixel(destY, x, 255, rgb.blue, rgb.green, rgb.red);
357
+ }
358
+ }
359
+ }
360
+ bit15() {
361
+ const stride = rowStride(this.width, 16);
362
+ const max = 31;
363
+ for (let srcRow = 0; srcRow < this.height; srcRow += 1) {
364
+ const rowStart = this.offset + srcRow * stride;
365
+ this.ensureReadable(rowStart, this.width * 2, "15-bit row");
366
+ const destY = this.bottomUp ? this.height - 1 - srcRow : srcRow;
367
+ for (let x = 0; x < this.width; x += 1) {
368
+ const value = this.readUInt16LE(rowStart + x * 2);
369
+ const blue = (value >> 0 & max) / max * 255;
370
+ const green = (value >> 5 & max) / max * 255;
371
+ const red = (value >> 10 & max) / max * 255;
372
+ const alpha = (value & 32768) !== 0 ? 255 : 0;
373
+ this.setPixel(destY, x, alpha, blue | 0, green | 0, red | 0);
374
+ }
375
+ }
376
+ }
377
+ scaleMasked(value, mask) {
378
+ if (mask === 0) return 0;
379
+ let shift = 0;
380
+ let bits = 0;
381
+ let m = mask;
382
+ while ((m & 1) === 0) {
383
+ shift += 1;
384
+ m >>>= 1;
385
+ }
386
+ while ((m & 1) === 1) {
387
+ bits += 1;
388
+ m >>>= 1;
389
+ }
390
+ const component = (value & mask) >>> shift;
391
+ if (bits >= 8) {
392
+ return component >>> bits - 8;
393
+ }
394
+ return component << 8 - bits & 255;
395
+ }
396
+ bit16() {
397
+ if (this.maskRed === 0 && this.maskGreen === 0 && this.maskBlue === 0) {
398
+ this.maskRed = 31744;
399
+ this.maskGreen = 992;
400
+ this.maskBlue = 31;
401
+ }
402
+ const stride = rowStride(this.width, 16);
403
+ for (let srcRow = 0; srcRow < this.height; srcRow += 1) {
404
+ const rowStart = this.offset + srcRow * stride;
405
+ this.ensureReadable(rowStart, this.width * 2, "16-bit row");
406
+ const destY = this.bottomUp ? this.height - 1 - srcRow : srcRow;
407
+ for (let x = 0; x < this.width; x += 1) {
408
+ const value = this.readUInt16LE(rowStart + x * 2);
409
+ const blue = this.scaleMasked(value, this.maskBlue);
410
+ const green = this.scaleMasked(value, this.maskGreen);
411
+ const red = this.scaleMasked(value, this.maskRed);
412
+ const alpha = this.maskAlpha !== 0 ? this.scaleMasked(value, this.maskAlpha) : 255;
413
+ this.setPixel(destY, x, alpha, blue, green, red);
414
+ }
415
+ }
416
+ }
417
+ bit24() {
418
+ const stride = rowStride(this.width, 24);
419
+ for (let srcRow = 0; srcRow < this.height; srcRow += 1) {
420
+ const rowStart = this.offset + srcRow * stride;
421
+ this.ensureReadable(rowStart, this.width * 3, "24-bit row");
422
+ const destY = this.bottomUp ? this.height - 1 - srcRow : srcRow;
423
+ for (let x = 0; x < this.width; x += 1) {
424
+ const base = rowStart + x * 3;
425
+ const blue = this.readUInt8(base);
426
+ const green = this.readUInt8(base + 1);
427
+ const red = this.readUInt8(base + 2);
428
+ this.setPixel(destY, x, 255, blue, green, red);
429
+ }
430
+ }
431
+ }
432
+ bit32() {
433
+ const stride = rowStride(this.width, 32);
434
+ for (let srcRow = 0; srcRow < this.height; srcRow += 1) {
435
+ const rowStart = this.offset + srcRow * stride;
436
+ this.ensureReadable(rowStart, this.width * 4, "32-bit row");
437
+ const destY = this.bottomUp ? this.height - 1 - srcRow : srcRow;
438
+ for (let x = 0; x < this.width; x += 1) {
439
+ const base = rowStart + x * 4;
440
+ if (this.compress === 3 || this.compress === 6) {
441
+ const pixel = this.readUInt32LE(base);
442
+ const red = this.scaleMasked(pixel, this.maskRed || 16711680);
443
+ const green = this.scaleMasked(pixel, this.maskGreen || 65280);
444
+ const blue = this.scaleMasked(pixel, this.maskBlue || 255);
445
+ const alpha = this.maskAlpha === 0 ? 255 : this.scaleMasked(pixel, this.maskAlpha);
446
+ this.setPixel(destY, x, alpha, blue, green, red);
447
+ } else {
448
+ const blue = this.readUInt8(base);
449
+ const green = this.readUInt8(base + 1);
450
+ const red = this.readUInt8(base + 2);
451
+ const alpha = this.readUInt8(base + 3);
452
+ this.setPixel(destY, x, alpha, blue, green, red);
453
+ }
454
+ }
455
+ }
456
+ }
457
+ bit8Rle() {
458
+ this.data.fill(255);
459
+ this.pos = this.offset;
460
+ let x = 0;
461
+ let y = this.bottomUp ? this.height - 1 : 0;
462
+ while (this.pos < this.bytes.length) {
463
+ const count = this.readUInt8();
464
+ const value = this.readUInt8();
465
+ if (count === 0) {
466
+ if (value === 0) {
467
+ x = 0;
468
+ y += this.bottomUp ? -1 : 1;
469
+ continue;
470
+ }
471
+ if (value === 1) {
472
+ break;
473
+ }
474
+ if (value === 2) {
475
+ x += this.readUInt8();
476
+ y += this.bottomUp ? -this.readUInt8() : this.readUInt8();
477
+ continue;
478
+ }
479
+ for (let i = 0; i < value; i += 1) {
480
+ const idx = this.readUInt8();
481
+ const rgb2 = this.getPaletteColor(idx);
482
+ if (x < this.width && y >= 0 && y < this.height) {
483
+ this.setPixel(y, x, 255, rgb2.blue, rgb2.green, rgb2.red);
484
+ }
485
+ x += 1;
486
+ }
487
+ if ((value & 1) === 1) {
488
+ this.pos += 1;
489
+ }
490
+ continue;
491
+ }
492
+ const rgb = this.getPaletteColor(value);
493
+ for (let i = 0; i < count; i += 1) {
494
+ if (x < this.width && y >= 0 && y < this.height) {
495
+ this.setPixel(y, x, 255, rgb.blue, rgb.green, rgb.red);
496
+ }
497
+ x += 1;
498
+ }
499
+ }
500
+ }
501
+ bit4Rle() {
502
+ this.data.fill(255);
503
+ this.pos = this.offset;
504
+ let x = 0;
505
+ let y = this.bottomUp ? this.height - 1 : 0;
506
+ while (this.pos < this.bytes.length) {
507
+ const count = this.readUInt8();
508
+ const value = this.readUInt8();
509
+ if (count === 0) {
510
+ if (value === 0) {
511
+ x = 0;
512
+ y += this.bottomUp ? -1 : 1;
513
+ continue;
514
+ }
515
+ if (value === 1) {
516
+ break;
517
+ }
518
+ if (value === 2) {
519
+ x += this.readUInt8();
520
+ y += this.bottomUp ? -this.readUInt8() : this.readUInt8();
521
+ continue;
522
+ }
523
+ let current = this.readUInt8();
524
+ for (let i = 0; i < value; i += 1) {
525
+ const nibble = i % 2 === 0 ? (current & 240) >> 4 : current & 15;
526
+ const rgb = this.getPaletteColor(nibble);
527
+ if (x < this.width && y >= 0 && y < this.height) {
528
+ this.setPixel(y, x, 255, rgb.blue, rgb.green, rgb.red);
529
+ }
530
+ x += 1;
531
+ if (i % 2 === 1 && i + 1 < value) {
532
+ current = this.readUInt8();
533
+ }
534
+ }
535
+ if ((value + 1 >> 1 & 1) === 1) {
536
+ this.pos += 1;
537
+ }
538
+ continue;
539
+ }
540
+ for (let i = 0; i < count; i += 1) {
541
+ const nibble = i % 2 === 0 ? (value & 240) >> 4 : value & 15;
542
+ const rgb = this.getPaletteColor(nibble);
543
+ if (x < this.width && y >= 0 && y < this.height) {
544
+ this.setPixel(y, x, 255, rgb.blue, rgb.green, rgb.red);
545
+ }
546
+ x += 1;
547
+ }
548
+ }
549
+ }
550
+ getData() {
551
+ return this.data;
552
+ }
553
+ };
554
+ function decode(bmpData, options) {
555
+ return new BmpDecoder(bmpData, options);
556
+ }
557
+
558
+ // src/encoder.ts
559
+ var FILE_HEADER_SIZE2 = 14;
560
+ var INFO_HEADER_SIZE = 40;
561
+ var BYTES_PER_PIXEL_ABGR = 4;
562
+ var SUPPORTED_BIT_DEPTHS = [1, 4, 8, 16, 24, 32];
563
+ function isSupportedBitDepth(value) {
564
+ return SUPPORTED_BIT_DEPTHS.includes(value);
565
+ }
566
+ function normalizeEncodeOptions(qualityOrOptions) {
567
+ if (typeof qualityOrOptions === "number" || typeof qualityOrOptions === "undefined") {
568
+ return {
569
+ orientation: "top-down",
570
+ bitPP: 24,
571
+ palette: []
572
+ };
573
+ }
574
+ return {
575
+ orientation: qualityOrOptions.orientation ?? "top-down",
576
+ bitPP: qualityOrOptions.bitPP ?? 24,
577
+ palette: qualityOrOptions.palette ?? []
578
+ };
579
+ }
580
+ var BmpEncoder = class {
581
+ pixelData;
582
+ width;
583
+ height;
584
+ options;
585
+ palette;
586
+ exactPaletteIndex = /* @__PURE__ */ new Map();
587
+ constructor(imgData, options) {
588
+ this.pixelData = imgData.data;
589
+ this.width = imgData.width;
590
+ this.height = imgData.height;
591
+ this.options = options;
592
+ this.palette = this.normalizePalette(options);
593
+ assertInteger("width", this.width);
594
+ assertInteger("height", this.height);
595
+ if (!isSupportedBitDepth(this.options.bitPP)) {
596
+ throw new Error(
597
+ `Unsupported encode bit depth: ${this.options.bitPP}. Supported: 1, 4, 8, 16, 24, 32.`
598
+ );
599
+ }
600
+ const minLength = this.width * this.height * BYTES_PER_PIXEL_ABGR;
601
+ if (this.pixelData.length < minLength) {
602
+ throw new Error(
603
+ `Image data is too short: expected at least ${minLength} bytes for ${this.width}x${this.height} ABGR data.`
604
+ );
605
+ }
606
+ for (let i = 0; i < this.palette.length; i += 1) {
607
+ const color = this.palette[i];
608
+ const key = this.paletteKey(color.quad, color.blue, color.green, color.red);
609
+ if (!this.exactPaletteIndex.has(key)) {
610
+ this.exactPaletteIndex.set(key, i);
611
+ }
612
+ }
613
+ }
614
+ normalizePalette(options) {
615
+ if (options.bitPP === 1) {
616
+ const palette = options.palette.length ? options.palette : [
617
+ { red: 255, green: 255, blue: 255, quad: 0 },
618
+ { red: 0, green: 0, blue: 0, quad: 0 }
619
+ ];
620
+ this.validatePalette(options.bitPP, palette);
621
+ return palette;
622
+ }
623
+ if (options.bitPP === 4 || options.bitPP === 8) {
624
+ if (options.palette.length === 0) {
625
+ throw new Error(`Encoding ${options.bitPP}-bit BMP requires a non-empty palette.`);
626
+ }
627
+ this.validatePalette(options.bitPP, options.palette);
628
+ return options.palette;
629
+ }
630
+ return [];
631
+ }
632
+ validatePalette(bitPP, palette) {
633
+ const maxSize = 1 << bitPP;
634
+ if (palette.length === 0 || palette.length > maxSize) {
635
+ throw new Error(
636
+ `Palette size ${palette.length} is invalid for ${bitPP}-bit BMP. Expected 1..${maxSize}.`
637
+ );
638
+ }
639
+ for (const color of palette) {
640
+ this.validateChannel("palette.red", color.red);
641
+ this.validateChannel("palette.green", color.green);
642
+ this.validateChannel("palette.blue", color.blue);
643
+ this.validateChannel("palette.quad", color.quad);
644
+ }
645
+ }
646
+ validateChannel(name, value) {
647
+ if (!Number.isInteger(value) || value < 0 || value > 255) {
648
+ throw new Error(`${name} must be an integer between 0 and 255.`);
649
+ }
650
+ }
651
+ rowStride() {
652
+ return Math.floor((this.options.bitPP * this.width + 31) / 32) * 4;
653
+ }
654
+ sourceY(fileRow) {
655
+ return this.options.orientation === "top-down" ? fileRow : this.height - 1 - fileRow;
656
+ }
657
+ sourceOffset(x, y) {
658
+ return (y * this.width + x) * BYTES_PER_PIXEL_ABGR;
659
+ }
660
+ paletteKey(alpha, blue, green, red) {
661
+ return ((alpha & 255) << 24 | (blue & 255) << 16 | (green & 255) << 8 | red & 255) >>> 0;
662
+ }
663
+ findPaletteIndex(a, b, g, r) {
664
+ const exact = this.exactPaletteIndex.get(this.paletteKey(a, b, g, r));
665
+ if (exact !== void 0) {
666
+ return exact;
667
+ }
668
+ let bestIndex = 0;
669
+ let bestDistance = Number.POSITIVE_INFINITY;
670
+ for (let i = 0; i < this.palette.length; i += 1) {
671
+ const color = this.palette[i];
672
+ const dr = color.red - r;
673
+ const dg = color.green - g;
674
+ const db = color.blue - b;
675
+ const da = color.quad - a;
676
+ const distance = dr * dr + dg * dg + db * db + da * da;
677
+ if (distance < bestDistance) {
678
+ bestDistance = distance;
679
+ bestIndex = i;
680
+ }
681
+ }
682
+ return bestIndex;
683
+ }
684
+ writePalette(output, paletteOffset) {
685
+ for (let i = 0; i < this.palette.length; i += 1) {
686
+ const color = this.palette[i];
687
+ const base = paletteOffset + i * 4;
688
+ output[base] = color.blue;
689
+ output[base + 1] = color.green;
690
+ output[base + 2] = color.red;
691
+ output[base + 3] = color.quad;
692
+ }
693
+ }
694
+ encode1Bit(output, pixelOffset, stride) {
695
+ for (let fileRow = 0; fileRow < this.height; fileRow += 1) {
696
+ const srcY = this.sourceY(fileRow);
697
+ const rowStart = pixelOffset + fileRow * stride;
698
+ for (let x = 0; x < this.width; x += 8) {
699
+ let packed = 0;
700
+ for (let bit = 0; bit < 8; bit += 1) {
701
+ const px = x + bit;
702
+ if (px >= this.width) {
703
+ break;
704
+ }
705
+ const source = this.sourceOffset(px, srcY);
706
+ const a = this.pixelData[source] ?? 255;
707
+ const b = this.pixelData[source + 1] ?? 0;
708
+ const g = this.pixelData[source + 2] ?? 0;
709
+ const r = this.pixelData[source + 3] ?? 0;
710
+ const idx = this.findPaletteIndex(a, b, g, r) & 1;
711
+ packed |= idx << 7 - bit;
712
+ }
713
+ output[rowStart + Math.floor(x / 8)] = packed;
714
+ }
715
+ }
716
+ }
717
+ encode4Bit(output, pixelOffset, stride) {
718
+ for (let fileRow = 0; fileRow < this.height; fileRow += 1) {
719
+ const srcY = this.sourceY(fileRow);
720
+ const rowStart = pixelOffset + fileRow * stride;
721
+ for (let x = 0; x < this.width; x += 2) {
722
+ const sourceA = this.sourceOffset(x, srcY);
723
+ const idxA = this.findPaletteIndex(
724
+ this.pixelData[sourceA] ?? 255,
725
+ this.pixelData[sourceA + 1] ?? 0,
726
+ this.pixelData[sourceA + 2] ?? 0,
727
+ this.pixelData[sourceA + 3] ?? 0
728
+ );
729
+ let idxB = 0;
730
+ if (x + 1 < this.width) {
731
+ const sourceB = this.sourceOffset(x + 1, srcY);
732
+ idxB = this.findPaletteIndex(
733
+ this.pixelData[sourceB] ?? 255,
734
+ this.pixelData[sourceB + 1] ?? 0,
735
+ this.pixelData[sourceB + 2] ?? 0,
736
+ this.pixelData[sourceB + 3] ?? 0
737
+ );
738
+ }
739
+ output[rowStart + Math.floor(x / 2)] = (idxA & 15) << 4 | idxB & 15;
740
+ }
741
+ }
742
+ }
743
+ encode8Bit(output, pixelOffset, stride) {
744
+ for (let fileRow = 0; fileRow < this.height; fileRow += 1) {
745
+ const srcY = this.sourceY(fileRow);
746
+ const rowStart = pixelOffset + fileRow * stride;
747
+ for (let x = 0; x < this.width; x += 1) {
748
+ const source = this.sourceOffset(x, srcY);
749
+ output[rowStart + x] = this.findPaletteIndex(
750
+ this.pixelData[source] ?? 255,
751
+ this.pixelData[source + 1] ?? 0,
752
+ this.pixelData[source + 2] ?? 0,
753
+ this.pixelData[source + 3] ?? 0
754
+ );
755
+ }
756
+ }
757
+ }
758
+ encode16Bit(output, view, pixelOffset, stride) {
759
+ for (let fileRow = 0; fileRow < this.height; fileRow += 1) {
760
+ const srcY = this.sourceY(fileRow);
761
+ const rowStart = pixelOffset + fileRow * stride;
762
+ for (let x = 0; x < this.width; x += 1) {
763
+ const source = this.sourceOffset(x, srcY);
764
+ const b = this.pixelData[source + 1] ?? 0;
765
+ const g = this.pixelData[source + 2] ?? 0;
766
+ const r = this.pixelData[source + 3] ?? 0;
767
+ const value = (r >> 3 & 31) << 10 | (g >> 3 & 31) << 5 | b >> 3 & 31;
768
+ view.setUint16(rowStart + x * 2, value, true);
769
+ }
770
+ }
771
+ }
772
+ encode24Bit(output, pixelOffset, stride) {
773
+ for (let fileRow = 0; fileRow < this.height; fileRow += 1) {
774
+ const srcY = this.sourceY(fileRow);
775
+ const rowStart = pixelOffset + fileRow * stride;
776
+ for (let x = 0; x < this.width; x += 1) {
777
+ const source = this.sourceOffset(x, srcY);
778
+ const target = rowStart + x * 3;
779
+ output[target] = this.pixelData[source + 1] ?? 0;
780
+ output[target + 1] = this.pixelData[source + 2] ?? 0;
781
+ output[target + 2] = this.pixelData[source + 3] ?? 0;
782
+ }
783
+ }
784
+ }
785
+ encode32Bit(output, pixelOffset, stride) {
786
+ for (let fileRow = 0; fileRow < this.height; fileRow += 1) {
787
+ const srcY = this.sourceY(fileRow);
788
+ const rowStart = pixelOffset + fileRow * stride;
789
+ for (let x = 0; x < this.width; x += 1) {
790
+ const source = this.sourceOffset(x, srcY);
791
+ const target = rowStart + x * 4;
792
+ output[target] = this.pixelData[source + 1] ?? 0;
793
+ output[target + 1] = this.pixelData[source + 2] ?? 0;
794
+ output[target + 2] = this.pixelData[source + 3] ?? 0;
795
+ output[target + 3] = this.pixelData[source] ?? 255;
796
+ }
797
+ }
798
+ }
799
+ encode() {
800
+ const stride = this.rowStride();
801
+ const imageSize = stride * this.height;
802
+ const paletteSize = this.palette.length * 4;
803
+ const offset = FILE_HEADER_SIZE2 + INFO_HEADER_SIZE + paletteSize;
804
+ const totalSize = offset + imageSize;
805
+ const output = new Uint8Array(totalSize);
806
+ const view = new DataView(output.buffer, output.byteOffset, output.byteLength);
807
+ output[0] = 66;
808
+ output[1] = 77;
809
+ view.setUint32(2, totalSize, true);
810
+ view.setUint32(6, 0, true);
811
+ view.setUint32(10, offset, true);
812
+ view.setUint32(14, INFO_HEADER_SIZE, true);
813
+ view.setInt32(18, this.width, true);
814
+ const signedHeight = this.options.orientation === "top-down" ? -this.height : this.height;
815
+ view.setInt32(22, signedHeight, true);
816
+ view.setUint16(26, 1, true);
817
+ view.setUint16(28, this.options.bitPP, true);
818
+ view.setUint32(30, 0, true);
819
+ view.setUint32(34, imageSize, true);
820
+ view.setUint32(38, 0, true);
821
+ view.setUint32(42, 0, true);
822
+ view.setUint32(46, this.palette.length, true);
823
+ view.setUint32(50, 0, true);
824
+ if (this.palette.length > 0) {
825
+ this.writePalette(output, FILE_HEADER_SIZE2 + INFO_HEADER_SIZE);
826
+ }
827
+ switch (this.options.bitPP) {
828
+ case 1:
829
+ this.encode1Bit(output, offset, stride);
830
+ break;
831
+ case 4:
832
+ this.encode4Bit(output, offset, stride);
833
+ break;
834
+ case 8:
835
+ this.encode8Bit(output, offset, stride);
836
+ break;
837
+ case 16:
838
+ this.encode16Bit(output, view, offset, stride);
839
+ break;
840
+ case 24:
841
+ this.encode24Bit(output, offset, stride);
842
+ break;
843
+ case 32:
844
+ this.encode32Bit(output, offset, stride);
845
+ break;
846
+ }
847
+ return output;
848
+ }
849
+ };
850
+ function encode(imgData, qualityOrOptions) {
851
+ const options = normalizeEncodeOptions(qualityOrOptions);
852
+ const encoder = new BmpEncoder(imgData, options);
853
+ const data = encoder.encode();
854
+ return {
855
+ data,
856
+ width: imgData.width,
857
+ height: imgData.height
858
+ };
859
+ }
860
+
861
+ // src/sharp/errors.ts
862
+ var SharpAdapterError = class extends Error {
863
+ constructor(message) {
864
+ super(message);
865
+ this.name = "SharpAdapterError";
866
+ }
867
+ };
868
+ var NotBmpInputError = class extends SharpAdapterError {
869
+ constructor() {
870
+ super("Input is not a BMP file.");
871
+ this.name = "NotBmpInputError";
872
+ }
873
+ };
874
+ var SharpModuleLoadError = class extends SharpAdapterError {
875
+ constructor(message = "Unable to load optional peer dependency 'sharp'. Install it or pass a module instance.") {
876
+ super(message);
877
+ this.name = "SharpModuleLoadError";
878
+ }
879
+ };
880
+ var InvalidSharpRawInputError = class extends SharpAdapterError {
881
+ constructor(message) {
882
+ super(message);
883
+ this.name = "InvalidSharpRawInputError";
884
+ }
885
+ };
886
+
887
+ // src/sharp/index.ts
888
+ var require2 = (0, import_node_module.createRequire)(
889
+ typeof __filename === "string" ? __filename : `${process.cwd()}/package.json`
890
+ );
891
+ function toUint8Array2(input) {
892
+ if (input instanceof Uint8Array) {
893
+ return input;
894
+ }
895
+ return new Uint8Array(input);
896
+ }
897
+ function toAbgrData(input, channels) {
898
+ const pixelCount = Math.floor(input.length / channels);
899
+ const output = new Uint8Array(pixelCount * 4);
900
+ for (let src = 0, dst = 0; src < input.length; src += channels, dst += 4) {
901
+ const red = input[src] ?? 0;
902
+ const green = input[src + 1] ?? 0;
903
+ const blue = input[src + 2] ?? 0;
904
+ const alpha = channels === 4 ? input[src + 3] ?? 255 : 255;
905
+ output[dst] = alpha;
906
+ output[dst + 1] = blue;
907
+ output[dst + 2] = green;
908
+ output[dst + 3] = red;
909
+ }
910
+ return output;
911
+ }
912
+ function loadSharpModule(sharpModule) {
913
+ if (sharpModule) {
914
+ return sharpModule;
915
+ }
916
+ try {
917
+ const loaded = require2("sharp");
918
+ if (typeof loaded === "function") {
919
+ return loaded;
920
+ }
921
+ if (loaded.default && typeof loaded.default === "function") {
922
+ return loaded.default;
923
+ }
924
+ throw new SharpModuleLoadError("Loaded 'sharp' module has an unexpected shape.");
925
+ } catch (error) {
926
+ if (error instanceof SharpModuleLoadError) {
927
+ throw error;
928
+ }
929
+ throw new SharpModuleLoadError();
930
+ }
931
+ }
932
+ function assertPositiveInteger(name, value) {
933
+ if (!Number.isInteger(value) || value <= 0) {
934
+ throw new InvalidSharpRawInputError(`${name} must be a positive integer.`);
935
+ }
936
+ }
937
+ function normalizeBitDepth(channels, bitDepth) {
938
+ if (bitDepth !== void 0) {
939
+ return bitDepth;
940
+ }
941
+ return channels === 4 ? 32 : 24;
942
+ }
943
+ function isBmp(input) {
944
+ try {
945
+ const bytes = toUint8Array2(input);
946
+ return bytes.length >= 2 && bytes[0] === 66 && bytes[1] === 77;
947
+ } catch {
948
+ return false;
949
+ }
950
+ }
951
+ function decodeForSharp(input) {
952
+ const bytes = toUint8Array2(input);
953
+ if (!isBmp(bytes)) {
954
+ throw new NotBmpInputError();
955
+ }
956
+ const decoded = decode(bytes, { toRGBA: true });
957
+ const raw = {
958
+ width: decoded.width,
959
+ height: decoded.height,
960
+ channels: 4
961
+ };
962
+ return {
963
+ data: decoded.data,
964
+ raw,
965
+ width: decoded.width,
966
+ height: decoded.height,
967
+ channels: 4
968
+ };
969
+ }
970
+ function toSharpInput(input) {
971
+ return decodeForSharp(input);
972
+ }
973
+ function sharpFromBmp(input, sharpModule) {
974
+ const decoded = decodeForSharp(input);
975
+ const sharp = loadSharpModule(sharpModule);
976
+ return sharp(decoded.data, { raw: decoded.raw });
977
+ }
978
+ function encodeFromSharp(input, options = {}) {
979
+ const data = toUint8Array2(input.data);
980
+ const width = input.info.width;
981
+ const height = input.info.height;
982
+ const channels = input.info.channels;
983
+ assertPositiveInteger("info.width", width);
984
+ assertPositiveInteger("info.height", height);
985
+ if (channels !== 3 && channels !== 4) {
986
+ throw new InvalidSharpRawInputError(
987
+ `Unsupported channel count: ${channels}. Expected channels to be 3 or 4.`
988
+ );
989
+ }
990
+ const expectedLength = width * height * channels;
991
+ if (data.length !== expectedLength) {
992
+ throw new InvalidSharpRawInputError(
993
+ `Raw buffer length mismatch: expected ${expectedLength}, received ${data.length}.`
994
+ );
995
+ }
996
+ const bitDepth = normalizeBitDepth(channels, options.bitDepth);
997
+ const encodeOptions = {
998
+ bitPP: bitDepth,
999
+ orientation: options.topDown ? "top-down" : "bottom-up"
1000
+ };
1001
+ if (options.palette) {
1002
+ encodeOptions.palette = options.palette;
1003
+ }
1004
+ return encode(
1005
+ {
1006
+ data: toAbgrData(data, channels),
1007
+ width,
1008
+ height
1009
+ },
1010
+ encodeOptions
1011
+ ).data;
1012
+ }
1013
+ // Annotate the CommonJS export names for ESM import in node:
1014
+ 0 && (module.exports = {
1015
+ InvalidSharpRawInputError,
1016
+ NotBmpInputError,
1017
+ SharpAdapterError,
1018
+ SharpModuleLoadError,
1019
+ decodeForSharp,
1020
+ encodeFromSharp,
1021
+ isBmp,
1022
+ sharpFromBmp,
1023
+ toSharpInput
1024
+ });
1025
+ //# sourceMappingURL=index.cjs.map