Skip to content

Instantly share code, notes, and snippets.

@adrian154
Created February 12, 2023 18:43
Show Gist options
  • Select an option

  • Save adrian154/69aa8f4277c81e1bbfae545df09038eb to your computer and use it in GitHub Desktop.

Select an option

Save adrian154/69aa8f4277c81e1bbfae545df09038eb to your computer and use it in GitHub Desktop.
// BEWARE: THERE ARE SOME VERY SKETCHY BITS AHEAD!
// Never ever use, anywhere, outside of this project.
const fs = require("fs");
module.exports = class {
constructor(filename, bytesPerPixel) {
if(bytesPerPixel != 1 && bytesPerPixel != 2) throw new Error("unsupported bit depth");
// read full file
this.data = fs.readFileSync(filename);
this.offset = 0;
// read properties
if(this.readLine() != "P6") throw new Error("Magic string doesn't match");
this.width = Number(this.readLine());
this.height = Number(this.readLine());
this.max = Number(this.readLine());
this.bytesPerPixel = bytesPerPixel;
// read image data
this.data = this.data.slice(this.offset, this.data.length);
this.pixels = new Float32Array(this.width * this.height * 3);
for(let x = 0; x < this.width; x++) {
for(let y = 0; y < this.height; y++) {
const pixelIdx = (y * this.width + x) * 3;
const idx = pixelIdx * this.bytesPerPixel;
let r, g, b;
if(this.bytesPerPixel == 2) {
r = this.data.readUInt16BE(idx);
g = this.data.readUInt16BE(idx + 2);
b = this.data.readUInt16BE(idx + 4);
} else {
r = this.data[idx];
g = this.data[idx + 1];
b = this.data[idx + 2];
}
this.pixels[pixelIdx] = r / this.max;
this.pixels[pixelIdx + 1] = g / this.max;
this.pixels[pixelIdx + 2] = b / this.max;
}
}
this.data = null;
console.log("finished reading " + filename);
}
write(name) {
const stream = fs.createWriteStream(name);
stream.write(`P6\n${this.width}\n${this.height}\n${this.max}\n`);
for(let y = 0; y < this.height; y++) {
for(let x = 0; x < this.width; x++) {
const pixelIdx = (y * this.width + x) * 3;
const r = this.pixels[pixelIdx], g = this.pixels[pixelIdx + 1], b = this.pixels[pixelIdx + 2];
if(this.max > 256) {
const pixbuf = Buffer.allocUnsafe(6);
pixbuf.writeUInt16BE(r * this.max, 0);
pixbuf.writeUint16BE(g * this.max, 2);
pixbuf.writeUint16BE(b * this.max, 4);
stream.write(pixbuf.slice(0, 6));
} else {
const pixbuf = Buffer.allocUnsafe(3);
pixbuf.writeUint8(r * this.max, 0);
pixbuf.writeUint8(g * this.max, 1);
pixbuf.writeUint8(b * this.max, 2);
stream.write(pixbuf.slice(0, 3));
}
}
}
stream.end();
}
readLine() {
for(let i = this.offset; i < this.data.length; i++) {
if(this.data[i] == 0xA) {
const result = this.data.slice(this.offset, i);
this.offset = i + 1;
return result.toString("utf-8");
}
}
throw new Error("Unexpectedly reached end of file");
}
forEachPixel(func) {
for(let x = 0; x < this.width; x++) {
for(let y = 0; y < this.height; y++) {
const idx = (y * this.width + x) * 3;
func(
this.pixels[idx], this.pixels[idx + 1], this.pixels[idx + 2], // normalized RGB
idx,
x, y // coordinates
);
}
}
}
getPixel(x, y) {
const idx = (y * this.width + x) * 3;
return [this.pixels[idx], this.pixels[idx + 1], this.pixels[idx + 2]];
}
filter(func) {
this.forEachPixel((r, g, b, idx, x, y) => {
[this.pixels[idx], this.pixels[idx + 1], this.pixels[idx + 2]] = func(r, g, b, x, y);
});
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment