Task 564: .PNG File Format

Task 564: .PNG File Format

PNG File Format Specifications

The official specifications for the .PNG file format are detailed in the Portable Network Graphics (PNG) Specification (Third Edition) published by the W3C on June 24, 2025. This edition includes updates for HDR support, official EXIF integration, and animated PNG (APNG) enhancements. The format is designed for lossless, portable, well-compressed storage of raster images, supporting static and animated content with features like transparency, color management, and metadata.

  1. List of all properties of this file format intrinsic to its file system:

Based on the specification, the intrinsic properties refer to the structural elements and metadata fields embedded within the PNG file structure. These include the file signature, chunk formats, and data fields from critical and ancillary chunks. Here's a comprehensive list:

  • File Signature: An 8-byte sequence: 89 50 4E 47 0D 0A 1A 0A (in hexadecimal), identifying the file as PNG.
  • General Chunk Properties (applies to all chunks):
  • Length: 4-byte unsigned integer (0 to 2^31-1).
  • Type: 4-byte ASCII code (e.g., 'IHDR'); bits indicate critical/ancillary, public/private, safe-to-copy.
  • Data: Variable-length bytes specific to the chunk type.
  • CRC: 4-byte cyclic redundancy check for error detection.
  • Critical Chunk Properties:
  • IHDR (Image Header):
  • Width: 4-byte unsigned integer (>0).
  • Height: 4-byte unsigned integer (>0).
  • Bit depth: 1 byte (1, 2, 4, 8, or 16, depending on color type).
  • Color type: 1 byte (0=greyscale, 2=truecolor, 3=indexed-color, 4=greyscale+alpha, 6=truecolor+alpha).
  • Compression method: 1 byte (0=deflate/zlib).
  • Filter method: 1 byte (0=adaptive filtering).
  • Interlace method: 1 byte (0=none, 1=Adam7).
  • PLTE (Palette) (optional, required for color type 3):
  • Palette entries: Array of 3-byte RGB values (1-256 entries).
  • IDAT (Image Data) (one or more):
  • Compressed data: zlib/deflate stream of filtered scanlines.
  • IEND (Image Trailer):
  • No data (empty).
  • Ancillary Chunk Properties:
  • tRNS (Transparency) (optional, for color types 0, 2, 3):
  • Transparent color or alpha values: Varies by color type (e.g., 2-byte greyscale for type 0, array of 1-byte alphas for type 3).
  • cHRM (Primary Chromaticities and White Point):
  • White point x/y: 4 bytes each.
  • Red x/y: 4 bytes each.
  • Green x/y: 4 bytes each.
  • Blue x/y: 4 bytes each.
  • gAMA (Image Gamma):
  • Gamma value: 4-byte unsigned integer (×100000).
  • iCCP (Embedded ICC Profile):
  • Profile name: 1-79 bytes.
  • Compression method: 1 byte (0=zlib).
  • Compressed profile: Variable bytes.
  • sBIT (Significant Bits):
  • Significant bits per channel: 1-4 bytes depending on color type.
  • sRGB (Standard RGB Color Space):
  • Rendering intent: 1 byte (0=perceptual, 1=relative colorimetric, 2=saturation, 3=absolute colorimetric).
  • cICP (Coding-Independent Code Points):
  • Color primaries: 1 byte.
  • Transfer function: 1 byte.
  • Matrix coefficients: 1 byte.
  • Video full range flag: 1 byte.
  • mDCV (Mastering Display Color Volume):
  • Chromaticities for R/G/B/white point: Multiple 4-byte values.
  • Max/min luminance: 4 bytes each.
  • iTXt (International Textual Data) (multiple allowed):
  • Keyword: 1-79 bytes.
  • Compression flag/method: 1 byte each.
  • Language tag: Variable bytes.
  • Translated keyword: Variable bytes.
  • Text: Variable bytes (UTF-8).
  • tEXt (Textual Data) (multiple allowed):
  • Keyword: 1-79 bytes.
  • Text: Variable bytes (Latin-1).
  • zTXt (Compressed Textual Data) (multiple allowed):
  • Keyword: 1-79 bytes.
  • Compression method: 1 byte.
  • Compressed text: Variable bytes.
  • bKGD (Background Color):
  • Background color: Varies by color type (e.g., 6 bytes for truecolor).
  • hIST (Image Histogram):
  • Frequencies: 2 bytes per palette entry.
  • pHYs (Physical Pixel Dimensions):
  • Pixels per unit X/Y: 4 bytes each.
  • Unit: 1 byte (0=unknown, 1=meters).
  • sPLT (Suggested Palette) (multiple allowed):
  • Palette name: 1-79 bytes.
  • Sample depth: 1 byte (8 or 16).
  • Entries: Variable (RGB/alpha/frequency per entry).
  • eXIf (EXIF Information):
  • EXIF data: Variable bytes (per EXIF spec).
  • tIME (Image Last Modification Time):
  • Year: 2 bytes.
  • Month/Day/Hour/Minute/Second: 1 byte each.
  • cLLI (Content Light Level Information):
  • Max content light level: 4 bytes.
  • Max frame-average light level: 4 bytes.
  • Animation Properties (APNG):
  • acTL (Animation Control):
  • Number of frames: 4 bytes.
  • Number of plays: 4 bytes (0=infinite).
  • fcTL (Frame Control) (one per frame):
  • Sequence number: 4 bytes.
  • Width/height: 4 bytes each.
  • X/Y offset: 4 bytes each.
  • Delay num/den: 2 bytes each.
  • Dispose op: 1 byte.
  • Blend op: 1 byte.
  • fdAT (Frame Data) (one or more per frame):
  • Sequence number: 4 bytes.
  • Frame data: Variable bytes (zlib-compressed).
  • Other Intrinsic Properties:
  • Image types: Greyscale, truecolor, indexed, with/without alpha.
  • Sample depth scaling: Recorded in sBIT.
  • Filtering: Scanline filters (none, sub, up, average, Paeth).
  • Compression: zlib/deflate across IDAT/fdAT.
  • Color space: Specified via cICP, iCCP, sRGB, gAMA+cHRM.
  • Interlacing: Adam7 (7 passes) or none.
  • Media type: image/png or image/apng.

