Coordinate space
unreviewed draftHow hydra addresses pixels (a 0–1 square stretched over the canvas), what sources draw outside it, and why geometry functions seem to run backwards.
Almost every hydra parameter that moves, zooms or repeats something is measured in one coordinate system. Once you know it, the units on every page (screen widths, “positive moves left”, “wraps every 1”) follow from a few lines of math.
The unit square
Section titled “The unit square”Each pixel gets a coordinate called (hydra’s name for it; think ):
- runs from 0 at the left edge to 1 at the right.
- runs from 0 at the top to 1 at the bottom: it points down, as in most image formats.
- Both axes go 0 → 1 whatever the canvas size or shape. A 1280 × 720 canvas is still a “1 × 1” square; a step of 0.1 in is 128 pixels, a step of 0.1 in only 72.
gradient() draws exactly this: red = , green = . Probe on a 16 : 9 canvas: top-left
(5, 4, 0), top-right (250, 4, 0), bottom-left (5, 251, 0), bottom-right (250, 251, 0).
That last bullet has visible consequences. shape(64, 0.5) (a near-circle) covers 0.25–0.75 on both
axes, which on a 256 × 144 canvas is 128 pixels wide and 72 tall: an ellipse. Every distance-based
function (shape, rotate, kaleid, scale with equal multipliers) treats one unit of and one unit of
as equal, even though they aren’t on screen.
What a source draws outside 0–1
Section titled “What a source draws outside 0–1”Geometry functions can ask a source for coordinates outside the square (zoom out with .scale(0.5) and
the screen covers −0.5 to 1.5). What you see there depends on the source, because each one is just a
formula of :
| Source | Outside 0–1 | Evidence (probe with .scale(0.5)) |
|---|---|---|
src(…) (images, video, other outputs) | repeats (tiles): it samples at | quadrant texture appears 2 × 2 |
osc | continues the stripes | 4 stripes across instead of 2 |
noise | continues | different, non-repeating values at the edges |
gradient | clamps: below 0 shows black, above 1 full color | (0, 0, 0) near top-left, (255, 255, 0) near bottom-right |
shape | black: the shape sits at the center; everything else is empty | one small square, black around it |
solid | the same color everywhere |
gradient clamps because its output is the coordinate, and colors are stored in 0–1 between passes.
Textures loaded into s0…s3 (images, video, camera) are stretched to fill the 0–1 square, so a
square image on a wide canvas is displayed wide. They appear upright: the image’s top-left corner is at
.
Geometry moves the lookup, not the pixels
Section titled “Geometry moves the lookup, not the pixels”Geometry functions (scroll, scale, rotate, repeat, kaleid, pixelate, and the modulate…
family) never move pixels. For each screen pixel they compute which coordinate to ask the source for.
That’s why their parameters often seem inverted:
scroll(0.25, 0)asks for : each pixel shows what was a quarter screen to its right, so the image moves left.scale(2)asks for : it divides the distance from the center, so a pixel near the edge shows something closer to the middle, and the image looks twice as big. Probe:gradient().scale(2)at shows the color of .scale(0.5)shrinks; a negative amount divides by a negative number, which flips both axes (a 180° turn, not a mirror).rotate(θ)asks for the point rotated by around the center, so the image turns by , clockwise on screen because points down.
Chain order: geometry runs backwards
Section titled “Chain order: geometry runs backwards”In src(s0).rotate(…).scroll(…), hydra builds the shader so that the last geometry function in the
chain transforms the screen coordinate first:
Color functions (color, brightness, thresh, …) are the other way round: they run after sampling, in
chain order.
The good news: the two reversals cancel. If you read the chain left to right as operations on the
image, you get the right picture. Probe with a red | green / blue | white quadrant texture:
src(s0).rotate(Math.PI / 2, 0).scroll(0.25, 0) has a top row reading B B R R R R B B: the quadrants
turned 90° clockwise (blue top-left, red top-right), then the result slid a quarter screen left.
The backwards order matters when a function’s behavior depends on the coordinate values it receives.
scroll wraps its output into 0–1 with fract. Put .scale(0.5) after it in the chain and the scale
runs first, handing scroll coordinates from −0.5 to 1.5, which it wraps into tiles:
Why rotate shears
Section titled “Why rotate shears”Rotation is done on the unit square, and the unit square is stretched to the canvas. On a 16 : 9 canvas a rotation in space is a rotation in a squashed space, which on screen looks like a shear: right angles stop being right angles partway through a turn. The cure is to make the units square first, rotate, and undo it; rotate shows the chain. The same idea fixes ellipses and stretched images (examples below).
Examples
Section titled “Examples”gradient().out()shape(4, 0.3, 0.01).scroll(0, 0).scale(0.5).out()shape(64, 0.4, 0.01).scale(1, height / width, 1).out()s0.initImage("https://hydra-ref.audiodude.xyz/cards/photo.jpg")src(s0).scale(1, () => s0.src ? (s0.src.width / s0.src.height) / (width / height) : 1, 1).out()The circle example shrinks by the canvas’s height/width ratio, so one unit of and one of cover
the same number of pixels (probe on 256 × 144: about 64 × 54 px, against 96 × 54 px without the fix,
within the probe’s 8 px / 4.5 px sampling steps). The image example does the same with the image’s own
ratio, computed every frame by a function argument (so it’s correct once the image has loaded; until then
s0.src is null and the factor is 1). Because src() repeats, an image narrower than the canvas tiles at
the sides instead of leaving black bars: probe with a square texture on a 256 × 144 canvas gives a square
image 144 px wide in the middle with partial copies left and right. The sample photo is 16 : 9 itself, so on
a 16 : 9 canvas the factor is 1 and nothing changes; swap in a portrait image to see the difference.
Related
Section titled “Related”- scroll: the simplest geometry function, and the wrap.
- rotate: angles, the center pivot, aspect-correct rotation.
- osc: a source that only looks at .
- Arguments & time: numbers, arrays and functions as parameters.
Documents hydra-synth 1.4.0 (AGPL-3.0; this site runs it unmodified). Annotations drafted by Claude (Anthropic) from the hydra-synth source and checked against rendered output — see about. Site code AGPL-3.0, prose CC BY 4.0,source.