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