Formats and Encodings

Choose a format for each boundary between components or systems. The format must preserve the required data. It must also work with existing tools and resource limits.

QIP uses stable, open formats with simple parsers. Stable formats improve long-term compatibility. Open formats reduce vendor lock-in. Simple formats help people and coding agents write correct implementations.

Many older formats were built for computers with little memory. These formats often divide data into records, rows, or chunks. A program can process one unit and then reuse its working memory. This avoids a complete object tree and a separate allocation for each node.

Sequential processing can reduce peak memory and parser complexity. However, older formats are not always more efficient. BMP is simple to decode, but BMP files are large. TAR supports sequential access, but TAR does not compress data. SQLite supports indexed queries, but SQLite needs random access to its pages.

Before you select a format, answer these questions:

  1. Which data and metadata must the boundary preserve?
  2. Which formats do both systems support?
  3. Does the operation need sequential access or random access?
  4. Is the main constraint file size, CPU time, or working memory?

Images #

Use an uncompressed image inside a processing pipeline when components need direct access to pixels. Use a compressed image at a storage, download, or application boundary.

FormatUse it forTradeoff
image/bmpA simple uncompressed raster boundary.Parsing is simple, but files are large and row order can vary.
image/svg+xmlEditable or resolution-independent paths, diagrams, and text.A raster consumer must render the SVG before processing pixels.
PNGLossless storage and broad interoperability.Decoding needs more code and CPU time than BMP or QIP KTX2.
JPEGSmall photographs for systems that need lossy compression and broad support.JPEG does not preserve alpha and changes pixel values.
WebP or AVIFCompressed application and web boundaries.Confirm that all target tools can decode the selected format.
JPEG XLLossless images or lossy images when the recipient supports JPEG XL.Decoder support is less common than JPEG or WebP; check the target system.
JPEG 2000, GIF, or ICOInteroperation with a system that requires that format.Convert to a QIP working format after input enters the pipeline.

QIP can convert PNG, JPEG, WebP, AVIF, JPEG 2000, and SVG to a working raster format. QIP can write PNG, JPEG, WebP, AVIF, JPEG XL, and ICO. QIP can also process GIF.

Raster limits

Raster converters accept no more than 25,000,000 decoded pixels. Neither dimension can be more than 8192 pixels. Compressed PNG, JPEG, JPEG 2000, and WebP input also has a 64 MiB byte limit. Both limits apply. A small compressed file is still invalid if its decoded image exceeds the pixel limit.

The ICO component writes one BMP-backed image. The image can be no larger than 256 by 256 pixels. See Hard Limits for host memory and execution controls.

QIP KTX2 profiles #

KTX2 is QIP's canonical raster container between Content image components. GUI components also use it for complete rendered frames. QIP supports a narrow, uncompressed subset so components can address pixels directly.

QIP supports two payload layouts in a small subset of KTX2:

ProfilePayloadUse
RGBA8 sRGBVK_FORMAT_R8G8B8A8_SRGB, 4 bytes per pixelGeneral 8-bit Content images.
RGBA32FVK_FORMAT_R32G32B32A32_SFLOAT, 16 bytes per pixelLinear BT.709, linear Display P3, or transfer-encoded Display P3 data.

Declaring the profile in the content type

image/ktx2 alone does not say which profile a component reads or writes, so components declare it with content-type parameters that mirror the KTX2 header and Data Format Descriptor fields:

image/ktx2;vkFormat=R8G8B8A8_SRGB;colorPrimaries=BT709;transferFunction=SRGB
image/ktx2;vkFormat=B8G8R8A8_SRGB;colorPrimaries=BT709;transferFunction=SRGB
image/ktx2;vkFormat=R32G32B32A32_SFLOAT;colorPrimaries=BT709;transferFunction=LINEAR
image/ktx2;vkFormat=R32G32B32A32_SFLOAT;colorPrimaries=DISPLAYP3;transferFunction=LINEAR
image/ktx2;vkFormat=R32G32B32A32_SFLOAT;colorPrimaries=DISPLAYP3;transferFunction=SRGB

The values are the Vulkan VK_FORMAT_* and Khronos Data Format KHR_DF_PRIMARIES_* and KHR_DF_TRANSFER_* enumerants without their prefixes, in the specifications' own casing. Parameter names are case-insensitive; values are case-sensitive. The canonical spelling is the lowercase media type followed by ;name=value for each parameter with no whitespace anywhere, and the runtime rejects other spellings.

A pipeline matches content types on the media type. A parameter a component declares must match when the incoming content also carries it, so an RGBA8 output is refused by an RGBA32F input before the component runs. A bare image/ktx2 still matches any profile, which keeps older components valid.

Example pipelines

Recipes specifies how hosts validate every step of a pipeline; these examples show the KTX2 parameters at work.

An 8-bit pipeline. The PNG decoder emits image/ktx2, the resizer declares and emits the RGBA8 sRGB profile, and the WebP encoder accepts any image/ktx2:

