Formats and Encodings

qip intentionally chooses old & boring open formats:

For qip's one-input/one-output component model, stable interchange formats make QIP components more reusable.

Preferred formats #

Current formats directly supported by a qip command or supported by this repo’s modules in components/:

Examples:

Tradeoffs:

The ZIP listing components assign indices from central-directory order. entry_index counts every explicit archive entry, while file_index counts only regular files. This keeps zip-extract-file.wasm -u file_index=0 pointed at the first file even when directory or symlink entries precede it.

Image container names and pixel format names #

An image component name describes a container, a pixel layout, and sometimes a colour interpretation. bmp-b8g8r8a8-srgb describes a BMP file whose pixel array has four 8-bit channels and whose colour channels are treated as sRGB. By contrast, B8G8R8A8_SRGB without a container name is only a Vulkan pixel format. It does not describe a complete file by itself.

image/bmp: QIP BMP B8G8R8A8 sRGB

+----------------+------------------+-------------------------------+
| BMP file header| DIB header       | pixel array                   |
| "BM", offsets  | size, orientation| [B][G][R][A] [B][G][R][A]... |
+----------------+------------------+-------------------------------+
                                      8   8   8   8 bits = 32 bits

The signed BMP height controls whether rows are stored bottom-up or top-down.
The BMP headers and that row-order rule are part of `bmp-b8g8r8a8-srgb`.
image/ktx2: QIP canonical KTX2 RGBA8 sRGB profile

+----------------+-------------+------------------+-----------------------+
| KTX2 header    | level index | DFD + metadata   | level pixel payload   |
| vkFormat = 43  | offset, size| colour + `rd`    | [R][G][B][A] ...      |
+----------------+-------------+------------------+-----------------------+
       |                                                     |
       +-- VK_FORMAT_R8G8B8A8_SRGB names these payload bytes-+

The KTX2 profile fixes rows to top-down, left-to-right (`KTXorientation=rd`).

The BMP adapters change the container, reverse bottom-up rows when needed, and swap the red and blue byte positions:

bmp-b8g8r8a8-srgb             ktx2-r8g8b8a8-srgb
BMP container                KTX2 container
BGRA, 8 bits per channel     VK_FORMAT_R8G8B8A8_SRGB
sRGB colour                  sRGB colour
top-down or bottom-up    --> canonical top-down (`rd`), RGBA

The floating-point adapters perform a colour and storage conversion instead:

BMP [B,G,R,A] u8 sRGB  -->  KTX2 [R,G,B,A] f32 linear
     4 bytes per pixel          16 bytes per pixel

Use these naming rules for image components:

Name fragmentIt identifiesIt does not identify
bmp-b8g8r8a8-srgbA complete BMP file, four 8-bit channels, and QIP's sRGB interpretationA headerless pixel buffer
bmp-b8g8r8a8-icc-to-srgbA BMP with an explicit source ICC profile that the component converts to sRGBAn already-sRGB input profile
ktx2-r8g8b8a8-srgbA complete KTX2 file with QIP's canonical 8-bit sRGB Vulkan payload formatA BMP or a headerless Canvas ImageData buffer
ktx2-b8g8r8a8-srgbA complete KTX2 file with the named alternate Vulkan payload formatQIP's canonical 8-bit KTX2 profile
ktx2-rgba32floatA complete KTX2 file with QIP's linear RGBA f32 working profileTile-contract RGBA32Float memory
ktx2-rgba32float-display-p3-linearA complete KTX2 file with linear Display P3 RGBA f32, including values outside 0 through 1A guarantee that the browser can present HDR
ktx2-rgba32float-display-p3A complete KTX2 file with transfer-encoded Display P3 RGBA f32Binary16 storage or an 8-bit Display P3 image
b8g8r8a8-srgbA pixel format: four 8-bit channels and sRGB RGB valuesContainer headers, dimensions, row orientation, or mip levels

