From aa067569cd1bccd7ec1d4e22c39c9cb436507bed Mon Sep 17 00:00:00 2001 From: Konstantin Date: Wed, 8 Jul 2026 16:22:32 +0200 Subject: [PATCH 1/8] add jpeg codec --- codecs/jpeg/README.md | 70 +++++++++++++++++++++++++++++++++++++++++ codecs/jpeg/schema.json | 23 ++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 codecs/jpeg/README.md create mode 100644 codecs/jpeg/schema.json diff --git a/codecs/jpeg/README.md b/codecs/jpeg/README.md new file mode 100644 index 0000000..533a788 --- /dev/null +++ b/codecs/jpeg/README.md @@ -0,0 +1,70 @@ +# JPEG codec + +Defines an `array -> bytes` codec that encodes a chunk as a baseline (sequential DCT, Huffman-coded) JPEG image. + + +JPEG is a **lossy** image compression format, so this codec MUST NOT be used where exact values must be preserved (e.g. label / segmentation data). + +It is intended for `uint8` image data (grayscale or RGB) where a controlled loss of precision is acceptable in exchange for a high compression ratio. + +This codec is interoperable with the `jpeg` chunk encoding used by [neuroglancer](https://github.com/google/neuroglancer). + +## Codec name + +The value of the `name` member in the codec object MUST be `jpeg`. + +## Configuration parameters + +The codec has a single optional parameter: + +- `quality` (integer, optional, default `90`): the JPEG encoding quality, in the range `0`–`100` + +- higher values preserve more detail at the cost of a larger encoded size. Decoding is independent of it. + +## Supported data types + +- `uint8` + +Other data types are not supported by this codec. + +## Supported chunk shapes + +JPEG can only store images with **1 component** (grayscale) or **3 components** (RGB), so the channel count is derived from the chunk shape: + +- last axis of extent `3` → **3-channel RGB** (that axis is the interleaved channel axis); +- any other shape → **1-channel grayscale**. + +The chunk is flattened in **C order** (last axis varies fastest), so the channel axis must be the innermost axis. If it is not, place a [`transpose`](../transpose/) codec before `jpeg` to move it there. + +The JPEG image `width` and `height` are arbitrary. A decoder reshapes the decoded samples by the chunk shape, not by the image dimensions. + +These rules apply to the inner chunk shape when used inside [`sharding_indexed`](../sharding_indexed/). + +## Format and algorithm + +The output is a standard JFIF/JPEG bitstream using baseline (sequential DCT, Huffman) coding. + +### Encoding + +1. The `uint8` chunk data is read in C order into a contiguous buffer. +2. The number of channels (1 or 3) is determined from the chunk shape as described above, and the remaining pixel count is factored into a `width * height` image. + +### Decoding + +1. The JPEG bitstream is decoded to its `uint8` samples in raster order. +2. For grayscale, the single-component samples are read directly (no color-space conversion). For RGB, the three interleaved components are read per pixel. +3. The samples are reshaped to the chunk shape. + +## Interoperability notes + +- This codec matches neuroglancer `precomputed`'s `jpeg` encoding for 1- and 3-channel `uint8` data, with no data reordering. + +- This works because Zarr's C-order over `[z, y, x, channel]` visits samples in the same order (channel, then x, then y, then z) as neuroglancer's Fortran-order over `[x, y, z]`. + +- Because JPEG is lossy, decoded values are generally not bit-identical to the original values. Implementations and users should choose `quality` accordingly. + + + +## Current maintainers + +* [Konstantin Bobenko](https://github.com/konsti-bobenko) diff --git a/codecs/jpeg/schema.json b/codecs/jpeg/schema.json new file mode 100644 index 0000000..f7450f3 --- /dev/null +++ b/codecs/jpeg/schema.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "name": { + "const": "jpeg" + }, + "configuration": { + "type": "object", + "properties": { + "quality": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 90 + } + }, + "additionalProperties": false + } + }, + "required": ["name"], + "additionalProperties": false +} From 60174d268e6ac1c45a6b644594334d66fe5d905b Mon Sep 17 00:00:00 2001 From: Konstantin Date: Tue, 14 Jul 2026 12:11:05 +0200 Subject: [PATCH 2/8] adjusted jpeg codec --- codecs/jpeg/README.md | 56 ++++++++++++++++++++++++++++++----------- codecs/jpeg/schema.json | 6 +++++ 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/codecs/jpeg/README.md b/codecs/jpeg/README.md index 533a788..5e95919 100644 --- a/codecs/jpeg/README.md +++ b/codecs/jpeg/README.md @@ -15,11 +15,19 @@ The value of the `name` member in the codec object MUST be `jpeg`. ## Configuration parameters -The codec has a single optional parameter: +- `quality` (integer, optional, default `90`): the JPEG encoding quality, in the range `0`–`100`. Higher values preserve more detail at the cost of a larger encoded size. Decoding does not depend on it. -- `quality` (integer, optional, default `90`): the JPEG encoding quality, in the range `0`–`100` +- `encoded_color_space` (string): the color space of the samples as stored in the JPEG stream, for 3-component data. One of: + - `ycbcr`: the RGB input is converted to YCbCr before encoding (the JFIF-standard color space, and a prerequisite for chroma subsampling). Suitable for natural color images. + - `rgb`: the three components are stored as-is, with no color-space conversion. Suitable for independent scientific channels (fluorescence, multispectral, …) whose channels are not real colors. The encoder MUST write an APP14 Adobe marker indicating an "unknown" transform, so that decoders do not apply an inverse YCbCr transform. -- higher values preserve more detail at the cost of a larger encoded size. Decoding is independent of it. + This parameter is REQUIRED for 3-component data and has **no default**: the two color spaces are meant for different kinds of data, and silently applying YCbCr would destroy fidelity for data whose channels are not colors. It MUST NOT be set for grayscale data, whose encoded color space is always `grayscale` and is determined by the chunk shape. + + For maximum interoperability, note that encoders and decoders are only guaranteed to support `grayscale` and `ycbcr`; `rgb` (storing color components without conversion) is not supported by all implementations and SHOULD be used only when portability is not a concern. This parameter is defined as an open-ended color space, rather than a simple on/off transform, so that additional color spaces (e.g. XYB, CMYK) can be added later as a backwards-compatible extension. + +- `subsampling` (string, optional): the chroma subsampling scheme, one of `4:4:4`, `4:2:2`, or `4:2:0`. It is only meaningful together with `encoded_color_space: ycbcr`, where it defaults to `4:2:0`. With `encoded_color_space: rgb` it MUST be `4:4:4` (or omitted). It MUST NOT be set for grayscale data. + +An implementation MUST reject a configuration whose parameters are invalid for the chunk's channel count (for example, 3-component data without `encoded_color_space`, or `subsampling` other than `4:4:4` together with `encoded_color_space: rgb`) rather than silently ignoring them. ## Supported data types @@ -29,30 +37,42 @@ Other data types are not supported by this codec. ## Supported chunk shapes -JPEG can only store images with **1 component** (grayscale) or **3 components** (RGB), so the channel count is derived from the chunk shape: +JPEG stores images with either **1 component** (grayscale) or **3 components** (color). The component count is derived from the chunk shape: + +| Chunk shape | Result | +|---|---| +| `(N,)` (1D) | rejected — reshape to 2D first | +| `(H, W)` | 1-component (grayscale) | +| `(H, W, 1)` | 1-component (grayscale) | +| `(H, W, 3)` | 3-component (`encoded_color_space` REQUIRED) | +| `(H, W, 2)` or `(H, W, C)` with `C ≥ 4` | rejected — reshape/shard so the channel axis is 1 or 3 | +| 4D or higher | rejected — transpose/reshape down to 2D or 3D | -- last axis of extent `3` → **3-channel RGB** (that axis is the interleaved channel axis); -- any other shape → **1-channel grayscale**. +`(H, W, 1)` is accepted as grayscale so that data can be sharded over a size-1 channel axis. -The chunk is flattened in **C order** (last axis varies fastest), so the channel axis must be the innermost axis. If it is not, place a [`transpose`](../transpose/) codec before `jpeg` to move it there. +The chunk is flattened in **C order** (last axis varies fastest), so the channel axis must be the innermost axis. If it is not, place a [`transpose`](../transpose/) codec before `jpeg` to move it there. -The JPEG image `width` and `height` are arbitrary. A decoder reshapes the decoded samples by the chunk shape, not by the image dimensions. +The trailing spatial axes give the JPEG image `height` (`H`) and `width` (`W`). Each MUST NOT exceed `65535`, since the JPEG format stores each dimension as a 16-bit value. A decoder reshapes the decoded samples by the chunk shape. + +JPEG encodes samples in blocks — a *minimum coded unit* (MCU) of 8×8 samples, or up to 16×16 when chroma subsampling is used. When `H` or `W` is not a multiple of the block size, the encoder pads the image up to the next block boundary and the decoder crops back to the stored dimensions, so the chunk shape still round-trips exactly; however, the padded samples share quantized blocks with the real edge samples and can introduce extra artifacts near the right and bottom edges. To avoid this, `H` and `W` SHOULD be multiples of `16` (which covers every subsampling mode; `8` suffices for `4:4:4`). Because Zarr pads boundary chunks to the full chunk shape, choosing a chunk shape whose spatial dimensions are multiples of `16` ensures every encoded image — including boundary chunks — is block-aligned. These rules apply to the inner chunk shape when used inside [`sharding_indexed`](../sharding_indexed/). ## Format and algorithm -The output is a standard JFIF/JPEG bitstream using baseline (sequential DCT, Huffman) coding. +The output is a standard JFIF/JPEG bitstream using baseline (Color Transform, Block Splitting, DCT, Quantization, Huffman) as defined by [ITU-T81] and [ITU-T871]. ### Encoding -1. The `uint8` chunk data is read in C order into a contiguous buffer. -2. The number of channels (1 or 3) is determined from the chunk shape as described above, and the remaining pixel count is factored into a `width * height` image. +1. The component count (1 or 3) is determined from the chunk shape as described above; unsupported shapes are rejected. +2. The `uint8` chunk data is read in C order into a contiguous buffer; the trailing spatial axes give the image `height` (`H`) and `width` (`W`). +3. For 3-component data, the samples are converted to the `encoded_color_space` (RGB → YCbCr for `ycbcr`, no conversion for `rgb`) together with the chroma `subsampling` (for `ycbcr`). For `rgb`, the APP14 Adobe marker indicating an "unknown" transform is written. +4. The samples are encoded as a baseline JPEG at the configured `quality`. ### Decoding -1. The JPEG bitstream is decoded to its `uint8` samples in raster order. -2. For grayscale, the single-component samples are read directly (no color-space conversion). For RGB, the three interleaved components are read per pixel. +1. The JPEG bitstream is decoded to its `uint8` samples in raster order. The inverse color transform is determined by the stream itself: the APP14 marker, when present, indicates whether an inverse YCbCr transform is applied. +2. For grayscale, the single-component samples are read directly. For color, the three interleaved components are read per pixel. 3. The samples are reshaped to the chunk shape. ## Interoperability notes @@ -63,8 +83,16 @@ The output is a standard JFIF/JPEG bitstream using baseline (sequential DCT, Huf - Because JPEG is lossy, decoded values are generally not bit-identical to the original values. Implementations and users should choose `quality` accordingly. +## References + +[ITU-T81] ITU-T Recommendation T.81 | ISO/IEC 10918-1. Information technology – Digital compression and coding of continuous-tone still images – Requirements and guidelines. September 1992. URL: https://www.w3.org/Graphics/JPEG/itu-t81.pdf + +[ITU-T871] ITU-T Recommendation T.871 | ISO/IEC 10918-5. Information technology – Digital compression and coding of continuous-tone still images: JPEG File Interchange Format (JFIF). May 2011. URL: https://www.itu.int/rec/T-REC-T.871 + +[ITU-T81]: https://www.w3.org/Graphics/JPEG/itu-t81.pdf +[ITU-T871]: https://www.itu.int/rec/T-REC-T.871 ## Current maintainers -* [Konstantin Bobenko](https://github.com/konsti-bobenko) +* [Konstantin Bobenko](https://github.com/konstibob) diff --git a/codecs/jpeg/schema.json b/codecs/jpeg/schema.json index f7450f3..71f5627 100644 --- a/codecs/jpeg/schema.json +++ b/codecs/jpeg/schema.json @@ -13,6 +13,12 @@ "minimum": 0, "maximum": 100, "default": 90 + }, + "encoded_color_space": { + "enum": ["ycbcr", "rgb"] + }, + "subsampling": { + "enum": ["4:4:4", "4:2:2", "4:2:0"] } }, "additionalProperties": false From e21fc7c07801bc8f37eb9dc575804cc79b38bd89 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Tue, 14 Jul 2026 13:15:09 +0200 Subject: [PATCH 3/8] adjusted chroma subsampling format --- codecs/jpeg/README.md | 21 ++++++++++++++------- codecs/jpeg/schema.json | 14 +++++++++++++- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/codecs/jpeg/README.md b/codecs/jpeg/README.md index 5e95919..4227a02 100644 --- a/codecs/jpeg/README.md +++ b/codecs/jpeg/README.md @@ -25,9 +25,20 @@ The value of the `name` member in the codec object MUST be `jpeg`. For maximum interoperability, note that encoders and decoders are only guaranteed to support `grayscale` and `ycbcr`; `rgb` (storing color components without conversion) is not supported by all implementations and SHOULD be used only when portability is not a concern. This parameter is defined as an open-ended color space, rather than a simple on/off transform, so that additional color spaces (e.g. XYB, CMYK) can be added later as a backwards-compatible extension. -- `subsampling` (string, optional): the chroma subsampling scheme, one of `4:4:4`, `4:2:2`, or `4:2:0`. It is only meaningful together with `encoded_color_space: ycbcr`, where it defaults to `4:2:0`. With `encoded_color_space: rgb` it MUST be `4:4:4` (or omitted). It MUST NOT be set for grayscale data. +- `subsampling` (array, optional): the chroma subsampling, expressed declaratively as the per-component JPEG sampling factors. It is an array with **one entry per component**, and each entry is a two-element array `[horizontal, vertical]` giving that component's horizontal and vertical sampling factor as integers in the range `1`–`4`. A component with factor `[2, 2]` is stored at twice the horizontal and vertical resolution of a component with factor `[1, 1]`; the chroma components (Cb, Cr) are therefore subsampled relative to luma (Y) by the ratio of their sampling factors. The chroma components MUST have factor `[1, 1]`, and each luma factor MUST be greater than or equal to the corresponding chroma factor. -An implementation MUST reject a configuration whose parameters are invalid for the chunk's channel count (for example, 3-component data without `encoded_color_space`, or `subsampling` other than `4:4:4` together with `encoded_color_space: rgb`) rather than silently ignoring them. + For 3-component data, `subsampling` is only meaningful together with `encoded_color_space: ycbcr`, where it defaults to `[[2, 2], [1, 1], [1, 1]]` (the common `4:2:0` scheme). With `encoded_color_space: rgb` it MUST be `[[1, 1], [1, 1], [1, 1]]` (no subsampling) or omitted, since those components are independent and MUST NOT be subsampled. It MUST NOT be set for grayscale data. + + The common human-readable `J:a:b` chroma-subsampling notation maps to `subsampling` as follows. Implementations MUST support at least these schemes: + + | `J:a:b` | `subsampling` | Chroma resolution vs. luma | + |---|---|---| + | `4:4:4` | `[[1, 1], [1, 1], [1, 1]]` | full (no subsampling) | + | `4:2:2` | `[[2, 1], [1, 1], [1, 1]]` | half horizontally | + | `4:4:0` | `[[1, 2], [1, 1], [1, 1]]` | half vertically | + | `4:2:0` | `[[2, 2], [1, 1], [1, 1]]` | half horizontally and vertically | + +An implementation MUST reject a configuration whose parameters are invalid for the chunk's channel count (for example, 3-component data without `encoded_color_space`, a `subsampling` array whose length does not match the component count, or a `subsampling` other than `[[1, 1], [1, 1], [1, 1]]` together with `encoded_color_space: rgb`) rather than silently ignoring them. ## Supported data types @@ -89,10 +100,6 @@ The output is a standard JFIF/JPEG bitstream using baseline (Color Transform, Bl [ITU-T871] ITU-T Recommendation T.871 | ISO/IEC 10918-5. Information technology – Digital compression and coding of continuous-tone still images: JPEG File Interchange Format (JFIF). May 2011. URL: https://www.itu.int/rec/T-REC-T.871 -[ITU-T81]: https://www.w3.org/Graphics/JPEG/itu-t81.pdf - -[ITU-T871]: https://www.itu.int/rec/T-REC-T.871 - ## Current maintainers -* [Konstantin Bobenko](https://github.com/konstibob) +* [Konstantin Bobenko](https://github.com/konstibob) \ No newline at end of file diff --git a/codecs/jpeg/schema.json b/codecs/jpeg/schema.json index 71f5627..adf6619 100644 --- a/codecs/jpeg/schema.json +++ b/codecs/jpeg/schema.json @@ -18,7 +18,19 @@ "enum": ["ycbcr", "rgb"] }, "subsampling": { - "enum": ["4:4:4", "4:2:2", "4:2:0"] + "type": "array", + "items": { + "type": "array", + "items": { + "type": "integer", + "minimum": 1, + "maximum": 4 + }, + "minItems": 2, + "maxItems": 2 + }, + "minItems": 1, + "maxItems": 3 } }, "additionalProperties": false From 85bf751a8f5f36899daac22349970b59abd2737b Mon Sep 17 00:00:00 2001 From: konstibob <44369572+konstibob@users.noreply.github.com> Date: Mon, 20 Jul 2026 13:31:59 +0200 Subject: [PATCH 4/8] Apply suggestions from code review Co-authored-by: Norman Rzepka --- codecs/jpeg/README.md | 12 +++++------- codecs/jpeg/schema.json | 5 +++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/codecs/jpeg/README.md b/codecs/jpeg/README.md index 4227a02..4a5b607 100644 --- a/codecs/jpeg/README.md +++ b/codecs/jpeg/README.md @@ -7,15 +7,13 @@ JPEG is a **lossy** image compression format, so this codec MUST NOT be used whe It is intended for `uint8` image data (grayscale or RGB) where a controlled loss of precision is acceptable in exchange for a high compression ratio. -This codec is interoperable with the `jpeg` chunk encoding used by [neuroglancer](https://github.com/google/neuroglancer). - ## Codec name The value of the `name` member in the codec object MUST be `jpeg`. ## Configuration parameters -- `quality` (integer, optional, default `90`): the JPEG encoding quality, in the range `0`–`100`. Higher values preserve more detail at the cost of a larger encoded size. Decoding does not depend on it. +- `quality` (integer): the JPEG encoding quality, in the range `0`–`100`. Higher values preserve more detail at the cost of a larger encoded size. Decoding does not depend on it. - `encoded_color_space` (string): the color space of the samples as stored in the JPEG stream, for 3-component data. One of: - `ycbcr`: the RGB input is converted to YCbCr before encoding (the JFIF-standard color space, and a prerequisite for chroma subsampling). Suitable for natural color images. @@ -61,14 +59,14 @@ JPEG stores images with either **1 component** (grayscale) or **3 components** ( `(H, W, 1)` is accepted as grayscale so that data can be sharded over a size-1 channel axis. +When using different chunk shapes, the [`reshape`](../reshape/) codec should be used prior to the `jpeg` codec to change the chunk shapes into compatible shapes. + The chunk is flattened in **C order** (last axis varies fastest), so the channel axis must be the innermost axis. If it is not, place a [`transpose`](../transpose/) codec before `jpeg` to move it there. -The trailing spatial axes give the JPEG image `height` (`H`) and `width` (`W`). Each MUST NOT exceed `65535`, since the JPEG format stores each dimension as a 16-bit value. A decoder reshapes the decoded samples by the chunk shape. +The spatial axes give the JPEG image `height` (`H`) and `width` (`W`). Each MUST NOT exceed `65535`, since the JPEG format stores each dimension as a 16-bit value. A decoder reshapes the decoded samples by the chunk shape. JPEG encodes samples in blocks — a *minimum coded unit* (MCU) of 8×8 samples, or up to 16×16 when chroma subsampling is used. When `H` or `W` is not a multiple of the block size, the encoder pads the image up to the next block boundary and the decoder crops back to the stored dimensions, so the chunk shape still round-trips exactly; however, the padded samples share quantized blocks with the real edge samples and can introduce extra artifacts near the right and bottom edges. To avoid this, `H` and `W` SHOULD be multiples of `16` (which covers every subsampling mode; `8` suffices for `4:4:4`). Because Zarr pads boundary chunks to the full chunk shape, choosing a chunk shape whose spatial dimensions are multiples of `16` ensures every encoded image — including boundary chunks — is block-aligned. -These rules apply to the inner chunk shape when used inside [`sharding_indexed`](../sharding_indexed/). - ## Format and algorithm The output is a standard JFIF/JPEG bitstream using baseline (Color Transform, Block Splitting, DCT, Quantization, Huffman) as defined by [ITU-T81] and [ITU-T871]. @@ -76,7 +74,7 @@ The output is a standard JFIF/JPEG bitstream using baseline (Color Transform, Bl ### Encoding 1. The component count (1 or 3) is determined from the chunk shape as described above; unsupported shapes are rejected. -2. The `uint8` chunk data is read in C order into a contiguous buffer; the trailing spatial axes give the image `height` (`H`) and `width` (`W`). +2. The `uint8` chunk data is read in C order into a contiguous buffer; the spatial axes give the image `height` (`H`) and `width` (`W`). 3. For 3-component data, the samples are converted to the `encoded_color_space` (RGB → YCbCr for `ycbcr`, no conversion for `rgb`) together with the chroma `subsampling` (for `ycbcr`). For `rgb`, the APP14 Adobe marker indicating an "unknown" transform is written. 4. The samples are encoded as a baseline JPEG at the configured `quality`. diff --git a/codecs/jpeg/schema.json b/codecs/jpeg/schema.json index adf6619..0499c8c 100644 --- a/codecs/jpeg/schema.json +++ b/codecs/jpeg/schema.json @@ -33,9 +33,10 @@ "maxItems": 3 } }, - "additionalProperties": false + "additionalProperties": false, + "required": ["quality"] } }, - "required": ["name"], + "required": ["name", "configuration"], "additionalProperties": false } From f956d90e1aedf69b7811ca1dd6dea266f8d6d704 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Mon, 20 Jul 2026 13:37:28 +0200 Subject: [PATCH 5/8] adjusted wording and code reviews --- codecs/jpeg/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/codecs/jpeg/README.md b/codecs/jpeg/README.md index 4a5b607..ede99c1 100644 --- a/codecs/jpeg/README.md +++ b/codecs/jpeg/README.md @@ -23,9 +23,9 @@ The value of the `name` member in the codec object MUST be `jpeg`. For maximum interoperability, note that encoders and decoders are only guaranteed to support `grayscale` and `ycbcr`; `rgb` (storing color components without conversion) is not supported by all implementations and SHOULD be used only when portability is not a concern. This parameter is defined as an open-ended color space, rather than a simple on/off transform, so that additional color spaces (e.g. XYB, CMYK) can be added later as a backwards-compatible extension. -- `subsampling` (array, optional): the chroma subsampling, expressed declaratively as the per-component JPEG sampling factors. It is an array with **one entry per component**, and each entry is a two-element array `[horizontal, vertical]` giving that component's horizontal and vertical sampling factor as integers in the range `1`–`4`. A component with factor `[2, 2]` is stored at twice the horizontal and vertical resolution of a component with factor `[1, 1]`; the chroma components (Cb, Cr) are therefore subsampled relative to luma (Y) by the ratio of their sampling factors. The chroma components MUST have factor `[1, 1]`, and each luma factor MUST be greater than or equal to the corresponding chroma factor. +- `subsampling` (array, optional): the subsampling applied to the components of the encoded color space, expressed declaratively as the per-component JPEG sampling factors. It is an array with **one entry per component**, and each entry is a two-element array `[horizontal, vertical]` giving that component's horizontal and vertical sampling factor as integers in the range `1`–`4`. The factors are **relative**: a component is subsampled by the ratio of its factors to the largest factor across all components, so a component with factor `[1, 1]` is stored at half the resolution (in each direction) of a component with factor `[2, 2]`. All-equal factors (e.g. `[[1, 1], [1, 1], [1, 1]]`) therefore mean no subsampling. For `ycbcr`, this is how the chroma components (Cb, Cr) are subsampled relative to luma (Y). The second and third components MUST have factor `[1, 1]`, and the first component's factor MUST be greater than or equal to it in each direction. - For 3-component data, `subsampling` is only meaningful together with `encoded_color_space: ycbcr`, where it defaults to `[[2, 2], [1, 1], [1, 1]]` (the common `4:2:0` scheme). With `encoded_color_space: rgb` it MUST be `[[1, 1], [1, 1], [1, 1]]` (no subsampling) or omitted, since those components are independent and MUST NOT be subsampled. It MUST NOT be set for grayscale data. + For 3-component data, `subsampling` is only meaningful together with `encoded_color_space: ycbcr`, where it defaults to `[[2, 2], [1, 1], [1, 1]]` (the common `4:2:0` scheme). With `encoded_color_space: rgb` it MUST be `[[1, 1], [1, 1], [1, 1]]` (no subsampling) or omitted, since those components are independent and MUST NOT be subsampled. For grayscale (1-component) data, `subsampling` MAY be set but its only valid value is `[[1, 1]]`; since there is only one component there is nothing to subsample relative to, so it has no effect. The common human-readable `J:a:b` chroma-subsampling notation maps to `subsampling` as follows. Implementations MUST support at least these schemes: @@ -65,7 +65,7 @@ The chunk is flattened in **C order** (last axis varies fastest), so the channel The spatial axes give the JPEG image `height` (`H`) and `width` (`W`). Each MUST NOT exceed `65535`, since the JPEG format stores each dimension as a 16-bit value. A decoder reshapes the decoded samples by the chunk shape. -JPEG encodes samples in blocks — a *minimum coded unit* (MCU) of 8×8 samples, or up to 16×16 when chroma subsampling is used. When `H` or `W` is not a multiple of the block size, the encoder pads the image up to the next block boundary and the decoder crops back to the stored dimensions, so the chunk shape still round-trips exactly; however, the padded samples share quantized blocks with the real edge samples and can introduce extra artifacts near the right and bottom edges. To avoid this, `H` and `W` SHOULD be multiples of `16` (which covers every subsampling mode; `8` suffices for `4:4:4`). Because Zarr pads boundary chunks to the full chunk shape, choosing a chunk shape whose spatial dimensions are multiples of `16` ensures every encoded image — including boundary chunks — is block-aligned. +JPEG encodes samples in blocks — a *minimum coded unit* (MCU) of 8×8 samples, or up to 16×16 when subsampling is used. When `H` or `W` is not a multiple of the block size, the encoder pads the image up to the next block boundary and the decoder crops back to the stored dimensions, so the chunk shape still round-trips exactly; however, the padded samples share quantized blocks with the real edge samples and can introduce extra artifacts near the right and bottom edges. To avoid this, `H` and `W` SHOULD be multiples of `16` (which covers every subsampling mode; `8` suffices for `4:4:4`). Because Zarr pads boundary chunks to the full chunk shape, choosing a chunk shape whose spatial dimensions are multiples of `16` ensures every encoded image — including boundary chunks — is block-aligned. ## Format and algorithm @@ -74,8 +74,8 @@ The output is a standard JFIF/JPEG bitstream using baseline (Color Transform, Bl ### Encoding 1. The component count (1 or 3) is determined from the chunk shape as described above; unsupported shapes are rejected. -2. The `uint8` chunk data is read in C order into a contiguous buffer; the spatial axes give the image `height` (`H`) and `width` (`W`). -3. For 3-component data, the samples are converted to the `encoded_color_space` (RGB → YCbCr for `ycbcr`, no conversion for `rgb`) together with the chroma `subsampling` (for `ycbcr`). For `rgb`, the APP14 Adobe marker indicating an "unknown" transform is written. +2. The `uint8` chunk data is read in C order into a contiguous buffer; the trailing spatial axes give the image `height` (`H`) and `width` (`W`). +3. For 3-component data, the samples are converted to the `encoded_color_space` (RGB → YCbCr for `ycbcr`, no conversion for `rgb`) together with the `subsampling` (for `ycbcr`). For `rgb`, the APP14 Adobe marker indicating an "unknown" transform is written. 4. The samples are encoded as a baseline JPEG at the configured `quality`. ### Decoding From 38c490ce276943c99de764c9ae91956e5045ed74 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Mon, 20 Jul 2026 14:28:55 +0200 Subject: [PATCH 6/8] adjusted required params --- codecs/jpeg/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/codecs/jpeg/README.md b/codecs/jpeg/README.md index ede99c1..0a543d1 100644 --- a/codecs/jpeg/README.md +++ b/codecs/jpeg/README.md @@ -13,9 +13,9 @@ The value of the `name` member in the codec object MUST be `jpeg`. ## Configuration parameters -- `quality` (integer): the JPEG encoding quality, in the range `0`–`100`. Higher values preserve more detail at the cost of a larger encoded size. Decoding does not depend on it. +- `quality` (integer, required): the JPEG encoding quality, in the range `0`–`100`. Higher values preserve more detail at the cost of a larger encoded size. Decoding does not depend on it. -- `encoded_color_space` (string): the color space of the samples as stored in the JPEG stream, for 3-component data. One of: +- `encoded_color_space` (string, required): the color space of the samples as stored in the JPEG stream, for 3-component data. One of: - `ycbcr`: the RGB input is converted to YCbCr before encoding (the JFIF-standard color space, and a prerequisite for chroma subsampling). Suitable for natural color images. - `rgb`: the three components are stored as-is, with no color-space conversion. Suitable for independent scientific channels (fluorescence, multispectral, …) whose channels are not real colors. The encoder MUST write an APP14 Adobe marker indicating an "unknown" transform, so that decoders do not apply an inverse YCbCr transform. @@ -23,7 +23,7 @@ The value of the `name` member in the codec object MUST be `jpeg`. For maximum interoperability, note that encoders and decoders are only guaranteed to support `grayscale` and `ycbcr`; `rgb` (storing color components without conversion) is not supported by all implementations and SHOULD be used only when portability is not a concern. This parameter is defined as an open-ended color space, rather than a simple on/off transform, so that additional color spaces (e.g. XYB, CMYK) can be added later as a backwards-compatible extension. -- `subsampling` (array, optional): the subsampling applied to the components of the encoded color space, expressed declaratively as the per-component JPEG sampling factors. It is an array with **one entry per component**, and each entry is a two-element array `[horizontal, vertical]` giving that component's horizontal and vertical sampling factor as integers in the range `1`–`4`. The factors are **relative**: a component is subsampled by the ratio of its factors to the largest factor across all components, so a component with factor `[1, 1]` is stored at half the resolution (in each direction) of a component with factor `[2, 2]`. All-equal factors (e.g. `[[1, 1], [1, 1], [1, 1]]`) therefore mean no subsampling. For `ycbcr`, this is how the chroma components (Cb, Cr) are subsampled relative to luma (Y). The second and third components MUST have factor `[1, 1]`, and the first component's factor MUST be greater than or equal to it in each direction. +- `subsampling` (array, required): the subsampling applied to the components of the encoded color space, expressed declaratively as the per-component JPEG sampling factors. It is an array with **one entry per component**, and each entry is a two-element array `[horizontal, vertical]` giving that component's horizontal and vertical sampling factor as integers in the range `1`–`4`. The factors are **relative**: a component is subsampled by the ratio of its factors to the largest factor across all components, so a component with factor `[1, 1]` is stored at half the resolution (in each direction) of a component with factor `[2, 2]`. All-equal factors (e.g. `[[1, 1], [1, 1], [1, 1]]`) therefore mean no subsampling. For `ycbcr`, this is how the chroma components (Cb, Cr) are subsampled relative to luma (Y). The second and third components MUST have factor `[1, 1]`, and the first component's factor MUST be greater than or equal to it in each direction. For 3-component data, `subsampling` is only meaningful together with `encoded_color_space: ycbcr`, where it defaults to `[[2, 2], [1, 1], [1, 1]]` (the common `4:2:0` scheme). With `encoded_color_space: rgb` it MUST be `[[1, 1], [1, 1], [1, 1]]` (no subsampling) or omitted, since those components are independent and MUST NOT be subsampled. For grayscale (1-component) data, `subsampling` MAY be set but its only valid value is `[[1, 1]]`; since there is only one component there is nothing to subsample relative to, so it has no effect. From 14e9a5e2808df3825624dd56643c808d389e5ea8 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Mon, 20 Jul 2026 14:44:24 +0200 Subject: [PATCH 7/8] add examples --- codecs/jpeg/README.md | 95 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 2 deletions(-) diff --git a/codecs/jpeg/README.md b/codecs/jpeg/README.md index 0a543d1..e3d5fc8 100644 --- a/codecs/jpeg/README.md +++ b/codecs/jpeg/README.md @@ -25,9 +25,9 @@ The value of the `name` member in the codec object MUST be `jpeg`. - `subsampling` (array, required): the subsampling applied to the components of the encoded color space, expressed declaratively as the per-component JPEG sampling factors. It is an array with **one entry per component**, and each entry is a two-element array `[horizontal, vertical]` giving that component's horizontal and vertical sampling factor as integers in the range `1`–`4`. The factors are **relative**: a component is subsampled by the ratio of its factors to the largest factor across all components, so a component with factor `[1, 1]` is stored at half the resolution (in each direction) of a component with factor `[2, 2]`. All-equal factors (e.g. `[[1, 1], [1, 1], [1, 1]]`) therefore mean no subsampling. For `ycbcr`, this is how the chroma components (Cb, Cr) are subsampled relative to luma (Y). The second and third components MUST have factor `[1, 1]`, and the first component's factor MUST be greater than or equal to it in each direction. - For 3-component data, `subsampling` is only meaningful together with `encoded_color_space: ycbcr`, where it defaults to `[[2, 2], [1, 1], [1, 1]]` (the common `4:2:0` scheme). With `encoded_color_space: rgb` it MUST be `[[1, 1], [1, 1], [1, 1]]` (no subsampling) or omitted, since those components are independent and MUST NOT be subsampled. For grayscale (1-component) data, `subsampling` MAY be set but its only valid value is `[[1, 1]]`; since there is only one component there is nothing to subsample relative to, so it has no effect. +For 3-component data, `subsampling` is only meaningful together with `encoded_color_space: ycbcr`, where it defaults to `[[2, 2], [1, 1], [1, 1]]` (the common `4:2:0` scheme). With `encoded_color_space: rgb` it MUST be `[[1, 1], [1, 1], [1, 1]]` (no subsampling) or omitted, since those components are independent and MUST NOT be subsampled. For grayscale (1-component) data, `subsampling` MAY be set but its only valid value is `[[1, 1]]`; since there is only one component there is nothing to subsample relative to, so it has no effect. - The common human-readable `J:a:b` chroma-subsampling notation maps to `subsampling` as follows. Implementations MUST support at least these schemes: +The common human-readable `J:a:b` chroma-subsampling notation maps to `subsampling` as follows. Implementations MUST support at least these schemes: | `J:a:b` | `subsampling` | Chroma resolution vs. luma | |---|---|---| @@ -67,6 +67,97 @@ The spatial axes give the JPEG image `height` (`H`) and `width` (`W`). Each MUST JPEG encodes samples in blocks — a *minimum coded unit* (MCU) of 8×8 samples, or up to 16×16 when subsampling is used. When `H` or `W` is not a multiple of the block size, the encoder pads the image up to the next block boundary and the decoder crops back to the stored dimensions, so the chunk shape still round-trips exactly; however, the padded samples share quantized blocks with the real edge samples and can introduce extra artifacts near the right and bottom edges. To avoid this, `H` and `W` SHOULD be multiples of `16` (which covers every subsampling mode; `8` suffices for `4:4:4`). Because Zarr pads boundary chunks to the full chunk shape, choosing a chunk shape whose spatial dimensions are multiples of `16` ensures every encoded image — including boundary chunks — is block-aligned. +## Examples + +### Grayscale + +A 2D `uint8` chunk (shape `(H, W)`). `encoded_color_space` MUST NOT be set — it is fixed to `grayscale` by the chunk shape — and `subsampling` is not needed (it would be a no-op). + +```json +{ + "codecs": [ + { "name": "jpeg", "configuration": { "quality": 90 } } + ] +} +``` + +### Natural color (RGB → YCbCr, 4:2:0) + +A 3-component chunk (shape `(H, W, 3)`) holding a natural color image. It is converted to YCbCr and the chroma is subsampled with the common `4:2:0` scheme. + +```json +{ + "codecs": [ + { + "name": "jpeg", + "configuration": { + "quality": 90, + "encoded_color_space": "ycbcr", + "subsampling": [[2, 2], [1, 1], [1, 1]] + } + } + ] +} +``` + +### Natural color, no chroma subsampling (4:4:4) + +The same shape at higher fidelity: YCbCr conversion but every component kept at full resolution. + +```json +{ + "codecs": [ + { + "name": "jpeg", + "configuration": { + "quality": 95, + "encoded_color_space": "ycbcr", + "subsampling": [[1, 1], [1, 1], [1, 1]] + } + } + ] +} +``` + +### Independent scientific channels (`rgb`, no color transform) + +Three unrelated `uint8` channels (e.g. fluorescence) that are not real colors. No color-space conversion is applied, and the components MUST NOT be subsampled. + +```json +{ + "codecs": [ + { + "name": "jpeg", + "configuration": { + "quality": 90, + "encoded_color_space": "rgb", + "subsampling": [[1, 1], [1, 1], [1, 1]] + } + } + ] +} +``` + +### Channel axis not innermost + +`jpeg` requires the channel axis to be innermost. For a chunk laid out as `(3, H, W)`, place a [`transpose`](../transpose/) codec first to move the channel axis last. + +```json +{ + "codecs": [ + { "name": "transpose", "configuration": { "order": [1, 2, 0] } }, + { + "name": "jpeg", + "configuration": { + "quality": 90, + "encoded_color_space": "ycbcr", + "subsampling": [[2, 2], [1, 1], [1, 1]] + } + } + ] +} +``` + ## Format and algorithm The output is a standard JFIF/JPEG bitstream using baseline (Color Transform, Block Splitting, DCT, Quantization, Huffman) as defined by [ITU-T81] and [ITU-T871]. From 7e6865dff95da7dc80f9345d895206f747e32615 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Mon, 20 Jul 2026 14:52:48 +0200 Subject: [PATCH 8/8] add examples and fixes --- codecs/jpeg/README.md | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/codecs/jpeg/README.md b/codecs/jpeg/README.md index e3d5fc8..51804ec 100644 --- a/codecs/jpeg/README.md +++ b/codecs/jpeg/README.md @@ -19,13 +19,13 @@ The value of the `name` member in the codec object MUST be `jpeg`. - `ycbcr`: the RGB input is converted to YCbCr before encoding (the JFIF-standard color space, and a prerequisite for chroma subsampling). Suitable for natural color images. - `rgb`: the three components are stored as-is, with no color-space conversion. Suitable for independent scientific channels (fluorescence, multispectral, …) whose channels are not real colors. The encoder MUST write an APP14 Adobe marker indicating an "unknown" transform, so that decoders do not apply an inverse YCbCr transform. - This parameter is REQUIRED for 3-component data and has **no default**: the two color spaces are meant for different kinds of data, and silently applying YCbCr would destroy fidelity for data whose channels are not colors. It MUST NOT be set for grayscale data, whose encoded color space is always `grayscale` and is determined by the chunk shape. + This parameter is REQUIRED for 3-component data and has **no default**: the two color spaces are meant for different kinds of data, and silently applying YCbCr would destroy fidelity for data whose channels are not colors. For maximum interoperability, note that encoders and decoders are only guaranteed to support `grayscale` and `ycbcr`; `rgb` (storing color components without conversion) is not supported by all implementations and SHOULD be used only when portability is not a concern. This parameter is defined as an open-ended color space, rather than a simple on/off transform, so that additional color spaces (e.g. XYB, CMYK) can be added later as a backwards-compatible extension. - `subsampling` (array, required): the subsampling applied to the components of the encoded color space, expressed declaratively as the per-component JPEG sampling factors. It is an array with **one entry per component**, and each entry is a two-element array `[horizontal, vertical]` giving that component's horizontal and vertical sampling factor as integers in the range `1`–`4`. The factors are **relative**: a component is subsampled by the ratio of its factors to the largest factor across all components, so a component with factor `[1, 1]` is stored at half the resolution (in each direction) of a component with factor `[2, 2]`. All-equal factors (e.g. `[[1, 1], [1, 1], [1, 1]]`) therefore mean no subsampling. For `ycbcr`, this is how the chroma components (Cb, Cr) are subsampled relative to luma (Y). The second and third components MUST have factor `[1, 1]`, and the first component's factor MUST be greater than or equal to it in each direction. -For 3-component data, `subsampling` is only meaningful together with `encoded_color_space: ycbcr`, where it defaults to `[[2, 2], [1, 1], [1, 1]]` (the common `4:2:0` scheme). With `encoded_color_space: rgb` it MUST be `[[1, 1], [1, 1], [1, 1]]` (no subsampling) or omitted, since those components are independent and MUST NOT be subsampled. For grayscale (1-component) data, `subsampling` MAY be set but its only valid value is `[[1, 1]]`; since there is only one component there is nothing to subsample relative to, so it has no effect. +For 3-component data, `subsampling` is only meaningful together with `encoded_color_space: ycbcr`; the common `4:2:0` scheme is `[[2, 2], [1, 1], [1, 1]]`. With `encoded_color_space: rgb` it MUST be `[[1, 1], [1, 1], [1, 1]]` (no subsampling), since those components are independent and MUST NOT be subsampled. For grayscale (1-component) data, `subsampling` MUST be `[[1, 1]]`; since there is only one component there is nothing to subsample relative to, so it has no effect. The common human-readable `J:a:b` chroma-subsampling notation maps to `subsampling` as follows. Implementations MUST support at least these schemes: @@ -53,7 +53,7 @@ JPEG stores images with either **1 component** (grayscale) or **3 components** ( | `(N,)` (1D) | rejected — reshape to 2D first | | `(H, W)` | 1-component (grayscale) | | `(H, W, 1)` | 1-component (grayscale) | -| `(H, W, 3)` | 3-component (`encoded_color_space` REQUIRED) | +| `(H, W, 3)` | 3-component | | `(H, W, 2)` or `(H, W, C)` with `C ≥ 4` | rejected — reshape/shard so the channel axis is 1 or 3 | | 4D or higher | rejected — transpose/reshape down to 2D or 3D | @@ -69,18 +69,6 @@ JPEG encodes samples in blocks — a *minimum coded unit* (MCU) of 8×8 samples, ## Examples -### Grayscale - -A 2D `uint8` chunk (shape `(H, W)`). `encoded_color_space` MUST NOT be set — it is fixed to `grayscale` by the chunk shape — and `subsampling` is not needed (it would be a no-op). - -```json -{ - "codecs": [ - { "name": "jpeg", "configuration": { "quality": 90 } } - ] -} -``` - ### Natural color (RGB → YCbCr, 4:2:0) A 3-component chunk (shape `(H, W, 3)`) holding a natural color image. It is converted to YCbCr and the chroma is subsampled with the common `4:2:0` scheme. @@ -121,7 +109,7 @@ The same shape at higher fidelity: YCbCr conversion but every component kept at ### Independent scientific channels (`rgb`, no color transform) -Three unrelated `uint8` channels (e.g. fluorescence) that are not real colors. No color-space conversion is applied, and the components MUST NOT be subsampled. +No color-space conversion is applied, and the components MUST NOT be subsampled. ```json { @@ -140,7 +128,7 @@ Three unrelated `uint8` channels (e.g. fluorescence) that are not real colors. N ### Channel axis not innermost -`jpeg` requires the channel axis to be innermost. For a chunk laid out as `(3, H, W)`, place a [`transpose`](../transpose/) codec first to move the channel axis last. +For a chunk laid out as `(3, H, W)`, place a [`transpose`](../transpose/) codec first to move the channel axis last. ```json {