Pixel Editor: Built for a Grid, Not a Canvas
Every stroke snaps to a pixel grid on purpose, and the frame timeline underneath it is where the real complexity actually lives.
I built this one right after the four tools that shipped yesterday, and it's a genuinely different kind of tool from all of them: not a general-purpose utility, but something built specifically for one style of art. Every stroke snaps to a fixed pixel grid, on purpose, with no anti-aliasing softening the edges. That's not a limitation of the drawing engine — it's the entire reason this tool exists instead of someone just using the Image Editor I shipped yesterday and zooming in a lot.
Why a general image editor can't do this job
A regular paint canvas smooths every line you draw, because for most drawing that's exactly what you want — natural-looking curves and edges. Pixel art depends on the opposite: every pixel is a deliberate, individual choice, and smoothing destroys that. Building a canvas that snaps coordinates to grid cells instead of letting strokes fall at arbitrary sub-pixel positions sounds like a small technical difference from a normal drawing surface, but it changes the entire interaction model — brush size becomes "how many grid cells," not "how many pixels of radius," and every tool from the pencil to the fill bucket has to think in cells rather than continuous coordinates.
The timeline is the actual hard part
Drawing a single static pixel-art image is a solved, fairly straightforward problem. Frame-by-frame animation is where this tool actually earns its complexity. The bottom timeline lets you add, duplicate, and reorder frames, set a per-frame delay, and scrub through or play the sequence back — which means the editor has to hold an entire ordered stack of canvases in memory, not just one, and every tool has to operate correctly on whichever frame is currently active without accidentally leaking a change onto the wrong one. Duplicating a frame specifically for small, incremental edits between frames is probably the single most-used timeline feature, since most sprite animations are small variations on a shared pose rather than completely distinct drawings frame to frame.
Why export had to support more than "just the current frame"
Export offers three genuinely different outputs: the current frame alone, every frame as separate images, or the full sequence combined into a single animated GIF. Those serve different real workflows rather than being redundant options for the sake of having options. Someone building a game sprite sheet usually wants individual frames they can lay out themselves in an engine. Someone wanting a quick shareable preview wants the animated GIF. Collapsing this down to just one export format would have quietly picked a winner between two audiences that both have legitimate, different needs, and I didn't want this tool to be opinionated about which one matters more.
Resizing a canvas after you've already drawn on it
Letting someone resize the canvas after they've already started drawing, rather than locking the dimensions in permanently at creation, took more thought than I expected. Growing a canvas is easy — just add empty cells around the existing artwork. Shrinking one is destructive by definition, since pixels outside the new boundary have to go somewhere, and I didn't want that to happen silently. Right now shrinking crops from the top-left rather than trying to guess which direction the player actually wants content preserved from, which is a blunt but at least predictable rule. It's not the smartest possible behavior, but it's one you can learn once and rely on, which matters more for a destructive operation than being clever about it.
The fill bucket needed its own edge-case handling
A flood fill on a grid-locked canvas sounds trivial until you hit a sprite with a one-pixel-wide gap in an outline that was meant to be a closed shape. On a smooth, anti-aliased canvas, a tiny visual gap like that might not even register as a real opening. On a pixel grid, one unfilled cell is a wide-open door, and a flood fill will happily pour straight through it and color half the canvas instead of just the intended region. There's no way to fully prevent that from a tooling perspective — it's a drawing mistake, not a bug — but I did add a contiguous-fill boundary check specifically so a fill that goes wrong stops at a sane limit rather than silently consuming the entire canvas without any visible warning that something had leaked.
The eyedropper tool interacts with the same grid-first philosophy in a quieter way — it samples the exact color of whatever pixel cell you click, with no averaging or blending across neighboring cells the way an eyedropper on a smooth canvas might reasonably do. Pixel art convention is that colors are discrete, chosen values, not a continuous gradient, so even the tools that read colors back off the canvas had to respect that same discreteness rather than importing habits from general image editing.
What I already know is missing
Onion skinning — seeing a faint ghost of the previous frame while drawing the current one — isn't in this version, and it's the feature I most expect to add once I've used this myself for a real animation longer than a couple of frames. Right now, matching the previous frame's pose means either memorizing it or flipping back and forth between frames to check, which works for short sequences and gets genuinely tedious past that. I'm leaving it out of this first version specifically because I want to feel that friction myself a few more times before committing to exactly how onion skinning should look and behave here, rather than guessing at it up front.
A proper saved-palette system is the other obvious gap. Right now color choice is swatches, hex fields, and a fixed preset palette, with no way to save and reuse a custom set of colors across projects. For one quick icon that's plenty. For anyone building a full sprite set for an actual game, re-picking the same handful of colors from scratch on every new canvas is exactly the kind of repetitive friction this tool should be removing, not leaving in place. It didn't make this first cut because I wanted the core drawing and animation experience solid before adding a whole persistence layer for saved palettes on top of it.