Draw a curve on either grid below. Both are drawn by the same brush engine, compiled to WebAssemblyA compact binary format that browsers run at close to native speed. Languages such as Rust compile to it, so the same code can run natively and on the web. and running in this page, and the only difference between them is one setting.
Pixel-perfect off
Pixel-perfect on
That's Pixel Stroke, a pixel-art and animation editor I've been building since April. It runs on Oniq, the engine I build my games on, and it's the first program on Oniq that isn't a game. Its brushes, color math, TilemapA level or background built from a grid of small reusable images called tiles., bones, selections, text and export pipeline are all engine features, which is a large part of why it could grow as fast as it did.
The web version is out. At pixel-stroke.com you can create a free account and open the full editor in your browser, or try a few pieces of it without signing up. A native app for iPad, iPhone and the Mac comes next, and it's already drawing.
Why pixel-perfect matters
Pixel art is drawn one square at a time, and the eye notices every square. When you drag a one-pixel pencil along a curve, the mouse rarely moves exactly one pixel diagonally, so the line picks up extra pixels at every turn, little L-shaped corners that make it look thick and jagged. Artists clean those up by hand, pixel by pixel.
The Pixel-perfectA pencil setting for pixel art that removes the extra corner pixels a hand-drawn one-pixel line picks up, so lines stay one pixel wide. filter does that cleanup while you draw: when a new pixel would form an L with the two before it, the middle one goes. Pixel artists look for that setting before almost any other, because without it every freehand line needs fixing afterward.
The brush engine underneath is Oniq's: it turns pointer samples into stamps with spacing, pressure and the pixel-perfect filter, and Pixel Stroke configures it per tool. Every figure in this article is built on that engine code, compiled to WebAssembly.
Symmetry
A lot of pixel art is symmetric: faces, sprites seen head-on, icons, tiles. Drawing it twice is busywork, so the brush can mirror every stroke as you draw it.
Layer 1
Palettes
Pixel art usually lives in a small, deliberate palette, and one of the oldest tricks in it is the palette swap: store each pixel as a slot in the palette instead of a color, and changing the palette recolors the whole piece at once. That's how old consoles turned one sprite into a second player or a night level.
Sprite
Palette
Pixel Stroke works the same way. A document can hold several palettes with the same slots, a Day and a Night, say, and a layer can be linked to them so each pixel stores a slot instead of a color. Switching palettes recolors every linked layer in every frame at once, editing a slot's color updates every pixel that uses it, and a hue, saturation or lightness shift can turn a whole palette or one ramp of it. On a linked layer the brush always paints the active slot, so edges stay hard, and the eyedropper picks slots, not colors.
A full-color layer converts to slots by finding each color's nearest match by eye, using CIEDE2000, and tells you how many colors matched exactly and how many it merged. It converts back just as easily. Palettes come and go as Lospec, GIMP and JASC palette files, and a Sprite sheetOne image holding every frame of an animation in a grid, which a game cuts apart as it plays. can be exported once per palette.
Colors come from an HSV color wheel, and palettes can come from generators written as scripts. This one builds a triadic palette from a base color:
// scripts/palette/triadic.rhai
let hsl = color_to_hsl(color);
let offsets = [0, 120, 240];
for i in 0..3 {
let new_h = (hsl[0] + offsets[i].to_float()) % 360.0;
print(color_from_hsl(new_h, hsl[1], hsl[2]));
}Scriptable down to the panels
That script is RhaiA small scripting language designed to be embedded in Rust programs. It runs anywhere Rust does, WebAssembly included., a small scripting language that runs inside RustA programming language known for being fast and for catching whole classes of bugs before a program ever runs. programs and, importantly for me, in WebAssembly. Pixel Stroke started with Lua and moved to Rhai on its first day so the same scripts would run in the browser.
Scripting goes deeper than macros. The editor's panels (the
toolbar, layers, timeline, color, palette, tilemap and bone
panels among them) are Rhai scripts, and nearly three hundred
functions expose the document to them. A console with
autocomplete runs scripts against the open document, and a
macro can be as plain as this one, which scrolls the current
frame by dx and dy pixels with wraparound:
// scripts/animation/shift.rhai, after it copies the frame into `buffer`
let w = ps.doc_width();
let h = ps.doc_height();
for y in 0..h {
for x in 0..w {
// Source coordinates with wrapping
let sx = (x - dx) % w;
if sx < 0 { sx += w; }
let sy = (y - dy) % h;
if sy < 0 { sy += h; }
ps.pixel_set(x, y, buffer[sy][sx]);
}
}Animation
Each document holds frames, each frame its own stack of layers, with Onion skinningShowing the frames before and after the current one faintly underneath it, so an animator can see the motion while drawing. and a timeline you can scrub. A document can hold several named animations (idle, walk, attack), and a state machine editor maps game states to those animations, so you can preview how a character moves between them before it ever reaches a game engine.
Exports cover the usual targets: PNG, JPEG, WebP and animated GIF, sprite sheets, packed texture atlases, and tilemaps as TMX, JSONA plain-text format for structured data, built from named fields and lists, that almost every programming language can read and write. or C arrays for small engines and microcontrollers.
Off the screen
Pixel art doesn't have to stay on a screen. ArduinoA family of small, inexpensive microcontroller boards for electronics projects, programmed from a desktop computer.'s UNO R4 WiFi has a 12x8 red LED matrix on the board, the UNO Q has a 13x8 one, and Pixel Stroke can draw for both directly.
Frame 1
UNO R4 WiFi
Export · pxlma1
The format, pxlma1, is simple on purpose: a 16-byte header
with the size, color depth, frame count and frame rate, then the
frames. A one-color frame packs each row into bits, so a whole
12x8 frame is 16 bytes. It's small enough to decode by hand, and
the editor ships a reference Arduino sketch that does exactly
that, with nothing beyond the board's own serial port.
Built on Oniq, and on my other libraries
Pixel Stroke is a thin product layer over the engine. Text tools shape their GlyphOne drawn shape in a font. A character and a glyph aren't the same thing: one character can need several glyphs, and one glyph can stand for several characters. with sigilbuzz, documents save to SurrealDBAn open-source database that stores ordinary tables, linked records and nested documents in one engine. Kayak, Copal and Antumbra are all built on it. through surql-rs, in the browser too, and the brush, tilemap, atlas, color, bone and image-processing code is Oniq's. There's even an MCP (Model Context Protocol)The standard way AI coding agents such as Claude Code connect to outside tools and data. server, so an AI agentAn AI model set up to take actions, such as calling tools, running commands or editing files, rather than only chatting. can work on a document too.
The web version is Rust compiled to WebAssembly, drawing to a canvas. It comes with five themes: Inkwell, the dark default, Graycard, a neutral graphite for judging color, Vellum and Lightbox for paper and light-table whites, and Stencil for high contrast. A test checks every one of them against WCAG AA contrast, and you can make and share your own in a live theme editor.
Native, starting with the iPad
The next version isn't a web page in a wrapper. It's the same Rust, running natively on Oniq, and the iPad is where it starts, because the Apple Pencil is the best pixel-art tool most people can buy.
The native editor draws the whole document as one texture and, after each stroke, uploads only the rectangle that changed. Strokes follow every sample the Pencil reports, not one position per frame, so a fast curve stays continuous, and the Pencil's pressure reaches the brush. Your palm can rest on the glass: while the Pencil is down, and until every finger lifts, fingers don't draw. Two fingers pan and pinch to zoom, a two-finger tap undoes and a three-finger tap redoes, as in other iPad drawing apps, and the document saves itself a couple of seconds after you stop drawing.
The panels come across without being rewritten. The native app runs the same Rhai panel scripts as the web editor and draws their controls with Oniq's own touch-sized widgets, and the first two, Layers and Tool Options, already work. The engine grew the pieces this needed: Pencil-grade input with pressure and tilt, a screen-space layer for interface drawn over the canvas, and a renderer for its panels.
So far it runs in Apple's iPad simulator, which can't fake a Pencil, so the Pencil itself is the part still to be tried on a real iPad.
What the figures found
A demo is a good test, and building these figures turned up four bugs in the brush code that the test suites hadn't caught:
- The pixel-perfect filter ran before repeated stamps were removed, which hid most corners from it, and the editor painted each stamp the moment it arrived, so a corner already on the canvas could never be taken back.
- Round brushes of odd sizes were centered on a pixel corner, so a size 3 brush painted the same block as size 2.
- Mirrored strokes landed one column to the right of the true mirror image.
- With light pen pressure, stamps were still spaced for the full brush size, so a soft stroke came out beaded.
All four are fixed now, in Oniq and in the editor, and each fix came with a test that failed before it. Part of how they slipped through is plain: Oniq's CI (continuous integration)Automated checks that build and test every proposed change before it is merged, so problems are caught before they ship. never turned on the brush feature, so its brush tests never ran. They run on every change now.
Fixing them turned up two more gaps that are closed too: the editor never passed pen pressure to its tools at all, and the pencil ignored its opacity setting. Only a pen's pressure counts, so a mouse or a finger still draws at full strength. And the editor now repaints each stroke from its whole path as you draw, which is what lets pixel-perfect take back a corner it already drew. It also means a translucent stroke no longer darkens where it crosses itself, a whole stroke undoes in one step, and the brush cursor shows the exact pixels the tip will cover.
How it got here
The first CommitA saved snapshot of changes in a repository, identified by a unique hash, so any past state of the code can be named and found again. is from April 18, 2026, and that first month accounts for 286 of its more than 470 commits: layers, frames and the timeline, the color wheel, SVGA web image format for drawings, written as text. Because it can also carry scripts, services treat it as potentially active content. import and export, tilemaps, bones, the LED matrix exporter and SurrealDB persistence all landed in April. Today it's about 72,000 lines of Rust, with around 1,400 Rust tests and nearly 200 browser tests that drive the editor end to end.
What's coming
- The native app, for iPad, iPhone and the Mac first, then Linux and Windows. The color, palette and timeline panels are next, then testing on a real iPad.
- Drawing together: several people in one canvas, live. Rooms, accounts and permissions are built, and the live transport is next.
- Spine, through Chine: open a rig, repaint its art, and watch it animate while you paint. Oniq already draws Spine rigs in the browser, so the hard part underneath is done.
The web version is at pixel-stroke.com now, with a free account. The native app is the next release, and it will land there too.