apixed

A free cross-platform pixel art editor.

Backstory

"apixed" stands for arguit's pixel editor. It is a highly opinionated pixel editor with the functions I need, focused on working on our games (Pushmania and Lost City).

I have been using Pyxel Edit for a long time, but after almost fully migrating my workstation to Linux, I needed that kind of editor. I was also learning Zig and raylib, so creating my own editor was a great opportunity to practice both.

I chose a relatively small set of functionality that I used in Pyxel Edit and also added some features from GIMP, such as HSV noise, so I wouldn't have to switch between the two.

František Záhora // arguit

Features

A native, cross-platform pixel-art editor built in Zig (raylib/raygui), with an embedded Lua console for scripting. Version 0.6.X.

Document & File Management

  • Multiple documents open in tabs, quick-switch with Ctrl+1…Ctrl+0
  • New (Ctrl+N), Open (Ctrl+O), Save (Ctrl+S), Save As (Ctrl+Shift+S), Close (Ctrl+W)
  • Custom binary .apixed file format (versioned schema, backward-compatible readers)
  • Unsaved-changes confirmation on document close and app exit, listing all modified docs
  • Import image as new document: PNG, BMP, JPEG, QOI
  • Export document as image: PNG, BMP, JPEG, QOI
  • Export animation as GIF, with adjustable FPS and export scale (1x–32x)
  • Canvas size up to 4096×4096 pixels

Canvas & Viewport

  • Zoom toward cursor (mouse wheel / +/-), 1×–128×
  • Pan via middle-mouse drag
  • Pixel grid toggle (Ctrl+P) and tile grid toggle (Ctrl+T)
  • Tile-aware canvas resize (add/remove rows/columns by tile increments)
  • Per-document zoom/pan persisted in the save file

Layers

  • Add, remove, duplicate, merge-down, rename, reorder
  • Per-layer visibility toggle and opacity slider
  • Full undo/redo for all layer operations

Selection

  • Rectangle select (add/subtract/move/copy-move modifiers, double-click = select tile)
  • Fuzzy/magic-wand select (add/subtract, select-all-occurrences)
  • Select All (Ctrl+A), Invert (Ctrl+I)
  • Copy / Cut / Paste (Ctrl+C/X/V)
  • Flip horizontal/vertical (Ctrl+F / Shift+F)
  • Rotate CW/CCW (Ctrl+R / Shift+R)
  • Scale via drag handles, commit-on-paste/deselect editing model
  • Escape cancels an in-progress move
  • All operations undoable

Drawing Tools

ToolKeyNotes
SelectSrectangular select/move
Fuzzy SelectWmagic wand
PenDRMB picks color; Shift = straight line; Shift+wheel = brush size
EraserERMB picks color; Shift = straight line; Shift+wheel = brush size
RectangleRoutline or filled (Shift)
EllipseCoutline or filled (Shift)
FillFflood fill; RMB picks color
Color ReplacerQswaps BG→FG color under cursor
HSV NoiseHrandomized hue/sat/value noise with live preview and savable named presets
AnimationAclick canvas tiles to build a frame sequence

Color & Palette

  • Foreground/background color pair with quick swap (X)
  • Color picker: RGB, HSV, hex, alpha inputs; revert-to-previous; copy hex to clipboard
  • Per-document custom palette: add, remove, clear, reorder
  • Import palette from an image (color extraction/sampling)
  • All palette edits undoable

Animation

  • Multiple named, tile-based animations per document
  • Floating preview panel: play/pause, FPS +/-, next/previous frame, zoom/pan, enlarge/shrink
  • GIF export with selectable scale multiplier (1×/2×/4×/8×/16×/32×)

Undo/Redo

  • Full undo (Ctrl+Z) / redo (Ctrl+Shift+Z)
  • Command-pattern history (27+ command types) covering drawing, selection, layers, palette, resize
  • Batched pixel-level change recording
  • Per-document modified/unsaved state tracking

