ICO decode support plan
August 8, 2026 · View on GitHub
This document is the implementation plan and eventual capability contract for
PureJsImage's first-party Windows icon decoder. The target is static .ico
files used for favicons, Windows icons, and uploaded web assets. ICO encoding is
intentionally out of scope.
A checked implementation item is already present and tested. An unchecked item is unsupported until implemented and independently validated.
Scope decisions
- Decode
.icofiles only; do not implement ICO encoding - Support multi-image icon files and deterministic image selection
- Support both PNG-backed and DIB-backed icon entries
- Reuse the first-party PNG decoder and keep the DIB row kernel specialized for ICO mask and legacy-alpha semantics without introducing a runtime dependency
- Treat
.icoand MIME metadata as hints; select the codec from file contents - Keep cursor, animated-icon, executable-resource, and shell-resource formats outside v1
Group 1: common ICO decode — required for v1
Directory and image selection
- Parse the six-byte
ICONDIRheader and require reserved value 0, icon type 1, and a non-zero bounded image count - Parse every 16-byte
ICONDIRENTRYwith checked width, height, palette, planes, bit depth, byte length, and offset fields - Interpret directory width or height byte 0 as 256 pixels
- Validate every entry extent before inspecting or decoding its payload
- Report every embedded image's dimensions, bit depth, storage type, alpha capability, and directory index
- Default to the largest image, then highest useful bit depth and alpha, with original directory order as the final tie-breaker
- For a requested resize, select the smallest suitable source that is not smaller than the target; fall back to the largest entry
- Allow explicit directory-index selection
- Reject zero-length, overlapping-invalid, out-of-file, or contradictory entries explicitly
PNG-backed entries
- Detect an embedded PNG by its signature within the validated ICO entry
- Require the complete PNG datastream to remain inside that entry
- Decode indexed, grayscale, RGB, grayscale-alpha, and RGBA PNG entries through the existing first-party PNG codec
- Support common 256x256 PNG icon entries
- Preserve exact PNG alpha
- Reject APNG animation inside an ICO entry rather than silently selecting an arbitrary frame
- Avoid copying the embedded PNG when a bounded view can be passed to the decoder
DIB-backed entries
- Parse common
BITMAPCOREHEADER,BITMAPINFOHEADER, V4, and V5 DIB headers without requiring the normal 14-byte BMP file header - Validate that the stored DIB height describes the combined XOR image and AND mask and derive the actual icon height safely
- Decode bottom-up XOR pixels and four-byte row padding
- Decode 1-, 4-, and 8-bit palette entries
- Decode 16-bit RGB555/RGB565 and valid bitfield layouts
- Decode 24-bit BGR and 32-bit BGRA entries
- Decode the one-bit AND mask with its independent four-byte row padding
- Apply the AND mask to palette, 16-bit, and 24-bit entries
- Prefer meaningful 32-bit alpha while honoring the AND mask where required
- Handle legacy 32-bit icons whose alpha bytes are all zero using a pinned, Windows-compatible opaque/AND-mask fallback
- Preserve partial alpha rather than reducing it to one-bit transparency
- Emit bounded
rgba8pixel blocks into the normal crop, resize, and encoder pipeline
Public behavior
- Register
.icoand the supported ICO MIME types - Return selected-image dimensions and total embedded image count from metadata
- Support ICO-to-PNG, ICO-to-JPEG with explicit alpha flattening, ICO-to-WebP, crop, resize, and contain workflows
- Keep selection deterministic across metadata inspection and pixel decode
Group 2: compatibility — should have
- RLE4 and RLE8 DIB-backed entries through the existing BMP RLE logic
- Unusual but valid DIB header sizes encountered in real Windows resources
- Top-down DIB entries when independently verified consumers accept them
- Entries whose directory bit depth disagrees with the embedded PNG or DIB, using payload metadata while reporting the discrepancy
- Duplicate sizes with different color depths or alpha representations
- Color-profile handling for PNG and V5 DIB entries
- Resolution metadata exposure where present
- Selection by exact size, minimum size, maximum size, or preferred storage type
Nice to have later
- Windows cursor (
.cur, directory type 2) decode with hotspot metadata - Direct extraction of ICO resources from PE executables, DLLs, or resource files as a separately scoped container feature
- Browser- and Windows-specific selection presets if their behavior differs materially from the default policy
- Region decode within the selected DIB entry
Explicitly skip
- ICO encoding
- Animated cursor (
.ani) decode - Executing or interpreting shell extensions and executable resources
- Embedded JPEG or undocumented vendor payloads
- Returning all icon images as one composited canvas
- Guessing dimensions or pixels from malformed directory entries
Memory and safety contract
- Bound input bytes, image count, dimensions, selected pixels, palette entries, PNG expansion, DIB stride, XOR bytes, AND-mask bytes, and decoded output before allocation
- Use checked arithmetic for directory size, entry extents, row strides, doubled DIB heights, palette sizes, masks, and pixel counts
- Inspect directory metadata before decoding any payload
- Decode only the selected image unless the caller explicitly requests another entry
- Never materialize every embedded image merely to choose one
- Keep PNG and DIB payloads as bounded views into the original input
- Preserve bounded-row behavior for the selected image without a duplicate source-sized RGBA bitmap
- Reject recursive container tricks, truncated PNG chunks, malformed DIB headers, overlapping masks, and decompression bombs explicitly
Fixtures and verification
- Pin redistributable first-party mixed-size, 24-bit DIB, and 32-bit DIB icon fixtures
- Cover 16x16, 32x32, 48x48, and 256x256 entries
- Cover PNG-backed icons plus 1/4/8/16/24/32-bit DIB entries
- Cover opaque, one-bit mask, partial-alpha, all-zero-alpha fallback, odd widths, duplicate sizes, and mixed PNG/DIB files
- Validate metadata and selected pixels against Windows and at least one independent ICO decoder
- Test target-aware and explicit-index selection deterministically
- Verify benchmark output before recording speed or memory
- Benchmark metadata, PNG-backed decode, DIB-backed decode, selection, ICO-to-PNG, and favicon resize workflows in isolated processes
- Add malformed directory, offset, length, palette, DIB height, stride, bitfield, PNG extent, XOR, AND-mask, and allocation-limit fixtures
Decode v1 is complete when
- Every Group 1 item is implemented and covered by pinned fixtures
- Unsupported compatibility and deferred inputs fail explicitly
- Independent decoders confirm image count, selection, dimensions, and RGBA pixels
- Common mixed-size favicons decode and resize without decoding every entry
-
npm run checkand the isolated ICO fixture verification pass