@huh-david/bmp-js 0.3.0 → 0.4.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 -1
- package/dist/index.cjs +466 -392
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -32
- package/dist/index.d.ts +46 -32
- package/dist/index.js +466 -392
- package/dist/index.js.map +1 -1
- package/package.json +20 -19
package/dist/index.js
CHANGED
|
@@ -1,13 +1,36 @@
|
|
|
1
|
+
// src/binary.ts
|
|
2
|
+
function toUint8Array(input) {
|
|
3
|
+
if (input instanceof ArrayBuffer) {
|
|
4
|
+
return new Uint8Array(input);
|
|
5
|
+
}
|
|
6
|
+
return new Uint8Array(input.buffer, input.byteOffset, input.byteLength);
|
|
7
|
+
}
|
|
8
|
+
function assertInteger(name, value) {
|
|
9
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
10
|
+
throw new Error(`${name} must be a positive integer`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
1
14
|
// src/decoder.ts
|
|
15
|
+
var FILE_HEADER_SIZE = 14;
|
|
16
|
+
var INFO_HEADER_MIN = 40;
|
|
17
|
+
var CORE_HEADER_SIZE = 12;
|
|
18
|
+
function rowStride(width, bitPP) {
|
|
19
|
+
return Math.floor((bitPP * width + 31) / 32) * 4;
|
|
20
|
+
}
|
|
2
21
|
var BmpDecoder = class {
|
|
3
22
|
pos = 0;
|
|
4
|
-
|
|
5
|
-
|
|
23
|
+
bytes;
|
|
24
|
+
view;
|
|
25
|
+
options;
|
|
6
26
|
bottomUp = true;
|
|
27
|
+
dibStart = FILE_HEADER_SIZE;
|
|
28
|
+
paletteEntrySize = 4;
|
|
29
|
+
externalMaskOffset = 0;
|
|
7
30
|
maskRed = 0;
|
|
8
31
|
maskGreen = 0;
|
|
9
32
|
maskBlue = 0;
|
|
10
|
-
|
|
33
|
+
maskAlpha = 0;
|
|
11
34
|
fileSize;
|
|
12
35
|
reserved;
|
|
13
36
|
offset;
|
|
@@ -24,67 +47,172 @@ var BmpDecoder = class {
|
|
|
24
47
|
importantColors;
|
|
25
48
|
palette;
|
|
26
49
|
data;
|
|
27
|
-
constructor(
|
|
28
|
-
this.
|
|
29
|
-
this.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
this.
|
|
50
|
+
constructor(input, options = {}) {
|
|
51
|
+
this.bytes = toUint8Array(input);
|
|
52
|
+
this.view = new DataView(this.bytes.buffer, this.bytes.byteOffset, this.bytes.byteLength);
|
|
53
|
+
this.options = {
|
|
54
|
+
treat16BitAs15BitAlpha: options.treat16BitAs15BitAlpha ?? false
|
|
55
|
+
};
|
|
56
|
+
this.parseFileHeader();
|
|
57
|
+
this.parseDibHeader();
|
|
58
|
+
this.parsePalette();
|
|
59
|
+
this.pos = this.offset;
|
|
35
60
|
this.parseRGBA();
|
|
36
61
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
this.reserved = this.buffer.readUInt32LE(this.pos);
|
|
41
|
-
this.pos += 4;
|
|
42
|
-
this.offset = this.buffer.readUInt32LE(this.pos);
|
|
43
|
-
this.pos += 4;
|
|
44
|
-
this.headerSize = this.buffer.readUInt32LE(this.pos);
|
|
45
|
-
this.pos += 4;
|
|
46
|
-
this.width = this.buffer.readUInt32LE(this.pos);
|
|
47
|
-
this.pos += 4;
|
|
48
|
-
this.height = this.buffer.readInt32LE(this.pos);
|
|
49
|
-
this.pos += 4;
|
|
50
|
-
this.planes = this.buffer.readUInt16LE(this.pos);
|
|
51
|
-
this.pos += 2;
|
|
52
|
-
this.bitPP = this.buffer.readUInt16LE(this.pos);
|
|
53
|
-
this.pos += 2;
|
|
54
|
-
this.compress = this.buffer.readUInt32LE(this.pos);
|
|
55
|
-
this.pos += 4;
|
|
56
|
-
this.rawSize = this.buffer.readUInt32LE(this.pos);
|
|
57
|
-
this.pos += 4;
|
|
58
|
-
this.hr = this.buffer.readUInt32LE(this.pos);
|
|
59
|
-
this.pos += 4;
|
|
60
|
-
this.vr = this.buffer.readUInt32LE(this.pos);
|
|
61
|
-
this.pos += 4;
|
|
62
|
-
this.colors = this.buffer.readUInt32LE(this.pos);
|
|
63
|
-
this.pos += 4;
|
|
64
|
-
this.importantColors = this.buffer.readUInt32LE(this.pos);
|
|
65
|
-
this.pos += 4;
|
|
66
|
-
if (this.bitPP === 16 && this.isWithAlpha) {
|
|
67
|
-
this.bitPP = 15;
|
|
62
|
+
ensureReadable(offset, size, context) {
|
|
63
|
+
if (offset < 0 || size < 0 || offset + size > this.bytes.length) {
|
|
64
|
+
throw new Error(`BMP decode out-of-range while reading ${context}`);
|
|
68
65
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
66
|
+
}
|
|
67
|
+
readUInt8(offset = this.pos) {
|
|
68
|
+
this.ensureReadable(offset, 1, "uint8");
|
|
69
|
+
if (offset === this.pos) this.pos += 1;
|
|
70
|
+
return this.view.getUint8(offset);
|
|
71
|
+
}
|
|
72
|
+
readUInt16LE(offset = this.pos) {
|
|
73
|
+
this.ensureReadable(offset, 2, "uint16");
|
|
74
|
+
if (offset === this.pos) this.pos += 2;
|
|
75
|
+
return this.view.getUint16(offset, true);
|
|
76
|
+
}
|
|
77
|
+
readInt16LE(offset = this.pos) {
|
|
78
|
+
this.ensureReadable(offset, 2, "int16");
|
|
79
|
+
if (offset === this.pos) this.pos += 2;
|
|
80
|
+
return this.view.getInt16(offset, true);
|
|
81
|
+
}
|
|
82
|
+
readUInt32LE(offset = this.pos) {
|
|
83
|
+
this.ensureReadable(offset, 4, "uint32");
|
|
84
|
+
if (offset === this.pos) this.pos += 4;
|
|
85
|
+
return this.view.getUint32(offset, true);
|
|
86
|
+
}
|
|
87
|
+
readInt32LE(offset = this.pos) {
|
|
88
|
+
this.ensureReadable(offset, 4, "int32");
|
|
89
|
+
if (offset === this.pos) this.pos += 4;
|
|
90
|
+
return this.view.getInt32(offset, true);
|
|
91
|
+
}
|
|
92
|
+
parseFileHeader() {
|
|
93
|
+
this.ensureReadable(0, FILE_HEADER_SIZE, "file header");
|
|
94
|
+
if (this.bytes[0] !== 66 || this.bytes[1] !== 77) {
|
|
95
|
+
throw new Error("Invalid BMP file signature");
|
|
79
96
|
}
|
|
97
|
+
this.pos = 2;
|
|
98
|
+
this.fileSize = this.readUInt32LE();
|
|
99
|
+
this.reserved = this.readUInt32LE();
|
|
100
|
+
this.offset = this.readUInt32LE();
|
|
101
|
+
if (this.offset < FILE_HEADER_SIZE || this.offset > this.bytes.length) {
|
|
102
|
+
throw new Error(`Invalid pixel data offset: ${this.offset}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
parseDibHeader() {
|
|
106
|
+
this.pos = this.dibStart;
|
|
107
|
+
this.headerSize = this.readUInt32LE();
|
|
108
|
+
if (this.headerSize < CORE_HEADER_SIZE) {
|
|
109
|
+
throw new Error(`Unsupported DIB header size: ${this.headerSize}`);
|
|
110
|
+
}
|
|
111
|
+
this.ensureReadable(this.dibStart, this.headerSize, "DIB header");
|
|
112
|
+
if (this.headerSize === CORE_HEADER_SIZE) {
|
|
113
|
+
this.parseCoreHeader();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (this.headerSize < INFO_HEADER_MIN) {
|
|
117
|
+
throw new Error(`Unsupported DIB header size: ${this.headerSize}`);
|
|
118
|
+
}
|
|
119
|
+
this.parseInfoHeader();
|
|
120
|
+
}
|
|
121
|
+
parseCoreHeader() {
|
|
122
|
+
const width = this.readUInt16LE(this.dibStart + 4);
|
|
123
|
+
const height = this.readUInt16LE(this.dibStart + 6);
|
|
124
|
+
this.width = width;
|
|
125
|
+
this.height = height;
|
|
126
|
+
this.planes = this.readUInt16LE(this.dibStart + 8);
|
|
127
|
+
this.bitPP = this.readUInt16LE(this.dibStart + 10);
|
|
128
|
+
this.compress = 0;
|
|
129
|
+
this.rawSize = 0;
|
|
130
|
+
this.hr = 0;
|
|
131
|
+
this.vr = 0;
|
|
132
|
+
this.colors = 0;
|
|
133
|
+
this.importantColors = 0;
|
|
134
|
+
this.bottomUp = true;
|
|
135
|
+
this.paletteEntrySize = 3;
|
|
136
|
+
this.externalMaskOffset = this.dibStart + this.headerSize;
|
|
137
|
+
this.validateDimensions();
|
|
138
|
+
}
|
|
139
|
+
parseInfoHeader() {
|
|
140
|
+
const rawWidth = this.readInt32LE(this.dibStart + 4);
|
|
141
|
+
const rawHeight = this.readInt32LE(this.dibStart + 8);
|
|
142
|
+
this.width = rawWidth;
|
|
143
|
+
this.height = rawHeight;
|
|
144
|
+
this.planes = this.readUInt16LE(this.dibStart + 12);
|
|
145
|
+
this.bitPP = this.readUInt16LE(this.dibStart + 14);
|
|
146
|
+
this.compress = this.readUInt32LE(this.dibStart + 16);
|
|
147
|
+
this.rawSize = this.readUInt32LE(this.dibStart + 20);
|
|
148
|
+
this.hr = this.readUInt32LE(this.dibStart + 24);
|
|
149
|
+
this.vr = this.readUInt32LE(this.dibStart + 28);
|
|
150
|
+
this.colors = this.readUInt32LE(this.dibStart + 32);
|
|
151
|
+
this.importantColors = this.readUInt32LE(this.dibStart + 36);
|
|
152
|
+
this.paletteEntrySize = 4;
|
|
153
|
+
this.externalMaskOffset = this.dibStart + this.headerSize;
|
|
80
154
|
if (this.height < 0) {
|
|
81
155
|
this.height *= -1;
|
|
82
156
|
this.bottomUp = false;
|
|
83
157
|
}
|
|
158
|
+
if (this.width < 0) {
|
|
159
|
+
this.width *= -1;
|
|
160
|
+
}
|
|
161
|
+
if (this.bitPP === 16 && this.options.treat16BitAs15BitAlpha) {
|
|
162
|
+
this.bitPP = 15;
|
|
163
|
+
}
|
|
164
|
+
this.validateDimensions();
|
|
165
|
+
this.parseBitMasks();
|
|
166
|
+
}
|
|
167
|
+
validateDimensions() {
|
|
168
|
+
if (!Number.isInteger(this.width) || !Number.isInteger(this.height) || this.width <= 0 || this.height <= 0) {
|
|
169
|
+
throw new Error(`Invalid BMP dimensions: ${this.width}x${this.height}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
parseBitMasks() {
|
|
173
|
+
if (!(this.bitPP === 16 || this.bitPP === 32) || !(this.compress === 3 || this.compress === 6)) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const inHeaderMaskStart = this.dibStart + 40;
|
|
177
|
+
const hasMasksInHeader = this.headerSize >= 52;
|
|
178
|
+
const maskStart = hasMasksInHeader ? inHeaderMaskStart : this.externalMaskOffset;
|
|
179
|
+
const maskCount = this.compress === 6 || this.headerSize >= 56 ? 4 : 3;
|
|
180
|
+
this.ensureReadable(maskStart, maskCount * 4, "bit masks");
|
|
181
|
+
this.maskRed = this.readUInt32LE(maskStart);
|
|
182
|
+
this.maskGreen = this.readUInt32LE(maskStart + 4);
|
|
183
|
+
this.maskBlue = this.readUInt32LE(maskStart + 8);
|
|
184
|
+
this.maskAlpha = maskCount >= 4 ? this.readUInt32LE(maskStart + 12) : 0;
|
|
185
|
+
if (!hasMasksInHeader) {
|
|
186
|
+
this.externalMaskOffset += maskCount * 4;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
parsePalette() {
|
|
190
|
+
if (this.bitPP >= 16) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const colorCount = this.colors === 0 ? 1 << this.bitPP : this.colors;
|
|
194
|
+
if (colorCount <= 0) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const paletteStart = this.externalMaskOffset;
|
|
198
|
+
const paletteSize = colorCount * this.paletteEntrySize;
|
|
199
|
+
if (paletteStart + paletteSize > this.offset) {
|
|
200
|
+
throw new Error("Palette data overlaps or exceeds pixel data offset");
|
|
201
|
+
}
|
|
202
|
+
this.palette = new Array(colorCount);
|
|
203
|
+
for (let i = 0; i < colorCount; i += 1) {
|
|
204
|
+
const base = paletteStart + i * this.paletteEntrySize;
|
|
205
|
+
const blue = this.readUInt8(base);
|
|
206
|
+
const green = this.readUInt8(base + 1);
|
|
207
|
+
const red = this.readUInt8(base + 2);
|
|
208
|
+
const quad = this.paletteEntrySize === 4 ? this.readUInt8(base + 3) : 0;
|
|
209
|
+
this.palette[i] = { red, green, blue, quad };
|
|
210
|
+
}
|
|
84
211
|
}
|
|
85
212
|
parseRGBA() {
|
|
86
|
-
const
|
|
87
|
-
|
|
213
|
+
const pixelCount = this.width * this.height;
|
|
214
|
+
const len = pixelCount * 4;
|
|
215
|
+
this.data = new Uint8Array(len);
|
|
88
216
|
switch (this.bitPP) {
|
|
89
217
|
case 1:
|
|
90
218
|
this.bit1();
|
|
@@ -116,312 +244,254 @@ var BmpDecoder = class {
|
|
|
116
244
|
if (color) {
|
|
117
245
|
return color;
|
|
118
246
|
}
|
|
119
|
-
return {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
247
|
+
return { red: 255, green: 255, blue: 255, quad: 0 };
|
|
248
|
+
}
|
|
249
|
+
setPixel(destY, x, alpha, blue, green, red) {
|
|
250
|
+
const base = (destY * this.width + x) * 4;
|
|
251
|
+
this.data[base] = alpha;
|
|
252
|
+
this.data[base + 1] = blue;
|
|
253
|
+
this.data[base + 2] = green;
|
|
254
|
+
this.data[base + 3] = red;
|
|
125
255
|
}
|
|
126
256
|
bit1() {
|
|
127
|
-
const
|
|
128
|
-
const
|
|
129
|
-
for (let
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const rgb = this.getPaletteColor(b >> 7 - i & 1);
|
|
139
|
-
this.data[location + i * 4] = 0;
|
|
140
|
-
this.data[location + i * 4 + 1] = rgb.blue;
|
|
141
|
-
this.data[location + i * 4 + 2] = rgb.green;
|
|
142
|
-
this.data[location + i * 4 + 3] = rgb.red;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
if (mode !== 0) {
|
|
146
|
-
this.pos += 4 - mode;
|
|
257
|
+
const stride = rowStride(this.width, 1);
|
|
258
|
+
const bytesPerRow = Math.ceil(this.width / 8);
|
|
259
|
+
for (let srcRow = 0; srcRow < this.height; srcRow += 1) {
|
|
260
|
+
const rowStart = this.offset + srcRow * stride;
|
|
261
|
+
this.ensureReadable(rowStart, bytesPerRow, "1-bit row");
|
|
262
|
+
const destY = this.bottomUp ? this.height - 1 - srcRow : srcRow;
|
|
263
|
+
for (let x = 0; x < this.width; x += 1) {
|
|
264
|
+
const packed = this.readUInt8(rowStart + Math.floor(x / 8));
|
|
265
|
+
const bit = packed >> 7 - x % 8 & 1;
|
|
266
|
+
const rgb = this.getPaletteColor(bit);
|
|
267
|
+
this.setPixel(destY, x, 0, rgb.blue, rgb.green, rgb.red);
|
|
147
268
|
}
|
|
148
269
|
}
|
|
149
270
|
}
|
|
150
271
|
bit4() {
|
|
151
272
|
if (this.compress === 2) {
|
|
152
|
-
|
|
153
|
-
const rgb = this.getPaletteColor(rgbIndex);
|
|
154
|
-
this.data[location] = 0;
|
|
155
|
-
this.data[location + 1] = rgb.blue;
|
|
156
|
-
this.data[location + 2] = rgb.green;
|
|
157
|
-
this.data[location + 3] = rgb.red;
|
|
158
|
-
location += 4;
|
|
159
|
-
};
|
|
160
|
-
var setPixelData = setPixelData2;
|
|
161
|
-
this.data.fill(255);
|
|
162
|
-
let location = 0;
|
|
163
|
-
let lines = this.bottomUp ? this.height - 1 : 0;
|
|
164
|
-
let lowNibble = false;
|
|
165
|
-
while (location < this.data.length) {
|
|
166
|
-
const a = this.buffer.readUInt8(this.pos++);
|
|
167
|
-
const b = this.buffer.readUInt8(this.pos++);
|
|
168
|
-
if (a === 0) {
|
|
169
|
-
if (b === 0) {
|
|
170
|
-
lines += this.bottomUp ? -1 : 1;
|
|
171
|
-
location = lines * this.width * 4;
|
|
172
|
-
lowNibble = false;
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
|
-
if (b === 1) {
|
|
176
|
-
break;
|
|
177
|
-
}
|
|
178
|
-
if (b === 2) {
|
|
179
|
-
const x = this.buffer.readUInt8(this.pos++);
|
|
180
|
-
const y = this.buffer.readUInt8(this.pos++);
|
|
181
|
-
lines += this.bottomUp ? -y : y;
|
|
182
|
-
location += y * this.width * 4 + x * 4;
|
|
183
|
-
continue;
|
|
184
|
-
}
|
|
185
|
-
let c = this.buffer.readUInt8(this.pos++);
|
|
186
|
-
for (let i = 0; i < b; i += 1) {
|
|
187
|
-
if (lowNibble) {
|
|
188
|
-
setPixelData2.call(this, c & 15);
|
|
189
|
-
} else {
|
|
190
|
-
setPixelData2.call(this, (c & 240) >> 4);
|
|
191
|
-
}
|
|
192
|
-
if ((i & 1) === 1 && i + 1 < b) {
|
|
193
|
-
c = this.buffer.readUInt8(this.pos++);
|
|
194
|
-
}
|
|
195
|
-
lowNibble = !lowNibble;
|
|
196
|
-
}
|
|
197
|
-
if ((b + 1 >> 1 & 1) === 1) {
|
|
198
|
-
this.pos += 1;
|
|
199
|
-
}
|
|
200
|
-
} else {
|
|
201
|
-
for (let i = 0; i < a; i += 1) {
|
|
202
|
-
if (lowNibble) {
|
|
203
|
-
setPixelData2.call(this, b & 15);
|
|
204
|
-
} else {
|
|
205
|
-
setPixelData2.call(this, (b & 240) >> 4);
|
|
206
|
-
}
|
|
207
|
-
lowNibble = !lowNibble;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
273
|
+
this.bit4Rle();
|
|
211
274
|
return;
|
|
212
275
|
}
|
|
213
|
-
const
|
|
214
|
-
const
|
|
215
|
-
for (let
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
this.
|
|
224
|
-
this.data[location + 1] = rgb.blue;
|
|
225
|
-
this.data[location + 2] = rgb.green;
|
|
226
|
-
this.data[location + 3] = rgb.red;
|
|
227
|
-
if (x * 2 + 1 >= this.width) {
|
|
228
|
-
break;
|
|
229
|
-
}
|
|
230
|
-
rgb = this.getPaletteColor(after);
|
|
231
|
-
this.data[location + 4] = 0;
|
|
232
|
-
this.data[location + 5] = rgb.blue;
|
|
233
|
-
this.data[location + 6] = rgb.green;
|
|
234
|
-
this.data[location + 7] = rgb.red;
|
|
235
|
-
}
|
|
236
|
-
if (mode !== 0) {
|
|
237
|
-
this.pos += 4 - mode;
|
|
276
|
+
const stride = rowStride(this.width, 4);
|
|
277
|
+
const bytesPerRow = Math.ceil(this.width / 2);
|
|
278
|
+
for (let srcRow = 0; srcRow < this.height; srcRow += 1) {
|
|
279
|
+
const rowStart = this.offset + srcRow * stride;
|
|
280
|
+
this.ensureReadable(rowStart, bytesPerRow, "4-bit row");
|
|
281
|
+
const destY = this.bottomUp ? this.height - 1 - srcRow : srcRow;
|
|
282
|
+
for (let x = 0; x < this.width; x += 1) {
|
|
283
|
+
const packed = this.readUInt8(rowStart + Math.floor(x / 2));
|
|
284
|
+
const idx = x % 2 === 0 ? (packed & 240) >> 4 : packed & 15;
|
|
285
|
+
const rgb = this.getPaletteColor(idx);
|
|
286
|
+
this.setPixel(destY, x, 0, rgb.blue, rgb.green, rgb.red);
|
|
238
287
|
}
|
|
239
288
|
}
|
|
240
289
|
}
|
|
241
290
|
bit8() {
|
|
242
291
|
if (this.compress === 1) {
|
|
243
|
-
|
|
244
|
-
const rgb = this.getPaletteColor(rgbIndex);
|
|
245
|
-
this.data[location] = 0;
|
|
246
|
-
this.data[location + 1] = rgb.blue;
|
|
247
|
-
this.data[location + 2] = rgb.green;
|
|
248
|
-
this.data[location + 3] = rgb.red;
|
|
249
|
-
location += 4;
|
|
250
|
-
};
|
|
251
|
-
var setPixelData = setPixelData2;
|
|
252
|
-
this.data.fill(255);
|
|
253
|
-
let location = 0;
|
|
254
|
-
let lines = this.bottomUp ? this.height - 1 : 0;
|
|
255
|
-
while (location < this.data.length) {
|
|
256
|
-
const a = this.buffer.readUInt8(this.pos++);
|
|
257
|
-
const b = this.buffer.readUInt8(this.pos++);
|
|
258
|
-
if (a === 0) {
|
|
259
|
-
if (b === 0) {
|
|
260
|
-
lines += this.bottomUp ? -1 : 1;
|
|
261
|
-
location = lines * this.width * 4;
|
|
262
|
-
continue;
|
|
263
|
-
}
|
|
264
|
-
if (b === 1) {
|
|
265
|
-
break;
|
|
266
|
-
}
|
|
267
|
-
if (b === 2) {
|
|
268
|
-
const x = this.buffer.readUInt8(this.pos++);
|
|
269
|
-
const y = this.buffer.readUInt8(this.pos++);
|
|
270
|
-
lines += this.bottomUp ? -y : y;
|
|
271
|
-
location += y * this.width * 4 + x * 4;
|
|
272
|
-
continue;
|
|
273
|
-
}
|
|
274
|
-
for (let i = 0; i < b; i += 1) {
|
|
275
|
-
const c = this.buffer.readUInt8(this.pos++);
|
|
276
|
-
setPixelData2.call(this, c);
|
|
277
|
-
}
|
|
278
|
-
if ((b & 1) === 1) {
|
|
279
|
-
this.pos += 1;
|
|
280
|
-
}
|
|
281
|
-
} else {
|
|
282
|
-
for (let i = 0; i < a; i += 1) {
|
|
283
|
-
setPixelData2.call(this, b);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
292
|
+
this.bit8Rle();
|
|
287
293
|
return;
|
|
288
294
|
}
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
295
|
+
const stride = rowStride(this.width, 8);
|
|
296
|
+
const bytesPerRow = this.width;
|
|
297
|
+
for (let srcRow = 0; srcRow < this.height; srcRow += 1) {
|
|
298
|
+
const rowStart = this.offset + srcRow * stride;
|
|
299
|
+
this.ensureReadable(rowStart, bytesPerRow, "8-bit row");
|
|
300
|
+
const destY = this.bottomUp ? this.height - 1 - srcRow : srcRow;
|
|
292
301
|
for (let x = 0; x < this.width; x += 1) {
|
|
293
|
-
const
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
this.data[location] = 0;
|
|
297
|
-
this.data[location + 1] = rgb.blue;
|
|
298
|
-
this.data[location + 2] = rgb.green;
|
|
299
|
-
this.data[location + 3] = rgb.red;
|
|
300
|
-
}
|
|
301
|
-
if (mode !== 0) {
|
|
302
|
-
this.pos += 4 - mode;
|
|
302
|
+
const idx = this.readUInt8(rowStart + x);
|
|
303
|
+
const rgb = this.getPaletteColor(idx);
|
|
304
|
+
this.setPixel(destY, x, 0, rgb.blue, rgb.green, rgb.red);
|
|
303
305
|
}
|
|
304
306
|
}
|
|
305
307
|
}
|
|
306
308
|
bit15() {
|
|
307
|
-
const
|
|
308
|
-
const
|
|
309
|
-
for (let
|
|
310
|
-
const
|
|
309
|
+
const stride = rowStride(this.width, 16);
|
|
310
|
+
const max = 31;
|
|
311
|
+
for (let srcRow = 0; srcRow < this.height; srcRow += 1) {
|
|
312
|
+
const rowStart = this.offset + srcRow * stride;
|
|
313
|
+
this.ensureReadable(rowStart, this.width * 2, "15-bit row");
|
|
314
|
+
const destY = this.bottomUp ? this.height - 1 - srcRow : srcRow;
|
|
311
315
|
for (let x = 0; x < this.width; x += 1) {
|
|
312
|
-
const value = this.
|
|
313
|
-
|
|
314
|
-
const
|
|
315
|
-
const
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
const location = line * this.width * 4 + x * 4;
|
|
319
|
-
this.data[location] = alpha;
|
|
320
|
-
this.data[location + 1] = blue | 0;
|
|
321
|
-
this.data[location + 2] = green | 0;
|
|
322
|
-
this.data[location + 3] = red | 0;
|
|
316
|
+
const value = this.readUInt16LE(rowStart + x * 2);
|
|
317
|
+
const blue = (value >> 0 & max) / max * 255;
|
|
318
|
+
const green = (value >> 5 & max) / max * 255;
|
|
319
|
+
const red = (value >> 10 & max) / max * 255;
|
|
320
|
+
const alpha = (value & 32768) !== 0 ? 255 : 0;
|
|
321
|
+
this.setPixel(destY, x, alpha, blue | 0, green | 0, red | 0);
|
|
323
322
|
}
|
|
324
|
-
this.pos += difW;
|
|
325
323
|
}
|
|
326
324
|
}
|
|
325
|
+
scaleMasked(value, mask) {
|
|
326
|
+
if (mask === 0) return 0;
|
|
327
|
+
let shift = 0;
|
|
328
|
+
let bits = 0;
|
|
329
|
+
let m = mask;
|
|
330
|
+
while ((m & 1) === 0) {
|
|
331
|
+
shift += 1;
|
|
332
|
+
m >>>= 1;
|
|
333
|
+
}
|
|
334
|
+
while ((m & 1) === 1) {
|
|
335
|
+
bits += 1;
|
|
336
|
+
m >>>= 1;
|
|
337
|
+
}
|
|
338
|
+
const component = (value & mask) >>> shift;
|
|
339
|
+
if (bits >= 8) {
|
|
340
|
+
return component >>> bits - 8;
|
|
341
|
+
}
|
|
342
|
+
return component << 8 - bits & 255;
|
|
343
|
+
}
|
|
327
344
|
bit16() {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
this.
|
|
336
|
-
this.
|
|
337
|
-
this.
|
|
338
|
-
this.maskBlue = this.buffer.readUInt32LE(this.pos);
|
|
339
|
-
this.pos += 4;
|
|
340
|
-
this.mask0 = this.buffer.readUInt32LE(this.pos);
|
|
341
|
-
this.pos += 4;
|
|
342
|
-
}
|
|
343
|
-
const ns = [0, 0, 0];
|
|
344
|
-
for (let i = 0; i < 16; i += 1) {
|
|
345
|
-
if ((this.maskRed >> i & 1) !== 0) ns[0] += 1;
|
|
346
|
-
if ((this.maskGreen >> i & 1) !== 0) ns[1] += 1;
|
|
347
|
-
if ((this.maskBlue >> i & 1) !== 0) ns[2] += 1;
|
|
348
|
-
}
|
|
349
|
-
ns[1] += ns[0];
|
|
350
|
-
ns[2] += ns[1];
|
|
351
|
-
ns[0] = 8 - ns[0];
|
|
352
|
-
ns[1] -= 8;
|
|
353
|
-
ns[2] -= 8;
|
|
354
|
-
for (let y = this.height - 1; y >= 0; y -= 1) {
|
|
355
|
-
const line = this.bottomUp ? y : this.height - 1 - y;
|
|
345
|
+
if (this.maskRed === 0 && this.maskGreen === 0 && this.maskBlue === 0) {
|
|
346
|
+
this.maskRed = 31744;
|
|
347
|
+
this.maskGreen = 992;
|
|
348
|
+
this.maskBlue = 31;
|
|
349
|
+
}
|
|
350
|
+
const stride = rowStride(this.width, 16);
|
|
351
|
+
for (let srcRow = 0; srcRow < this.height; srcRow += 1) {
|
|
352
|
+
const rowStart = this.offset + srcRow * stride;
|
|
353
|
+
this.ensureReadable(rowStart, this.width * 2, "16-bit row");
|
|
354
|
+
const destY = this.bottomUp ? this.height - 1 - srcRow : srcRow;
|
|
356
355
|
for (let x = 0; x < this.width; x += 1) {
|
|
357
|
-
const value = this.
|
|
358
|
-
this.
|
|
359
|
-
const
|
|
360
|
-
const
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
this.data[location] = 0;
|
|
364
|
-
this.data[location + 1] = blue;
|
|
365
|
-
this.data[location + 2] = green;
|
|
366
|
-
this.data[location + 3] = red;
|
|
356
|
+
const value = this.readUInt16LE(rowStart + x * 2);
|
|
357
|
+
const blue = this.scaleMasked(value, this.maskBlue);
|
|
358
|
+
const green = this.scaleMasked(value, this.maskGreen);
|
|
359
|
+
const red = this.scaleMasked(value, this.maskRed);
|
|
360
|
+
const alpha = this.maskAlpha !== 0 ? this.scaleMasked(value, this.maskAlpha) : 0;
|
|
361
|
+
this.setPixel(destY, x, alpha, blue, green, red);
|
|
367
362
|
}
|
|
368
|
-
this.pos += difW;
|
|
369
363
|
}
|
|
370
364
|
}
|
|
371
365
|
bit24() {
|
|
372
|
-
|
|
373
|
-
|
|
366
|
+
const stride = rowStride(this.width, 24);
|
|
367
|
+
for (let srcRow = 0; srcRow < this.height; srcRow += 1) {
|
|
368
|
+
const rowStart = this.offset + srcRow * stride;
|
|
369
|
+
this.ensureReadable(rowStart, this.width * 3, "24-bit row");
|
|
370
|
+
const destY = this.bottomUp ? this.height - 1 - srcRow : srcRow;
|
|
374
371
|
for (let x = 0; x < this.width; x += 1) {
|
|
375
|
-
const
|
|
376
|
-
const
|
|
377
|
-
const
|
|
378
|
-
const
|
|
379
|
-
this.
|
|
380
|
-
this.data[location + 1] = blue;
|
|
381
|
-
this.data[location + 2] = green;
|
|
382
|
-
this.data[location + 3] = red;
|
|
372
|
+
const base = rowStart + x * 3;
|
|
373
|
+
const blue = this.readUInt8(base);
|
|
374
|
+
const green = this.readUInt8(base + 1);
|
|
375
|
+
const red = this.readUInt8(base + 2);
|
|
376
|
+
this.setPixel(destY, x, 0, blue, green, red);
|
|
383
377
|
}
|
|
384
|
-
this.pos += this.width % 4;
|
|
385
378
|
}
|
|
386
379
|
}
|
|
387
380
|
bit32() {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
this.
|
|
391
|
-
this.
|
|
392
|
-
this.
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
const alpha = this.
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
const
|
|
404
|
-
const
|
|
405
|
-
this.
|
|
406
|
-
this.
|
|
407
|
-
this.
|
|
408
|
-
this.data[location + 3] = red;
|
|
381
|
+
const stride = rowStride(this.width, 32);
|
|
382
|
+
for (let srcRow = 0; srcRow < this.height; srcRow += 1) {
|
|
383
|
+
const rowStart = this.offset + srcRow * stride;
|
|
384
|
+
this.ensureReadable(rowStart, this.width * 4, "32-bit row");
|
|
385
|
+
const destY = this.bottomUp ? this.height - 1 - srcRow : srcRow;
|
|
386
|
+
for (let x = 0; x < this.width; x += 1) {
|
|
387
|
+
const base = rowStart + x * 4;
|
|
388
|
+
if (this.compress === 3 || this.compress === 6) {
|
|
389
|
+
const pixel = this.readUInt32LE(base);
|
|
390
|
+
const red = this.scaleMasked(pixel, this.maskRed || 16711680);
|
|
391
|
+
const green = this.scaleMasked(pixel, this.maskGreen || 65280);
|
|
392
|
+
const blue = this.scaleMasked(pixel, this.maskBlue || 255);
|
|
393
|
+
const alpha = this.maskAlpha === 0 ? 0 : this.scaleMasked(pixel, this.maskAlpha);
|
|
394
|
+
this.setPixel(destY, x, alpha, blue, green, red);
|
|
395
|
+
} else {
|
|
396
|
+
const blue = this.readUInt8(base);
|
|
397
|
+
const green = this.readUInt8(base + 1);
|
|
398
|
+
const red = this.readUInt8(base + 2);
|
|
399
|
+
const alpha = this.readUInt8(base + 3);
|
|
400
|
+
this.setPixel(destY, x, alpha, blue, green, red);
|
|
409
401
|
}
|
|
410
402
|
}
|
|
411
|
-
return;
|
|
412
403
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
404
|
+
}
|
|
405
|
+
bit8Rle() {
|
|
406
|
+
this.data.fill(255);
|
|
407
|
+
this.pos = this.offset;
|
|
408
|
+
let x = 0;
|
|
409
|
+
let y = this.bottomUp ? this.height - 1 : 0;
|
|
410
|
+
while (this.pos < this.bytes.length) {
|
|
411
|
+
const count = this.readUInt8();
|
|
412
|
+
const value = this.readUInt8();
|
|
413
|
+
if (count === 0) {
|
|
414
|
+
if (value === 0) {
|
|
415
|
+
x = 0;
|
|
416
|
+
y += this.bottomUp ? -1 : 1;
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
if (value === 1) {
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
422
|
+
if (value === 2) {
|
|
423
|
+
x += this.readUInt8();
|
|
424
|
+
y += this.bottomUp ? -this.readUInt8() : this.readUInt8();
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
for (let i = 0; i < value; i += 1) {
|
|
428
|
+
const idx = this.readUInt8();
|
|
429
|
+
const rgb2 = this.getPaletteColor(idx);
|
|
430
|
+
if (x < this.width && y >= 0 && y < this.height) {
|
|
431
|
+
this.setPixel(y, x, 0, rgb2.blue, rgb2.green, rgb2.red);
|
|
432
|
+
}
|
|
433
|
+
x += 1;
|
|
434
|
+
}
|
|
435
|
+
if ((value & 1) === 1) {
|
|
436
|
+
this.pos += 1;
|
|
437
|
+
}
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
const rgb = this.getPaletteColor(value);
|
|
441
|
+
for (let i = 0; i < count; i += 1) {
|
|
442
|
+
if (x < this.width && y >= 0 && y < this.height) {
|
|
443
|
+
this.setPixel(y, x, 0, rgb.blue, rgb.green, rgb.red);
|
|
444
|
+
}
|
|
445
|
+
x += 1;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
bit4Rle() {
|
|
450
|
+
this.data.fill(255);
|
|
451
|
+
this.pos = this.offset;
|
|
452
|
+
let x = 0;
|
|
453
|
+
let y = this.bottomUp ? this.height - 1 : 0;
|
|
454
|
+
while (this.pos < this.bytes.length) {
|
|
455
|
+
const count = this.readUInt8();
|
|
456
|
+
const value = this.readUInt8();
|
|
457
|
+
if (count === 0) {
|
|
458
|
+
if (value === 0) {
|
|
459
|
+
x = 0;
|
|
460
|
+
y += this.bottomUp ? -1 : 1;
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
if (value === 1) {
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
466
|
+
if (value === 2) {
|
|
467
|
+
x += this.readUInt8();
|
|
468
|
+
y += this.bottomUp ? -this.readUInt8() : this.readUInt8();
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
let current = this.readUInt8();
|
|
472
|
+
for (let i = 0; i < value; i += 1) {
|
|
473
|
+
const nibble = i % 2 === 0 ? (current & 240) >> 4 : current & 15;
|
|
474
|
+
const rgb = this.getPaletteColor(nibble);
|
|
475
|
+
if (x < this.width && y >= 0 && y < this.height) {
|
|
476
|
+
this.setPixel(y, x, 0, rgb.blue, rgb.green, rgb.red);
|
|
477
|
+
}
|
|
478
|
+
x += 1;
|
|
479
|
+
if (i % 2 === 1 && i + 1 < value) {
|
|
480
|
+
current = this.readUInt8();
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if ((value + 1 >> 1 & 1) === 1) {
|
|
484
|
+
this.pos += 1;
|
|
485
|
+
}
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
for (let i = 0; i < count; i += 1) {
|
|
489
|
+
const nibble = i % 2 === 0 ? (value & 240) >> 4 : value & 15;
|
|
490
|
+
const rgb = this.getPaletteColor(nibble);
|
|
491
|
+
if (x < this.width && y >= 0 && y < this.height) {
|
|
492
|
+
this.setPixel(y, x, 0, rgb.blue, rgb.green, rgb.red);
|
|
493
|
+
}
|
|
494
|
+
x += 1;
|
|
425
495
|
}
|
|
426
496
|
}
|
|
427
497
|
}
|
|
@@ -429,92 +499,96 @@ var BmpDecoder = class {
|
|
|
429
499
|
return this.data;
|
|
430
500
|
}
|
|
431
501
|
};
|
|
432
|
-
function decode(bmpData) {
|
|
433
|
-
return new BmpDecoder(bmpData);
|
|
502
|
+
function decode(bmpData, options) {
|
|
503
|
+
return new BmpDecoder(bmpData, options);
|
|
434
504
|
}
|
|
435
505
|
|
|
436
506
|
// src/encoder.ts
|
|
507
|
+
var FILE_HEADER_SIZE2 = 14;
|
|
508
|
+
var INFO_HEADER_SIZE = 40;
|
|
509
|
+
var RGB_TRIPLE_SIZE = 3;
|
|
510
|
+
var BYTES_PER_PIXEL_ABGR = 4;
|
|
511
|
+
function rowStride24(width) {
|
|
512
|
+
const raw = width * RGB_TRIPLE_SIZE;
|
|
513
|
+
return raw + 3 & ~3;
|
|
514
|
+
}
|
|
515
|
+
function normalizeEncodeOptions(qualityOrOptions) {
|
|
516
|
+
if (typeof qualityOrOptions === "number" || typeof qualityOrOptions === "undefined") {
|
|
517
|
+
return {
|
|
518
|
+
orientation: "top-down",
|
|
519
|
+
bitPP: 24
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
return {
|
|
523
|
+
orientation: qualityOrOptions.orientation ?? "top-down",
|
|
524
|
+
bitPP: qualityOrOptions.bitPP ?? 24
|
|
525
|
+
};
|
|
526
|
+
}
|
|
437
527
|
var BmpEncoder = class {
|
|
438
|
-
|
|
528
|
+
pixelData;
|
|
439
529
|
width;
|
|
440
530
|
height;
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
flag = "BM";
|
|
445
|
-
reserved = 0;
|
|
446
|
-
offset = 54;
|
|
447
|
-
fileSize;
|
|
448
|
-
planes = 1;
|
|
449
|
-
bitPP = 24;
|
|
450
|
-
compress = 0;
|
|
451
|
-
hr = 0;
|
|
452
|
-
vr = 0;
|
|
453
|
-
colors = 0;
|
|
454
|
-
importantColors = 0;
|
|
455
|
-
pos = 0;
|
|
456
|
-
constructor(imgData) {
|
|
457
|
-
this.buffer = imgData.data;
|
|
531
|
+
options;
|
|
532
|
+
constructor(imgData, options) {
|
|
533
|
+
this.pixelData = imgData.data;
|
|
458
534
|
this.width = imgData.width;
|
|
459
535
|
this.height = imgData.height;
|
|
460
|
-
this.
|
|
461
|
-
|
|
462
|
-
this.
|
|
463
|
-
this.
|
|
536
|
+
this.options = options;
|
|
537
|
+
assertInteger("width", this.width);
|
|
538
|
+
assertInteger("height", this.height);
|
|
539
|
+
if (this.options.bitPP !== 24) {
|
|
540
|
+
throw new Error(
|
|
541
|
+
`Unsupported encode bit depth: ${this.options.bitPP}. Only 24-bit output is supported.`
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
const minLength = this.width * this.height * BYTES_PER_PIXEL_ABGR;
|
|
545
|
+
if (this.pixelData.length < minLength) {
|
|
546
|
+
throw new Error(
|
|
547
|
+
`Image data is too short: expected at least ${minLength} bytes for ${this.width}x${this.height} ABGR data.`
|
|
548
|
+
);
|
|
549
|
+
}
|
|
464
550
|
}
|
|
465
551
|
encode() {
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
this.
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
this.pos += 4;
|
|
493
|
-
tempBuffer.writeUInt32LE(this.colors, this.pos);
|
|
494
|
-
this.pos += 4;
|
|
495
|
-
tempBuffer.writeUInt32LE(this.importantColors, this.pos);
|
|
496
|
-
this.pos += 4;
|
|
497
|
-
let i = 0;
|
|
498
|
-
const rowBytes = 3 * this.width + this.extraBytes;
|
|
499
|
-
for (let y = 0; y < this.height; y += 1) {
|
|
552
|
+
const stride = rowStride24(this.width);
|
|
553
|
+
const imageSize = stride * this.height;
|
|
554
|
+
const offset = FILE_HEADER_SIZE2 + INFO_HEADER_SIZE;
|
|
555
|
+
const totalSize = offset + imageSize;
|
|
556
|
+
const output = new Uint8Array(totalSize);
|
|
557
|
+
const view = new DataView(output.buffer, output.byteOffset, output.byteLength);
|
|
558
|
+
output[0] = 66;
|
|
559
|
+
output[1] = 77;
|
|
560
|
+
view.setUint32(2, totalSize, true);
|
|
561
|
+
view.setUint32(6, 0, true);
|
|
562
|
+
view.setUint32(10, offset, true);
|
|
563
|
+
view.setUint32(14, INFO_HEADER_SIZE, true);
|
|
564
|
+
view.setInt32(18, this.width, true);
|
|
565
|
+
const signedHeight = this.options.orientation === "top-down" ? -this.height : this.height;
|
|
566
|
+
view.setInt32(22, signedHeight, true);
|
|
567
|
+
view.setUint16(26, 1, true);
|
|
568
|
+
view.setUint16(28, 24, true);
|
|
569
|
+
view.setUint32(30, 0, true);
|
|
570
|
+
view.setUint32(34, imageSize, true);
|
|
571
|
+
view.setUint32(38, 0, true);
|
|
572
|
+
view.setUint32(42, 0, true);
|
|
573
|
+
view.setUint32(46, 0, true);
|
|
574
|
+
view.setUint32(50, 0, true);
|
|
575
|
+
for (let fileRow = 0; fileRow < this.height; fileRow += 1) {
|
|
576
|
+
const srcY = this.options.orientation === "top-down" ? fileRow : this.height - 1 - fileRow;
|
|
577
|
+
const rowStart = offset + fileRow * stride;
|
|
500
578
|
for (let x = 0; x < this.width; x += 1) {
|
|
501
|
-
const
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
}
|
|
507
|
-
if (this.extraBytes > 0) {
|
|
508
|
-
const fillOffset = this.pos + y * rowBytes + this.width * 3;
|
|
509
|
-
tempBuffer.fill(0, fillOffset, fillOffset + this.extraBytes);
|
|
579
|
+
const source = (srcY * this.width + x) * BYTES_PER_PIXEL_ABGR;
|
|
580
|
+
const target = rowStart + x * RGB_TRIPLE_SIZE;
|
|
581
|
+
output[target] = this.pixelData[source + 1] ?? 0;
|
|
582
|
+
output[target + 1] = this.pixelData[source + 2] ?? 0;
|
|
583
|
+
output[target + 2] = this.pixelData[source + 3] ?? 0;
|
|
510
584
|
}
|
|
511
585
|
}
|
|
512
|
-
return
|
|
586
|
+
return output;
|
|
513
587
|
}
|
|
514
588
|
};
|
|
515
|
-
function encode(imgData,
|
|
516
|
-
|
|
517
|
-
const encoder = new BmpEncoder(imgData);
|
|
589
|
+
function encode(imgData, qualityOrOptions) {
|
|
590
|
+
const options = normalizeEncodeOptions(qualityOrOptions);
|
|
591
|
+
const encoder = new BmpEncoder(imgData, options);
|
|
518
592
|
const data = encoder.encode();
|
|
519
593
|
return {
|
|
520
594
|
data,
|