@thi.ng/pixel-io-netpbm
Version:
Multi-format NetPBM reader & writer support for @thi.ng/pixel
128 lines (127 loc) • 3.48 kB
JavaScript
import { assert } from "@thi.ng/errors/assert";
import { unsupported } from "@thi.ng/errors/unsupported";
import { GRAY16 } from "@thi.ng/pixel/format/gray16";
import { GRAY8 } from "@thi.ng/pixel/format/gray8";
import { RGB888 } from "@thi.ng/pixel/format/rgb888";
import { intBuffer } from "@thi.ng/pixel/int";
const __isLinebreak = (c) => c === 10;
const __isWS = (c) => c === 32 || c >= 9 && c <= 13;
const __readUntil = (src, i, end = __isLinebreak) => {
let res = "";
for (; i < src.length; i++) {
let c = src[i];
if (end(c)) {
i++;
break;
}
res += String.fromCharCode(c);
}
return [res, i];
};
const __readComments = (src, acc, i) => {
while (src[i] === 35) {
const [comment, j] = __readUntil(src, i);
assert(j !== i, `EOF reached`);
acc.push(comment.substring(1).trim());
i = j;
}
return i;
};
const parseHeader = (src) => {
let type;
let sw, sh;
let norm;
let max;
const comments = [];
let i = __readComments(src, comments, 0);
[type, i] = __readUntil(src, i);
i = __readComments(src, comments, i);
[sw, i] = __readUntil(src, i, __isWS);
[sh, i] = __readUntil(src, i, __isWS);
const width = parseInt(sw);
const height = parseInt(sh);
assert(width > 0 && height > 0, `invalid NetPBM header`);
if (type === "P5" || type === "P6") {
[norm, i] = __readUntil(src, i);
max = parseInt(norm);
}
return {
type,
width,
height,
max,
start: i,
comments
};
};
const read = (src) => {
const { type, width, height, max, start } = parseHeader(src);
switch (type) {
case "P4":
return readPBM(src, start, width, height);
case "P5":
return max < 256 ? readPGM8(src, start, width, height, max) : readPGM16(src, start, width, height, max);
case "P6":
return readPPM(src, start, width, height, max);
default:
unsupported(`PBM type: ${type}`);
}
};
const readPBM = (src, i, width, height) => {
const buf = intBuffer(width, height, GRAY8);
const data = buf.data;
const w1 = width - 1;
for (let y = 0, j = 0; y < height; y++) {
for (let x = 0; x < width; x++, j++) {
data[j] = src[i] & 1 << (~x & 7) ? 0 : 255;
if ((x & 7) === 7 || x === w1) i++;
}
}
return buf;
};
const readPGM8 = (src, i, width, height, max = 255) => {
const buf = intBuffer(width, height, GRAY8);
const data = buf.data;
if (max === 255) {
data.set(src.subarray(i));
} else {
max = 255 / max;
for (let j = 0, n = data.length; j < n; i++, j++) {
data[j] = src[i] * max | 0;
}
}
return buf;
};
const readPGM16 = (src, i, width, height, max = 65535) => {
const buf = intBuffer(width, height, GRAY16);
const data = buf.data;
max = 65535 / max;
for (let j = 0, n = data.length; j < n; i += 2, j++) {
data[j] = (src[i] << 8 | src[i + 1]) * max | 0;
}
return buf;
};
const readPPM = (src, i, width, height, max = 255) => {
const buf = intBuffer(width, height, RGB888);
const data = buf.data;
assert(max <= 255, `unsupported max value: ${max}`);
if (max === 255) {
for (let j = 0, n = data.length; j < n; i += 3, j++) {
data[j] = src[i] << 16 | src[i + 1] << 8 | src[i + 2];
}
} else {
max = 255 / max;
for (let j = 0, n = data.length; j < n; i += 3, j++) {
data[j] = src[i] * max << 16 | src[i + 1] * max << 8 | src[i + 2] * max;
}
}
return buf;
};
export {
parseHeader,
read,
readPBM,
readPGM16,
readPGM8,
readPPM
};