Two direct download links for .PNG files:

Ghost blog embedded HTML JavaScript for drag-and-drop PNG property dump:

## PNG File Format Specifications The official specifications for the .PNG file format are detailed in the Portable Network Graphics (PNG) Specification (Third Edition) published by the W3C on June 24, 2025. This edition includes updates for HDR support, official EXIF integration, and animated PNG (APNG) enhancements. The format is designed for lossless, portable, well-compressed storage of raster images, supporting static and animated content with features like transparency, color management, and metadata. 1. **List of all properties of this file format intrinsic to its file system**: Based on the specification, the intrinsic properties refer to the structural elements and metadata fields embedded within the PNG file structure. These include the file signature, chunk formats, and data fields from critical and ancillary chunks. Here's a comprehensive list: - **File Signature**: An 8-byte sequence: `89 50 4E 47 0D 0A 1A 0A` (in hexadecimal), identifying the file as PNG. - **General Chunk Properties** (applies to all chunks): - Length: 4-byte unsigned integer (0 to 2^31-1). - Type: 4-byte ASCII code (e.g., 'IHDR'); bits indicate critical/ancillary, public/private, safe-to-copy. - Data: Variable-length bytes specific to the chunk type. - CRC: 4-byte cyclic redundancy check for error detection. - **Critical Chunk Properties**: - **IHDR (Image Header)**: - Width: 4-byte unsigned integer (>0). - Height: 4-byte unsigned integer (>0). - Bit depth: 1 byte (1, 2, 4, 8, or 16, depending on color type). - Color type: 1 byte (0=greyscale, 2=truecolor, 3=indexed-color, 4=greyscale+alpha, 6=truecolor+alpha). - Compression method: 1 byte (0=deflate/zlib). - Filter method: 1 byte (0=adaptive filtering). - Interlace method: 1 byte (0=none, 1=Adam7). - **PLTE (Palette)** (optional, required for color type 3): - Palette entries: Array of 3-byte RGB values (1-256 entries). - **IDAT (Image Data)** (one or more): - Compressed data: zlib/deflate stream of filtered scanlines. - **IEND (Image Trailer)**: - No data (empty). - **Ancillary Chunk Properties**: - **tRNS (Transparency)** (optional, for color types 0, 2, 3): - Transparent color or alpha values: Varies by color type (e.g., 2-byte greyscale for type 0, array of 1-byte alphas for type 3). - **cHRM (Primary Chromaticities and White Point)**: - White point x/y: 4 bytes each. - Red x/y: 4 bytes each. - Green x/y: 4 bytes each. - Blue x/y: 4 bytes each. - **gAMA (Image Gamma)**: - Gamma value: 4-byte unsigned integer (×100000). - **iCCP (Embedded ICC Profile)**: - Profile name: 1-79 bytes. - Compression method: 1 byte (0=zlib). - Compressed profile: Variable bytes. - **sBIT (Significant Bits)**: - Significant bits per channel: 1-4 bytes depending on color type. - **sRGB (Standard RGB Color Space)**: - Rendering intent: 1 byte (0=perceptual, 1=relative colorimetric, 2=saturation, 3=absolute colorimetric). - **cICP (Coding-Independent Code Points)**: - Color primaries: 1 byte. - Transfer function: 1 byte. - Matrix coefficients: 1 byte. - Video full range flag: 1 byte. - **mDCV (Mastering Display Color Volume)**: - Chromaticities for R/G/B/white point: Multiple 4-byte values. - Max/min luminance: 4 bytes each. - **iTXt (International Textual Data)** (multiple allowed): - Keyword: 1-79 bytes. - Compression flag/method: 1 byte each. - Language tag: Variable bytes. - Translated keyword: Variable bytes. - Text: Variable bytes (UTF-8). - **tEXt (Textual Data)** (multiple allowed): - Keyword: 1-79 bytes. - Text: Variable bytes (Latin-1). - **zTXt (Compressed Textual Data)** (multiple allowed): - Keyword: 1-79 bytes. - Compression method: 1 byte. - Compressed text: Variable bytes. - **bKGD (Background Color)**: - Background color: Varies by color type (e.g., 6 bytes for truecolor). - **hIST (Image Histogram)**: - Frequencies: 2 bytes per palette entry. - **pHYs (Physical Pixel Dimensions)**: - Pixels per unit X/Y: 4 bytes each. - Unit: 1 byte (0=unknown, 1=meters). - **sPLT (Suggested Palette)** (multiple allowed): - Palette name: 1-79 bytes. - Sample depth: 1 byte (8 or 16). - Entries: Variable (RGB/alpha/frequency per entry). - **eXIf (EXIF Information)**: - EXIF data: Variable bytes (per EXIF spec). - **tIME (Image Last Modification Time)**: - Year: 2 bytes. - Month/Day/Hour/Minute/Second: 1 byte each. - **cLLI (Content Light Level Information)**: - Max content light level: 4 bytes. - Max frame-average light level: 4 bytes. - **Animation Properties (APNG)**: - **acTL (Animation Control)**: - Number of frames: 4 bytes. - Number of plays: 4 bytes (0=infinite). - **fcTL (Frame Control)** (one per frame): - Sequence number: 4 bytes. - Width/height: 4 bytes each. - X/Y offset: 4 bytes each. - Delay num/den: 2 bytes each. - Dispose op: 1 byte. - Blend op: 1 byte. - **fdAT (Frame Data)** (one or more per frame): - Sequence number: 4 bytes. - Frame data: Variable bytes (zlib-compressed). - **Other Intrinsic Properties**: - Image types: Greyscale, truecolor, indexed, with/without alpha. - Sample depth scaling: Recorded in sBIT. - Filtering: Scanline filters (none, sub, up, average, Paeth). - Compression: zlib/deflate across IDAT/fdAT. - Color space: Specified via cICP, iCCP, sRGB, gAMA+cHRM. - Interlacing: Adam7 (7 passes) or none. - Media type: image/png or image/apng. 2. **Two direct download links for .PNG files**: - https://upload.wikimedia.org/wikipedia/commons/7/70/Example.png - https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png 3. **Ghost blog embedded HTML JavaScript for drag-and-drop PNG property dump**: ```html PNG Property Dumper

