wuffs_base__pixel_subsampling
is a uint32_t
that encodes whether samples
cover one pixel or cover multiple pixels. RGBA or BGRA pixel formats are
typically one pixel per sample. For YCbCr pixel formats (i.e. the native
formats of JPEG and WebP lossy images), the luma channel (Y) is also typically
one pixel per sample but the chroma channels (Cb, Cr) are often 2 or 4 pixels
per sample. In JPEG terminology, 4:4:4, 4:2:2 or 4:2:0 subsampling correspond
to 1, 2 or 4 pixels per chroma
sample.
Equivalently, wuffs_base__pixel_subsampling
encodes the mapping of pixel
space coordinates (x, y)
to sample space coordinates (i, j)
, also known as
pixel buffer indices. That mapping can differ for each plane p
. A Wuffs image
can have up to 4 planes or channels. For a depth of 8 bits (1 byte), the p
'th
plane's sample starts at (planes[p].ptr + (j * planes[p].stride) + i)
.
For interleaved pixel formats, which are always one pixel per sample, the
mapping is trivial: i = x
and j = y
. For planar pixel formats, the mapping
can differ due to chroma subsampling. For example, consider a three plane YCbCr
pixel format with 4:2:2 subsampling. For the luma (Y) channel, there is one
sample for every pixel, but for the chroma (Cb, Cr) channels, there is one
sample for every two pixels: pairs of horizontally adjacent pixels form one
macropixel, i = x / 2
and j == y
. In general, for a given p
:
i = (x + bias_x) / denominator_x
.j = (y + bias_y) / denominator_y
.
where biases and denominators are in the range 0 ..= 3
and 1 ..= 4
respectively.
In general, the biases will be zero after decoding an image. However, making
a sub-image may change the bias, since the (x, y)
coordinates are relative
to the sub-image's top-left origin, but the backing pixel buffers were
created relative to the original image's origin.
For example, consider a 10×3 image with what JPEG calls 4:1:1 subsampling,
where each Chroma sample covers a macropixel (a block of pixels) 4 wide and 1
high. For a Chroma channel, there are 30 pixels and 9 samples. bias_x = 0
and
denominator_x = 2
, so that the 6th pixel column (x = 5
) maps to the 2nd
sample column (i = 1
).
Pixel space:
+---------------------------------------+
|l00 l01 l02 l03 m04 m05 m06 m07 n08 n09|
| |
|p10 p11 p12 p13 q14 q15 q16 q17 r18 r19|
| +-----------+ |
|t21 t21 t22|t23 u24 u25|u26 u27 v28 v29|
+-----------+-----------+---------------+
Chroma sample space:
+-----+
|l m n|
| |
|p q r|
| |
|t u v|
+-----+
Now consider the 3×1 sub-image shown above. Even though its pixel width (3) is
less than the macropixel width (4 columns per sample), as that sub-image shares
the sample buffer with its containing image, it still straddles 2 sample
columns (t
and u
). denominator_x = 2
, the same as before, but now bias_x = 3
.
Pixel space:
+-----------+
|t23 u24 u25|
+-----------+
Chroma sample space:
+---+
|t u|
+---+
For each plane p
, the adjusted denominator is defined to be one less than the
denominator. The plane's four numbers (two biases and two adjusted
denominators) are each in the range 0 ..= 3
, and are each encodable in two
bits. Four groups of two bits combine to form an 8 bit unsigned integer:
e_p = (bias_x << 6) | (adj_denom_x << 4) | (bias_y << 2) | (adj_denom_y << 0)
Those e_p
values (e_0
for the first plane, e_1
for the second plane, etc)
combine to form a wuffs_base__pixel_subsampling
value:
pixsub = (e_3 << 24) | (e_2 << 16) | (e_1 << 8) | (e_0 << 0)
For example, visualizing 4:2:2 YCbCr (with no bias):
Pixel space:
+-------------------+
|l00 l01 m02 m03 n04|
| |
|p10 p11 q12 q13 r14|
+-------------------+
Chroma sample space:
+-----+
|l m n|
| |
|p q r|
+-----+
This corresponds to a denominator_x
of 1, 2 and 2 for the three planes (Y,
Cb, Cr) and a denominator_y
of 1, 1 and 1. The uint32_t
encoding is
0x101000
.
The wuffs_base__pixel_subsampling
bit packing is documented for explanation
and to assist in debugging (e.g. printf
ing the bits in %x
format).
However, do not manipulate its bits directly; they are private implementation
details. Use functions such as wuffs_base__pixel_subsampling__bias_x
instead.