Skip to content

Pixel Art Editor Online — Draw Sprites in Your Browser

A pixel editor for game sprites that opens in a browser tab: the pencil, bucket, shapes, selections and layers you know from Aseprite, on the same project as the Studio's animation, packing and engine exports. Nothing is installed and your pictures never leave the device.

To draw a pixel-art sprite, fix three things before the first pixel: a canvas sized for the game's own resolution (a character that walks on a 16 px tile grid fits in 16×16 to 32×32), a small palette, and hard 1 px edges with no anti-aliasing. In Nerulio's Pixel workspace, Pixel › New sprite… opens a 32×32 canvas (any size from 1 to 4096) with the DawnBringer 32 palette, in RGB or indexed colour, and the tools answer to Aseprite's keys. Save the result as .aseprite or a PNG frame; it runs in the browser and the picture never leaves the device.

Drop a sprite, frames, a GIF or an .aseprite file to paint on — or an upscaled picture to clean up
Runs in your browser. Files are not uploaded.
Studio Pixel workspace: a CC0 sprite zoomed in with the pencil, two layers (one set to Multiply), the sprite palette and the timeline
The real Studio with a CC0 asset (OpenGameArt).
Exports to
  • .asepriteVerified· Aseprite 1.3.18
  • Palette filesMeasured· read back
  • Engine bundlesVerified· Godot 4.7.2 · Unity 6000.5
  • Frame PNG · .nerulioPlain files

Size, colour mode and clean edges: the three decisions behind a sprite

Pixel art is drawn at the size the game renders it, then enlarged by a whole number so every pixel stays square. Start from the game's internal resolution: a game drawn at 320×180 is scaled ×6 to fill 1920×1080, ×8 for 2560×1440 and ×12 for 3840×2160; at 640×360 the factors are 3, 4 and 6. With 16 px tiles, 320×180 shows 20 × 11.25 tiles, so a 16×16 or 16×24 hero is one tile wide. Size also sets the workload: every doubling of the side quadruples the pixels of every animation frame (16×16 = 256 px, 32×32 = 1024 px, 64×64 = 4096 px).

RGB (RGBA) stores four values per pixel and does not depend on the palette: in an RGB sprite the palette is a set of swatches, and editing a swatch changes no pixel. Indexed colour stores one number per pixel that points into a palette of at most 256 entries, with one entry drawn as transparent (index 0 by default, the same convention Aseprite uses). Changing an entry recolours every pixel that uses it on every frame, which is what makes palette swaps cheap. Nerulio keeps indexed cels as PNG-8 (a PLTE palette plus tRNS transparency) and a brush in an indexed sprite always lands on the nearest palette colour.

Clean lines come from regular steps. A freehand stroke leaves L-shaped double corners on diagonals; the pixel-perfect filter removes them while you draw and is on by default. Straight lines look even when the steps repeat: Shift on the line tool snaps to 0°, 26.57° (2 across, 1 up), 45°, 63.43° (1 across, 2 up) and 90°.

Internal resolution
The size the game draws the world at before scaling it up, for example 320×180.
Integer scaling
Enlarging by 2, 3, 4… so each source pixel becomes an exact square block.
Indexed colour
Each pixel is a palette index (0–255); the palette holds the actual colours.
Transparent index
The palette entry drawn as transparent in an indexed sprite; 0 unless changed.
Pixel-perfect stroke
A freehand line with the extra corner pixels removed, so diagonals stay one pixel thick.

Example: sizing a 16 px-grid hero for a 320×180 game

The numbers behind the canvas you type into Pixel › New sprite…:

game resolution      320 × 180        → 1920 × 1080 at ×6, 2560 × 1440 at ×8
tiles on screen      320 / 16 = 20 columns, 180 / 16 = 11.25 rows
hero canvas          16 × 24 px  (one tile wide, one and a half tall)
with a 1 px outline  canvas 18 × 26 px  (Canvas size +1 on every side)
pixels per frame     16 × 24 = 384      (32 × 32 would be 1024, 2.7× the work)
RGBA frame (raw)     384 px × 4 bytes = 1536 bytes
indexed frame (raw)  384 px × 1 byte  = 384 bytes + a 33-entry palette (transparent + DB32)

Raw sizes show what each pixel stores; the PNG files on disk are compressed and smaller. The 33-entry palette is what a new indexed sprite starts with: index 0 transparent, then the 32 DawnBringer colours.