Drag and Drop PNG File

Drop PNG file here
``` This HTML page allows dragging and dropping a PNG file, parses it client-side, and dumps all properties as JSON to the screen. Note: Full parsing for all chunks is partial; extend parseChunk for complete decoding. 4. **Python class for PNG handling**: ```python import struct import zlib import binascii class PNG: def __init__(self, filename): with open(filename, 'rb') as f: self.data = f.read() self.chunks = [] self.properties = {} self.parse() def parse(self): if self.data[:8] != b'\x89PNG\r\n\x1A\n': raise ValueError('Invalid PNG signature') self.properties['signature'] = '89 50 4E 47 0D 0A 1A 0A' offset = 8 while offset < len(self.data): length = struct.unpack('>I', self.data[offset:offset+4])[0] type_bytes = self.data[offset+4:offset+8] type_str = type_bytes.decode('ascii') chunk_data = self.data[offset+8:offset+8+length] crc = struct.unpack('>I', self.data[offset+8+length:offset+12+length])[0] chunk_prop = self.parse_chunk(type_str, chunk_data) chunk_prop['length'] = length chunk_prop['crc'] = hex(crc).upper()[2:] self.properties[type_str] = chunk_prop self.chunks.append((type_str, chunk_data, crc)) offset += 12 + length if type_str == 'IEND': break def parse_chunk(self, type, data): parsed = {'raw_data': list(data)} if type == 'IHDR': parsed['width'] = struct.unpack('>I', data[0:4])[0] parsed['height'] = struct.unpack('>I', data[4:8])[0] parsed['bit_depth'] = data[8] parsed['color_type'] = data[9] parsed['compression_method'] = data[10] parsed['filter_method'] = data[11] parsed['interlace_method'] = data[12] elif type == 'PLTE': parsed['entries'] = [(data[i], data[i+1], data[i+2]) for i in range(0, len(data), 3)] elif type in ('IDAT', 'fdAT'): parsed['compressed_size'] = len(data) # Could decompress: zlib.decompress(data) elif type == 'tRNS': # Depends on color_type, raw for now pass elif type == 'cHRM': keys = ['white_x', 'white_y', 'red_x', 'red_y', 'green_x', 'green_y', 'blue_x', 'blue_y'] for i, key in enumerate(keys): parsed[key] = struct.unpack('>I', data[i*4:(i+1)*4])[0] / 100000 elif type == 'gAMA': parsed['gamma'] = struct.unpack('>I', data[0:4])[0] / 100000 elif type == 'iCCP': null_pos = data.index(0) parsed['profile_name'] = data[:null_pos].decode('latin-1') parsed['compression_method'] = data[null_pos+1] # compressed_profile = data[null_pos+2:] elif type == 'sBIT': # Varies pass elif type == 'sRGB': parsed['rendering_intent'] = data[0] elif type == 'cICP': parsed['color_primaries'] = data[0] parsed['transfer_function'] = data[1] parsed['matrix_coefficients'] = data[2] parsed['full_range_flag'] = data[3] elif type in ('tEXt', 'zTXt', 'iTXt'): null_pos = data.index(0) parsed['keyword'] = data[:null_pos].decode('latin-1') # Further parsing if type == 'zTXt': parsed['compression_method'] = data[null_pos+1] # Decompress if needed elif type == 'iTXt': # More fields pass elif type == 'bKGD': # Varies pass elif type == 'hIST': parsed['frequencies'] = [struct.unpack('>H', data[i:i+2])[0] for i in range(0, len(data), 2)] elif type == 'pHYs': parsed['pixels_per_unit_x'] = struct.unpack('>I', data[0:4])[0] parsed['pixels_per_unit_y'] = struct.unpack('>I', data[4:8])[0] parsed['unit'] = data[8] elif type == 'sPLT': null_pos = data.index(0) parsed['palette_name'] = data[:null_pos].decode('latin-1') parsed['sample_depth'] = data[null_pos+1] # Entries elif type == 'eXIf': parsed['exif_data'] = 'Raw EXIF' elif type == 'tIME': parsed['year'] = struct.unpack('>H', data[0:2])[0] parsed['month'] = data[2] parsed['day'] = data[3] parsed['hour'] = data[4] parsed['minute'] = data[5] parsed['second'] = data[6] elif type == 'cLLI': parsed['max_cll'] = struct.unpack('>I', data[0:4])[0] parsed['max_fall'] = struct.unpack('>I', data[4:8])[0] elif type == 'acTL': parsed['num_frames'] = struct.unpack('>I', data[0:4])[0] parsed['num_plays'] = struct.unpack('>I', data[4:8])[0] elif type == 'fcTL': parsed['sequence_number'] = struct.unpack('>I', data[0:4])[0] parsed['width'] = struct.unpack('>I', data[4:8])[0] parsed['height'] = struct.unpack('>I', data[8:12])[0] parsed['x_offset'] = struct.unpack('>I', data[12:16])[0] parsed['y_offset'] = struct.unpack('>I', data[16:20])[0] parsed['delay_num'] = struct.unpack('>H', data[20:22])[0] parsed['delay_den'] = struct.unpack('>H', data[22:24])[0] parsed['dispose_op'] = data[24] parsed['blend_op'] = data[25] # Add more for other chunks return parsed def print_properties(self): import json print(json.dumps(self.properties, indent=2)) def write(self, filename): # Rebuild data with current chunks (assumes no modifications or recalculates CRC if needed) new_data = b'\x89PNG\r\n\x1A\n' for type_str, chunk_data, crc in self.chunks: length = len(chunk_data) type_bytes = type_str.encode('ascii') crc_bytes = struct.pack('>I', crc) # Use original CRC; recalculate if modified new_data += struct.pack('>I', length) + type_bytes + chunk_data + crc_bytes with open(filename, 'wb') as f: f.write(new_data) # Example usage: # png = PNG('example.png') # png.print_properties() # png.write('output.png') ``` This class opens a PNG, decodes and reads properties, prints them to console as JSON, and can write the file back. For full write support with modifications, update chunks and recalculate CRC using zlib.crc32. 5. **Java class for PNG handling**: ```java import java.io.*; import java.nio.*; import java.util.*; import java.util.zip.CRC32; public class PNG { private byte[] data; private Map properties = new HashMap<>(); private List chunks = new ArrayList<>(); static class Chunk { String type; byte[] chunkData; long crc; } public PNG(String filename) throws IOException { try (FileInputStream fis = new FileInputStream(filename)) { data = fis.readAllBytes(); } parse(); } private void parse() { ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN); if (!checkSignature(bb)) { throw new RuntimeException("Invalid PNG signature"); } properties.put("signature", "89 50 4E 47 0D 0A 1A 0A"); int offset = 8; while (offset < data.length) { int length = bb.getInt(offset); String type = new String(data, offset + 4, 4, "ASCII"); byte[] chunkData = Arrays.copyOfRange(data, offset + 8, offset + 8 + length); long crc = Integer.toUnsignedLong(bb.getInt(offset + 8 + length)); Map chunkProp = parseChunk(type, chunkData); chunkProp.put("length", length); chunkProp.put("crc", Long.toHexString(crc).toUpperCase()); properties.put(type, chunkProp); Chunk chunk = new Chunk(); chunk.type = type; chunk.chunkData = chunkData; chunk.crc = crc; chunks.add(chunk); offset += 12 + length; if (type.equals("IEND")) break; } } private boolean checkSignature(ByteBuffer bb) { byte[] sig = {(byte)0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}; for (int i = 0; i < 8; i++) { if (bb.get(i) != sig[i]) return false; } return true; } private Map parseChunk(String type, byte[] data) { Map parsed = new HashMap<>(); parsed.put("raw_data", data.clone()); ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN); switch (type) { case "IHDR": parsed.put("width", bb.getInt(0)); parsed.put("height", bb.getInt(4)); parsed.put("bit_depth", Byte.toUnsignedInt(bb.get(8))); parsed.put("color_type", Byte.toUnsignedInt(bb.get(9))); parsed.put("compression_method", Byte.toUnsignedInt(bb.get(10))); parsed.put("filter_method", Byte.toUnsignedInt(bb.get(11))); parsed.put("interlace_method", Byte.toUnsignedInt(bb.get(12))); break; case "PLTE": List entries = new ArrayList<>(); for (int i = 0; i < data.length; i += 3) { entries.add(new int[]{Byte.toUnsignedInt(data[i]), Byte.toUnsignedInt(data[i+1]), Byte.toUnsignedInt(data[i+2])}); } parsed.put("entries", entries); break; case "IDAT": case "fdAT": parsed.put("compressed_size", data.length); break; // Add similar parsing for other chunks as in Python default: break; } return parsed; } public void printProperties() { System.out.println(properties); // Simple print; use JSON library for pretty } public void write(String filename) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); baos.write(new byte[]{(byte)0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}); for (Chunk chunk : chunks) { ByteBuffer lengthBb = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(chunk.chunkData.length); baos.write(lengthBb.array()); baos.write(chunk.type.getBytes("ASCII")); baos.write(chunk.chunkData); ByteBuffer crcBb = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt((int) chunk.crc); baos.write(crcBb.array()); } try (FileOutputStream fos = new FileOutputStream(filename)) { fos.write(baos.toByteArray()); } } // To recalculate CRC if modified private long calculateCRC(String type, byte[] chunkData) { CRC32 crc32 = new CRC32(); crc32.update(type.getBytes()); crc32.update(chunkData); return crc32.getValue(); } public static void main(String[] args) throws IOException { PNG png = new PNG("example.png"); png.printProperties(); png.write("output.png"); } } ``` This Java class does similar: reads, parses, prints (basic), writes. Extend parseChunk for more. 6. **JavaScript class for PNG handling**: ```javascript class PNG { constructor(buffer) { this.data = new Uint8Array(buffer); this.properties = {}; this.chunks = []; this.parse(); } parse() { if (!this.checkSignature()) { throw new Error('Invalid PNG signature'); } this.properties.signature = '89 50 4E 47 0D 0A 1A 0A'; let offset = 8; while (offset < this.data.length) { const length = this.readUint32(offset); const type = String.fromCharCode(...this.data.subarray(offset + 4, offset + 8)); const chunkData = this.data.subarray(offset + 8, offset + 8 + length); const crc = this.readUint32(offset + 8 + length); const chunkProp = this.parseChunk(type, chunkData); chunkProp.length = length; chunkProp.crc = crc.toString(16).toUpperCase(); this.properties[type] = chunkProp; this.chunks.push({ type, chunkData: new Uint8Array(chunkData), crc }); offset += 12 + length; if (type === 'IEND') break; } } checkSignature() { const sig = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]; return sig.every((b, i) => this.data[i] === b); } readUint32(offset) { return (this.data[offset] << 24) | (this.data[offset + 1] << 16) | (this.data[offset + 2] << 8) | this.data[offset + 3]; } parseChunk(type, data) { const parsed = { raw_data: Array.from(data) }; // Similar parsing as in HTML script // Extend as needed return parsed; } printProperties() { console.log(JSON.stringify(this.properties, null, 2)); } write() { const output = new Uint8Array(this.data.length); // Approximate size let pos = 0; const sig = new Uint8Array([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]); output.set(sig, pos); pos += 8; for (const chunk of this.chunks) { const lengthBytes = new Uint32Array([chunk.chunkData.length]); output.set(new Uint8Array(lengthBytes.buffer).reverse(), pos); // Big-endian pos += 4; output.set(chunk.type.split('').map(c => c.charCodeAt(0)), pos); pos += 4; output.set(chunk.chunkData, pos); pos += chunk.chunkData.length; const crcBytes = new Uint32Array([chunk.crc]); output.set(new Uint8Array(crcBytes.buffer).reverse(), pos); pos += 4; } return output.subarray(0, pos); } } // Example usage: // const reader = new FileReader(); // reader.onload = (e) => { // const png = new PNG(e.target.result); // png.printProperties(); // // To write: new Blob([png.write()]); // }; // reader.readAsArrayBuffer(file); ``` This JS class parses from ArrayBuffer, prints to console, and can write back as Uint8Array. 7. **C++ class for PNG handling**: ```cpp #include #include #include #include #include #include #include #include // For CRC, but use crc32 function class PNG { private: std::vector data; std::map> properties; struct Chunk { std::string type; std::vector chunkData; uint32_t crc; }; std::vector chunks; public: PNG(const std::string& filename) { std::ifstream file(filename, std::ios::binary); if (!file) throw std::runtime_error("File not found"); data.assign((std::istreambuf_iterator(file)), std::istreambuf_iterator()); parse(); } void parse() { if (!checkSignature()) { throw std::runtime_error("Invalid PNG signature"); } properties["signature"]["value"] = "89 50 4E 47 0D 0A 1A 0A"; size_t offset = 8; while (offset < data.size()) { uint32_t length = readUint32(offset); std::string type(reinterpret_cast(&data[offset + 4]), 4); std::vector chunkData(data.begin() + offset + 8, data.begin() + offset + 8 + length); uint32_t crc = readUint32(offset + 8 + length); auto chunkProp = parseChunk(type, chunkData); chunkProp["length"] = std::to_string(length); chunkProp["crc"] = toHex(crc); properties[type] = chunkProp; Chunk chunk; chunk.type = type; chunk.chunkData = chunkData; chunk.crc = crc; chunks.push_back(chunk); offset += 12 + length; if (type == "IEND") break; } } bool checkSignature() { uint8_t sig[8] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}; for (int i = 0; i < 8; ++i) { if (data[i] != sig[i]) return false; } return true; } uint32_t readUint32(size_t offset) { return (data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3]; } std::map parseChunk(const std::string& type, const std::vector& chunkData) { std::map parsed; // Raw data as hex string or something std::ostringstream oss; for (auto b : chunkData) oss << std::hex << std::setw(2) << std::setfill('0') << (int)b << " "; parsed["raw_data"] = oss.str(); if (type == "IHDR") { parsed["width"] = std::to_string(readUint32(0)); parsed["height"] = std::to_string(readUint32(4)); parsed["bit_depth"] = std::to_string(data[8]); // etc. } // Add more return parsed; } std::string toHex(uint32_t val) { std::ostringstream oss; oss << std::hex << std::uppercase << val; return oss.str(); } void printProperties() { for (const auto& [key, val] : properties) { std::cout << key << ": " << std::endl; for (const auto& [subkey, subval] : val) { std::cout << " " << subkey << ": " << subval << std::endl; } } } void write(const std::string& filename) { std::ofstream file(filename, std::ios::binary); uint8_t sig[8] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}; file.write(reinterpret_cast(sig), 8); for (const auto& chunk : chunks) { uint32_t length = chunk.chunkData.size(); file.write(reinterpret_cast(&length), 4); // Note: big-endian, assume host is little, swap if needed file.write(chunk.type.c_str(), 4); file.write(reinterpret_cast(chunk.chunkData.data()), length); file.write(reinterpret_cast(&chunk.crc), 4); } } }; // Example: // int main() { // PNG png("example.png"); // png.printProperties(); // png.write("output.png"); // return 0; // } ``` This C++ class (using std::map for properties) reads, parses, prints to console, and writes. For big-endian, add byte swaps if host is little-endian. Extend parseChunk.

