Coming from p5.js

Why you might move a sketch to Python, and how to bring your p5.js habits to Kiln

If you have written a p5.js sketch, most of a Kiln sketch will look familiar: a setup, a draw, a canvas you draw shapes, images and text on, mouse and keyboard, noise and random. The names are different, the language is Python, and a few defaults differ in ways that change a drawing without an error. This page says what you gain and give up, ports one sketch line by line, lists the pitfalls, and ends with a table of the names you use most.

Why Python?

What you gain

  • numpy next to every draw call. The bulk calls (kiln.circles, kiln.rects, kiln.lines, kiln.ellipses) take whole numpy arrays, kiln.noise takes arrays too, and an image’s pixels are a numpy array you edit in place. A sketch of ten thousand particles is a few array expressions, not a loop of objects.
  • The Python ecosystem in the same file. numpy, PyTorch or MLX, the LLM SDKs, pandas and the rest of the data tools import into the sketch directly, where a browser sketch reaches for ml5.js or TensorFlow.js.
  • Desktop GPU work. Kiln runs on OpenGL 4.1: GLSL materials and raw programs, float Fbos, per-instance attributes and GPGPU simulations (see Shaders, Instancing and GPGPU particles).
  • One language from sketch to notebook to production. kiln.testing.render_frames runs a sketch in a hidden window and returns its frames as numpy arrays, so the same class renders in a notebook, a test or a batch job. The executed examples in these docs are rendered that way.
  • Live controls built in. kiln.Gui draws panels of sliders, toggles, colour pickers and more on top of the sketch, with presets saved as JSON.

What you give up

  • The browser. There is no URL to share, nothing runs on a phone, and a sketch can’t be embedded in a web page. You share a script, a video or a still.
  • Other platforms, for now. Kiln runs on macOS on Apple Silicon only.
  • The zero-install editor. You install Python, uv and Kiln (see Getting started) instead of opening an editor in a tab.
  • The DOM and the web APIs. No HTML elements, no createCapture camera or video, no fetch. kiln.Gui covers the sliders and buttons; for the rest, Python’s own libraries take the place of the web APIs.
  • 3D and sound, for now. Kiln draws in 2D: there is no WEBGL-style 3D scene, camera or lighting yet, and nothing like p5.sound yet (sound is being worked on).
  • A large community. p5.js has years of examples, courses and forum answers. Kiln is new.

Kiln makes no claim to be faster than p5.js here: nobody has measured the two side by side. The Performance page has Kiln’s own numbers.

One sketch, side by side

Twelve tiles ride a noise curve above a row of circles: the larger a circle, the higher its tile. The p5.js version first:

function setup() {
  createCanvas(600, 300);
  colorMode(HSB, 360, 100, 100);
  stroke(0, 0, 100);
  strokeWeight(2);
}

function draw() {
  background(230, 40, 15);
  for (let i = 0; i < 12; i++) {
    const x = 50 * i + 25;
    const n = noise(i * 0.3, frameCount * 0.01);
    fill(20 + 30 * n, 80, 95);
    push();
    translate(x, 120 - 200 * (n - 0.5));
    rotate(n * TWO_PI);
    rect(-12, -12, 24, 24);
    pop();
    circle(x, 250, 40 * n);
  }
}

The same sketch in Kiln, in the same order:

import math
import kiln

def hsb(h, s, b):
    """p5.js's HSB ranges (360, 100, 100) to Kiln's 0-255."""
    return kiln.Color.from_hsb(h * 255 / 360, s * 2.55, b * 2.55)

class Tiles(kiln.App):
    def setup(self):
        kiln.stroke(255, width=2)

    def draw(self):
        kiln.background(hsb(230, 40, 15))
        for i in range(12):
            x = 50 * i + 25
            n = kiln.noise(i * 0.3, kiln.clock.frame * 0.01)
            kiln.fill(hsb(20 + 30 * n, 80, 95))
            with kiln.transform():
                kiln.translate(x, 120 - 200 * (n - 0.5))
                kiln.rotate(n * math.tau)
                kiln.rect(-12, -12, 24, 24)
            kiln.circle(x, 250, 20 * n)  # a radius: half of p5.js's 40 * n

# kiln.run(Tiles(), 600, 300) opens a window. Here we render frame 60 instead.
show(Tiles(), frames=60, size=(600, 300))