What it does

  • Eleven tools on Aseprite's keys: pencil (B), eraser (E), bucket (G), eyedropper (I), line (L), rectangle and ellipse (U, Shift+U), marquee (M), lasso (Q), magic wand (W) and move (V). Brushes are square or round, 1 to 16 px, changed with [ and ].
  • Pixel-perfect strokes are on by default: the L-shaped corners a freehand stroke leaves on a staircase are removed as you draw. Shift+click draws a straight line from the last point; Shift on the line tool snaps to pixel-art angles.
  • Inks for shading work: alpha compositing, lock alpha, shading along a selected palette ramp (right button steps the other way) and dither patterns (Bayer 2, 4 and 8, checker, rows, columns) with a density setting.
  • Layers with visibility, lock, opacity and the 19 Aseprite blend modes, computed with Aseprite's own blend maths; merge down, duplicate and reorder. Symmetry vertical, horizontal or both, with the axis position typed in.
  • Every stroke, fill, move, flip, palette edit or layer change is exactly one entry in History. Projects autosave in the browser and save as a .nerulio file; .aseprite and PNG export are in the Pixel menu.

How to do it

  1. Drop a PNG, numbered frames, a GIF or an .aseprite file on this page, or open the empty Pixel workspace and start a new sprite (size, RGB or indexed).
  2. Pick the pencil with B; left button paints the foreground colour, right button the background colour, and X swaps them.
  3. Add a layer with Shift+N for shading, set its blend mode in the Layers panel and lock the line-art layer so it cannot be painted by accident.
  4. Save the .aseprite from the Pixel menu to keep layers and palette, or open Pack & Export when the sprite is ready for Godot, Unity or Phaser.

Check the sprite before you animate it

  • View it at 100 % and at a whole zoom (200 %, 300 %): the silhouette should still read at 1×, where the player will see it.
  • Open the Palette audit and press Check: the colour count per frame should be at or under your budget, with no off-palette colours.
  • Draw a diagonal with the pencil and zoom in: no L-shaped corner pixels means pixel-perfect is on.
  • Export the frame as PNG from the Pixel menu and reopen it: an indexed frame that uses only palette colours is written as PNG-8 with its palette.

Aseprite keys that work in the Pixel workspace

Press ? in the Studio for the full list. These are the ones used most while drawing.

KeyTool or commandNote
B / E / GPencil / eraser / bucketBucket: contiguous or global, tolerance, sample all layers
L / U / Shift+ULine / rectangle / ellipseShift snaps lines to pixel-art angles and makes squares or circles
M / Q / WMarquee / lasso / magic wandShift adds, Alt subtracts, Shift+Alt intersects
Ctrl+C / X / VCopy / cut / pastePasted pixels float: arrows nudge 1 px, Enter drops, Esc cancels
Shift+N / Ctrl+J / Ctrl+ENew layer / duplicate / merge downMerge down keeps every frame where either layer has a cel
[ / ]Brush size 1–16Square or round tip

Troubleshooting

