template-project/node_modules/bmp-ts/dist/esm/encoder.js
2025-05-30 18:13:30 +08:00

263 lines
8.1 KiB
JavaScript

import HeaderTypes from './header-types.js';
function createInteger(numbers) {
return numbers.reduce((final, n) => (final << 1) | n, 0);
}
function createColor(color) {
return ((color.quad << 24) | (color.red << 16) | (color.green << 8) | color.blue);
}
export default class BmpEncoder {
fileSize;
reserved1;
reserved2;
offset;
width;
flag;
height;
planes;
bitPP;
compress;
hr;
vr;
colors;
importantColors;
rawSize;
headerSize;
data;
palette;
extraBytes;
buffer;
bytesInColor;
pos;
constructor(imgData) {
this.buffer = imgData.data;
this.width = imgData.width;
this.height = imgData.height;
this.headerSize = HeaderTypes.BITMAP_INFO_HEADER;
// Header
this.flag = 'BM';
this.bitPP = imgData.bitPP || 24;
this.offset = 54;
this.reserved1 = imgData.reserved1 || 0;
this.reserved2 = imgData.reserved2 || 0;
this.planes = 1;
this.compress = 0;
this.hr = imgData.hr || 0;
this.vr = imgData.vr || 0;
this.importantColors = imgData.importantColors || 0;
this.colors = Math.min(2 ** (this.bitPP - 1 || 1), imgData.colors || Infinity);
this.palette = imgData.palette || [];
if (this.colors && this.bitPP < 16) {
this.offset += this.colors * 4;
}
else {
this.colors = 0;
}
switch (this.bitPP) {
case 32:
this.bytesInColor = 4;
break;
case 16:
this.bytesInColor = 2;
break;
case 8:
this.bytesInColor = 1;
break;
case 4:
this.bytesInColor = 1 / 2;
break;
case 1:
this.bytesInColor = 1 / 8;
break;
default:
this.bytesInColor = 3;
this.bitPP = 24;
}
const rowWidth = (this.width * this.bitPP) / 32;
const rowBytes = Math.ceil(rowWidth);
this.extraBytes = (rowBytes - rowWidth) * 4;
// Why 2?
this.rawSize = this.height * rowBytes * 4 + 2;
this.fileSize = this.rawSize + this.offset;
this.data = Buffer.alloc(this.fileSize, 0x1);
this.pos = 0;
this.encode();
}
encode() {
this.pos = 0;
this.writeHeader();
switch (this.bitPP) {
case 32:
this.bit32();
break;
case 16:
this.bit16();
break;
case 8:
this.bit8();
break;
case 4:
this.bit4();
break;
case 1:
this.bit1();
break;
default:
this.bit24();
}
}
writeHeader() {
this.data.write(this.flag, this.pos, 2);
this.pos += 2;
this.writeUInt32LE(this.fileSize);
// Writing 2 UInt16LE resulted in a weird bug
this.writeUInt32LE((this.reserved1 << 16) | this.reserved2);
this.writeUInt32LE(this.offset);
this.writeUInt32LE(this.headerSize);
this.writeUInt32LE(this.width);
this.writeUInt32LE(this.height);
this.data.writeUInt16LE(this.planes, this.pos);
this.pos += 2;
this.data.writeUInt16LE(this.bitPP, this.pos);
this.pos += 2;
this.writeUInt32LE(this.compress);
this.writeUInt32LE(this.rawSize);
this.writeUInt32LE(this.hr);
this.writeUInt32LE(this.vr);
this.writeUInt32LE(this.colors);
this.writeUInt32LE(this.importantColors);
}
bit1() {
if (this.palette.length && this.colors === 2) {
this.initColors(1);
}
else {
this.writeUInt32LE(0x00ffffff); // Black
this.writeUInt32LE(0x00000000); // White
}
this.pos += 1; // ?
let lineArr = [];
this.writeImage((p, index, x) => {
let i = index;
i++;
const b = this.buffer[i++];
const g = this.buffer[i++];
const r = this.buffer[i++];
const brightness = r * 0.2126 + g * 0.7152 + b * 0.0722;
lineArr.push(brightness > 127 ? 0 : 1);
if ((x + 1) % 8 === 0) {
this.data[p - 1] = createInteger(lineArr);
lineArr = [];
}
else if (x === this.width - 1 && lineArr.length > 0) {
this.data[p - 1] = createInteger(lineArr) << 4;
lineArr = [];
}
return i;
});
}
bit4() {
const colors = this.initColors(4);
let integerPair = [];
this.writeImage((p, index, x) => {
let i = index;
const colorInt = createColor({
quad: this.buffer[i++],
blue: this.buffer[i++],
green: this.buffer[i++],
red: this.buffer[i++],
});
const colorExists = colors.findIndex((c) => c === colorInt);
if (colorExists !== -1) {
integerPair.push(colorExists);
}
else {
integerPair.push(0);
}
if ((x + 1) % 2 === 0) {
this.data[p] = (integerPair[0] << 4) | integerPair[1];
integerPair = [];
}
return i;
});
}
bit8() {
const colors = this.initColors(8);
this.writeImage((p, index) => {
let i = index;
const colorInt = createColor({
quad: this.buffer[i++],
blue: this.buffer[i++],
green: this.buffer[i++],
red: this.buffer[i++],
});
const colorExists = colors.findIndex((c) => c === colorInt);
if (colorExists !== -1) {
this.data[p] = colorExists;
}
else {
this.data[p] = 0;
}
return i;
});
}
bit16() {
this.writeImage((p, index) => {
let i = index + 1;
const b = this.buffer[i++] / 8; // b
const g = this.buffer[i++] / 8; // g
const r = this.buffer[i++] / 8; // r
const color = (r << 10) | (g << 5) | b;
this.data[p] = color & 0x00ff;
this.data[p + 1] = (color & 0xff00) >> 8;
return i;
});
}
bit24() {
this.writeImage((p, index) => {
let i = index + 1;
this.data[p] = this.buffer[i++]; //b
this.data[p + 1] = this.buffer[i++]; //g
this.data[p + 2] = this.buffer[i++]; //r
return i;
});
}
bit32() {
this.writeImage((p, index) => {
let i = index;
this.data[p + 3] = this.buffer[i++]; // a
this.data[p] = this.buffer[i++]; // b
this.data[p + 1] = this.buffer[i++]; // g
this.data[p + 2] = this.buffer[i++]; // r
return i;
});
}
writeImage(writePixel) {
const rowBytes = this.extraBytes + this.width * this.bytesInColor;
let i = 0;
for (let y = 0; y < this.height; y++) {
for (let x = 0; x < this.width; x++) {
const p = Math.floor(this.pos + (this.height - 1 - y) * rowBytes + x * this.bytesInColor);
i = writePixel.call(this, p, i, x, y);
}
}
}
initColors(bit) {
const colors = [];
if (this.palette.length) {
for (let i = 0; i < this.colors; i++) {
const rootColor = createColor(this.palette[i]);
this.writeUInt32LE(rootColor);
colors.push(rootColor);
}
}
else {
throw new Error(`To encode ${bit}-bit BMPs a pallette is needed. Please choose up to ${this.colors} colors. Colors must be 32-bit integers.`);
}
return colors;
}
writeUInt32LE(value) {
this.data.writeUInt32LE(value, this.pos);
this.pos += 4;
}
}
//# sourceMappingURL=encoder.js.map