Skip to main content

gradient_dither_offset

Function gradient_dither_offset 

Source
pub fn gradient_dither_offset(x: f32, y: f32) -> f32
Expand description

The ordered-dither offset a gradient gets at device pixel (x, y), in output levels — the same value Skia adds, so a Cranpose gradient lands on the same bytes as the Jetpack Compose gradient it is standing in for.

Skia dithers every gradient it draws to an 8-bit target. The pattern is not noise: it is a 4x4 Bayer matrix built by striping the low two bits of the device coordinate — (X:a1a2, Y:b1b2) becomes b1 a1 b2 a2 — and mapped onto [-15/32, +15/32], half a level either way. Undithered, a slow ramp quantises into visible bands; dithered, the band edges break into the checkerboard every Android gradient has.

 x→   0   1   2   3
y 0   0   4   1   5
  1   8  12   9  13
  2   2   6   3   7
  3  10  14  11  15

Recovered from device captures rather than from memory: binning compose − cranpose over a radial gradient by (x % 4, y % 4) reproduces this matrix, and the per-cell means track m / 16 − 15 / 32 to within the estimator’s own bias.

The pattern is anchored one pixel on from the coordinate handed in, and that is measured too. Evaluated at the fragment’s own coordinate the dither came out as the mirror of Skia’s — two dithers disagreeing is worse than one, and a captured frame went from 39.5% identical pixels against the Compose build to 23.6%. A probe shader that painted floor(position) straight into the frame said why: the fragment that lands on captured column N reports column N-1. The pattern is a phase as much as a matrix, so the phase is part of what has to match.