Scripting (Lua Console)

  • In-app developer console (Ctrl+Backtick to toggle)
  • Commands: /help, /version, /clear, /run <script> (downloads from the community repo first if missing locally), /stop, /api, /list, /download <script>, /update [script]
  • Scripts run on a background thread (UI stays responsive), with instruction-count safety limit; only one script runs at a time
  • Lua API for pixel/layer/selection manipulation and shape drawing (get/set pixel, layers, draw line/rect/ellipse, fill, replace color, etc.) — enables procedural generation and batch operations

Community Script Repository

Scripts can be shared via the public github.com/arguit/apixed-scripts repo (flat .lua files in its root).

  • /list — lists scripts available in the repo ([downloaded] / [downloaded, update available]) plus any local-only scripts in ./scripts/
  • /run <script> — falls back to downloading the script from the repo if it isn't found in ./scripts/ locally, then runs it
  • /download <script> — (re-)downloads a script from the repo, overwriting any local copy regardless of version
  • /update [script] — re-downloads one (or, with no name, every) previously-downloaded script that has a newer version in the repo
  • Network operations run on a background thread (polled once per frame), same as script execution, so the UI never blocks

Lua API

All coordinates are pixel coordinates on the active document; colors are four 0-255 component arguments (r, g, b, a), not a table. Layer indices are 1-based (Lua convention) and get translated internally. Functions that read/write pixels return/accept nil when out of bounds or no document is open.

Document info

  • log(message) — prints to the in-app console
  • get_doc_w(), get_doc_h() — document dimensions in pixels
  • get_tile_w(), get_tile_h() — tile dimensions

Active-layer pixels

  • get_pixel(x, y) -> r, g, b, a | nil
  • set_pixel(x, y, r, g, b, a)
  • erase_pixel(x, y) — shorthand for set_pixel(x, y, 0, 0, 0, 0)

Layers

  • get_layer_count() -> count
  • get_layer_name(index) -> name | nil
  • get_layer_by_name(name) -> index | nil
  • get_layer_pixel(index, x, y) -> r, g, b, a | nil
  • set_layer_pixel(index, x, y, r, g, b, a)
  • erase_layer_pixel(index, x, y)
  • create_layer() -> index | nil — becomes the active layer
  • remove_layer(index) -> ok
  • move_layer_up(index) -> ok, move_layer_down(index) -> ok
  • rename_layer(index, name) -> ok

Selection (write-only mask)

  • select_pixel(x, y), deselect_pixel(x, y)

Shape drawing

Each runs as one whole-canvas operation and one undo step, using the same helpers the interactive tools use; honors the active selection like the equivalent tool would.

  • draw_line(x0, y0, x1, y1, r, g, b, a)
  • draw_rectangle(x0, y0, x1, y1, r, g, b, a) — outline
  • draw_rectangle_filled(x0, y0, x1, y1, r, g, b, a)
  • draw_ellipse(x0, y0, x1, y1, r, g, b, a) — outline
  • draw_ellipse_filled(x0, y0, x1, y1, r, g, b, a)
  • fill(x, y, r, g, b, a) — flood fill from (x, y)
  • replace_color(r0, g0, b0, a0, r1, g1, b1, a1) — replaces one exact color with another across the document (or selection, if any)

Foreground/background color

  • get_fg_color() -> r, g, b, a, set_fg_color(r, g, b, a)
  • get_bg_color() -> r, g, b, a, set_bg_color(r, g, b, a)

Pen tool

  • get_pen_size() -> size, set_pen_size(size) — brush size, clamped to 1-256

This surface is enough to script procedural pixel-art generation, batch edits across layers, and repetitive shape/fill operations that would be tedious by hand.

Feedback & UX

  • Status-bar notifications (info/success/error)
  • Contextual tooltips with keyboard shortcuts on nearly every control
  • Per-tool help/controls overlay
Installation (macOS)

Apixed isn't notarized, so macOS Gatekeeper will quarantine it as "unidentified." If it won't open, remove the quarantine flag in Terminal (from the folder with the app):

xattr -d com.apple.quarantine Apixed.app