IInbox
Composed

Textures

Six seamlessly tiling procedural textures from periodic noise — deterministic, exportable, and measured rather than eyeballed.

Grain, fibre, hatch, dots, clouds and speckle. Every one tiles, and that is the whole point: a texture that does not tile is a decoration you can use once; one that does is an asset you set as a background-repeat and forget about.

The forge is at /lab/textures.

Installation

npx shadcn@latest add https://ui.whoisroktim.lol/r/textures.json
import { renderTexture } from "@/components/app/textures";

const rgba = renderTexture("fibre", { size: 256, scale: 20, seed: 3 });
const img = ctx.createImageData(256, 256);
img.data.set(rgba);
ctx.putImageData(img, 0, 0);

Seamlessness is structural

You cannot fix a seam after the fact. Mirroring the edges produces a visible axis of symmetry; blurring them produces a visible soft band. It has to come from the noise being genuinely periodic, which means wrapping the integer lattice coordinates before hashing — so the cell at x = period is literally the same cell as x = 0. Wrapping the sampled position instead leaves a discontinuity in the interpolation, which shows up as a faint but unmistakable line down the join.

Two consequences that are easy to miss:

The fractal sum has to double the period along with the frequency. Doubling only the frequency — the usual way an fbm is written — puts the seam back for every octave after the first.

Periods are per axis. A stretched texture like paper fibre samples one axis several times finer than the other. Scaling a coordinate while leaving a single shared period behind stops the wrap landing on the tile edge. This one was a real bug, caught by the test below rather than by looking.

Measured, not eyeballed

seamRatio compares the jump across the wrap against the texture's own local variation, and returns roughly 1 for a clean tile.

That relative framing matters. A raw difference between the two edges flags a fine hatch as broken purely for being high-frequency — adjacent columns in a hatch legitimately differ a great deal. The test suite also runs the metric against a deliberately non-periodic gradient, so a check that always passed could not go unnoticed.

Determinism

No Math.random anywhere: everything comes from an integer hash of position and seed. The same seed gives the same tile on every machine and every run, which is what makes a texture something you can commit to a design rather than something you have to save the moment you find it.