PNG Chunk Manipulation for Privacy Focused Metadata Removal

PNG files store data in discrete chunks such that each chunk has a specific purpose. Some chunks define the image data whereas other chunks add metadata to the image.1

You will appreciate the design of PNG files because you can delete metadata chunks without touching image data. The file remains valid, the compression stays exact, and your privacy improves.

Why PNG Needs Different Handling

JPEG uses markers to separate segments whereas PNG uses chunks carrying CRC checksums. JPEG uses lossy algorithms to compress image data whereas PNG uses lossless DEFLATE compression.

In short, JPEG parsing techniques do not work on PNG files. We need a parser that understands the specific rules PNG files follow regarding chunk types, ordering, and validation.

This extra work pays off with true lossless metadata removal. Unlike JPEG, where quality loss creeps in through editing cycles, PNG files withstand repeated modification without degradation.

A Note on Parsing Styles

I usually prefer functional programming and declarative parsers. However, I chose imperative JavaScript for this article to make the concepts accessible to the widest possible audience. Most developers can read a DataView loop, even if they have never seen a parser combinator.

If you want to see how this looks in a functional language, I wrote a separate comparison: Imperative vs Declarative Parsing. It contrasts the manual byte-shuffling you see here with the safer, more expressive approach possible in languages like PureScript.

PNG File Structure

Every PNG begins with an 8-byte signature: 137 80 78 71 13 10 26 10 (decimal). This signature helps software identify PNG files and detect transmission corruption.

After the signature comes a series of chunks. Each chunk follows this format:

[4 bytes: length] [4 bytes: type] [length bytes: data] [4 bytes: CRC]

The length field specifies data bytes only and excludes the type and CRC. A chunk with length 0x00000010 has 16 data bytes, which makes the total chunk size 24 bytes (4+4+16+4).

Here is a minimal PNG structure:

[8 bytes: PNG signature]
IHDR chunk (image header: width, height, bit depth)
IDAT chunk (compressed image data)
IEND chunk (end marker)

Real PNG files contain many more chunks2. Metadata chunks sit between IHDR and IDAT or between IDAT and IEND.

Critical vs Ancillary Chunks

PNG defines chunk types using four ASCII characters. The case of the first letter indicates importance.

Critical chunks (uppercase first letter) Required for proper decoding. Remove these and the image breaks.

  • IHDR Image header (dimensions, color type, compression)
  • PLTE Palette for indexed-color images
  • IDAT Compressed image data
  • IEND End-of-file marker

Ancillary chunks (lowercase first letter) Optional data. Remove these and the image still looks right.

  • tEXt Textual metadata (keywords and values)
  • zTXt Compressed textual metadata
  • iTXt International textual metadata (UTF-8)
  • eXIf EXIF data in PNG format
  • tIME Last modification timestamp
  • pHYs Physical pixel dimensions

Metadata lives exclusively in ancillary chunks3. This makes scrubbing simple: keep critical chunks, discard ancillary chunks that contain identifying information.

Common Metadata Chunks

We need to understand what sits inside each ancillary chunk to decide what to remove.

Text Chunks (tEXt, zTXt, iTXt) Store key-value pairs like “Author: John Smith” or “Description: Vacation photo.” These often contain software names, copyright notices, and creation dates.

EXIF Chunk (eXIf) Stores camera settings, GPS data, timestamps, and device information in the same format as JPEG EXIF. The PNG 1.5 specification introduced this chunk.

Color Profile Chunks (iCCP, sRGB, gAMA, cHRM) Define color space information. While necessary for accurate color reproduction, these can identify specific software or hardware. Remove them if privacy matters more to you than color accuracy.

Physical Dimension Chunk (pHYs) Specifies pixels-per-unit measurements. Might reveal printing or scanning information.

Time Chunk (tIME) Records last modification timestamp. Reveals when you edited the file.

Transparency Chunk (tRNS) Defines transparency for palette or grayscale images. Keep this chunk because removing it can turn transparent areas opaque.

The Scrubbing Algorithm

PNG scrubbing filters chunks instead of parsing complex structures. Here is the process.

Step 1 Validate the PNG Signature

