Tools — left rail, or keys 1–8 Brush: freehand square brush, 1–8 px Eraser: writes transparency, so the layers underneath show through Line: click-drag, Bresenham, uses the brush size Bucket: 4-connected flood fill on the active layer, clipped to the selection Gradient: click-drag; foreground to background along the drag vector Select: drag a rectangular marquee; a single click deselects Move: drag the selected pixels around inside the active layer Eyedropper: samples the composite and updates the H/S/V sliders Layers — the panel on the right. Click a row to make it active, click the disc to toggle visibility, drag the bar to set opacity. LAYER on the menu bar has fill, clear, duplicate up, merge down, raise, lower and flatten. Colour — the 16 swatches in the rail, the hue ramp along the bottom, or the SAT and VAL sliders. X swaps foreground and background. History — the palette on the right lists every state since the document was opened. Click any row to jump there; rows below the cursor are the redo tail and are dimmed. Z undo, Y redo. Q/W change brush size. Menus — IMAGE (11 adjustments), FILTER (7), LAYER (7), EDIT (undo, redo, fill, clear, select all, deselect, reset). Adjustments and filters apply to the active layer, and only inside the selection if there is one. The project opens on a document that is itself nine history steps — clouds, blur and contrast on layer 1, a gradient band on layer 2 at 80% opacity, and a flood-filled diamond on layer 3. Press Z a few times and watch it come apart.
A layered image editor in Scratch: four real RGB layers with per-layer visibility, opacity and stacking order, composited bottom-to-top; brush, eraser, line, flood fill, gradient, marquee, move and eyedropper; eleven adjustments and seven filters including a Sobel edge detector and a value-noise cloud renderer; and a Photoshop-style history palette you can click backwards through. THE LAYER STACK IS THE WHOLE POINT An editor with one canvas and a lot of tools is MS Paint. What makes it Photoshop-shaped is that the picture you see does not exist as a picture: it is computed, every time anything changes, from a stack of independent layers with their own visibility and opacity. So the document is four 48×48 grids of packed RGB in one flat Scratch list — slot L owns (L-1)2304+1 .. L2304 and never moves — plus zo, which maps stacking depth to layer slot. Reordering the stack is therefore two integer swaps rather than 4,608 pixel copies. A pixel is one number: -1 for transparent, otherwise 0xRRGGBB, which is exactly what set pen color to takes, so the common case of compositing costs one list read and a comparison and no arithmetic at all. Channels are only unpacked where the maths needs them — partial opacity, adjustments, convolution. comp_range() walks the stack bottom-to-top for a range of pixels and writes the result into a 2,304-entry composite buffer. It is written as a range, not as a whole-canvas pass, so the same and only implementation of "what colour is this pixel" serves both the full recomposite after a blur and the two or three rows a brush dab touches. It deliberately contains no function calls: a custom-block call is the most expensive operation in the language and this loop runs up to 9,216 times. A layer at partial opacity over nothing has no honest answer in an opaque buffer, so it blends against the transparency checkerboard — which is what is actually on screen there, so it looks right. UNDO: AN OPERATION LOG, PLUS EXACTLY ONE SNAPSHOT PEN STUDIO could not snapshot the pen layer, so it made the document a stroke list and replayed it. RASTER has the same problem for the same reason and one extra one: a snapshot of these pixels is 4 × 2,304 = 9,216 numbers, so thirty history states would be 276,000 list slots — past Scratch's 200,000-item limit — and copying one costs more than most of the edits it would be recording. So the document is again not the pixels. It is the list of operations that produced them (15 parallel lists: opcode, target layer, six arguments, the clip rectangle, a slice of a shared point buffer, and a display name), and the pixels are a cache. Every operation is required to be a pure function of its recorded arguments and the state before it, which is why the ops that would otherwise read a live variable record what they read: FLATTEN packs all four layers' visibility and opacity into one base-202 integer, and RENDER CLOUDS records its noise seed. That makes the two directions asymmetric: - Forward — redo, or clicking down the palette — applies only the entries between here and there. The current pixels are already the right starting point, so it costs exactly what doing the edit costs. - Backward has to rebuild from an empty document. Which is fine until the log contains a filter. All original - code, art and sound. See Inside is open.