Ollin quick reference
This page is the whole shape of Ollin in one read. It covers how a sketch runs, what the calls are called, their units, the command line, and the mistakes that fail with no error. Each part links to the page with the rest. It is written for anybody writing a sketch, and for an assistant helping somebody write one. Read it once, then look details up with the commands under Looking something up.
New to Swift? The Swift quick reference does the same for the language. Coming from p5.js or Processing, Appendix C of the Guide translates the API you know, and Appendix E does it for openFrameworks and OPENRNDR.
Contents
- A sketch
- The clock and randomness
- The calls, by area
- Names you might reach for
- Units and conventions
- Parameters
- The command line
- Looking something up
- Things that fail quietly
- Checking your work
A sketch
A sketch is a class that builds on Sketch. setup() runs once, and draw() runs every frame at the display's rate, so anything that reads time moves.
import Ollin
@main
final class Rings: Sketch {
override var canvasSize: CanvasSize { .vertical1080 }
override var loopDuration: Double? { 6 }
@Param(4...40) var count = 12
@Param var ink: Color = .black
override func setup() {
seed(7)
}
override func draw() {
background(.white)
noFill()
stroke(ink)
strokeWeight(2 * scale)
let t = loopProgress(over: 6)
for i in 0..<count {
let wobble = sin((t + Double(i) / Double(count)) * .tau) * 12
drawCircle(width / 2, height / 2, (40 + Double(i) * 30 + wobble) * scale)
}
}
}
- Configuration is overridden, not called.
canvasSize,windowMode,loopDuration,title, andcolorOutputare properties a sketch overrides. - The hooks are
setup(),draw(),mousePressed(),mouseReleased(),mouseScrolled(),keyPressed(),keyReleased(),filesDropped(), andreloaded()(after a hot swap). Mouse movement has no hook; readmouseX,mouseY, andmouseIsPressedindraw(). - Draw in
draw(), not insetup(). The frame's geometry is emptied before the firstdraw(), and per-frame state (the camera, lights, shadows) resets each frame. draw*calls emit shapes;fill,stroke, andbackgroundset state;translate,rotate, andscalemove the coordinates.withState { }keeps a change of state or transform inside its block.- Keep state in properties. A
Feedback, anAccumulator, a loadedImage, or aMeshis made once, insetup()or as a stored property, and used every frame. - Loading a file throws.
loadImage,loadMesh,loadScene,loadSVG,loadPalette,loadTable,loadJSON, and the initializers under them throw aFileError(missingorunreadable, with the path and a sentence).setup()may throw, so a baretrystops the sketch with that sentence (on stderr, and in a host's error strip, with the window held open), or writetry?to carry on withnil.
Sketch covers the lifecycle and Canvas the canvas and window.
The clock and randomness
timeis seconds sincesetup(). In a window it adds up the real frame steps, each capped at 0.25 s, so a stall pauses the clock rather than jumping it. In an export it is exactly the frame number over the frame rate.frameCountis 1 during the firstdraw().deltaTimeis the step since the last frame: 0 on the first frame in a window, exactly1 / fpsin an export.loopProgress(over:phase:)runs from 0 up to 1 over the given seconds and wraps.pingPong(over:phase:)goes 0 to 1 and back.loopDurationdeclares the length of one lap. It does not changetime. It tells--export-loophow many frames make exactly one lap, and makes a web export loop.noLoop()stops the window's timer after the current frame. Exports hold that frame too, so a frame asked for past it is the frame it stopped on, drawn once.redraw()draws one more frame of a stopped sketch, from an input hook.isExportingis true through the whole of an export,setup()included, and false in a window: work spread over live frames is done at once when it is true.random()returns 0 up to but not including 1,random(a, b)a up to b, andrandomGaussian()has mean 0 and deviation 1.seed(n)fixes bothrandomandnoiseand setsvariation.randomSeedandnoiseSeedfix one each. A sketch that never seeds gets a different variation at every launch.
Animation, Random, and Determinism have the rest.
The calls, by area
The names a sketch reaches for first in each area. ollin api <name> gives any of them in full.
| Area | Calls | Page |
|---|---|---|
| Canvas and frame | background, width, height, center, scale, noClear, makeAccumulator, withAccumulator, noLoop, redraw, isExporting | Sketch, Accumulation |
| 2D shapes | drawCircle, drawEllipse, drawRect, drawLine, drawPoint, drawTriangle, drawArc, drawPolygon, drawNgon, drawStar, drawRing, drawBezier, drawArrow | Drawing |
| Style | fill, noFill, stroke, noStroke, strokeWeight, strokeCap, strokeJoin, strokeDash, strokeAlign, blendMode, hollow, solid | Drawing |
| Transforms | translate, rotate, scale, withState, mirrored, repeated, withClip | Drawing |
| Outlines | drawShape, drawCurve, drawPolyline, Path, Shape, Contour, Vector2, drawSVG | Geometry |
| Text | drawText, textFont, textSize, textAlign, textWidth, textToShapes | Text |
| Images | loadImage, drawImage, Image, tint, noTint | Images |
| Color | Color, Palette, Ramp, Gradient, CosinePalette, Colormap, OKLCH | Color |
| Math and noise | map, lerp, clamp, smoothstep, polar, spherical, random, noise, signedNoise, fbm, curlNoise | Math, Noise |
| Motion | loopProgress, pingPong, Easing, Eased, Sprung, every, after | Animation |
| Layers and effects | makeRenderTarget, withTarget, filtered, Filter, combined, postProcess, makeFeedback, withFeedback, generate | Effects |
| Your own shaders | Shader, Filter, Generator, Visual, drawVisual, compute | Shaders |
| 3D | camera, Camera3D, cameraControl, drawBox, drawSphere, drawMesh, Mesh, material, Material, directionalLight, pointLight, environment, castShadows, temporalAntialiasing, motionBlur | 3D |
| Input | mouseX, mouseY, mouse, mouseIsPressed, key, keyIsPressed, isKeyDown, droppedFiles, chooseFiles, pointerShape, hidePointer | Input |
| Parameters | Param, ParamGroup, Saved, variation | Parameters |
| Techniques | poissonDisk, voronoi, delaunay, flowField, drawLSystem, drawWFC, packCircles, drawTruchet, stipple | Generators |
Names you might reach for
Some names from other frameworks mean something else here, or nothing. ollin api catches the near misses and lists the sketch calls spelled like the name you typed. It cannot catch these:
| You might type | In Ollin |
|---|---|
circle(x, y, d) | drawCircle(x, y, radius), which takes a radius rather than a diameter |
strokeWidth, lineWidth | strokeWeight |
push() and pop() | withState { } |
createCanvas(w, h) | override var canvasSize: CanvasSize { .size(w, h) } |
saveFrame | --export or --export-sequence on the command line, or OllinApp.image(of:frame:) in code; copyFrame() puts the frame on the clipboard |
cursor(CROSS) and noCursor() | pointerShape(.crosshair) and hidePointer(), with showPointer() to bring it back |
selectInput(prompt, callback) | chooseFiles(withExtensions:), with the pick read from chosenFiles() in filesChosen() |
millis() | time, in seconds |
radians(45) | .degrees(45), which converts degrees to the radians every drawing call takes |
beginShape() and endShape() | drawShape { } with a Path, or drawPolyline for points |
| a Poisson fill, a Laplace or membrane fill of the space between marks | .diffuse() filtered over a layer holding the marks. poissonDisk is something else: it scatters points. |
Units and conventions
| What | Convention |
|---|---|
| 2D coordinates | The origin is the top-left corner and y points down. width and height are the canvas in pixels. |
scale (the property) | min(width, height) / 1000. Write sizes for a 1000-pixel canvas and multiply by it, and the sketch holds at any canvas size. It is separate from the scale(_:) transform. |
| 2D angles | Radians, 0 along +x, turning clockwise on screen because y points down. rotate, drawArc, polar, and Vector2(angle:length:) all work this way. |
| Degrees | .degrees(45) and .turns(0.125) convert to radians. The few calls that take degrees say so: an LSystem angle, Ocean.windDirection, the Physarum angles, and the latitude, longitude, and sun position of a Place. |
| The complex plane | Every generator that paints it and the shader library's complexPlane put center in the middle, 3 / zoom units across the shorter side, and the imaginary axis up. A generator's plane is that framing as a ComplexPlane, with canvasPoint(of:in:) and planePoint(at:in:). |
| Hue | A turn from 0 to 1 that wraps, in Color(hue:saturation:brightness:) and in OKLCH and OKHSL. |
| 3D coordinates | Right-handed with y up, and the camera looks down its own −z. Nothing draws in 3D until a camera is set with camera, cameraControl, or the like, and the camera resets every frame. |
| 3D transforms | Three numbers or a Vector3: translate(x, y, z), scale(s, s, s), rotateY(a) or rotate(a, axis:). The two- and one-number forms, translate(x, y), scale(s), and rotate(a), are the 2D transform and leave a mesh where it was. |
| Field of view | Vertical, in radians, .pi / 3 by default. A taller canvas keeps the height and shows less width, so a portrait render of a scene framed wide crops its sides. |
| Orbit | Camera3D.orbiting(target:radius:azimuth:elevation:) takes radians. Elevation 0 is the horizon and positive looks down from above. Azimuth 0 puts the eye on +z looking toward −z, and π/2 puts it on +x. |
| Spot light | coneAngle is the full cone in radians, .pi / 6 by default. penumbra runs from 0 to 1. |
| Color | A Color holds sRGB components with straight alpha. Fills, strokes, lights, materials, and tints are converted to linear light for you. linearRGB and Color(linear:green:blue:alpha:) cross over by hand. |
| Brighter than white | A component can pass 1: Color(linear: c.linearRGB * 3). It only shows brighter than white on screen when the sketch overrides colorOutput with .extended; otherwise the tone map clips it. |
| Noise | noise, simplexNoise, and fbm return 0 to 1. The signed forms (signedNoise, signedFbm) return −1 to 1. curlNoise returns a vector that is not normalized. noise(loop:radius:) loops as loop runs 0 to 1. |
| Export time | --frame N counts from 0 and draws at N / fps, and --at S is the same moment in seconds (the frame is S × fps, rounded; it wins when both are given). In --export-sequence, file n is drawn at (n − 1) / fps. --skip is in seconds and --start only renumbers the files. |
| Depth-of-field light | A SprayLine's light is the whole segment's, not light per unit length. A long line and a short one at the same light put down the same amount of light. |
Parameters
@Param makes a property tunable. The live hosts show a control for it, and the command line can set it.
@Param(0...200) var radius = 120.0 // a slider
@Param("Speed", 0.1...4, step: 0.1) var rate = 1.0 // a label and a step
@Param(1...12) var rings = 5 // an Int is a stepper
@Param var tint: Color = .purple // a color well
@Param(x: 0...1080, y: 0...1080) var origin = Vector2(540, 540)
@Param(group: .folded("Advanced")) var jitter = false // a card that starts closed
- An enum that is
ParamOption(CaseIterable) becomes a menu, andstyle: .segmentedshows every case at once. A built-inMaterial,Easing,Fog, orEnvironmentis a menu too (@Param var surroundings = Environment.studio). group:puts a parameter in a titled card, and parameters with no group come first.icon:takes an SF Symbol name.$rate.show(when: $mode) { $0 == .fast }insetup()hides a row. The value still holds.- A value tuned in the inspector lives in that window until "Save parameters to Sketch.swift" writes it into the source. An export never opens the inspector, so it uses the values written in the source. The dot after a tuned parameter's name, or Reset in its row's menu, puts it back to what the line declares, and Reset all puts every parameter back, with the sketch still running.
ollin api Paramlists every form a parameter can be declared with.
Parameters has the rest.
The command line
A packaged sketch runs with swift run <Target> <flags>, and a single file with ollin Sketch.swift <flags>. ollin <package folder> <flags> runs a package's sketch the way the single file runs, every file of its target compiled together. With no flag the sketch opens in a window. With an export flag it renders without one and exits.
| Flag | What it does |
|---|---|
--export frame.png | One still. --frame N picks the frame (from 0, default 0) or --at S names the moment in seconds, and --fps sets the rate (default 60). Every frame before it is drawn first. |
--export-sequence dir | frame-00001.png, frame-00002.png, and so on, for --seconds S or --frames N. --skip S runs S seconds before the first file is written. |
--export-video out.mp4, --export-gif out.gif | A movie or a GIF, with the same --seconds, --frames, --fps, and --skip. |
--export-loop out.mp4 | Exactly one lap of loopDuration. |
--seed N | The variation to render, set before setup(). A seed(n) in setup() overrides it. |
--param name=value | Sets a parameter, before canvasSize is read and before and after setup(). Repeat it for more. A color is #RRGGBB, a vector x,y, a menu its label. |
--list-params | Prints every parameter in the form --param takes, and stops. |
--settle N | Draws each written frame N times with the clock held. A LineSpray and an Accumulator need it to converge. Anti-aliasing over time and path tracing don't. |
--render-quality | performance, default, or detail: the sampling budgets for soft shadows, depth of field, and ambient occlusion. Exports default to detail. |
--render-scale N | Draws each frame N times larger and averages it down. It smooths fills and polygons; shapes and strokes are already smooth. |
--subframes N | Draws each frame at N moments across the shutter and averages them: motion blur for anything the sketch draws, 2D included. --shutter F sets the opening (0.5 when left out). |
--bench | Times 600 frames and reports the cost per frame, without writing anything. --gpu adds the GPU's side. It measures the window's quality tier; an export lifts .default to .detail, so add --render-quality detail to time what an export pays. |
Export has every flag.
Looking something up
Three commands answer most questions without a browser. They read the checkout you build against, so they describe the version you are running.
ollin api drawCircle # what a name takes, what its comment says, where it is used
ollin docs Drawing/Color#ramp # one section of one page
ollin examples ocean --source # an example that does it, as source
ollin api <name>answers "what is it called, what does it take, and is it public". It reads the listings underAPI/, which hold every public declaration, so a name it does not know cannot be reached from a sketch. It prints each declaration as its source writes it, with the comment above it, where units and ranges are written down.ollin api Mesh.tubepicks one type's member, andollin api Paramlists a whole type.ollin docs <topic>opens a page by name or path, and<topic>#<heading>opens one section.ollin docs --search "long exposure"finds every line that says it.ollin examples <word>lists the sketches whose name, folder, description, or header credit matches (an exact name alone, anything looser together, names first), and--sourceprints one.
ollin is Scripts/ollin in the checkout, and it works from any folder. ollin install puts it on your PATH. Its output is plain text under a pipe, so it composes with grep. The reference offline has the rest.
Things that fail quietly
Each of these gives a wrong or empty picture and no error.
- A layer that is drawn into but never drawn. Nothing reaches the canvas until its image does:
drawImage(layer.image, 0, 0). ALineSpray's pass count climbs all the same, and the canvas shows only the background untildrawImage(spray.developed(exposure: 1).image, 0, 0). - Drawing in
setup(). The geometry is thrown away before the first frame, and a camera, a light, or shadows set there are gone by the firstdraw(). Draw and set per-frame state indraw(). - A concave outline through
drawPolygon. It fills as a fan from the first point, which only works for a convex shape. A concave one goes throughdrawShape { }or aShape. - A
FeedbackorAccumulatormade insidedraw(). A fresh one every frame never builds up. Make it once, insetup(). - A layer that draws a
filtered(_:)image. Every layer draws before any filter resolves, so a target that draws a filtered layer's image this frame comes out empty, as a picture and as data, and aSimFieldcannot take a filtered layer as an input either. A filter's result reaches the canvas, or the next filter in a chain, and nothing else. - A data texel with alpha at or below zero.
drawImagewrites nothing for it, so a signed field copied throughdrawImageinto a float layer loses those texels while every other channel arrives verbatim. Keep alpha above zero (one plus the coverage, say) and carry the sign in a color channel, or read the layer where it is withsampleRaw. - A long, faint sum on a
noClear()canvas. The canvas is half-float by default, so a light added a little at a time stops brightening. UsenoClear(precision: .float32),makeFeedback(precision: .float32), or anAccumulator. - A far contour of a distance field measured on a half-float layer. Past 1,024 pixels a half float holds a distance only to the nearest whole pixel, so a contour cut there comes out ragged. Draw the marks into
makeRenderTarget(precision: .float32); the field and every filter after it keep single precision. - A still at frame 0.
--exportdraws frame 0 unless told otherwise, and nothing came before it: motion blur is off and a trail or a pile holds one frame. Pick a later frame with--frame N. ALineSprayor anAccumulatoris the exception:--settle Nredraws the frame with the clock held, so it converges even at frame 0. - Motion driven by
frameCountunder--settle. The clock holds for the extra draws butframeCountkeeps counting, so a camera driven by it moves and restarts the converging picture. Drive motion fromtime. - Motion driven by
frameCountunder--subframes. Every moment is a draw, so a fixed step once perdraw()runs N times as fast and does not blur. Drive it fromtime, or step it bydeltaTime. - Values tuned in the inspector, in an export. The export uses the values in the source. Save them first, or pass
--param. - Values the command line reads loosely. A
--paramoutside the declared range is clamped, an unknown--render-qualitybecomesdetail, and an--fpsthat does not parse becomes the default. None of them says so. Filter.grainin the shadows. It adds noise and clips it, which lifts black..filmGrainleaves black and white alone, though its grain still shows most in near-black tones.- A
@Paramicon that is not an SF Symbol. The row shows an empty space where the icon goes. - 3D on a
noClear()canvas. Nothing is sorted by depth and nothing casts a shadow. - A volume, and the 2D drawn after it.
drawVolumewith no camera set draws nothing. With one, every volume composites over the frame's whole drawing once it is done, so a caption drawn later indraw()lies under the cloud. Draw the caption inwithOverlay { }. See Volumes. - A texture on a mesh with no texture coordinates. The texture is ignored, and the mesh draws in its base color.
redraw(),copyFrame(),chooseFiles, andpointerShapein an export. There is no window, so each does nothing. Aredraw()insidedraw()asks for nothing either: that frame is the one being drawn.- Noise offsets 256 apart. Every Perlin field repeats every 256 units on each axis, so copies told apart by an offset of
32 * iread the same noise every eighth copy. Use an offset that is not a power of two, or a seed per copy. - Noise at its ends.
noiseis clamped after its contrast is raised, so about 2% of it sits exactly at 0 and 2% exactly at 1, and a curve read straight from it runs flat there. - A
printduring an exported sequence. It lands on the same terminal line as the progress text, and later than expected when the output is piped. Write to standard error with a newline first, or read the value after the run.
Checking your work
- Judge speed in a release build.
swift runbuilds the framework for debugging, which runs several times slower. Useswift run -c release, orollin, which runs a release host and compiles the sketch optimized.--benchgives numbers. - A color or vector parameter reads through a lock. A number, an integer, or a switch reads in under a nanosecond, like a stored property; a
Color, aVector2, a palette, or a text value takes a lock, about 40 ns, so read one of those into a local before a loop that would read it per vertex. See Parameters. - Judge motion in a sequence, not a still. Anything that needs earlier frames (motion blur, trails, a pile, a settling picture) shows properly only a few frames in. A short
--export-sequenceshows it. - Compare pixels, not bytes. Every exported PNG carries its recipe in its description: the seed, every parameter, the sketch's commit and the framework's, the frame, and the frame rate. Two files with the same pixels differ when any of those do, so compare the pictures, for instance with
compare -metric AE a.png b.png null:from ImageMagick. - Fix the seed before comparing two runs. Call
seed(n)insetup()or pass--seed N, or every run is a new variation.
See also
- Documentation index: every page, by area
- Swift quick reference: the language, at the same length
- The reference offline:
ollin docs,ollin examples, andollin api - Guide Chapter 1: the first sketch, run and changed