function scrubPng(arrayBuffer) {
    const view = new DataView(arrayBuffer);
    
    // PNG signature: 137 80 78 71 13 10 26 10
    const signature = [137, 80, 78, 71, 13, 10, 26, 10];
    
    for (let i = 0; i < 8; i++) {
        if (view.getUint8(i) !== signature[i]) {
            throw new Error('Invalid PNG signature');
        }
    }
    
    // Start output with signature
    const chunks = [new Uint8Array(arrayBuffer.slice(0, 8))];
    let offset = 8;
    
    // Continue parsing...
}

Step 2 Read Chunks

Parse each chunk by reading its length, type, data, and CRC.

while (offset < arrayBuffer.byteLength) {
    // Read chunk length (4 bytes, big-endian)
    const length = view.getUint32(offset);
    
    // Read chunk type (4 bytes, ASCII)
    const type = String.fromCharCode(
        view.getUint8(offset + 4),
        view.getUint8(offset + 5),
        view.getUint8(offset + 6),
        view.getUint8(offset + 7)
    );
    
    // Calculate chunk boundaries
    const chunkStart = offset;
    const chunkSize = 12 + length; // 4(length) + 4(type) + length + 4(CRC)
    const chunkEnd = offset + chunkSize;
    
    // Extract complete chunk
    const chunkData = new Uint8Array(
        arrayBuffer.slice(chunkStart, chunkEnd)
    );
    
    // Process chunk...
    offset = chunkEnd;
}

Step 3 Filter Chunks

Whitelist critical chunks and specific ancillary chunks you want to preserve. Discard everything else.

const KEEP_CHUNKS = new Set([
    'IHDR',  // Image header (critical)
    'PLTE',  // Palette (critical for indexed images)
    'IDAT',  // Image data (critical)
    'IEND',  // End marker (critical)
    'tRNS',  // Transparency (preserve transparency)
]);

const REMOVE_CHUNKS = new Set([
    'tEXt', 'zTXt', 'iTXt',  // Textual metadata
    'eXIf',                   // EXIF data
    'iCCP', 'sRGB',          // Color profiles
    'gAMA', 'cHRM',          // Gamma and chromaticity
    'pHYs',                   // Physical dimensions
    'tIME',                   // Timestamp
    'sBIT',                   // Significant bits
    'sPLT',                   // Suggested palette
    'hIST',                   // Palette histogram
    'bKGD',                   // Background color
]);

if (KEEP_CHUNKS.has(type)) {
    chunks.push(chunkData);
} else if (REMOVE_CHUNKS.has(type)) {
    // Skip this chunk (metadata removed)
} else {
    // Unknown chunk - discard for safety
}

Step 4 Reconstruct the PNG

Concatenate preserved chunks into a new file.

// Calculate total size
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);

// Allocate output buffer
const output = new Uint8Array(totalLength);

// Copy chunks
let position = 0;
for (const chunk of chunks) {
    output.set(chunk, position);
    position += chunk.length;
}

return new Blob([output], { type: 'image/png' });

CRC Validation and Preservation

Each PNG chunk includes a CRC-32 checksum that covers the chunk type and its data which allows it to detect corruption during transmission or storage.

When chunks are removed, the CRCs of the remaining chunks remain valid making my app’s job simpler.

If chunk data was modified (which this scrubber does not do currently), the app would need to recalculate the CRCs of the remaining chunks.

function calculateCRC(chunkType, chunkData) {
    const crcTable = generateCRCTable();
    let crc = 0xFFFFFFFF;
    
    // Process type bytes
    for (let i = 0; i < 4; i++) {
        const byte = chunkType.charCodeAt(i);
        crc = (crc >>> 8) ^ crcTable[(crc ^ byte) & 0xFF];
    }
    
    // Process data bytes
    for (let i = 0; i < chunkData.length; i++) {
        crc = (crc >>> 8) ^ crcTable[(crc ^ chunkData[i]) & 0xFF];
    }
    
    return (crc ^ 0xFFFFFFFF) >>> 0;
}

Since EXIF Scrubber copies chunks wholesale, we skip this calculation. The existing CRCs travel with their chunks.

Chunk Ordering Rules

PNG imposes strict ordering rules on chunks.