This HTML page allows dragging and dropping a PNG file, parses it client-side, and dumps all properties as JSON to the screen. Note: Full parsing for all chunks is partial; extend parseChunk for complete decoding.

  1. Python class for PNG handling:
import struct
import zlib
import binascii

class PNG:
    def __init__(self, filename):
        with open(filename, 'rb') as f:
            self.data = f.read()
        self.chunks = []
        self.properties = {}
        self.parse()

    def parse(self):
        if self.data[:8] != b'\x89PNG\r\n\x1A\n':
            raise ValueError('Invalid PNG signature')
        self.properties['signature'] = '89 50 4E 47 0D 0A 1A 0A'

        offset = 8
        while offset < len(self.data):
            length = struct.unpack('>I', self.data[offset:offset+4])[0]
            type_bytes = self.data[offset+4:offset+8]
            type_str = type_bytes.decode('ascii')
            chunk_data = self.data[offset+8:offset+8+length]
            crc = struct.unpack('>I', self.data[offset+8+length:offset+12+length])[0]

            chunk_prop = self.parse_chunk(type_str, chunk_data)
            chunk_prop['length'] = length
            chunk_prop['crc'] = hex(crc).upper()[2:]
            self.properties[type_str] = chunk_prop

            self.chunks.append((type_str, chunk_data, crc))

            offset += 12 + length
            if type_str == 'IEND':
                break

    def parse_chunk(self, type, data):
        parsed = {'raw_data': list(data)}
        if type == 'IHDR':
            parsed['width'] = struct.unpack('>I', data[0:4])[0]
            parsed['height'] = struct.unpack('>I', data[4:8])[0]
            parsed['bit_depth'] = data[8]
            parsed['color_type'] = data[9]
            parsed['compression_method'] = data[10]
            parsed['filter_method'] = data[11]
            parsed['interlace_method'] = data[12]
        elif type == 'PLTE':
            parsed['entries'] = [(data[i], data[i+1], data[i+2]) for i in range(0, len(data), 3)]
        elif type in ('IDAT', 'fdAT'):
            parsed['compressed_size'] = len(data)
            # Could decompress: zlib.decompress(data)
        elif type == 'tRNS':
            # Depends on color_type, raw for now
            pass
        elif type == 'cHRM':
            keys = ['white_x', 'white_y', 'red_x', 'red_y', 'green_x', 'green_y', 'blue_x', 'blue_y']
            for i, key in enumerate(keys):
                parsed[key] = struct.unpack('>I', data[i*4:(i+1)*4])[0] / 100000
        elif type == 'gAMA':
            parsed['gamma'] = struct.unpack('>I', data[0:4])[0] / 100000
        elif type == 'iCCP':
            null_pos = data.index(0)
            parsed['profile_name'] = data[:null_pos].decode('latin-1')
            parsed['compression_method'] = data[null_pos+1]
            # compressed_profile = data[null_pos+2:]
        elif type == 'sBIT':
            # Varies
            pass
        elif type == 'sRGB':
            parsed['rendering_intent'] = data[0]
        elif type == 'cICP':
            parsed['color_primaries'] = data[0]
            parsed['transfer_function'] = data[1]
            parsed['matrix_coefficients'] = data[2]
            parsed['full_range_flag'] = data[3]
        elif type in ('tEXt', 'zTXt', 'iTXt'):
            null_pos = data.index(0)
            parsed['keyword'] = data[:null_pos].decode('latin-1')
            # Further parsing
            if type == 'zTXt':
                parsed['compression_method'] = data[null_pos+1]
                # Decompress if needed
            elif type == 'iTXt':
                # More fields
                pass
        elif type == 'bKGD':
            # Varies
            pass
        elif type == 'hIST':
            parsed['frequencies'] = [struct.unpack('>H', data[i:i+2])[0] for i in range(0, len(data), 2)]
        elif type == 'pHYs':
            parsed['pixels_per_unit_x'] = struct.unpack('>I', data[0:4])[0]
            parsed['pixels_per_unit_y'] = struct.unpack('>I', data[4:8])[0]
            parsed['unit'] = data[8]
        elif type == 'sPLT':
            null_pos = data.index(0)
            parsed['palette_name'] = data[:null_pos].decode('latin-1')
            parsed['sample_depth'] = data[null_pos+1]
            # Entries
        elif type == 'eXIf':
            parsed['exif_data'] = 'Raw EXIF'
        elif type == 'tIME':
            parsed['year'] = struct.unpack('>H', data[0:2])[0]
            parsed['month'] = data[2]
            parsed['day'] = data[3]
            parsed['hour'] = data[4]
            parsed['minute'] = data[5]
            parsed['second'] = data[6]
        elif type == 'cLLI':
            parsed['max_cll'] = struct.unpack('>I', data[0:4])[0]
            parsed['max_fall'] = struct.unpack('>I', data[4:8])[0]
        elif type == 'acTL':
            parsed['num_frames'] = struct.unpack('>I', data[0:4])[0]
            parsed['num_plays'] = struct.unpack('>I', data[4:8])[0]
        elif type == 'fcTL':
            parsed['sequence_number'] = struct.unpack('>I', data[0:4])[0]
            parsed['width'] = struct.unpack('>I', data[4:8])[0]
            parsed['height'] = struct.unpack('>I', data[8:12])[0]
            parsed['x_offset'] = struct.unpack('>I', data[12:16])[0]
            parsed['y_offset'] = struct.unpack('>I', data[16:20])[0]
            parsed['delay_num'] = struct.unpack('>H', data[20:22])[0]
            parsed['delay_den'] = struct.unpack('>H', data[22:24])[0]
            parsed['dispose_op'] = data[24]
            parsed['blend_op'] = data[25]
        # Add more for other chunks
        return parsed

    def print_properties(self):
        import json
        print(json.dumps(self.properties, indent=2))

    def write(self, filename):
        # Rebuild data with current chunks (assumes no modifications or recalculates CRC if needed)
        new_data = b'\x89PNG\r\n\x1A\n'
        for type_str, chunk_data, crc in self.chunks:
            length = len(chunk_data)
            type_bytes = type_str.encode('ascii')
            crc_bytes = struct.pack('>I', crc)  # Use original CRC; recalculate if modified
            new_data += struct.pack('>I', length) + type_bytes + chunk_data + crc_bytes
        with open(filename, 'wb') as f:
            f.write(new_data)

