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

> Documentation for the PNG image output format

# PNG

| Input | Output | Alias |
| ----- | ------ | ----- |
| ✗     | ✔      | ✗     |

<h2 id="description">
  Description
</h2>

Renders the result of a query as a PNG image. This is useful as a built-in visualization tool.

The size of the output image is fixed by the settings
[`output_format_image_width`](/docs/reference/settings/formats/output-format#output_format_image_width) and
[`output_format_image_height`](/docs/reference/settings/formats/output-format#output_format_image_height)
(both default to 1024). Pixels that are not covered by the result are filled with black
(in `RGB` and grayscale modes) or with transparent black (in `RGBA` mode).

The color mode is determined automatically from the column names and types of the result:

| Columns              | Mode                                               |
| -------------------- | -------------------------------------------------- |
| `r`, `g`, `b`        | 8-bit RGB                                          |
| `r`, `g`, `b`, `a`   | 8-bit RGBA                                         |
| `v` of integer type  | 8-bit grayscale                                    |
| `v` of `Float*` type | 8-bit grayscale (values in `[0, 1]` → `[0, 255]`)  |
| `v` of `Bool` type   | Binary (rendered as 8-bit grayscale: `0` or `255`) |

Column names are matched case-insensitively. If the color mode cannot be unambiguously
determined (e.g. unknown column names, mixed `v` with `r`/`g`/`b`/`a`, or one of `r`/`g`/`b` missing),
the query throws an exception.

For pixel channels, integer values are clamped to `[0, 255]` and floating-point values
are clamped to `[0, 1]` and then scaled to `[0, 255]`.

The position of each record in the image is determined by one of two modes:

* **Implicit** (the default — when neither `x` nor `y` is present). Each record corresponds
  to a single pixel; pixels are filled in scanline order: left to right, top to bottom.
* **Explicit** (when `x` and `y` columns are present, both of integer types).
  The `x` and `y` columns give the pixel coordinates. Records with coordinates outside
  the image are silently ignored. In case of multiple records with the same coordinates,
  the last one wins (painter's algorithm).

<h2 id="example-usage">
  Example usage
</h2>

<h3 id="implicit-rgb">
  Implicit coordinates (row-per-pixel), RGB
</h3>

```sql theme={null}
SELECT
    toUInt8(x * 25) AS r,
    toUInt8(y * 25) AS g,
    toUInt8((x + y) * 12) AS b
FROM
(
    SELECT number % 10 AS x, intDiv(number, 10) AS y FROM numbers(100)
)
INTO OUTFILE 'gradient.png'
FORMAT PNG
SETTINGS output_format_image_width = 10, output_format_image_height = 10;
```

<h3 id="explicit-grayscale">
  Explicit coordinates, grayscale
</h3>

```sql theme={null}
SELECT
    toInt32(x) AS x,
    toInt32(y) AS y,
    toUInt8(intensity) AS v
FROM points
INTO OUTFILE 'points.png'
FORMAT PNG
SETTINGS output_format_image_width = 512, output_format_image_height = 512;
```

<h2 id="animation">
  Animation
</h2>

If the result has a `t` column of an integer type, the format produces an animated PNG (`APNG`) instead of a
still image. Records are grouped into frames by the value of `t`, which is the relative time offset of the
frame. Every frame is an independent image: the canvas is empty at the start of each frame, and in the
implicit coordinate mode the cursor restarts from the top-left corner. The `t` column can be combined with
either coordinate mode.

The unit of `t` is given by
[`output_format_image_time_multiplier_seconds`](/docs/reference/settings/formats/output-format#output_format_image_time_multiplier_seconds)
and
[`output_format_image_time_divisor_seconds`](/docs/reference/settings/formats/output-format#output_format_image_time_divisor_seconds):
one unit of `t` is `output_format_image_time_multiplier_seconds / output_format_image_time_divisor_seconds`
seconds. With the default values (`1` and `60`) one unit of `t` is 1/60 of a second.

A frame is displayed until the next frame begins, so its duration is the difference between two consecutive
values of `t`. The last frame is displayed for as long as the frame before it. The animation loops forever.

```sql theme={null}
SELECT
    number % 60 AS t,
    toInt32(intDiv(number, 60) % 64) AS x,
    toInt32((number * 7) % 64) AS y,
    toUInt8(255) AS v
FROM numbers(60 * 64)
INTO OUTFILE 'animation.png'
FORMAT PNG
SETTINGS output_format_image_width = 64, output_format_image_height = 64;
```

<h3 id="streaming-animation">
  Streaming the frames
</h3>

By default all frames are collected in memory and written out at the end of the query, which keeps one image
buffer per distinct value of `t` and lets `t` arrive in any order.

The setting
[`output_format_image_streaming_animation`](/docs/reference/settings/formats/output-format#output_format_image_streaming_animation)
writes each frame out as soon as the next value of `t` is seen. Only one image buffer is kept in memory, and
frames reach the output while the query is still running, so a viewer can display them as they are produced.
In exchange:

* `t` must be non-decreasing; the query throws an exception otherwise. Add `ORDER BY t` if needed.
* The number of frames is not known when the header has to be written, so the `acTL` chunk declares an upper
  bound instead of the exact count. Browsers play such a file, but decoders that trust the declared count
  (for example, `Pillow` and some command-line `APNG` tools) report an error after the last real frame.
  An animation of a single frame is the exception: the whole result has been read by the time that frame is
  written, so the count is declared exactly and the output conforms to the specification.

Because an inline terminal image protocol carries the whole datastream as a single payload, the frames cannot
reach the terminal early and this setting only affects how much memory is used there. The exact frame count is
patched into the buffered payload before it is sent, so the caveat about the upper bound does not apply.

An animation is displayed only in the `iterm` terminal mode. The `sixel` protocol cannot represent an
animation at all, and the Kitty graphics protocol animates only through a separate flow of per-frame commands,
not through an animated datastream, so it would display just the first frame; both modes reject a result with
a `t` column.

<h2 id="terminal-mode">
  Displaying images in the terminal
</h2>

By default, the `PNG` format writes the raw image bytes. The setting
[`output_format_image_terminal_mode`](/docs/reference/settings/formats/output-format#output_format_image_terminal_mode)
makes the format render the image directly to the terminal using an inline image protocol instead:

| Value        | Behaviour                                                                                                                                       |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| \`\` (empty) | Write the raw image bytes (the default).                                                                                                        |
| `iterm`      | Use the iTerm2 inline image protocol.                                                                                                           |
| `kitty`      | Use the Kitty graphics protocol. Cannot display an animation.                                                                                   |
| `sixel`      | Use the Sixel protocol. The image is reduced to a fixed 6×6×6 palette and the alpha channel, if any, is composited over a black background.     |
| `auto`       | If the output is a terminal, detect its capabilities and use `iterm`, `kitty`, or `sixel` (in this order); otherwise write the raw image bytes. |

```sql theme={null}
SELECT toUInt8(x * 25) AS r, toUInt8(y * 25) AS g, toUInt8((x + y) * 12) AS b
FROM (SELECT number % 10 AS x, intDiv(number, 10) AS y FROM numbers(100))
FORMAT PNG
SETTINGS output_format_image_width = 10, output_format_image_height = 10, output_format_image_terminal_mode = 'auto';
```

<h2 id="format-settings">
  Format settings
</h2>

| Setting                                       | Description                                                 | Default      |
| --------------------------------------------- | ----------------------------------------------------------- | ------------ |
| `output_format_image_width`                   | Width of the output image in pixels.                        | `1024`       |
| `output_format_image_height`                  | Height of the output image in pixels.                       | `1024`       |
| `output_format_image_terminal_mode`           | Inline terminal image protocol (see above).                 | \`\` (empty) |
| `output_format_image_time_multiplier_seconds` | Numerator of the time unit of the `t` column, in seconds.   | `1`          |
| `output_format_image_time_divisor_seconds`    | Denominator of the time unit of the `t` column, in seconds. | `60`         |
| `output_format_image_streaming_animation`     | Write each frame as soon as `t` advances (see above).       | `0`          |