IHDR must be first (after signature). This defines image parameters needed to decode subsequent chunks.

PLTE must precede IDAT (if present). Palette definition must exist before image data references it.

IDAT chunks should be consecutive (though the spec allows ancillary chunks between them). Keeping IDATs together improves decoder efficiency.

IEND must be last. This marks the file’s end. Nothing should follow it.

When filtering chunks, preserve their original order. Do not rearrange chunks unless you feel certain about dependencies. The safest approach involves copying chunks in the order you encounter them and skipping only those on your removal list.

What Gets Removed

This chunk-filtering approach eliminates several metadata categories.

Textual Metadata Author names, descriptions, keywords, copyright notices stored in tEXt/zTXt/iTXt chunks.

EXIF Data Camera settings, GPS coordinates, timestamps, device identifiers stored in eXIf chunks.

Color Management ICC profiles (iCCP) and color space definitions (sRGB, gAMA, cHRM) that can identify software or hardware.

Physical Dimensions pHYs chunks revealing DPI or scanning resolution.

Timestamps tIME chunks showing when the file was last modified.

Background Colors bKGD chunks suggesting display colors.

The result? A PNG containing only pixel data and essential decoding information.

Preserving Transparency

The tRNS chunk defines transparency for palette-based and grayscale PNG images. RGB images use an alpha channel instead, stored in IDAT.

Remove tRNS from a palette image, and transparent pixels become opaque. This changes the image’s appearance, which violates the goal of invisible metadata removal.

Always whitelist tRNS to preserve transparency:

const KEEP_CHUNKS = new Set([
    'IHDR', 'PLTE', 'IDAT', 'IEND',
    'tRNS',  // Preserve transparency
]);

This guarantees scrubbed images look just like the originals.

Benefits of Chunk Filtering

True Lossless Processing PNG uses lossless compression. Filtering chunks never recompresses image data, so quality remains perfect.

Predictable File Size Removed chunks reduce file size in proportion to metadata volume.

Format Compliance The output follows PNG specification to the letter. All decoders accept it.

Speed Copying bytes is faster than decoding and re-encoding images.

Simplicity Chunk filtering logic beats JPEG segment parsing in simplicity. We handle fewer edge cases.

When to Use Canvas Fallback

Binary chunk filtering fails for specific cases.

Corrupted PNG files Missing chunks, invalid CRCs, or incorrect lengths cause parsing errors.

Non-standard PNGs Some tools create “PNG” files with proprietary chunks that violate the spec.

Animated PNGs (APNG) These use additional chunks (acTL, fcTL, fdAT) that require special handling. Binary scrubbing works but might remove animation data by mistake.

For these edge cases, fall back to canvas processing:

async function processPng(file) {
    try {
        const buffer = await file.arrayBuffer();
        return scrubPng(buffer);
    } catch (error) {
        console.warn('Chunk parsing failed, using canvas fallback');
        return processImageCanvas(file);
    }
}

Canvas processing sacrifices animated PNGs (flattens to first frame) but guarantees success for any displayable image.

Testing Our Implementation

Here are some tests we can run to verify our PNG scrubber, some more automatable than others.

Visual Inspection
Scrubbed PNGs should look identical to originals, including transparency.
Metadata Verification
Use exiftool -a -G1 scrubbed.png to confirm no metadata remains.
File Size Comparison
Output should be smaller if input contained metadata.
Validator Tools
Online PNG validators can verify chunk structure and CRC correctness.
Transparency Test
Create a PNG with transparent regions. Scrub it. Verify transparency persists.

Conclusion

PNG’s chunk-based architecture makes metadata removal precise and exact. By filtering ancillary chunks while preserving critical ones, we strip identifying information without touching image quality.

This approach powers EXIF Scrubber’s PNG processing, delivering clean files that maintain perfect image quality and transparency. No uploads. No quality loss. Just pure chunk filtering running in your browser.


  1. See PNG Structure on W3C for specification details. ↩︎

  2. See PNG Chunks on libpng.org for specification details. ↩︎

  3. See PNG extensions to v1.2 specification which includes EXIF information. ↩︎

S. Potter

S. Potter

Indie developer of EXIF Scrubber and recovering Site Reliability Engineer with penchant for functional programming.