Expand description
Physical orientation baking — rotate / flip a whole pixel buffer.
apply_orientation takes a (possibly strided) PixelSlice and an
Orientation and returns a fresh, tightly-allocated PixelBuffer with
the pixels physically rearranged. It is the “bake” half of the zen
orientation model: codecs that decode to a raster buffer and are asked to
resolve orientation (OrientationHint::bakes() is true) call this; the
cheap coordinate math (Orientation::forward_map / output_dimensions)
lives in zenpixels, and this is the buffer operation that consumes it.
apply_orientation_into writes a caller-owned buffer (no allocation), and
apply_orientation_in_place permutes the backing allocation itself.
§Algorithm
The eight orientations split into two classes:
-
Non-transposing (
Identity,FlipH,FlipV,Rotate180) — the output has the same dimensions, so each output row maps to exactly one input row.Identity/FlipVare pure row copies (memcpy, reordered forFlipV);FlipH/Rotate180additionally reverse thebpp-sized elements within each row. These are memory-bandwidth bound — a scalarcopy_from_sliceper row already runs at copy speed. -
Transposing (
Transpose,Rotate90,Rotate270,Transverse) — width and height swap, and the access pattern is a matrix transpose, which is the cache-hostile case: a naïve element loop strides one of the two buffers by a full row per step and thrashes the cache once the image exceeds L1/L2. We use the standard fix — loop tiling (cache blocking): process the image inTILE×TILEblocks so each block’s source and destination footprints (TILE*TILE*bppbytes each) stay resident while we transpose them. The orientation’s reflection (theh-1-sy/w-1-sxterms that turn a bare transpose into a 90°/270° rotation or anti-diagonal flip) is folded into the per-element destination address viaforward_map, so the whole thing is a single pass with no intermediate buffer.
The transpose runs in one of two tiers — selected at compile time by the
fast-transpose feature and at runtime by CPU capability. Both share the
cache-blocking structure above and fold the reflection into the destination
address; they differ only in how each tile is moved. [do_transpose] picks.
-
Portable scalar — the default, and the path on pre-AVX2 x86 and any arch without a SIMD kernel. [
transpose_tiled] is a per-width monomorphised gather: the four transposing maps are separable, so along one destination row the source column is fixed and the source offset steps by ±stride. That replaces the generic path’s per-elementforward_map,row_mut, and variable-length copy with one bounds check and a fixed-sizeBPP-byte copy per pixel, writing the destination sequentially (zenjpeg#150 measured the generic path losing to a naive linear-write gather at 3 bpp; this beats both). 16-byte pixels use [transpose16_deep] — a transpose there is pure block movement that autovectorises — and the generic [transpose_blocked] scatter covers any other width. -
SIMD register transpose (
fast-transpose) — per-pixel-width kernels that transpose a register-sized tile with an unpack/zip shuffle cascade, the shape production transpose libraries (ermig1979/Simd, fast_transpose, libyuv) win with. x86-64-v3 (AVX2) kernels cover 1/2/3/4/6/8/12 bpp inpxn_x86(24-bit RGB8 inrgb3_x86, which expands 3→4 bytes, transposes u32 lanes, then compresses); aarch64 NEON kernels cover the same widths inpxn_neon; 16-byte pixels fall to [transpose16_deep]. On otherfast-transposearches (wasm, …) only 4-byte pixels are SIMD — via magetypes’f32x4::transpose_4x4(the classic_MM_TRANSPOSE4_PSshuffle, generated for SSE/NEON/SIMD128/scalar from one#[magetypes]body and dispatched byincant!) — and other widths take the scalar tier.
A 4-byte pixel rides a transpose as one 32-bit lane; the kernels shuffle
whole lanes only (no arithmetic), so the byte reinterpret is bit-exact for
any 4-byte format, NaN patterns included. Every SIMD kernel transposes the
full tiles and leaves the right/bottom edge strips to a scalar forward_map
scatter (scalar_rect / scalar_edges). [transpose_blocked] (the generic
forward_map scatter) is the parity oracle every fast path is tested against
(simd_transpose_matches_scalar_reference_rgba8,
tiled_transpose_matches_blocked_reference_across_bpp,
exhaustive_dense_dims_all_orientations_vs_oracle) and the correct fallback
for any orientation added to the #[non_exhaustive] enum later.
Functions§
- apply_
orientation - Apply
orientationtosrc, returning a freshly-allocated buffer with the pixels physically rearranged. - apply_
orientation_ in_ place - Bake
orientationintodstin place, reusing its allocation — no second pixel buffer (the transposing orientations would otherwise need a 2× transient). - apply_
orientation_ into - Apply
orientationtosrc, writing into a caller-provideddst— no allocation, so callers can reuse / pool the target across many calls.