Created
February 12, 2023 18:43
-
-
Save adrian154/69aa8f4277c81e1bbfae545df09038eb to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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