# Example usage:
# png = PNG('example.png')
# png.print_properties()
# png.write('output.png')

This class opens a PNG, decodes and reads properties, prints them to console as JSON, and can write the file back. For full write support with modifications, update chunks and recalculate CRC using zlib.crc32.

  1. Java class for PNG handling:
import java.io.*;
import java.nio.*;
import java.util.*;
import java.util.zip.CRC32;

public class PNG {
    private byte[] data;
    private Map<String, Object> properties = new HashMap<>();
    private List<Chunk> chunks = new ArrayList<>();

    static class Chunk {
        String type;
        byte[] chunkData;
        long crc;
    }

    public PNG(String filename) throws IOException {
        try (FileInputStream fis = new FileInputStream(filename)) {
            data = fis.readAllBytes();
        }
        parse();
    }

    private void parse() {
        ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN);
        if (!checkSignature(bb)) {
            throw new RuntimeException("Invalid PNG signature");
        }
        properties.put("signature", "89 50 4E 47 0D 0A 1A 0A");

        int offset = 8;
        while (offset < data.length) {
            int length = bb.getInt(offset);
            String type = new String(data, offset + 4, 4, "ASCII");
            byte[] chunkData = Arrays.copyOfRange(data, offset + 8, offset + 8 + length);
            long crc = Integer.toUnsignedLong(bb.getInt(offset + 8 + length));

            Map<String, Object> chunkProp = parseChunk(type, chunkData);
            chunkProp.put("length", length);
            chunkProp.put("crc", Long.toHexString(crc).toUpperCase());

            properties.put(type, chunkProp);

            Chunk chunk = new Chunk();
            chunk.type = type;
            chunk.chunkData = chunkData;
            chunk.crc = crc;
            chunks.add(chunk);

            offset += 12 + length;
            if (type.equals("IEND")) break;
        }
    }

    private boolean checkSignature(ByteBuffer bb) {
        byte[] sig = {(byte)0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
        for (int i = 0; i < 8; i++) {
            if (bb.get(i) != sig[i]) return false;
        }
        return true;
    }

    private Map<String, Object> parseChunk(String type, byte[] data) {
        Map<String, Object> parsed = new HashMap<>();
        parsed.put("raw_data", data.clone());
        ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN);
        switch (type) {
            case "IHDR":
                parsed.put("width", bb.getInt(0));
                parsed.put("height", bb.getInt(4));
                parsed.put("bit_depth", Byte.toUnsignedInt(bb.get(8)));
                parsed.put("color_type", Byte.toUnsignedInt(bb.get(9)));
                parsed.put("compression_method", Byte.toUnsignedInt(bb.get(10)));
                parsed.put("filter_method", Byte.toUnsignedInt(bb.get(11)));
                parsed.put("interlace_method", Byte.toUnsignedInt(bb.get(12)));
                break;
            case "PLTE":
                List<int[]> entries = new ArrayList<>();
                for (int i = 0; i < data.length; i += 3) {
                    entries.add(new int[]{Byte.toUnsignedInt(data[i]), Byte.toUnsignedInt(data[i+1]), Byte.toUnsignedInt(data[i+2])});
                }
                parsed.put("entries", entries);
                break;
            case "IDAT":
            case "fdAT":
                parsed.put("compressed_size", data.length);
                break;
            // Add similar parsing for other chunks as in Python
            default:
                break;
        }
        return parsed;
    }

    public void printProperties() {
        System.out.println(properties);  // Simple print; use JSON library for pretty
    }

    public void write(String filename) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        baos.write(new byte[]{(byte)0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A});
        for (Chunk chunk : chunks) {
            ByteBuffer lengthBb = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(chunk.chunkData.length);
            baos.write(lengthBb.array());
            baos.write(chunk.type.getBytes("ASCII"));
            baos.write(chunk.chunkData);
            ByteBuffer crcBb = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt((int) chunk.crc);
            baos.write(crcBb.array());
        }
        try (FileOutputStream fos = new FileOutputStream(filename)) {
            fos.write(baos.toByteArray());
        }
    }

    // To recalculate CRC if modified
    private long calculateCRC(String type, byte[] chunkData) {
        CRC32 crc32 = new CRC32();
        crc32.update(type.getBytes());
        crc32.update(chunkData);
        return crc32.getValue();
    }

    public static void main(String[] args) throws IOException {
        PNG png = new PNG("example.png");
        png.printProperties();
        png.write("output.png");
    }
}

