Slowly work towards more pure functions

This commit is contained in:
Michael Smith 2023-05-31 20:59:03 +02:00
parent f937499e6f
commit e28b88aedf
2 changed files with 77 additions and 74 deletions

View File

@ -44,10 +44,17 @@ class BinaryStream {
} }
readByte() { readByte() {
const byte = this.dataView.getUint8(this.index); return this.readUint8();
}
this.index += 1; readBytes(length) {
return byte; const bytes = [];
for (let i = 0; i < length; i++) {
bytes.push(this.readUint8());
}
return bytes;
} }
readInt16BE() { readInt16BE() {

View File

@ -26,6 +26,30 @@
import BinaryStream from "./binarystream.js"; import BinaryStream from "./binarystream.js";
function decompress(binaryStream, length) {
const result = [];
const endOfChunkIndex = binaryStream.index + length;
while (binaryStream.index < endOfChunkIndex) {
const byte = binaryStream.readByte();
if (byte > 128) {
const nextByte = binaryStream.readByte();
for (let i = 0; i < 257 - byte; i++) {
result.push(nextByte);
}
} else if (byte < 128) {
for (let i = 0; i < byte + 1; i++) {
result.push(binaryStream.readByte());
}
} else {
break;
}
}
return result;
}
// Parse Bitmap Header chunk // Parse Bitmap Header chunk
function parseBMHD(binaryStream, image) { function parseBMHD(binaryStream, image) {
image.width = binaryStream.readUint16BE(); image.width = binaryStream.readUint16BE();
@ -45,9 +69,9 @@ function parseBMHD(binaryStream, image) {
} }
// Parse Palette chunk // Parse Palette chunk
function parseCMAP(binaryStream, image) { function parseCMAP(binaryStream, numPlanes) {
const numColors = 2 ** image.numPlanes; const palette = [];
image.palette = []; const numColors = 2 ** numPlanes;
// TODO(m): Read 3 bytes at a time? // TODO(m): Read 3 bytes at a time?
for (let i = 0; i < numColors; i++) { for (let i = 0; i < numColors; i++) {
@ -55,8 +79,10 @@ function parseCMAP(binaryStream, image) {
for (let j = 0; j < 3; j++) { for (let j = 0; j < 3; j++) {
rgb.push(binaryStream.readByte()); rgb.push(binaryStream.readByte());
} }
image.palette.push(rgb); palette.push(rgb);
} }
return palette;
} }
// Parse Color range chunk // Parse Color range chunk
@ -89,78 +115,34 @@ function parseCRNG(binaryStream) {
}; };
} }
function decompress(binaryStream, endOfChunkIndex) {
const result = [];
while (binaryStream.index < endOfChunkIndex) {
const byte = binaryStream.readByte();
if (byte > 128) {
const nextByte = binaryStream.readByte();
for (let i = 0; i < 257 - byte; i++) {
result.push(nextByte);
}
} else if (byte < 128) {
for (let i = 0; i < byte + 1; i++) {
result.push(binaryStream.readByte());
}
} else {
break;
}
}
return result;
}
// TODO(m): Read a range of bytes straight into an array?
// Use arrayBuffers throughout instead?
function readUncompressed(binaryStream, endOfChunkIndex) {
const result = [];
while (binaryStream.index < endOfChunkIndex) {
const byte = binaryStream.readByte();
result.push(byte);
}
return result;
}
// Parse Thumbnail chunk // Parse Thumbnail chunk
function parseTINY(binaryStream, image, chunkLength) { function parseTINY(binaryStream, compression, chunkLength) {
image.thumbnail = { const thumbnail = {};
// FIXME(m): Remove need for reference to image palette in thumbnail data
palette: image.palette,
};
const endOfChunkIndex = binaryStream.index + chunkLength;
image.thumbnail.width = binaryStream.readUint16BE(); thumbnail.width = binaryStream.readUint16BE();
image.thumbnail.height = binaryStream.readUint16BE(); thumbnail.height = binaryStream.readUint16BE();
image.thumbnail.size = image.thumbnail.width * image.thumbnail.height; thumbnail.size = thumbnail.width * thumbnail.height;
if (image.compression === 1) { if (compression === 1) {
image.thumbnail.pixelData = decompress(binaryStream, endOfChunkIndex); thumbnail.pixelData = decompress(binaryStream, chunkLength - 4);
} else { } else {
image.thumbnail.pixelData = readUncompressed(binaryStream, endOfChunkIndex); thumbnail.pixelData = binaryStream.readBytes(chunkLength);
} }
return thumbnail;
} }
// Parse Image data chunk // Parse Image data chunk
function parseBODY(binaryStream, image, chunkLength) { function parseBODY(binaryStream, compression, chunkLength) {
const endOfChunkIndex = binaryStream.index + chunkLength; if (compression === 1) {
return decompress(binaryStream, chunkLength);
if (image.compression === 1) {
image.pixelData = decompress(binaryStream, endOfChunkIndex);
} else {
image.pixelData = readUncompressed(binaryStream, endOfChunkIndex);
} }
return binaryStream.readBytes(chunkLength);
} }
// Parse FORM chunk // Parse FORM chunk
function parseFORM(binaryStream) { function parseFORM(binaryStream, image) {
const image = {
cyclingRanges: [],
};
let chunkId = binaryStream.readString(4); let chunkId = binaryStream.readString(4);
let chunkLength = binaryStream.readUint32BE(); let chunkLength = binaryStream.readUint32BE();
const formatId = binaryStream.readString(4); const formatId = binaryStream.readString(4);
@ -194,7 +176,7 @@ function parseFORM(binaryStream) {
parseBMHD(binaryStream, image); parseBMHD(binaryStream, image);
break; break;
case "CMAP": case "CMAP":
parseCMAP(binaryStream, image); image.palette = parseCMAP(binaryStream, image.numPlanes);
break; break;
case "DPPS": case "DPPS":
// NOTE(m): Ignore unknown DPPS chunk of size 110 bytes // NOTE(m): Ignore unknown DPPS chunk of size 110 bytes
@ -204,10 +186,21 @@ function parseFORM(binaryStream) {
image.cyclingRanges.push(parseCRNG(binaryStream)); image.cyclingRanges.push(parseCRNG(binaryStream));
break; break;
case "TINY": case "TINY":
parseTINY(binaryStream, image, chunkLength); image.thumbnail = parseTINY(
binaryStream,
image.compression,
chunkLength
);
// FIXME(m): Remove need for reference to image palette in thumbnail data
image.thumbnail.palette = image.palette;
break; break;
case "BODY": case "BODY":
parseBODY(binaryStream, image, chunkLength); image.pixelData = parseBODY(
binaryStream,
image.compression,
chunkLength
);
break; break;
default: default:
throw new Error( throw new Error(
@ -218,15 +211,16 @@ function parseFORM(binaryStream) {
// Skip chunk padding byte when chunkLength is not a multiple of 2 // Skip chunk padding byte when chunkLength is not a multiple of 2
if (chunkLength % 2 === 1) binaryStream.jump(1); if (chunkLength % 2 === 1) binaryStream.jump(1);
} }
return image;
} }
export default function parsePBM(arrayBuffer) { export default function parsePBM(arrayBuffer) {
const binaryStream = new BinaryStream(arrayBuffer); const binaryStream = new BinaryStream(arrayBuffer);
const image = {
cyclingRanges: [],
};
try { try {
const image = parseFORM(binaryStream); parseFORM(binaryStream, image);
return image;
} catch (error) { } catch (error) {
if (error instanceof RangeError) { if (error instanceof RangeError) {
throw new Error(`Failed to parse file.`); throw new Error(`Failed to parse file.`);
@ -234,4 +228,6 @@ export default function parsePBM(arrayBuffer) {
throw error; // re-throw the error unchanged throw error; // re-throw the error unchanged
} }
} }
return image;
} }