> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nika.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# nika:image_compose

> Usage, declared contract and source links for this Nika builtin.

[Builtins catalog](https://docs.nika.sh/reference/builtins.md) / `nika:image_compose`

> [card] Media
>
> Tool family from the canonical registry.

> [card] [invoke](https://docs.nika.sh/reference/language/words/invoke.md)
>
> Call this builtin through the invoke verb.

## Read this contract

This reference preserves the specification at the revision linked below. Source examples are **fragments**: their surrounding tasks, inputs and permissions are not supplied here. Consult [engine status](https://docs.nika.sh/reference/status.md) and `nika catalog --tools --json` for the installed implementation; a registry declaration is not a runtime qualification.

## Usage and behavior

```yaml illustration theme={"system"}
invoke:
  tool: "nika:image_compose"
  args:
    inputs:                              # ordered · 1..=64 PNG paths (each read-gated)
      - out/fx/sunset-duotone.png
      - out/fx/sunset-gameboy.png
      - out/fx/harbour-duotone.png
      - out/fx/harbour-gameboy.png
    out: out/contact-sheet.png           # the artifact · .png (write-gated)
    layout: grid                         # grid (default) | row | column
    scale: fit                           # none (default) | fit | fill
    cell: { width: 256, height: 256 }    # fit · fill only · default: the largest input box
    gap: 6
    padding: 12
    background: "#10141c"
```

The §Media graduate #5 — several PNG images become ONE: a contact sheet
or grid, a side-by-side (`row`), a vertical stack (`column`). Like
`image_fx` it is pure pixel work: NO provider, NO network, NO clock, NO
keys, and **byte-identical output** for identical `(input bytes, args)` —
the artifact's sha256 joins the hash-chained trace, re-render IS the
tamper check, and the recipe (contract tag `image_compose/v1` · each
input's sha256 and size · the resolved layout) rides the artifact as a
PNG `nika` tEXt chunk. The recipe carries no timestamp and no path: the
same bytes composed from another folder are the same file. Identical
bytes already at `out:` = idempotent skip (`skipped_existing`).

| Arg | Contract |
| - | - |
| `inputs` | REQUIRED · an ordered list of 1..=64 source paths · each **PNG v1** (the `image_fx` input law: depth 8 · gray, gray+alpha, RGB or RGBA · no Adam7 — anything else `-003`) · each read rides `permits.fs.read` · placed row-major, `inputs[0]` first |
| `out` | REQUIRED · the artifact path · `.png` · the write rides `permits.fs.write` |
| `layout` | `grid` (default) · `row` (one row, left to right) · `column` (one column, top to bottom) |
| `columns` · `rows` | `grid` only (`-001` with `row` / `column`) · 1..=64 each · both absent: `columns` = ⌈√n⌉ and `rows` = ⌈n ÷ columns⌉ · one given: the other is ⌈n ÷ given⌉ · both given: `columns × rows` ≥ n (`-001` otherwise · trailing cells stay background) |
| `scale` | `none` (default · each image at its own size) · `fit` (scaled up or down, aspect kept, to the largest box inside its cell · letterboxed on `background`) · `fill` (scaled, aspect kept, to cover its cell, then center-cropped to it) |
| `cell` | `{ width, height }` · 1..=16384 each · `fit` / `fill` only (`-001` with `scale: none`) · default: the largest input box (the widest width × the tallest height) |
| `gap` · `padding` | pixels between cells · around the sheet · 0..=1024 each · default 0 |
| `background` | `#rrggbb` or `[r, g, b]` · default `#ffffff` · fills the canvas, the gaps, the letterboxes and the empty trailing cells |

Geometry (NORMATIVE — for a byte-deterministic builtin the arithmetic IS
the wire contract; `round(a ÷ b)` is `⌊(a + ⌊b ÷ 2⌋) ÷ b⌋`, never below 1) ·

* the placed size of a `w × h` input in a `cw × ch` cell: `none` → `w × h` ·
  `fit` → `round(w·ch ÷ h) × ch` when `w·ch ≤ h·cw`, else `cw × round(h·cw ÷ w)` ·
  `fill` → the cover box (`round(w·ch ÷ h) × ch` when `w·ch ≥ h·cw`, else
  `cw × round(h·cw ÷ w)`), cropped to `cw × ch` at `(⌊(sw − cw) ÷ 2⌋, ⌊(sh − ch) ÷ 2⌋)`;
* scaling is the `image_fx` `resize` op with `filter: auto` (linear light)
  to the placed box — the same bytes that op produces;
* tracks: with `fit` / `fill` every column is `cw` wide and every row `ch`
  high; with `none` a column is as wide as its widest image and a row as
  high as its tallest;
* each image is centered in its cell at `(⌊(track_w − pw) ÷ 2⌋, ⌊(track_h − ph) ÷ 2⌋)`;
* the canvas is `2·padding + Σ column widths + gap·(columns − 1)` by
  `2·padding + Σ row heights + gap·(rows − 1)`;
* an input's alpha is composited over `background` BEFORE any scaling,
  per channel `⌊(c·a + bg·(255 − a) + 127) ÷ 255⌋` in sRGB integers · the
  artifact is RGB8 (PNG color type 2).

Caps (normative · `-005`) · each input ≤ 16384 per side · the SUM of the
inputs' pixels ≤ 2^26 · each `fit` / `fill` scaled box (the placed size
above, before the `fill` crop) ≤ 16384 per side and ≤ 2^26 pixels · the
canvas ≤ 16384 per side and ≤ 2^26 pixels — all judged from the PNG
headers and the args BEFORE any decompression or canvas allocation. An
input carries exactly one IHDR, its first chunk (PNG-3): the size the caps
judged is the size decoded, and a second IHDR is `-004`. The input count
(1..=64) is an argument, `-001`.

Output · `&#123; path, sha256, size_bytes, width, height, format, layout,
columns, rows, scale, inputs: [&#123; path, sha256, width, height &#125;],
skipped_existing &#125;` — artifact bytes NEVER ride outputs (the disk law).

**Authority.** The tool grant, `permits.fs.read` for EVERY input and
`permits.fs.write` for `out:`. A static checker judges each literal input
path (or whole bare `const:` reference) as a read and a literal `out:` as a
write, before anything runs: a missing read grant is a check-time
`NIKA-SEC-004`, never a surprise at run, and a read grant an input uses is
a use, never `declared-but-unused` advice. A templated list or element
defers to the run's boundary (`NIKA-SEC-004` there).

Determinism contract (normative) · no wall-clock · no randomness ·
integer/fixed-point pixel math · identical `(input bytes, args)` MUST
produce byte-identical artifacts across platforms and releases within a
stdlib major (the recipe carries the stable contract tag
`image_compose/v1`, never the engine build). An engine that cannot honor
this is not conformant (07-conformance §3). Labels and JPEG input are not
part of v1.

Throws · `NIKA-BUILTIN-IMAGE_COMPOSE-001` invalid arguments (unknown key ·
out-of-range value · `columns`/`rows` outside `grid` · `cell` with
`scale: none` · a grid smaller than the list · `out:` not `.png` ·
`validation_error`) · `-002` an input read failed (`tool_error` · the
message names the index) · `-003` unsupported input format (non-PNG ·
palette · exotic depth/interlace · `validation_error`) · `-004` decode
failed (structural corruption · CRC/Adler mismatch · `tool_error`) ·
`-005` a cap exceeded (`security_error` · never transient) · `-006`
artifact save failed (`tool_error`) — `image_fx`'s ladder, code for code.
Plus the boundary `NIKA-SEC-004` (paths outside `permits.fs`).

***

## Related concepts

> [card] [Arguments](https://docs.nika.sh/reference/language/words/args.md)
>
> Pass the values required by the tool contract.

> [card] [Permissions](https://docs.nika.sh/concepts/security.md)
>
> Understand authority before granting effects.

> [card] [Errors and recovery](https://docs.nika.sh/reference/error-codes.md)
>
> Read diagnostics and choose a recovery policy.

> [card] [Workflow templates](https://docs.nika.sh/guides/templates.md)
>
> Put the fragment inside a complete workflow.

## Contract provenance

[Read the pinned specification](https://github.com/supernovae-st/nika-spec/blob/5cd95cd25c0ff453be266794a03666e5e3f2d7a8/stdlib/builtins-v0.1.md#L979) · [Canonical registry](https://github.com/supernovae-st/nika-spec/blob/5cd95cd25c0ff453be266794a03666e5e3f2d7a8/canon/builtins.yaml).

Tool identity: `nika:image_compose`. Specification revision: `5cd95cd25c0f`.

> [card] [Documentation](https://docs.nika.sh/introduction.md)
>
> Guides, reference contracts and working examples.

> [card] [Language spec](https://github.com/supernovae-st/nika-spec)
>
> the nine-key envelope, Apache-2.0. The contract these docs explain.

> [card] [Engine source](https://github.com/supernovae-st/nika)
>
> Rust, AGPL-3.0-or-later. Every commit public.

> [card] [TypeScript SDK](https://docs.nika.sh/sdk/overview.md)
>
> Use one typed lifecycle locally or against authenticated nika serve HTTP.

> [card] [Editor extension](https://marketplace.visualstudio.com/items?itemName=supernovae.nika)
>
> The editor extension, frozen at 0.120 and not qualified with 0.124: use `nika lsp` and the JSON Schema.

> [card] [Homebrew tap](https://github.com/supernovae-st/homebrew-tap)
>
> `brew install supernovae-st/tap/nika` for the latest tagged CLI release.

> [card] [SuperNovae](https://supernovae.studio)
>
> The Paris studio crafting Nika. 🦋


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.