This Java class does similar: reads, parses, prints (basic), writes. Extend parseChunk for more.

  1. JavaScript class for PNG handling:
class PNG {
    constructor(buffer) {
        this.data = new Uint8Array(buffer);
        this.properties = {};
        this.chunks = [];
        this.parse();
    }

    parse() {
        if (!this.checkSignature()) {
            throw new Error('Invalid PNG signature');
        }
        this.properties.signature = '89 50 4E 47 0D 0A 1A 0A';

        let offset = 8;
        while (offset < this.data.length) {
            const length = this.readUint32(offset);
            const type = String.fromCharCode(...this.data.subarray(offset + 4, offset + 8));
            const chunkData = this.data.subarray(offset + 8, offset + 8 + length);
            const crc = this.readUint32(offset + 8 + length);

            const chunkProp = this.parseChunk(type, chunkData);
            chunkProp.length = length;
            chunkProp.crc = crc.toString(16).toUpperCase();

            this.properties[type] = chunkProp;

            this.chunks.push({ type, chunkData: new Uint8Array(chunkData), crc });

            offset += 12 + length;
            if (type === 'IEND') break;
        }
    }

    checkSignature() {
        const sig = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
        return sig.every((b, i) => this.data[i] === b);
    }

    readUint32(offset) {
        return (this.data[offset] << 24) | (this.data[offset + 1] << 16) | (this.data[offset + 2] << 8) | this.data[offset + 3];
    }

