@huh-david/bmp-js 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -3
- package/dist/index.cjs +259 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -2
- package/dist/index.d.ts +26 -2
- package/dist/index.js +259 -33
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,12 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
A pure TypeScript BMP encoder/decoder for Node.js.
|
|
4
4
|
|
|
5
|
+
## Maintenance
|
|
6
|
+
|
|
7
|
+
This fork is actively maintained and tracks unresolved upstream `shaozilee/bmp-js` issues and PRs.
|
|
8
|
+
|
|
9
|
+
- Repository: https://github.com/Huh-David/bmp-js
|
|
10
|
+
- Latest release: https://github.com/Huh-David/bmp-js/releases/tag/v0.4.0
|
|
11
|
+
|
|
5
12
|
## Features
|
|
6
13
|
|
|
7
14
|
- Decoding for BMP bit depths: 1, 4, 8, 15, 16, 24, 32
|
|
8
15
|
- Decoding support for RLE-4 and RLE-8 compressed BMPs
|
|
9
16
|
- Robust DIB handling for CORE/INFO/V4/V5 headers
|
|
10
|
-
- Encoding output
|
|
17
|
+
- Encoding output bit depths: 1, 4, 8, 16, 24, 32
|
|
11
18
|
- Dual package output: ESM + CommonJS
|
|
12
19
|
- First-class TypeScript types
|
|
13
20
|
|
|
@@ -50,7 +57,9 @@ const encoded = encode(
|
|
|
50
57
|
},
|
|
51
58
|
{
|
|
52
59
|
orientation: "bottom-up", // default: "top-down"
|
|
53
|
-
bitPP:
|
|
60
|
+
bitPP: 32, // supported: 1, 4, 8, 16, 24, 32
|
|
61
|
+
// palette is required for 4/8-bit and optional for 1-bit
|
|
62
|
+
// palette: [{ red: 0, green: 0, blue: 0, quad: 0 }, ...],
|
|
54
63
|
},
|
|
55
64
|
);
|
|
56
65
|
```
|
|
@@ -67,9 +76,20 @@ const encoded = bmp.encode(decoded);
|
|
|
67
76
|
fs.writeFileSync("./roundtrip.bmp", encoded.data);
|
|
68
77
|
```
|
|
69
78
|
|
|
79
|
+
### Decode options
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { decode } from "@huh-david/bmp-js";
|
|
83
|
+
|
|
84
|
+
const decoded = decode(inputBytes, {
|
|
85
|
+
toRGBA: true, // return RGBA instead of default ABGR
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
70
89
|
## Data layout
|
|
71
90
|
|
|
72
|
-
Decoded pixel data is a byte buffer in `ABGR` order.
|
|
91
|
+
Decoded pixel data is a byte buffer in `ABGR` order by default.
|
|
92
|
+
If `toRGBA: true` is provided to `decode`, output is returned in `RGBA`.
|
|
73
93
|
|
|
74
94
|
- `A`: alpha
|
|
75
95
|
- `B`: blue
|
package/dist/index.cjs
CHANGED
|
@@ -81,13 +81,15 @@ var BmpDecoder = class {
|
|
|
81
81
|
this.bytes = toUint8Array(input);
|
|
82
82
|
this.view = new DataView(this.bytes.buffer, this.bytes.byteOffset, this.bytes.byteLength);
|
|
83
83
|
this.options = {
|
|
84
|
-
treat16BitAs15BitAlpha: options.treat16BitAs15BitAlpha ?? false
|
|
84
|
+
treat16BitAs15BitAlpha: options.treat16BitAs15BitAlpha ?? false,
|
|
85
|
+
toRGBA: options.toRGBA ?? false
|
|
85
86
|
};
|
|
86
87
|
this.parseFileHeader();
|
|
87
88
|
this.parseDibHeader();
|
|
88
89
|
this.parsePalette();
|
|
89
90
|
this.pos = this.offset;
|
|
90
91
|
this.parseRGBA();
|
|
92
|
+
this.transformToRgbaIfNeeded();
|
|
91
93
|
}
|
|
92
94
|
ensureReadable(offset, size, context) {
|
|
93
95
|
if (offset < 0 || size < 0 || offset + size > this.bytes.length) {
|
|
@@ -269,6 +271,21 @@ var BmpDecoder = class {
|
|
|
269
271
|
throw new Error(`Unsupported BMP bit depth: ${this.bitPP}`);
|
|
270
272
|
}
|
|
271
273
|
}
|
|
274
|
+
transformToRgbaIfNeeded() {
|
|
275
|
+
if (!this.options.toRGBA) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
for (let i = 0; i < this.data.length; i += 4) {
|
|
279
|
+
const alpha = this.data[i] ?? 0;
|
|
280
|
+
const blue = this.data[i + 1] ?? 0;
|
|
281
|
+
const green = this.data[i + 2] ?? 0;
|
|
282
|
+
const red = this.data[i + 3] ?? 0;
|
|
283
|
+
this.data[i] = red;
|
|
284
|
+
this.data[i + 1] = green;
|
|
285
|
+
this.data[i + 2] = blue;
|
|
286
|
+
this.data[i + 3] = alpha;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
272
289
|
getPaletteColor(index) {
|
|
273
290
|
const color = this.palette?.[index];
|
|
274
291
|
if (color) {
|
|
@@ -294,7 +311,7 @@ var BmpDecoder = class {
|
|
|
294
311
|
const packed = this.readUInt8(rowStart + Math.floor(x / 8));
|
|
295
312
|
const bit = packed >> 7 - x % 8 & 1;
|
|
296
313
|
const rgb = this.getPaletteColor(bit);
|
|
297
|
-
this.setPixel(destY, x,
|
|
314
|
+
this.setPixel(destY, x, 255, rgb.blue, rgb.green, rgb.red);
|
|
298
315
|
}
|
|
299
316
|
}
|
|
300
317
|
}
|
|
@@ -313,7 +330,7 @@ var BmpDecoder = class {
|
|
|
313
330
|
const packed = this.readUInt8(rowStart + Math.floor(x / 2));
|
|
314
331
|
const idx = x % 2 === 0 ? (packed & 240) >> 4 : packed & 15;
|
|
315
332
|
const rgb = this.getPaletteColor(idx);
|
|
316
|
-
this.setPixel(destY, x,
|
|
333
|
+
this.setPixel(destY, x, 255, rgb.blue, rgb.green, rgb.red);
|
|
317
334
|
}
|
|
318
335
|
}
|
|
319
336
|
}
|
|
@@ -331,7 +348,7 @@ var BmpDecoder = class {
|
|
|
331
348
|
for (let x = 0; x < this.width; x += 1) {
|
|
332
349
|
const idx = this.readUInt8(rowStart + x);
|
|
333
350
|
const rgb = this.getPaletteColor(idx);
|
|
334
|
-
this.setPixel(destY, x,
|
|
351
|
+
this.setPixel(destY, x, 255, rgb.blue, rgb.green, rgb.red);
|
|
335
352
|
}
|
|
336
353
|
}
|
|
337
354
|
}
|
|
@@ -387,7 +404,7 @@ var BmpDecoder = class {
|
|
|
387
404
|
const blue = this.scaleMasked(value, this.maskBlue);
|
|
388
405
|
const green = this.scaleMasked(value, this.maskGreen);
|
|
389
406
|
const red = this.scaleMasked(value, this.maskRed);
|
|
390
|
-
const alpha = this.maskAlpha !== 0 ? this.scaleMasked(value, this.maskAlpha) :
|
|
407
|
+
const alpha = this.maskAlpha !== 0 ? this.scaleMasked(value, this.maskAlpha) : 255;
|
|
391
408
|
this.setPixel(destY, x, alpha, blue, green, red);
|
|
392
409
|
}
|
|
393
410
|
}
|
|
@@ -403,7 +420,7 @@ var BmpDecoder = class {
|
|
|
403
420
|
const blue = this.readUInt8(base);
|
|
404
421
|
const green = this.readUInt8(base + 1);
|
|
405
422
|
const red = this.readUInt8(base + 2);
|
|
406
|
-
this.setPixel(destY, x,
|
|
423
|
+
this.setPixel(destY, x, 255, blue, green, red);
|
|
407
424
|
}
|
|
408
425
|
}
|
|
409
426
|
}
|
|
@@ -420,7 +437,7 @@ var BmpDecoder = class {
|
|
|
420
437
|
const red = this.scaleMasked(pixel, this.maskRed || 16711680);
|
|
421
438
|
const green = this.scaleMasked(pixel, this.maskGreen || 65280);
|
|
422
439
|
const blue = this.scaleMasked(pixel, this.maskBlue || 255);
|
|
423
|
-
const alpha = this.maskAlpha === 0 ?
|
|
440
|
+
const alpha = this.maskAlpha === 0 ? 255 : this.scaleMasked(pixel, this.maskAlpha);
|
|
424
441
|
this.setPixel(destY, x, alpha, blue, green, red);
|
|
425
442
|
} else {
|
|
426
443
|
const blue = this.readUInt8(base);
|
|
@@ -458,7 +475,7 @@ var BmpDecoder = class {
|
|
|
458
475
|
const idx = this.readUInt8();
|
|
459
476
|
const rgb2 = this.getPaletteColor(idx);
|
|
460
477
|
if (x < this.width && y >= 0 && y < this.height) {
|
|
461
|
-
this.setPixel(y, x,
|
|
478
|
+
this.setPixel(y, x, 255, rgb2.blue, rgb2.green, rgb2.red);
|
|
462
479
|
}
|
|
463
480
|
x += 1;
|
|
464
481
|
}
|
|
@@ -470,7 +487,7 @@ var BmpDecoder = class {
|
|
|
470
487
|
const rgb = this.getPaletteColor(value);
|
|
471
488
|
for (let i = 0; i < count; i += 1) {
|
|
472
489
|
if (x < this.width && y >= 0 && y < this.height) {
|
|
473
|
-
this.setPixel(y, x,
|
|
490
|
+
this.setPixel(y, x, 255, rgb.blue, rgb.green, rgb.red);
|
|
474
491
|
}
|
|
475
492
|
x += 1;
|
|
476
493
|
}
|
|
@@ -503,7 +520,7 @@ var BmpDecoder = class {
|
|
|
503
520
|
const nibble = i % 2 === 0 ? (current & 240) >> 4 : current & 15;
|
|
504
521
|
const rgb = this.getPaletteColor(nibble);
|
|
505
522
|
if (x < this.width && y >= 0 && y < this.height) {
|
|
506
|
-
this.setPixel(y, x,
|
|
523
|
+
this.setPixel(y, x, 255, rgb.blue, rgb.green, rgb.red);
|
|
507
524
|
}
|
|
508
525
|
x += 1;
|
|
509
526
|
if (i % 2 === 1 && i + 1 < value) {
|
|
@@ -519,7 +536,7 @@ var BmpDecoder = class {
|
|
|
519
536
|
const nibble = i % 2 === 0 ? (value & 240) >> 4 : value & 15;
|
|
520
537
|
const rgb = this.getPaletteColor(nibble);
|
|
521
538
|
if (x < this.width && y >= 0 && y < this.height) {
|
|
522
|
-
this.setPixel(y, x,
|
|
539
|
+
this.setPixel(y, x, 255, rgb.blue, rgb.green, rgb.red);
|
|
523
540
|
}
|
|
524
541
|
x += 1;
|
|
525
542
|
}
|
|
@@ -536,22 +553,23 @@ function decode(bmpData, options) {
|
|
|
536
553
|
// src/encoder.ts
|
|
537
554
|
var FILE_HEADER_SIZE2 = 14;
|
|
538
555
|
var INFO_HEADER_SIZE = 40;
|
|
539
|
-
var RGB_TRIPLE_SIZE = 3;
|
|
540
556
|
var BYTES_PER_PIXEL_ABGR = 4;
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
return
|
|
557
|
+
var SUPPORTED_BIT_DEPTHS = [1, 4, 8, 16, 24, 32];
|
|
558
|
+
function isSupportedBitDepth(value) {
|
|
559
|
+
return SUPPORTED_BIT_DEPTHS.includes(value);
|
|
544
560
|
}
|
|
545
561
|
function normalizeEncodeOptions(qualityOrOptions) {
|
|
546
562
|
if (typeof qualityOrOptions === "number" || typeof qualityOrOptions === "undefined") {
|
|
547
563
|
return {
|
|
548
564
|
orientation: "top-down",
|
|
549
|
-
bitPP: 24
|
|
565
|
+
bitPP: 24,
|
|
566
|
+
palette: []
|
|
550
567
|
};
|
|
551
568
|
}
|
|
552
569
|
return {
|
|
553
570
|
orientation: qualityOrOptions.orientation ?? "top-down",
|
|
554
|
-
bitPP: qualityOrOptions.bitPP ?? 24
|
|
571
|
+
bitPP: qualityOrOptions.bitPP ?? 24,
|
|
572
|
+
palette: qualityOrOptions.palette ?? []
|
|
555
573
|
};
|
|
556
574
|
}
|
|
557
575
|
var BmpEncoder = class {
|
|
@@ -559,16 +577,19 @@ var BmpEncoder = class {
|
|
|
559
577
|
width;
|
|
560
578
|
height;
|
|
561
579
|
options;
|
|
580
|
+
palette;
|
|
581
|
+
exactPaletteIndex = /* @__PURE__ */ new Map();
|
|
562
582
|
constructor(imgData, options) {
|
|
563
583
|
this.pixelData = imgData.data;
|
|
564
584
|
this.width = imgData.width;
|
|
565
585
|
this.height = imgData.height;
|
|
566
586
|
this.options = options;
|
|
587
|
+
this.palette = this.normalizePalette(options);
|
|
567
588
|
assertInteger("width", this.width);
|
|
568
589
|
assertInteger("height", this.height);
|
|
569
|
-
if (this.options.bitPP
|
|
590
|
+
if (!isSupportedBitDepth(this.options.bitPP)) {
|
|
570
591
|
throw new Error(
|
|
571
|
-
`Unsupported encode bit depth: ${this.options.bitPP}.
|
|
592
|
+
`Unsupported encode bit depth: ${this.options.bitPP}. Supported: 1, 4, 8, 16, 24, 32.`
|
|
572
593
|
);
|
|
573
594
|
}
|
|
574
595
|
const minLength = this.width * this.height * BYTES_PER_PIXEL_ABGR;
|
|
@@ -577,11 +598,204 @@ var BmpEncoder = class {
|
|
|
577
598
|
`Image data is too short: expected at least ${minLength} bytes for ${this.width}x${this.height} ABGR data.`
|
|
578
599
|
);
|
|
579
600
|
}
|
|
601
|
+
for (let i = 0; i < this.palette.length; i += 1) {
|
|
602
|
+
const color = this.palette[i];
|
|
603
|
+
const key = this.paletteKey(color.quad, color.blue, color.green, color.red);
|
|
604
|
+
if (!this.exactPaletteIndex.has(key)) {
|
|
605
|
+
this.exactPaletteIndex.set(key, i);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
normalizePalette(options) {
|
|
610
|
+
if (options.bitPP === 1) {
|
|
611
|
+
const palette = options.palette.length ? options.palette : [
|
|
612
|
+
{ red: 255, green: 255, blue: 255, quad: 0 },
|
|
613
|
+
{ red: 0, green: 0, blue: 0, quad: 0 }
|
|
614
|
+
];
|
|
615
|
+
this.validatePalette(options.bitPP, palette);
|
|
616
|
+
return palette;
|
|
617
|
+
}
|
|
618
|
+
if (options.bitPP === 4 || options.bitPP === 8) {
|
|
619
|
+
if (options.palette.length === 0) {
|
|
620
|
+
throw new Error(`Encoding ${options.bitPP}-bit BMP requires a non-empty palette.`);
|
|
621
|
+
}
|
|
622
|
+
this.validatePalette(options.bitPP, options.palette);
|
|
623
|
+
return options.palette;
|
|
624
|
+
}
|
|
625
|
+
return [];
|
|
626
|
+
}
|
|
627
|
+
validatePalette(bitPP, palette) {
|
|
628
|
+
const maxSize = 1 << bitPP;
|
|
629
|
+
if (palette.length === 0 || palette.length > maxSize) {
|
|
630
|
+
throw new Error(
|
|
631
|
+
`Palette size ${palette.length} is invalid for ${bitPP}-bit BMP. Expected 1..${maxSize}.`
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
for (const color of palette) {
|
|
635
|
+
this.validateChannel("palette.red", color.red);
|
|
636
|
+
this.validateChannel("palette.green", color.green);
|
|
637
|
+
this.validateChannel("palette.blue", color.blue);
|
|
638
|
+
this.validateChannel("palette.quad", color.quad);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
validateChannel(name, value) {
|
|
642
|
+
if (!Number.isInteger(value) || value < 0 || value > 255) {
|
|
643
|
+
throw new Error(`${name} must be an integer between 0 and 255.`);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
rowStride() {
|
|
647
|
+
return Math.floor((this.options.bitPP * this.width + 31) / 32) * 4;
|
|
648
|
+
}
|
|
649
|
+
sourceY(fileRow) {
|
|
650
|
+
return this.options.orientation === "top-down" ? fileRow : this.height - 1 - fileRow;
|
|
651
|
+
}
|
|
652
|
+
sourceOffset(x, y) {
|
|
653
|
+
return (y * this.width + x) * BYTES_PER_PIXEL_ABGR;
|
|
654
|
+
}
|
|
655
|
+
paletteKey(alpha, blue, green, red) {
|
|
656
|
+
return ((alpha & 255) << 24 | (blue & 255) << 16 | (green & 255) << 8 | red & 255) >>> 0;
|
|
657
|
+
}
|
|
658
|
+
findPaletteIndex(a, b, g, r) {
|
|
659
|
+
const exact = this.exactPaletteIndex.get(this.paletteKey(a, b, g, r));
|
|
660
|
+
if (exact !== void 0) {
|
|
661
|
+
return exact;
|
|
662
|
+
}
|
|
663
|
+
let bestIndex = 0;
|
|
664
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
665
|
+
for (let i = 0; i < this.palette.length; i += 1) {
|
|
666
|
+
const color = this.palette[i];
|
|
667
|
+
const dr = color.red - r;
|
|
668
|
+
const dg = color.green - g;
|
|
669
|
+
const db = color.blue - b;
|
|
670
|
+
const da = color.quad - a;
|
|
671
|
+
const distance = dr * dr + dg * dg + db * db + da * da;
|
|
672
|
+
if (distance < bestDistance) {
|
|
673
|
+
bestDistance = distance;
|
|
674
|
+
bestIndex = i;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
return bestIndex;
|
|
678
|
+
}
|
|
679
|
+
writePalette(output, paletteOffset) {
|
|
680
|
+
for (let i = 0; i < this.palette.length; i += 1) {
|
|
681
|
+
const color = this.palette[i];
|
|
682
|
+
const base = paletteOffset + i * 4;
|
|
683
|
+
output[base] = color.blue;
|
|
684
|
+
output[base + 1] = color.green;
|
|
685
|
+
output[base + 2] = color.red;
|
|
686
|
+
output[base + 3] = color.quad;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
encode1Bit(output, pixelOffset, stride) {
|
|
690
|
+
for (let fileRow = 0; fileRow < this.height; fileRow += 1) {
|
|
691
|
+
const srcY = this.sourceY(fileRow);
|
|
692
|
+
const rowStart = pixelOffset + fileRow * stride;
|
|
693
|
+
for (let x = 0; x < this.width; x += 8) {
|
|
694
|
+
let packed = 0;
|
|
695
|
+
for (let bit = 0; bit < 8; bit += 1) {
|
|
696
|
+
const px = x + bit;
|
|
697
|
+
if (px >= this.width) {
|
|
698
|
+
break;
|
|
699
|
+
}
|
|
700
|
+
const source = this.sourceOffset(px, srcY);
|
|
701
|
+
const a = this.pixelData[source] ?? 255;
|
|
702
|
+
const b = this.pixelData[source + 1] ?? 0;
|
|
703
|
+
const g = this.pixelData[source + 2] ?? 0;
|
|
704
|
+
const r = this.pixelData[source + 3] ?? 0;
|
|
705
|
+
const idx = this.findPaletteIndex(a, b, g, r) & 1;
|
|
706
|
+
packed |= idx << 7 - bit;
|
|
707
|
+
}
|
|
708
|
+
output[rowStart + Math.floor(x / 8)] = packed;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
encode4Bit(output, pixelOffset, stride) {
|
|
713
|
+
for (let fileRow = 0; fileRow < this.height; fileRow += 1) {
|
|
714
|
+
const srcY = this.sourceY(fileRow);
|
|
715
|
+
const rowStart = pixelOffset + fileRow * stride;
|
|
716
|
+
for (let x = 0; x < this.width; x += 2) {
|
|
717
|
+
const sourceA = this.sourceOffset(x, srcY);
|
|
718
|
+
const idxA = this.findPaletteIndex(
|
|
719
|
+
this.pixelData[sourceA] ?? 255,
|
|
720
|
+
this.pixelData[sourceA + 1] ?? 0,
|
|
721
|
+
this.pixelData[sourceA + 2] ?? 0,
|
|
722
|
+
this.pixelData[sourceA + 3] ?? 0
|
|
723
|
+
);
|
|
724
|
+
let idxB = 0;
|
|
725
|
+
if (x + 1 < this.width) {
|
|
726
|
+
const sourceB = this.sourceOffset(x + 1, srcY);
|
|
727
|
+
idxB = this.findPaletteIndex(
|
|
728
|
+
this.pixelData[sourceB] ?? 255,
|
|
729
|
+
this.pixelData[sourceB + 1] ?? 0,
|
|
730
|
+
this.pixelData[sourceB + 2] ?? 0,
|
|
731
|
+
this.pixelData[sourceB + 3] ?? 0
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
output[rowStart + Math.floor(x / 2)] = (idxA & 15) << 4 | idxB & 15;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
encode8Bit(output, pixelOffset, stride) {
|
|
739
|
+
for (let fileRow = 0; fileRow < this.height; fileRow += 1) {
|
|
740
|
+
const srcY = this.sourceY(fileRow);
|
|
741
|
+
const rowStart = pixelOffset + fileRow * stride;
|
|
742
|
+
for (let x = 0; x < this.width; x += 1) {
|
|
743
|
+
const source = this.sourceOffset(x, srcY);
|
|
744
|
+
output[rowStart + x] = this.findPaletteIndex(
|
|
745
|
+
this.pixelData[source] ?? 255,
|
|
746
|
+
this.pixelData[source + 1] ?? 0,
|
|
747
|
+
this.pixelData[source + 2] ?? 0,
|
|
748
|
+
this.pixelData[source + 3] ?? 0
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
encode16Bit(output, view, pixelOffset, stride) {
|
|
754
|
+
for (let fileRow = 0; fileRow < this.height; fileRow += 1) {
|
|
755
|
+
const srcY = this.sourceY(fileRow);
|
|
756
|
+
const rowStart = pixelOffset + fileRow * stride;
|
|
757
|
+
for (let x = 0; x < this.width; x += 1) {
|
|
758
|
+
const source = this.sourceOffset(x, srcY);
|
|
759
|
+
const b = this.pixelData[source + 1] ?? 0;
|
|
760
|
+
const g = this.pixelData[source + 2] ?? 0;
|
|
761
|
+
const r = this.pixelData[source + 3] ?? 0;
|
|
762
|
+
const value = (r >> 3 & 31) << 10 | (g >> 3 & 31) << 5 | b >> 3 & 31;
|
|
763
|
+
view.setUint16(rowStart + x * 2, value, true);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
encode24Bit(output, pixelOffset, stride) {
|
|
768
|
+
for (let fileRow = 0; fileRow < this.height; fileRow += 1) {
|
|
769
|
+
const srcY = this.sourceY(fileRow);
|
|
770
|
+
const rowStart = pixelOffset + fileRow * stride;
|
|
771
|
+
for (let x = 0; x < this.width; x += 1) {
|
|
772
|
+
const source = this.sourceOffset(x, srcY);
|
|
773
|
+
const target = rowStart + x * 3;
|
|
774
|
+
output[target] = this.pixelData[source + 1] ?? 0;
|
|
775
|
+
output[target + 1] = this.pixelData[source + 2] ?? 0;
|
|
776
|
+
output[target + 2] = this.pixelData[source + 3] ?? 0;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
encode32Bit(output, pixelOffset, stride) {
|
|
781
|
+
for (let fileRow = 0; fileRow < this.height; fileRow += 1) {
|
|
782
|
+
const srcY = this.sourceY(fileRow);
|
|
783
|
+
const rowStart = pixelOffset + fileRow * stride;
|
|
784
|
+
for (let x = 0; x < this.width; x += 1) {
|
|
785
|
+
const source = this.sourceOffset(x, srcY);
|
|
786
|
+
const target = rowStart + x * 4;
|
|
787
|
+
output[target] = this.pixelData[source + 1] ?? 0;
|
|
788
|
+
output[target + 1] = this.pixelData[source + 2] ?? 0;
|
|
789
|
+
output[target + 2] = this.pixelData[source + 3] ?? 0;
|
|
790
|
+
output[target + 3] = this.pixelData[source] ?? 255;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
580
793
|
}
|
|
581
794
|
encode() {
|
|
582
|
-
const stride =
|
|
795
|
+
const stride = this.rowStride();
|
|
583
796
|
const imageSize = stride * this.height;
|
|
584
|
-
const
|
|
797
|
+
const paletteSize = this.palette.length * 4;
|
|
798
|
+
const offset = FILE_HEADER_SIZE2 + INFO_HEADER_SIZE + paletteSize;
|
|
585
799
|
const totalSize = offset + imageSize;
|
|
586
800
|
const output = new Uint8Array(totalSize);
|
|
587
801
|
const view = new DataView(output.buffer, output.byteOffset, output.byteLength);
|
|
@@ -595,23 +809,35 @@ var BmpEncoder = class {
|
|
|
595
809
|
const signedHeight = this.options.orientation === "top-down" ? -this.height : this.height;
|
|
596
810
|
view.setInt32(22, signedHeight, true);
|
|
597
811
|
view.setUint16(26, 1, true);
|
|
598
|
-
view.setUint16(28,
|
|
812
|
+
view.setUint16(28, this.options.bitPP, true);
|
|
599
813
|
view.setUint32(30, 0, true);
|
|
600
814
|
view.setUint32(34, imageSize, true);
|
|
601
815
|
view.setUint32(38, 0, true);
|
|
602
816
|
view.setUint32(42, 0, true);
|
|
603
|
-
view.setUint32(46,
|
|
817
|
+
view.setUint32(46, this.palette.length, true);
|
|
604
818
|
view.setUint32(50, 0, true);
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
819
|
+
if (this.palette.length > 0) {
|
|
820
|
+
this.writePalette(output, FILE_HEADER_SIZE2 + INFO_HEADER_SIZE);
|
|
821
|
+
}
|
|
822
|
+
switch (this.options.bitPP) {
|
|
823
|
+
case 1:
|
|
824
|
+
this.encode1Bit(output, offset, stride);
|
|
825
|
+
break;
|
|
826
|
+
case 4:
|
|
827
|
+
this.encode4Bit(output, offset, stride);
|
|
828
|
+
break;
|
|
829
|
+
case 8:
|
|
830
|
+
this.encode8Bit(output, offset, stride);
|
|
831
|
+
break;
|
|
832
|
+
case 16:
|
|
833
|
+
this.encode16Bit(output, view, offset, stride);
|
|
834
|
+
break;
|
|
835
|
+
case 24:
|
|
836
|
+
this.encode24Bit(output, offset, stride);
|
|
837
|
+
break;
|
|
838
|
+
case 32:
|
|
839
|
+
this.encode32Bit(output, offset, stride);
|
|
840
|
+
break;
|
|
615
841
|
}
|
|
616
842
|
return output;
|
|
617
843
|
}
|