What changed, apart from spelling:

  • The sketch is a class. State lives on self, and kiln.run(Tiles(), 600, 300), outside the class, takes the place of createCanvas.
  • circle takes a diameter and kiln.circle a radius, hence 20 * n.
  • Kiln has no colorMode: colours are RGB, 0-255. kiln.Color.from_hsb takes hue, saturation and brightness in 0-255 too, so hsb() converts p5.js’s ranges.
  • fill(c) is kiln.fill(c), and stroke(c) is kiln.stroke(c).
  • push and pop are one with kiln.transform(): block; the style has its own, with kiln.style():.
  • noise is a different function (simplex noise), so the tiles sit at different heights than in p5.js, and frameCount is one ahead of kiln.clock.frame.

Pitfalls

Most of these change a drawing without raising an error.

  • circle and ellipse take diameters, kiln.circle and kiln.ellipse radii. Halve the sizes: ellipse(x, y, w, h) is kiln.ellipse(x, y, w / 2, h / 2).
  • rectMode and ellipseMode. kiln.rect_mode has kiln.RectMode.CORNER (the default) and kiln.RectMode.CENTER; there is no CORNERS or RADIUS. Circles and ellipses are always centred, and there is no ellipseMode. Images follow the rect mode, which also stands in for imageMode.
  • colorMode(HSB) ranges. p5.js’s HSB defaults to hue 0-360 and saturation and brightness 0-100. Kiln has no colour mode: kiln.fill and kiln.background take RGB in 0-255, and kiln.Color.from_hsb takes all three HSB channels in 0-255 (hue 85 is green). Alpha is 0-255 everywhere, never 0-1.
  • Global setup and draw become methods of a kiln.App subclass. Variables shared between them become attributes (self.angle), and kiln.run(app, width, height) starts the sketch. App.update runs before each draw, for the state changes.
  • The background is cleared every frame. p5.js keeps the canvas between frames unless you call background; Kiln starts every frame from the background colour, which the last kiln.background call set (dark gray before any). For trails and drawings that accumulate, pass auto_clear=False to kiln.run (or set kiln.window.auto_clear = False).
  • mouseX, frameCount, keyIsDown. The mouse is kiln.mouse.x and kiln.mouse.y. kiln.clock.frame is 0 in the first draw, where frameCount is 1. keyIsDown(LEFT_ARROW) is kiln.Key.LEFT in kiln.keys. In App.key_pressed(key) the key is a number: test key == ord("a"), not key === 'a'. deltaTime is in milliseconds, kiln.clock.dt in seconds.
  • No preload and no await. kiln.Image("photo.jpg"), kiln.Font and mesh = kiln.Mesh(); mesh.load(path) read the file before the next line runs. Load in setup, or anywhere: there is no callback to wait for.
  • WEBGL mode’s centred origin. Kiln’s origin is always the top-left corner, with y down, as in p5.js’s default 2D mode. A sketch written for WEBGL around (0, 0) needs kiln.translate(kiln.window.width / 2, kiln.window.height / 2) first.
  • kiln.rotate is always in radians. Kiln has no angleMode: where a sketch uses angleMode(DEGREES), convert with math.radians (kiln.rotate(math.radians(45))). Positive angles turn clockwise on screen in both.
  • pixelDensity and the pixels array. Kiln measures everything in points and renders a Retina display at twice the resolution by itself; there is nothing to set. kiln.Image.pixels is an (h, w, c) numpy array, indexed pixels[y, x], not a flat list indexed 4 * (y * width + x) (at pixel density 1). A loaded file gives c = 4 (RGBA) when it has transparency, 1 when it is gray and 3 (RGB) otherwise, a JPEG included; an image made from an array keeps the array’s channels. Edit it in place and call kiln.Image.update. For the canvas itself, kiln.Image.grab_screen copies a rectangle of it into an image, with w * pixel_ratio pixels across.
  • push and pop save the transform and the style together. Kiln splits them: a with kiln.transform(): block for the transform, and a with kiln.style(): block for the colours, line width and modes.
  • Fill and stroke. kiln.fill(c) and kiln.stroke(c, width=) work as in p5.js, and None turns either off (noFill() is kiln.fill(None)). The defaults differ: p5.js fills white and strokes 1-pixel black; Kiln fills white with no stroke until kiln.stroke sets one. Images, Fbos and meshes take kiln.tint, never the fill. kiln.stroke’s width is in points on screen and does not grow under kiln.scale, where strokeWeight scales with the shape (in p5.js’s 2D mode).

