|
| 1 | +import zlib from 'node:zlib'; |
| 2 | + |
| 3 | +export function createPNG(width: number, height: number, pixelData: Buffer<ArrayBuffer>): Buffer { |
| 4 | + // PNG signature |
| 5 | + const signature = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]); |
| 6 | + |
| 7 | + // IHDR chunk |
| 8 | + const ihdr = Buffer.alloc(25); |
| 9 | + ihdr.writeUInt32BE(13, 0); // Length |
| 10 | + ihdr.write('IHDR', 4); |
| 11 | + ihdr.writeUInt32BE(width, 8); |
| 12 | + ihdr.writeUInt32BE(height, 12); |
| 13 | + ihdr.writeUInt8(8, 16); // Bit depth |
| 14 | + ihdr.writeUInt8(2, 17); // Color type (RGB) |
| 15 | + ihdr.writeUInt8(0, 18); // Compression |
| 16 | + ihdr.writeUInt8(0, 19); // Filter |
| 17 | + ihdr.writeUInt8(0, 20); // Interlace |
| 18 | + |
| 19 | + // Calculate CRC for IHDR |
| 20 | + const ihdrCrc = crc32(ihdr.slice(4, 21)); |
| 21 | + ihdr.writeUInt32BE(ihdrCrc, 21); |
| 22 | + |
| 23 | + // Prepare image data with filter bytes |
| 24 | + const scanlineLength = width * 3 + 1; // 3 bytes per pixel + 1 filter byte |
| 25 | + const imageData = Buffer.alloc(height * scanlineLength); |
| 26 | + |
| 27 | + for (let y = 0; y < height; y++) { |
| 28 | + const offset = y * scanlineLength; |
| 29 | + imageData[offset] = 0; // Filter type: None |
| 30 | + |
| 31 | + for (let x = 0; x < width; x++) { |
| 32 | + const pixelOffset = offset + 1 + x * 3; |
| 33 | + const dataOffset = (y * width + x) * 3; |
| 34 | + |
| 35 | + imageData[pixelOffset] = pixelData[dataOffset]; // R |
| 36 | + imageData[pixelOffset + 1] = pixelData[dataOffset + 1]; // G |
| 37 | + imageData[pixelOffset + 2] = pixelData[dataOffset + 2]; // B |
| 38 | + } |
| 39 | + } |
| 40 | + |
| 41 | + const compressed = zlib.deflateSync(imageData); |
| 42 | + |
| 43 | + // IDAT chunk |
| 44 | + const idat = Buffer.alloc(compressed.length + 12); |
| 45 | + idat.writeUInt32BE(compressed.length, 0); |
| 46 | + idat.write('IDAT', 4); |
| 47 | + compressed.copy(idat, 8); |
| 48 | + const idatCrc = crc32(idat.slice(4, 8 + compressed.length)); |
| 49 | + idat.writeUInt32BE(idatCrc, 8 + compressed.length); |
| 50 | + |
| 51 | + // IEND chunk |
| 52 | + const iend = Buffer.from([0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130]); |
| 53 | + |
| 54 | + return Buffer.concat([signature, ihdr, idat, iend]); |
| 55 | +} |
| 56 | + |
| 57 | +function crc32(data: any): number { |
| 58 | + return zlib.crc32(data) >>> 0; // Convert to unsigned 32-bit |
| 59 | +} |
0 commit comments