SymptomLikely causeHow to checkFix
Diagonals look thick or jaggedPixel-perfect is off, or the brush is larger than 1 pxTool options bar: Pixel-perfect toggle and SizeTurn Pixel-perfect on, set the brush to 1 with [ and redraw the stroke
The brush paints a slightly different colour than the one pickedThe sprite is indexed: painting snaps to the nearest palette entryThe Colour panel says "Indexed sprite: painting uses the nearest palette colour"Add the colour to the palette first (Palette panel, add the foreground colour), then paint
Nothing happens when paintingThe layer is locked or hidden, or no layer is selectedThe status message names the locked or hidden layer; check the lock and eye icons in LayersUnlock or show the layer, or pick another one
The outline or a limb is cut at the borderThe art touches the canvas edgeLook for opaque pixels in the outermost row or columnPixel › Canvas size… (Ctrl+Alt+C) adds pixels on each side without scaling anything
The sprite is sharp here but blurry in the engineThe engine filters the texture (linear filtering or mipmaps)Zoom the game view: soft edges mean filteringSet nearest filtering in the engine; see blurry pixel art in Godot or in Unity

Other ways to do it

OptionWhen it is the better choice
Aseprite (paid desktop app)You need layer groups, tilemap layers, rotation of a selection, custom brushes or Lua scripts. The files move both ways; see the comparison.
Cleaning up an existing upscaled or generated image instead of drawingYour art already exists at the wrong scale: use the downscaler or the generated-art cleanup first, then edit the result here.
Drawing the frames as an animation from the startWhen the sprite will move, set up frames and tags right away: pixel-art animation.

Engine exports and how each was checked

"Verified" means the exported files were loaded by the real engine or tool on real CC0 assets and the result was compared with the source. Anything that was not loaded is labelled as such.

TargetFilesCheck
.asepritelayers · blend · opacity · locked · indexed palette · frames · tagsVerified Aseprite 1.3.18The real Aseprite command line reopened the file as INDEXED with 2 layers, Multiply at opacity 160 and the locked layer not editable; the frames are pixel-identical.
Palette files.gpl · .pal · .hex · .ase · .act · .jsonMeasured read backEach format was written and read back identical; Aseprite's own .gpl, .pal, .hex and .act and a real Adobe .ase (16 colours) were read.
Engine bundlesGodot 4 · Unity 6 · Phaser 3/4 · PixiJS 8 … through Pack & ExportVerified Godot 4.7.2 · Unity 6000.5The sprite you drew is packed in Pack & Export; those engine files were loaded in each engine on real CC0 assets (47 of 49 runs pass; the 2 failures are a Phaser 3.90 bug the Phaser 3 preset avoids).
Frame PNG · .nerulioPNG-8 with the palette when an indexed frame fits it · project filePlain filesPlain files for any tool; no engine run is claimed for them.

Evidence

Cleanup benchmark, 69 CC0 cases with a known 1× original: the exact size found for 81 % (±1 px: 90 %) and 74.0 % of pixels exact with the background kept; the best competitor found 30 % of sizes (perfectPixel) and the best exact-pixel score was 44.6 % (unfake.js). An Aseprite-made ×4 nearest upscale came back 100 % exact and a ×3.78 bilinear resize 99.7 %, both with the size found automatically. A 1 px outline and a drop shadow matched Aseprite pixel for pixel (1846 and 705 px). .aseprite files written here reopen in Aseprite 1.3.18 as indexed, with their layers, blend mode and lock.

Limits

  • No layer groups, tilemap layers, reference layers or linked cels; no rotation or scaling of a selection beyond flips and 90° turns.
  • No grayscale colour mode: sprites are RGB or indexed.
  • The symmetry axis is typed as a number, not dragged on the canvas.
  • Not in the Pixel workspace (Aseprite has them): layer groups, linked cels, tilemap layers, reference layers, RotSprite and free rotation or scaling of a selection (only flips and 90°), gradient, spray, contour and text tools, custom brushes (square or round, 1–16 px), tiled drawing, Lua scripting and a command line.
  • The symmetry axis is typed, not dragged on the canvas. On touch there is no right button, so the background colour needs the swap button or X.
  • Cleanup: JPEG input comes back close (84 % of pixels within ΔE 0.02 with the background kept) but rarely exact (12 %); simulated generated art gets the exact size 20 % of the time (±1 px: 80 %); none of 7 real generated images was snapped automatically — you type the pixel size the panel suggests.
  • Indexed conversion picks the nearest colour in Oklab, not Aseprite's RGB distance: 68 % of the test sprite's pixels got a different (perceptually closer) pick. The Lospec search is the only network request and asks first.

Versions and sources

Nerulio: the Pixel workspace's pure modules are covered by tests/studio-pixel.test.mjs (34 tests) and a 76-check browser suite on real CC0 sprites; .aseprite files written here reopened in Aseprite 1.3.18 with layers, blend mode and indexed palette intact. Painting a 512×512, 100-frame sprite stayed interactive (21–25 ms to commit a stroke) in headless Chromium. Colour-mode and default-palette statements about Aseprite follow its documentation.

Official documentation used

Questions

Does it work with Aseprite shortcuts?

The main ones, yes: B, E, G, I, L, U, M, Q, W and V pick the same tools, X swaps colours, Shift+N adds a layer, Ctrl+E merges down, Shift+H and Shift+V flip, and Enter, F3 and F7 play, show onion skin and open the preview window. There is no screen for remapping keys yet.

How big can a sprite be before painting slows down?

In a test with a 512×512 sprite of 100 frames, a stroke refreshed only its dirty rectangle (about 0.03 ms per refresh), committing a stroke took 21 to 25 ms and switching frames 16 ms, measured in headless Chromium.

Can I use it on a phone?

At phone width the panels open as a bottom sheet (Colour, Palette, Layers, Cleanup, Audit, Timeline, History) and a tap paints one pixel. Touch has no right button, so use the swap button or X for the background colour; two-finger gestures are not covered by tests.

Is it free, and do I need an account?

Everything on these pages is free and works without an account. There is no watermark and no export that is held back.

Are my sprites uploaded?

No. The Studio decodes, packs and exports in this browser tab. Projects are autosaved in the browser (IndexedDB) and can be saved as a .nerulio file.

Does it need Aseprite or an engine plugin installed?

No. .aseprite files are read and written in the browser, and each engine bundle carries what that engine needs (for Godot, the import settings and scene; for Unity and the Godot TileSet, a small editor script).