npx @qip.dev/qipx qip.dev run \
  image/png/png-to-ktx2-r8g8b8a8-srgb.wasm \
  image/ktx2/ktx2-r8g8b8a8-srgb-resize-down-lanczos3.wasm \
  image/ktx2/ktx2-r8g8b8a8-srgb-to-webp-lossy.wasm \
  < photo.png > photo.webp

A float pipeline. The two converters move between profiles, and the look component declares linear BT.709 RGBA32F on both sides:

npx @qip.dev/qipx qip.dev run \
  image/png/png-to-ktx2-r8g8b8a8-srgb.wasm \
  image/ktx2/ktx2-r8g8b8a8-srgb-to-ktx2-rgba32float.wasm \
  image/ktx2/ktx2-rgba32float-look-warm-fade.wasm \
  image/ktx2/ktx2-rgba32float-to-ktx2-r8g8b8a8-srgb.wasm \
  image/ktx2/ktx2-r8g8b8a8-or-b8g8r8a8-srgb-to-png.wasm \
  < photo.png > warm.png

An invalid pipeline. The resizer emits RGBA8 sRGB straight into the look component, which needs RGBA32F. Both media types are image/ktx2, so only the parameters reveal the mistake:

npx @qip.dev/qipx qip.dev run \
  image/png/png-to-ktx2-r8g8b8a8-srgb.wasm \
  image/ktx2/ktx2-r8g8b8a8-srgb-resize-down-lanczos3.wasm \
  image/ktx2/ktx2-rgba32float-look-warm-fade.wasm \
  < photo.png > warm.png

Every QIP host refuses it before running any component and exits with status

  1. qip, the Node.js qipx, and the Rust qipx print the same message; the

Rust tool prefixes it with qipx: . The message names the step that could not accept its input and its declared type, what it got and which step produced that, and each parameter that disagrees. When both sides share the media type, the got part lists only the incoming parameters:

step 3 image/ktx2/ktx2-rgba32float-look-warm-fade.wasm expected image/ktx2;vkFormat=R32G32B32A32_SFLOAT;colorPrimaries=BT709;transferFunction=LINEAR, got vkFormat=R8G8B8A8_SRGB;colorPrimaries=BT709;transferFunction=SRGB from step 2 image/ktx2/ktx2-r8g8b8a8-srgb-resize-down-lanczos3.wasm: vkFormat expected R32G32B32A32_SFLOAT got R8G8B8A8_SRGB; transferFunction expected LINEAR got SRGB

When the media types themselves differ, got shows the whole incoming type and the detail reads media type expected image/ktx2 got image/png.

Inserting ktx2-r8g8b8a8-srgb-to-ktx2-rgba32float.wasm between the resizer and the look component makes the pipeline valid again.

Component filenames may abbreviate a profile, such as ktx2-rgba32float-display-p3-linear. The content-type declaration and KTX2 header define the format; the filename does not have to repeat the MIME parameter names or their exact values.

Each profile contains one uncompressed image. The profiles do not accept compressed textures, mipmaps, arrays, or cubemaps. Keep linear float data between operations that need it. Convert the final image when transfer size is the main constraint. See the KTX2 component documentation for the complete profiles.

Text, markup, and structured data #

Text and markup

QIP uses UTF-8 for all text pipelines.

FormatUse it forTradeoff
Plain UTF-8 textText that has no document structure.The format cannot preserve structure that is not in the text itself.
text/markdownDocuments that people must read and edit.Markdown has fewer document semantics than HTML.
text/htmlBrowser documents and structured document fragments.A Document Object Model parser can allocate a large object tree.
text/css and text/javascriptWeb source transformations.Treat source text as code when it crosses a trust boundary.
text/uri-listA sequence of resource identifiers.The format carries a list, not resource metadata.
text/vnd.mermaidDiagrams that must remain text.A presentation system must render the diagram.

