Patterns and fields
How a field and a pattern turn into dots, and the built-in ones.
A dither is two functions meeting at every cell. The field answers “how much ink here?” with a tone from 0 (paper) to 1 (ink). The pattern answers “how much ink does this cell need before it turns on?” with a threshold. The cell is ink when the tone is above the threshold.
Patterns
| Pattern | Look |
|---|---|
DitherPatterns.Bayer2 |
Coarse four-step grid. Strong, graphic. |
DitherPatterns.Bayer4 |
The default. Sixteen steps, a crosshatch feel. |
DitherPatterns.Bayer8 |
Sixty-four steps and the smoothest ramps. |
DitherPatterns.BlueNoise |
Irregular and grain-like, with no repeating texture. A 64 by 64 texture made offline. |
A pattern implements DitherPattern.threshold(x, y), returning a value between 0 and 1 for any integer cell coordinates. You can write your own.
Fields
A field is a DitherField: tone(x, y, t, aspect). x and y run from 0 to 1 across the element, t is time in seconds and aspect is width over height. Fields are pure, so they work in common code and are easy to test.
| Built-in | Notes |
|---|---|
solid(tone) |
The same tone everywhere. |
linear(from, to, angleDegrees) |
A ramp that spans the element corner to corner at any angle. |
radial(inner, outer, centerX, centerY, radius) |
A round gradient. |
noise(scale, speed, level, contrast) |
Soft, drifting value noise. |
waves(count, speed, angleDegrees, low, high) |
Diagonal bands that travel. |
ripples(rings, speed, centerX, centerY, radius) |
Rings that spread and fade. |
sweep(progress, softness) |
A soft edge that moves from left to right. progress can be a () -> Float that is read when the field is drawn. |
dissolve(progress, scale, softness) |
A reveal in an irregular order: 0 everywhere at progress 0, 1 everywhere at 1. Use it with ditherMask. |
mesh(tones, drift, speed) |
A grid of tones blended smoothly, for a soft surface with several highlights. |
Static fields ignore t. Animated ones are frozen at the modifier’s stillTime when animation is off or reduced.
Mesh gradients
DitherFields.mesh takes a grid of tones, one list per row from top to bottom and one tone per column from left to right, with 2 to 8 rows and columns. The control points sit on a regular grid with the first and last at the edges, and each grid cell blends its four corners smoothly. A control point has exactly its tone, and no tone goes outside the range of the ones you gave.
val aurora = DitherFields.mesh(
tones = listOf(
listOf(0.1f, 0.5f, 0.9f, 0.4f),
listOf(0.6f, 1f, 0.3f, 0.8f),
listOf(0.2f, 0.4f, 0.7f, 0.1f),
),
drift = 0.1f,
)
Box(Modifier.fillMaxWidth().height(280.dp).dither(aurora, animated = true, stillTime = 0.5f))
With a drift above 0 the interior points wander at speed cycles per second while the edge points stay put, so the surface moves and always covers the element. The drift needs animated = true on the modifier. A mesh has a shader, and changing its tones or drift does not compile a new program, so it is cheap to animate or to edit live.