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