The repository also uses registered text/* types for C, Swift, and Zig source text. A streaming parser can process many text formats with bounded working memory. A Document Object Model (DOM) parser allocates a tree instead.

Structured data and databases

FormatUse it forTradeoff
application/jsonSmall structured messages and broad application interoperation.Many JSON APIs allocate a complete value tree.
application/design-tokens+jsonNamed design values shared between design and development tools.The DTCG structure is more verbose than application-specific JSON. Pin components to a published specification version.
application/xmlExisting XML protocols, namespaces, or mixed text and elements.XML has more syntax and processing rules than JSON.
text/csvFlat records that consumers can process in sequence.CSV loses nested structure and data types unless the application defines a schema.
application/vnd.sqlite3Tables, indexes, transactions, and selective queries.SQLite needs page access and a larger runtime than CSV.

QIP palette extractors target the stable DTCG 2025.10 format, not the changing preview draft.

JSON and XML permit streaming parsers. Some APIs still build a complete tree and increase peak memory. Check the parser API when working memory is limited.

SQLite can query a data set without loading the complete data set into memory. Use SQLite when consumers need random access or relational operations. Do not convert indexed or relational data to CSV when consumers still need those features.

Archives and forms #

FormatUse it forTradeoff
application/x-tarA file collection that a component reads or writes in order.TAR does not include compression.
application/zipA compressed archive for users and desktop tools.Some ZIP operations must read the central directory at the end of the file.
application/gzipOne compressed byte stream, such as a .tar.gz or .vcf.gz file.A reader must decompress from the start, unless the file is BGZF.
application/zstdOne Zstandard byte stream, such as a .tar.zst file or an HTTP body sent with Content-Encoding: zstd.Frames compress on their own, so small frames compress poorly.
application/x-www-form-urlencodedSmall named UTF-8 form fields.The format is not suitable for file bodies.
multipart/form-dataForms with files or separate metadata for each part.Boundary parsing is more complex than URL-encoded form parsing.

QIP uses TAR as the sequential archive inside pipelines and ZIP at external boundaries. The ZIP-to-TAR component accepts bounded classic ZIP archives. It supports stored and DEFLATE entries. It rejects ZIP64, encryption, split archives, special file types, and unsafe extraction paths.

bytes/gzip-compress.wasm writes one gzip member. bytes/bgzf-compress.wasm writes BGZF: gzip members of at most 64 KiB each, followed by an empty EOF member. tabix, samtools, and other htslib tools need BGZF to read a file at an indexed offset, and any gunzip still reads it. On 600 KB text files, its output is 3% to 6% larger than one gzip member, because each member compresses on its own and cannot refer to earlier members. Use bytes/gzip-decompress.wasm for any gzip or BGZF input. Use bytes/bgzf-decompress.wasm when the input must be complete, valid BGZF. It rejects plain gzip and files that have lost their EOF member.

bytes/zstd-compress.wasm writes one Zstandard frame with the content size and an XXH64 checksum, and no dictionary. It stores literals raw and codes matches with the predefined FSE tables, so on text its output is about 1.3 to 1.6 times the size of zstd -3. bytes/zstd-decompress.wasm reads any frames that zstd writes without a dictionary, as long as the window is at most 8 MiB, the limit browsers use. Concatenated frames decode to the concatenation of their contents and skippable frames are ignored, so a server can join compressed fragments without decompressing them.

WARC web snapshots #

application/warc carries a complete routed site between whole-site components. Each response record preserves its URI, status, headers, and body. This lets one component check links across pages while another adds routes or converts the finished site to a static TAR archive.

Use qip router warc ... to produce the archive. WARC is larger and less widely supported than ZIP or TAR, so convert it at the boundary where another system needs ordinary files.

Documents and fonts #

FormatUse it forTradeoff
application/pdfFixed page layout and vector artwork.PDF is harder to edit than the source document or SVG.
font/ttfSFNT fonts with TrueType glyf outlines.Consumers need a font parser or an outline conversion step.

QIP can extract text and images from PDF. QIP can also convert a strict SVG subset to PDF/A-2b. Font components can convert TrueType outlines to SVG paths or CSV data.

WebAssembly components #

application/wasm is both QIP's executable component format and a Content format that other components can process. Components such as validators, counters, translators, and instrumentation passes accept a Wasm module and return facts, source code, or another module. This higher-level boundary lets a component inspect or transform another component without adding that logic to the host.

Wasm is executable input, not a general data format. Before you run an untrusted module, apply the validation and execution policies in Hard Limits.

Encodings and pipeline compatibility #

A format defines file or container semantics. Examples include image/svg+xml, application/warc, and image/bmp. An encoding defines the representation of bytes or values in a processing stage.

QIP supports these processing encodings:

Valid UTF-8 is also valid raw byte input. Therefore, UTF-8 Content output can feed a raw-byte Content input. Arbitrary raw bytes cannot feed a UTF-8 input until the host validates the bytes.

RGBA32Float Tile data is not a subtype of Content bytes. The host uses the image bridge to cross this boundary. The host decodes Content image bytes to tiles before a contiguous Tile group. The host encodes the tiles as Content image bytes after the Tile group.

Validation and trade-offs #

Validate data at an untrusted boundary

A content type is a component precondition or guarantee. An image/png component can rely on its documented PNG profile. It does not have to repeat complete validation before processing starts.

A file extension or an HTTP Content-Type value does not prove that the bytes are valid. Validate untrusted data before another component relies on the format:

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

A pass-through validator can reject malformed data and return accepted bytes as image/png. A recipe must not infer validation from a claimed content type.

When not to use these formats

Use an application-specific format when the formats on this page cannot preserve the required semantics. Keep a simple format between QIP components, and add an adapter at system ingress or egress.

Do not use an uncompressed working format for downloads when transfer size is the main constraint. Do not use QIP's narrow KTX2 profiles when another system needs general KTX2 features such as compressed textures or mipmaps. Do not replace a normal application database or object model only to use a QIP pipeline.