Effects and line boil
effect() draws a halftone, stipple, splatter, dither, duotone or boil into a canvas with WebGL 2, and boil() moves a path's edges by stepped noise.
effect() returns a handle with set(params) and dispose(), and a canvas holds one live effect.
Its source is an <img>, a <video> or a <canvas>, and one from another origin needs CORS headers from its server.
Shading painted from marks is on Shading.
Effects
effect(canvas, { scene, source, kind, ...params }) draws halftone, stipple, splatter, dither or duotone from an <img>, a <video> or a <canvas> into a canvas, with WebGL 2.
It returns a handle with set(params), which changes some parameters, and dispose().
Every parameter can change on every frame, and each change costs one draw.
const { scene, effect, ramp } = wildflower.kinetic;
scene('poster', {
init() {
this.fx = effect(this.element.querySelector('canvas'), {
scene: this, // the scene's time, and disposed with it
source: this.element.querySelector('img'), // an <img>, <video> or <canvas>
kind: 'halftone',
});
},
seek(local) {
this.fx.set({ cell: 14, angle: 45, density: ramp(local, 0, 2), ink: '#1b1530', paper: '#f3ead8' });
},
});
A layer's scene
A layer made in a scene takes scene: this: effect(), shade() and blur(), text() and lottie() all have the option.
With it, the layer runs on the scene's local time and is disposed with the scene, wherever and whenever it was made, after an await too.
Without it, the layer runs on the composition's time and lasts until its dispose().
scene is the scene's own component, this in its init() or seek(); its element or its name throws KN-100, and set() cannot change it.
effect() on a canvas the page painted itself, three shaded spheres over a gradient: the kind and its parameters are the knobs, density sweeps from -0.35 to 0.35 and back over the loop, and a click on the stage pauses it.The kinds
The kinds are halftone, stipple, boil, splatter, dither and duotone, each with its own parameters.
| Kind | What it draws | Parameters (default) |
|---|---|---|
halftone | A dot in each cell of a grid, its area the source's darkness at the cell's center. | cell in px (12), angle in degrees (45), shape ('circle', 'square' or 'line'), density (0), ink, paper, color ('ink', or 'source' for each dot in the color under it) |
stipple | Ink in each square of scale px whose seeded hash falls under the source's darkness there. | scale in px (1), seed, a whole number (1), density (0), contrast (1), grade, how far a speck shrinks where the source is light (0 to 1; 0), speck ('square' or 'round'), color ('ink' or 'source'), ink, paper |
boil | The source itself, each pixel taken from up to about amount px away by smooth seeded noise: line boil on a canvas, an image or a video, with a new seed fps times a second. | amount in px (2), scale in px (30, the noise's size), seed (1), rough (1 to 6 octaves; 1) |
splatter | Round drops of ink in several sizes, more of them where the source is darker, and the large ones only where it is darkest, as spray leaves them. | min and max, the smallest and largest drops' size in px (2 and 24; up to six sizes between, each about half the last), seed (1), density (0), contrast (1), warp, how ragged a drop's edge is (0 to 1; 0.3), vary, how much each drop's strength varies (0 to 1; 0.5), color, ink, paper |
dither | An ordered dither on a Bayer matrix, in levels steps from ink to paper; above 2, the steps between are mixes of the two. | matrix (2, 4 or 8; 4), levels (2 to 16; 2), scale in px (1), color, ink, paper |
duotone | The source's brightness from the shadow color to the highlight color. | shadow, highlight, contrast (1), density (0) |
Brightness, density and color
- Brightness is the source's luma,
0.2126 R + 0.7152 G + 0.0722 B, and a transparent pixel reads as white.contraststretches the brightness about its middle. density, from -1 to 1, moves a halftone, a stipple, a splatter or a duotone toward ink or the shadow. In a halftone that makes every dot larger, and a halftone at full darkness fills its cells.- The colors are
#rgb,#rrggbb(with alpha or without) orrgb(), ink and the shadow black and paper and the highlight white by default.
When an effect draws
- The shaders read the parameters, the source and an integer hash, and no time or random number, so a frame is the same on every draw.
fps: non a stipple, a splatter or theboilkind moves itsseedon n times a second of the layer's time, anddelay: sholds the first seed for s seconds;set()changes both, and on another kind either throws KN-100.- Kinetic draws every live effect after each seek has settled, once the footage has landed on its frame, so a
<video>source shows the frame of the seek. While the clock plays, that is every frame. - An effect is drawn again only when its parameters, its size or its source's frame have changed.
- An
<img>source's frame changes once it has loaded, and an<img>that loads while the clock is paused is drawn when it loads. - A
<video>source's frame changes when its time moves. - A
<canvas>source's frame changes on every seek, since nothing tells when one was drawn on.
- An
- Readiness waits for each
<img>source to decode and draws each effect once before the page shows. - With the HyperFrames runtime on the page the runtime plays the media itself, so a
<video>source can be drawn from the frame before the seek's (Rendering through HyperFrames).
The canvas
- The canvas gets a WebGL 2 context with
preserveDrawingBuffer, socheck()'s canvas hash andrender()read it. The source is stretched over the canvas, so give the canvas the source's shape. - The canvas's drawing size is the size it is shown at, its box after transforms such as the fitted stage's scale, times the device pixel ratio, and at most its CSS box times that ratio. During a render it is the CSS box itself, at the composition's own scale.
- A canvas with no CSS size of its own would take its size from that drawing size, so Kinetic sets its CSS width and height to the size it had.
Lifetime and errors
dispose()stops the drawing and releases the canvas's WebGL context, after which that canvas takes no new effect. A canvas holds one live effect, andeffect()on a canvas whose effect is still live is KN-100.- An effect made with
scene: thisis disposed when the scene is destroyed, wherever its canvas is, and an effect whose canvas has left the page is disposed at the next seek, sodispose()is for stopping one sooner. - An effect whose canvas was never on the page stays live until
dispose(), or until its scene is destroyed when it was made with one. - A browser drops the oldest WebGL context when a page holds too many, and an effect whose context was lost draws nothing until the browser restores it. Development builds warn (KN-191), and Kinetic sets the effect up again and draws it at the next seek once the context is restored.
- An unknown kind or parameter, a value out of range, a source that is not an
<img>,<video>or<canvas>, and a canvas with no WebGL 2 context (one that already has a 2D context, for one) throw KN-100. - A source from another origin without CORS cannot be read by WebGL, so the first draw from it throws KN-204 with the source's URL, and the seek that drew it rejects.
The effect then skips that source, with no error, until the source changes.
Give it
crossorigin="anonymous", withAccess-Control-Allow-Originfrom its server. - A halftone of HTML itself needs the HTML's pixels, which only HTML-in-Canvas gives, so
effect()takes no HTML source. Still effects with an SVG filter covers HTML that does not change.
Line boil
Line boil moves edges by a little smooth noise, and with a new seed a few times a second the edges change as hand-drawn frames do.
amount is about the largest move in px (the noise is stretched and eased with tanh), scale is the noise's size in px, and fps is how many times a second the seed changes.
boil() takes SVG path data, and effect()'s boil kind takes a canvas, an image or a video.
const { scene, boil } = wildflower.kinetic;
const SUN = 'M 980 90 A 110 110 0 1 1 979.99 90 Z';
scene('sketch', {
computed: {
sun() { return { d: boil(SUN, { amount: 4, scale: 40, fps: 8, t: this.local }) }; },
},
});
boil() on a small sketch, the sun the call and the rays, the hill, and the bird the same call with another seed: the options are the knobs, the faint circle is the sun at rest, and a click on the stage pauses it.boil(d, options)returns SVG path data with its outline moved: each subpath sampled everydetailpx (2), each sample moved along its normal by noise read everyscalepx along the outline, written as a polyline. The options areamount(2),scale(30),fps(0, one seed),delay(0),t,seed(1) anddetail(2). Witht, usually the scene'sthis.local, andfps, the seed stepsfpstimes a second, anddelayholds the first seed for its seconds oft. A closed subpath stays closed, with no jump where it closes when it is longer thanscalepx, and an open one stays open. Return{ d: boil(…) }from a computed and bind it to the<path>withdata-bind-attr.boil()works on paths only, so HTML, page text included, does not boil. For a boiled title, draw it as a path, or withtext()on a canvas and through theboileffect.effect(canvas, { scene: this, source, kind: 'boil', fps: 8 })moves a canvas's, an image's or a video's pixels, with a new seed 8 times a second of the scene's local time;delayholds the first seed for its seconds. A stipple or a splatter fromeffect()takesfpsanddelaythe same way.- Every value comes from the time and the seed, so
check()and a render see the same edges. - Bad path data or a bad option throws KN-100.