UNPKG

@thi.ng/pixel-io-netpbm

Version:

Multi-format NetPBM reader & writer support for @thi.ng/pixel

128 lines (127 loc) 3.48 kB
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 };