Vocabulary

Each table maps a p5.js name to its Kiln name. The Processing column is filled only where Processing’s name differs; a few p5.js names (the DOM controls, deltaTime, keyIsDown, and the WebGL-only createFramebuffer and buildGeometry) have no Processing counterpart. scripts/check_p5_map.py checks that every Kiln name here exists, and the API reference documents each one in full.

Sketch and time

p5.js Processing Kiln Notes
setup App.setup A method of your kiln.App subclass.
draw App.draw App.update runs before it, every frame.
createCanvas size kiln.run kiln.run(Sketch(), width, height), outside the class.
width, height kiln.window.width, kiln.window.height Properties, in points.
frameRate kiln.clock.target_fps kiln.clock.fps reads it.
frameCount kiln.clock.frame 0 in the first draw.
deltaTime, millis kiln.clock.dt, kiln.clock.time The first in seconds, not milliseconds.
saveCanvas save kiln.save_screen Takes a file name with its extension.

Shapes

p5.js Processing Kiln Notes
background, clear kiln.background, kiln.clear background also sets the colour every frame is cleared to. kiln.clear takes a colour: clear() is kiln.clear(0, 0, 0, 0), transparent black.
circle kiln.circle A radius, not a diameter.
ellipse kiln.ellipse Radii, half the width and height; always centred.
rect, square kiln.rect rect’s fifth argument (a corner radius) is kiln.rect(x, y, w, h, radius=r); four radii are radius=(tl, tr, br, bl).
line, triangle kiln.line, kiln.triangle kiln.lines draws many lines from an array.
point kiln.circle point draws a dot strokeWeight across in the stroke colour; kiln.circle(x, y, d / 2) draws one d across in the kiln.fill colour. kiln.circles for many.
bezier, curve kiln.bezier, kiln.curve Same argument order. Filled under kiln.fill(): call kiln.fill() for a curve line.
beginShape, vertex, endShape kiln.shape, kiln.vertex with kiln.shape(): holds the vertices; endShape(CLOSE) is kiln.shape(close=True). A quad is a shape of four vertices.
bezierVertex, curveVertex kiln.bezier_vertex, kiln.curve_vertex
beginContour, endContour kiln.next_contour One call between contours. A contour inside another is a hole under the default winding rule, whichever way it turns.

Colour and style

p5.js Processing Kiln Notes
fill(c) kiln.fill Sets the fill colour of shapes and text.
noFill kiln.fill kiln.fill(None): outlines in the stroke colour, or nothing without one.
stroke, noStroke kiln.stroke kiln.stroke(None) is noStroke, the default.
strokeWeight kiln.stroke kiln.stroke(width=w), in points on screen, whatever the transform.
rectMode kiln.rect_mode kiln.RectMode.CORNER or kiln.RectMode.CENTER.
color kiln.Color RGBA, 0-255.
colorMode(HSB) kiln.Color.from_hsb Every channel in 0-255.
lerpColor kiln.Color.lerp a.lerp(b, amount), a new colour.
blendMode kiln.blend kiln.Blend.ADD, SUBTRACT, MULTIPLY, SCREEN, ALPHA (p5.js’s BLEND) or REPLACE; kiln.blend(None) is REPLACE too.

Transforms

p5.js Processing Kiln Notes
push, pop kiln.transform, kiln.style Two with blocks: with kiln.transform(): for the transform, with kiln.style(): for the style.
translate kiln.translate
rotate kiln.rotate Radians; there is no angleMode, so convert degrees with math.radians.
scale kiln.scale Line widths don’t scale.
resetMatrix kiln.reset_matrix

Input

p5.js Processing Kiln Notes
mouseX, mouseY kiln.mouse.x, kiln.mouse.y Live objects: they stay current after from kiln import mouse.
pmouseX, pmouseY kiln.mouse.previous_x, kiln.mouse.previous_y
mouseIsPressed mousePressed (the variable) kiln.mouse.pressed
mousePressed, mouseReleased, mouseMoved, mouseDragged App.mouse_pressed, App.mouse_released, App.mouse_moved, App.mouse_dragged Receive x and y; press, release and drag also the button, a kiln.MouseButton.
mouseWheel App.mouse_scrolled Receives x, y and the horizontal and vertical scroll.
keyPressed, keyReleased App.key_pressed, App.key_released Receive the key as a number: key == ord("a").
keyIsDown kiln.keys ord("a") in kiln.keys, or a kiln.Key such as kiln.Key.LEFT for LEFT_ARROW.

