Renders shading as a classic print halftone — a rotated screen of dots that grow and shrink with the light, reproducing a tone with just two colors the way newsprint and comics do.
JavaScript API:
HalftoneDitherColorMap
This is the halftone member of the ColorMap family: like every
color-map it turns a driver value into a color, but it lays down a rotated grid
of dots whose size tracks the tone, dithering between two colors into the
unmistakable look of screen-printed ink.
Key attributes. low-color is the dot fill and high-color the
background. dot-spacing sets the cell size in pixels (larger = coarser
screen); angle rotates the dot grid (the classic screen angle); shape
chooses the dot form (circle, line, …); softness feathers the dot edge.
The grain controls (size-jitter, wavy, seed) add organic, hand-printed
irregularity. It emits only the raw halftone color — the consuming
sk-tonemap applies its field and base-color compositing once.
Driving value is LIGHTNESS, not ink coverage. This is the opposite of the
intuition the attribute names invite, so it is worth stating plainly:
- t = 0 (darkest) → the LARGEST dots, growing until they merge and close the
cell entirely, leaving no background.
- t = 1 (lightest) → dots shrink to minDot (nothing, by default).
So dot coverage tracks ink (1 - t) for the area-metric shapes, and a mid-grey
renders as roughly half-covered cells. 'line' is the deliberate exception —
see HalftoneDotShape.
Related. Its two-tone siblings are sk-ordered-dither-color-map
(regular Bayer grid) and sk-blue-noise-dither-color-map (organic
stipple). For a smooth gradient, use sk-ramp-color-map. It is driven by
a sk-tonemap and can be a layer of a sk-composite-color-map.
Serve these docs to run the live example.
WebGPU needs a secure context — open this page via grunt serve
rather than double-clicking the file. The Markup tab works from disk.
<!-- Two spheres, same halftone dither, different screen `angle`. LEFT screens
at 15deg, RIGHT at 45deg (the classic printing angle) — compare the two dot
grids. Both use dot-spacing="7" so the dots read clearly; note how the dots
grow toward the shadow side (t=0) and shrink toward the light. -->
<sk-scene>
<sk-directional-light p="42deg" h="25deg" intensity="1.0"></sk-directional-light>
<sk-ambient-light intensity="0.3"></sk-ambient-light>
<!-- LEFT: 15deg screen. -->
<sk-halftone-dither-color-map id="a15" low-color="#14213d" high-color="#fca311"
dot-spacing="7" angle="15deg" shape="circle" softness="0.1"></sk-halftone-dither-color-map>
<sk-tonemap id="tm-a15" color-map="#a15"></sk-tonemap>
<sk-plain-material id="mat-a15" base-color="white" tonemap="#tm-a15"></sk-plain-material>
<sk-sphere radius="1.6" x="-2.2" segments="6">
<sk-surface-paint material="#mat-a15"></sk-surface-paint>
</sk-sphere>
<!-- RIGHT: 45deg screen. -->
<sk-halftone-dither-color-map id="a45" low-color="#14213d" high-color="#fca311"
dot-spacing="7" angle="45deg" shape="circle" softness="0.1"></sk-halftone-dither-color-map>
<sk-tonemap id="tm-a45" color-map="#a45"></sk-tonemap>
<sk-plain-material id="mat-a45" base-color="white" tonemap="#tm-a45"></sk-plain-material>
<sk-sphere radius="1.6" x="2.2" segments="6">
<sk-surface-paint material="#mat-a45"></sk-surface-paint>
</sk-sphere>
</sk-scene>
| Attribute | Type | Default | Description |
|---|---|---|---|
id |
<id> |
— | The element's unique identifier — the standard HTML global id attribute. |
fieldanimatable |
<id-ref> |
— | Gets the optional field that modulates this color-map's weight reference by id |
dot-spacinganimatable |
<number> |
8 |
Spacing between dot cells in pixels (>= 1). Default 8. |
low-coloranimatable |
<color> |
— | Color for low-value (dark / shadow / dot) bins. Animatable. Default: opaque black. |
high-coloranimatable |
<color> |
— | Color for high-value (bright / lit / background) bins. Animatable. Default: opaque white. |
angleanimatable |
<angle> |
0.785 |
Screen rotation of the dot grid, radians. Default 0.785. |
shapeanimatable |
<dither-shape> ("circle" | "square" | "diamond" | "line") |
"circle" |
Dot shape (structural). Default 'circle'. |
remap-curveanimatable |
<remap-curve> |
— | Reshapes the tone response: a remap-curve descriptor or a pre-built curve. Default none. |
remap-phaseanimatable |
<number> |
0 |
Horizontal remap phase — an animatable scalar that translates the tone |
screen-anchor |
<id-ref> |
— | Node whose projected screen position re-origins the dither screen reference by id |
softnessanimatable |
<number> |
0 |
Edge softness [0, 1]. Default 0. |
min-dotanimatable |
<number> |
0 |
Minimum dot radius fraction [0, 1]. Default 0. |
max-dotanimatable |
<number> |
1 |
Coverage ceiling at the darkest tone, as a fraction of cell closure [0, 1]. Default 1. |
size-jitteranimatable |
<number> |
0 |
Organic variation in dot size, as a fraction of the cell [0, 1]. Default 0. |
size-jitter-scaleanimatable |
<number> |
8 |
Cell size of the dot-size variation noise, in pixels (>= 1). Default 8. |
wavyanimatable |
<number> |
0 |
Organic waver of the dot grid [0, 1]. Default 0. |
wavy-scaleanimatable |
<number> |
16 |
Cell size of the waver noise, in pixels (>= 1). Default 16. |
seedanimatable |
<number> |
0 |
Position on the seed circle selecting which grain field is drawn (>= 0). Default 0. |
seed-periodanimatable |
<number> |
— | Seed advance that returns the identical grain field, i.e. the loop length (> 0). Default 16. |
color-interpolation |
<color-interpolation> ("auto" | "srgb" | "srgb-linear" | "oklab") |
"auto" |
The color space the dither interpolates its low↔high blend in — the |