    parseChunk(type, data) {
        const parsed = { raw_data: Array.from(data) };
        // Similar parsing as in HTML script
        // Extend as needed
        return parsed;
    }

    printProperties() {
        console.log(JSON.stringify(this.properties, null, 2));
    }

    write() {
        const output = new Uint8Array(this.data.length); // Approximate size
        let pos = 0;
        const sig = new Uint8Array([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
        output.set(sig, pos);
        pos += 8;
        for (const chunk of this.chunks) {
            const lengthBytes = new Uint32Array([chunk.chunkData.length]);
            output.set(new Uint8Array(lengthBytes.buffer).reverse(), pos); // Big-endian
            pos += 4;
            output.set(chunk.type.split('').map(c => c.charCodeAt(0)), pos);
            pos += 4;
            output.set(chunk.chunkData, pos);
            pos += chunk.chunkData.length;
            const crcBytes = new Uint32Array([chunk.crc]);
            output.set(new Uint8Array(crcBytes.buffer).reverse(), pos);
            pos += 4;
        }
        return output.subarray(0, pos);
    }
}

// Example usage:
// const reader = new FileReader();
// reader.onload = (e) => {
//     const png = new PNG(e.target.result);
//     png.printProperties();
//     // To write: new Blob([png.write()]);
// };
// reader.readAsArrayBuffer(file);

This JS class parses from ArrayBuffer, prints to console, and can write back as Uint8Array.

  1. C++ class for PNG handling:
#include <iostream>
#include <fstream>
#include <vector>
#include <map>
#include <string>
#include <cstdint>
#include <iomanip>
#include <zlib.h> // For CRC, but use crc32 function

class PNG {
private:
    std::vector<uint8_t> data;
    std::map<std::string, std::map<std::string, std::string>> properties;
    struct Chunk {
        std::string type;
        std::vector<uint8_t> chunkData;
        uint32_t crc;
    };
    std::vector<Chunk> chunks;

public:
    PNG(const std::string& filename) {
        std::ifstream file(filename, std::ios::binary);
        if (!file) throw std::runtime_error("File not found");
        data.assign((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
        parse();
    }

    void parse() {
        if (!checkSignature()) {
            throw std::runtime_error("Invalid PNG signature");
        }
        properties["signature"]["value"] = "89 50 4E 47 0D 0A 1A 0A";

        size_t offset = 8;
        while (offset < data.size()) {
            uint32_t length = readUint32(offset);
            std::string type(reinterpret_cast<char*>(&data[offset + 4]), 4);
            std::vector<uint8_t> chunkData(data.begin() + offset + 8, data.begin() + offset + 8 + length);
            uint32_t crc = readUint32(offset + 8 + length);

            auto chunkProp = parseChunk(type, chunkData);
            chunkProp["length"] = std::to_string(length);
            chunkProp["crc"] = toHex(crc);

            properties[type] = chunkProp;

            Chunk chunk;
            chunk.type = type;
            chunk.chunkData = chunkData;
            chunk.crc = crc;
            chunks.push_back(chunk);

            offset += 12 + length;
            if (type == "IEND") break;
        }
    }

    bool checkSignature() {
        uint8_t sig[8] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
        for (int i = 0; i < 8; ++i) {
            if (data[i] != sig[i]) return false;
        }
        return true;
    }

    uint32_t readUint32(size_t offset) {
        return (data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3];
    }

    std::map<std::string, std::string> parseChunk(const std::string& type, const std::vector<uint8_t>& chunkData) {
        std::map<std::string, std::string> parsed;
        // Raw data as hex string or something
        std::ostringstream oss;
        for (auto b : chunkData) oss << std::hex << std::setw(2) << std::setfill('0') << (int)b << " ";
        parsed["raw_data"] = oss.str();
        if (type == "IHDR") {
            parsed["width"] = std::to_string(readUint32(0));
            parsed["height"] = std::to_string(readUint32(4));
            parsed["bit_depth"] = std::to_string(data[8]);
            // etc.
        }
        // Add more
        return parsed;
    }

    std::string toHex(uint32_t val) {
        std::ostringstream oss;
        oss << std::hex << std::uppercase << val;
        return oss.str();
    }

    void printProperties() {
        for (const auto& [key, val] : properties) {
            std::cout << key << ": " << std::endl;
            for (const auto& [subkey, subval] : val) {
                std::cout << "  " << subkey << ": " << subval << std::endl;
            }
        }
    }

    void write(const std::string& filename) {
        std::ofstream file(filename, std::ios::binary);
        uint8_t sig[8] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
        file.write(reinterpret_cast<char*>(sig), 8);
        for (const auto& chunk : chunks) {
            uint32_t length = chunk.chunkData.size();
            file.write(reinterpret_cast<char*>(&length), 4); // Note: big-endian, assume host is little, swap if needed
            file.write(chunk.type.c_str(), 4);
            file.write(reinterpret_cast<char*>(chunk.chunkData.data()), length);
            file.write(reinterpret_cast<char*>(&chunk.crc), 4);
        }
    }
};

// Example:
// int main() {
//     PNG png("example.png");
//     png.printProperties();
//     png.write("output.png");
//     return 0;
// }

This C++ class (using std::map for properties) reads, parses, prints to console, and writes. For big-endian, add byte swaps if host is little-endian. Extend parseChunk.