The -srgb suffix records QIP's supported colour interpretation. It does not claim that ordinary BMP headers identify sRGB reliably. Components that accept this profile treat unprofiled colour channels as sRGB. A BMP with another embedded profile must pass through bmp-b8g8r8a8-icc-to-srgb first.

The bmp- prefix says that the component exchanges a complete BMP file, so bmp-b8g8r8a8-srgb does not imply a raw pixel buffer. B8G8R8A8 spells out the channel widths explicitly: four 8-bit channels and 32 bits per pixel.

Raster conversion limits #

Raster format converters use one decoded-image ceiling: 25,000,000 pixels, with neither dimension above 8192 pixels. A 32-bit BMP at that ceiling needs 100,000,054 bytes including its header. BMP-consuming converters reserve an additional 64 KiB for larger DIB headers and metadata.

Compressed PNG, JPEG, JPEG 2000, and WebP inputs have a separate 64 MiB byte cap. That cap does not replace the pixel limit: a small compressed file that expands beyond 25 MP is rejected before its pixel buffers are written.

The canonical KTX2 resizing components apply the same dimension and pixel ceilings to both input and output. They resample RGBA8 sRGB in linear light and with premultiplied alpha, then return straight-alpha RGBA8 sRGB. Reduction and enlargement are separate components so a pipeline cannot silently use an enlargement kernel for thumbnail generation or a reduction kernel for upscaling. The float32 variants require the exact linear BT.709 or linear Display P3 profile named in the component filename and preserve that profile in output.

These limits cost memory even for small conversions because the modules use fixed Wasm memories. PNG decoding uses fixed scanline batches and reserves about 162 MiB; its simd128 fork uses the same memory with vectorized row operations. BMP-to-PNG reserves about 332 MiB. It uses dynamic Huffman coding while its filtered input fits an 8 MiB token buffer, then switches to fixed Huffman coding for larger images. Both paths are lossless; the large-image path may produce a larger PNG in exchange for bounded scratch memory.

image/x-icon remains a format-specific exception because this repository's ICO component writes one BMP-backed image and the directory entry represents at most 256×256 directly.

Encodings #

Formats and encodings are at different layers:

An exact format is also a component precondition or guarantee. A component which accepts image/png may assume a valid PNG within its documented profile; it does not have to validate the complete file again before expensive work. Passing malformed bytes to that component breaks the caller contract and may trap.

A file extension, HTTP Content-Type, or other untrusted label does not prove that the bytes are valid. Validate at that boundary when later components need to rely on the format. A pass-through PNG validator can accept arbitrary bytes, export failure_modes_per_input_offset, reject malformed input, and expose the accepted bytes as image/png without changing them:

untrusted bytes -> validate PNG -> valid image/png -> expensive PNG transform

This avoids making every downstream PNG component repeat validation. A combined validate-and-transform component is also valid; it accepts the wider byte domain and reports malformed input through its render result. Recipes must not infer validation only from a source's claimed MIME type.

qip currently supports these encodings:

UTF-8 is a valid subset of raw bytes, so a UTF-8 Content output may feed a raw-bytes Content input. The reverse is rejected because arbitrary bytes are not guaranteed to be valid UTF-8.

Hosts validate arbitrary bytes when they enter the UTF-8 domain. Components with input_utf8_cap may then process the input as valid UTF-8 without checking the encoding again. A successful output_utf8_cap result preserves the guarantee for later UTF-8 stages. Debug and Compliance hosts may rescan output to detect a broken component, but normal pipeline execution does not require a scan between known-valid components.

RGBA32Float tiles are not a subtype of Content bytes even though their storage lives in Wasm linear memory. A mixed run pipeline crosses that boundary only through the host's explicit image bridge: image/bmp raw bytes are decoded to RGBA32Float tiles for a contiguous Tile group, then encoded back to image/bmp raw bytes for the next Content step.

Why these defaults:

Quick Decision Guide #

If you need:

When not to use these defaults #