Maths

p5.js Processing Kiln Notes
random(min, max) kiln.random Numbers only.
random(array) random.choice Python’s random module.
randomSeed kiln.random_seed Seeds kiln.random, not kiln.noise.
randomGaussian kiln.random_gaussian
noise kiln.noise Simplex noise in [0, 1], 1D to 4D, on numbers or numpy arrays. No noiseSeed or noiseDetail.
map kiln.map Same arguments, clamp included.
constrain kiln.clamp
lerp, dist kiln.lerp, kiln.dist kiln.lerp also takes numpy arrays.
sin, cos, PI, TWO_PI math.sin, math.cos, math.pi, math.tau Python’s math, or numpy’s for arrays.
createVector, p5.Vector PVector np.array A numpy array; np.linalg.norm is mag. Arrays of many vectors work in one expression.

Images and pixels

p5.js Processing Kiln Notes
loadImage, p5.Image PImage kiln.Image kiln.Image(path) loads before it returns.
image kiln.Image.draw img.draw(x, y, w, h), tinted by kiln.tint.
tint kiln.tint kiln.tint(255) (the default) draws images untinted.
imageMode kiln.rect_mode Or kiln.Image.anchor.
pixels, updatePixels (on an image) kiln.Image.pixels, kiln.Image.update An (h, w, c) array, pixels[y, x]; from a file, c is 4 (RGBA), 3 (RGB, a JPEG) or 1 (gray). kiln.Image.from_pixels makes an image from one.
loadPixels (on the canvas) kiln.Image.grab_screen Copies a rectangle of the screen into an image.

Offscreen drawing, shaders and geometry

p5.js Processing Kiln Notes
createGraphics, createFramebuffer PGraphics; none for createFramebuffer (WebGL only) kiln.Fbo with fbo: draws into it with the usual calls; fbo.draw(x, y) shows it. Float formats too.
createShader, loadShader PShader kiln.Shader A material (one shade() function that colours shapes, text and images) or a raw vertex and fragment program.
shader, setUniform set for setUniform kiln.Shader with shader: draws with it; shader["name"] = value sets a uniform.
buildGeometry, p5.Geometry PShape; none for buildGeometry (WebGL only) kiln.Mesh kiln.Vbo keeps the geometry on the GPU.
loadModel loadShape kiln.Mesh.load mesh = kiln.Mesh(); mesh.load(path). .obj and .ply; no SVG or STL.

Text and controls

p5.js Processing Kiln Notes
text kiln.text y is the baseline. Without a font=, kiln.text draws an 8x8 pixel font; pass a kiln.Font for a TrueType one.
loadFont, textFont, textSize PFont kiln.Font kiln.Font("Inter", 24): one font object per size. Inter is bundled.
textWidth kiln.Font.text_width
createSlider kiln.Gui.slider Returns a kiln.Parameter, which reads like the number it holds.
createButton kiln.Gui.button Takes the function to call on a click.
createCheckbox, createSelect, createInput, createColorPicker kiln.Gui.toggle, kiln.Gui.choice, kiln.Gui.text, kiln.Gui.color In that order.

Processing and py5

If you come from Processing, the table above is yours too: Processing and p5.js share most names, and the Processing column gives the ones that differ. size becomes the kiln.run call, a PGraphics (with beginDraw and endDraw) is a kiln.Fbo used as with fbo:, PVector is a numpy array, and PShape is a kiln.Mesh. HSB needs no converting: Processing’s colorMode(HSB) and py5’s color_mode keep every channel in 0-255 by default, so kiln.Color.from_hsb takes their values unchanged. py5 brings Processing itself to Python, in snake_case (frame_count, rect_mode), through the Java virtual machine. Kiln is a separate framework with its own renderer, so py5 code doesn’t run unchanged, but the names translate the same way: find the Processing name in the table and read across.

Checking this page

uv run python scripts/check_p5_map.py

prints 0 unknown names when every Kiln name on this page exists in kiln.