Docs

Commands

pxart help recipes walks through seven workflows end to end, command by command: port a pack and prove it lossless, merge packs with variants, build a dusk or night, slice a sheet, make a scene from a map, check an animation's feet, stamp an overlay behind an animation. pxart -h is a short overview: the format in a few lines and the commands by topic. pxart help all has the full reference, pxart help TOPIC one part of it (FORMAT, LOOKING, CHECKING, EDITING, DRAWING, CONVERTING, HELP, ERRORS), and pxart CMD -h (or pxart help CMD) prints one command's part (pxart poly -h): its usage and a line or three saying what it's for, its options one per line, then the details and heuristics, then a see-also line naming the shared notes of the reference it relies on (EDITING, FORMAT: selecting frames, ...) rather than repeating them. zsh users: write "${F}:walk", not "$F:walk"; pxart -h says so in a box. compose and palette, the two longest, open with a short list of rules (a new OUT imports the layers' shared @palette; variants merge by name; ...) and examples; pxart help compose-rules and pxart help palette-rules print only the rules.

  • Looking: render and sheet draw a grey checkerboard behind each frame, so a dark outline and the transparent pixels both show (--bg COLOR for a flat backdrop instead). render (--png also writes each single-frame .px at 1x, FILE.png beside it or in --png DIR, and prints the path; --plain writes one frame alone at its exact size, no grid, rulers or labels, to diff against a scene's PNG), sheet (a directory stands for every .px under it, sorted by path, and palette files are skipped with a note; --exclude GLOB leaves files out, room.px or a folder wip, in check and stats too; frames with one id from several files are labeled hero:idle/0, beast:idle/0; every cell is the largest frame's size, or with --fit each frame's own, rows as tall as their tallest, and a frame over 8x the median frame's area gets a note saying what it costs; --align pivot lines up each animation's frames by pivot, as anim does; --rows group puts each animation group on a row of its own), anim (one GIF, each frame at --scale with its 1x and 2x copies beside it in the same picture, plus a motion strip; without -o it prints only the per-frame numbers, like shift +0,-1 then 72px (20%) (the percent is of the frame's opaque pixels, or of both frames' when more changed, so never over 100%), and writes nothing; a file of several groups animates each on its own, a block of numbers per group (its top-level frames and @still groups left out, with a note), and -o DIR writes one GIF per group (DIR/walk/down.gif); "rows Y+ still" only when those rows are pixel-identical and the legs keep their shape in every frame (an idle; a walk frame whose legs happen to stay put reads as its bob, shift +0,+1 then 23px), and for the rise and the fall of one breath alike; a ground tile or a sparse overlay like falling snow that scrolls with wrap-around reads shift -1,+4 (wrap)), onion (B over A drawn as a red silhouette, and a printed readout of how B's edges moved from A's, like top -1, bottom +0, for a 1px jump too faint to see, and the best shift, or for two different characters different sprites: edges only; --feet N or --rows Y0-Y1 reads only that band, so a weapon swing doesn't hide the feet; --tint-a COLOR picks the silhouette's color and --fade-a draws A faded instead; --variant night draws both at night, so a halo lit only then counts), scene (.px/.png items at x,y, negative allowed, on a 96x64 scene unless --size or --map says otherwise, pixels past its edge cropped with a note, mirrored with a +h/+v suffix as in hero.px:walk/0+h@3,4; --variant V recolors the whole room; --tint '#10183080' lays a translucent color over the finished scene for night; an item or legend entry ending in %base keeps its base palette, a lamp in a night room; --bg transparent works, as does transparent anywhere a color is typed), and tint, which does the same to a PNG. --dry-run on render, sheet, anim, onion and scene prints the readout (notes and WARNINGs too) and each output's size and layout (sheet.png: 700x174 px, 5 frames in one row, every cell 128x128 ...) and writes nothing, saying (dry run; nothing written); -o may then be left off. An image over 4096 px on a side or 16M px gets a WARNING, dry run or not.
  • Maps (scene --map): a legend line is <char> <path>, and the rest of the line is the path, so a pack folder with spaces works as is (quotes optional). A legend entry that can't load is an error at its legend line. A # line before the rows is a comment unless it is exactly # FILE.px[:frame][%variant] or # FILE.png (or a quoted path), which makes # a map char (a wall row ####); check and scene note it. A line --- after the rows starts another layer of rows over the same legend (a tile and a sprite in one cell); later layers draw on top, and . is empty. Each item draws from its cell's top-left, so a prop bigger than a tile hangs right and down; a legend entry ending in +b (+hb with a flip) stands it on its cell instead, bottom-aligned and centered (L props/lamp.px+b). pxart help LOOKING has a worked map. compose --map room.map -o room.px builds the map's room as a .px frame instead of a PNG, with compose's palette, --rekey and --variant-map rules; it renders as scene --map does, pixel for pixel, in every variant. A new OUT's header says how it was made (# composed by: pxart compose --map room.map -o room.px, paths from its folder), so a variant WARNING about it can offer composing it again once the map's sprites have that variant.
  • Checking: check (format errors, size, off-palette colors, color budget, unused keys; .map tilemaps too; check crossover/ checks every .px and .map under it; notes Cyrillic/Greek/fullwidth letters posing as ASCII and @anim/@still lines with no frames, which --strict fails; one line per file, the failing frames under it, and a summary like 6 files, 150 frames, 3 warnings; -v for a line per frame; exits 1), stats (a directory too, as for sheet; stats hero.px:idle/0%night --colors lists the colors a variant renders, with their keys (--variant night: every frame's; a frame drawn only in keys lit at night says bbox none (96 px drawn, all transparent in the base; visible in night)), and --at 3,4 one pixel's key and color in every variant), diff A B (renders pixel by pixel, one line per pair: how many pixels differ and where, exit 1 when they do, so a copy can be proved to render as its original; diff town.px pack/ --labels pack/labels.csv checks every frame against the pack's PNG its row names, diff hero.px out/ against out/<id>.png, diff town/ pack/ --labels pack/labels.csv every frame of every .px under town/ (--exclude GLOB leaves some out), and two other directories pair files by path; a transparent pixel matches any other unless --strict-alpha; -o DIFF.png draws A, B and the differing pixels in magenta side by side (of several pairs, the one that differs, named), or for several pairs -o DIR one picture per pair that differs; a size mismatch against a render or a scaled copy gets a note naming render --plain), frames (--rm/--move print only what they did, and a move to where the frames already are says already in place; with a selector, frames hero.px:walk/left lists those frames, --rm removes them and --after ID moves them; removing a group's last frame removes its @anim/@still line; frames hero.px:walk --copy-to beast.px [--after ID] copies frames into another file, in order, with their ms, pivots and @anim line (a copy that lands as a still, a top-level id, drops its ms and keeps its pivot), under other ids with --prefix wick/ or --rename walk wick/walk; frames hero.px --rename walk hero/walk renames a group in place, @anim and @still lines too, and is the rename there is no rename command for; a move puts the @anim lines in play order).
  • Editing: new (a blank or filled frame, in a new or existing file; new party.px --empty --palette palette.px starts a file with no frames that imports a palette, for frames --copy-to to fill), put (put hero.px:walk/1 < rows.txt replaces one frame's grid with rows from stdin, with optional palette lines merged like compose's; checked like a file, errors at stdin's lines, nothing written on an error, and only that frame's lines change), fill (a region or the whole frame with one key; fill hero.px:walk/2 . clears a frame), flip, shift (the pixels it leaves behind become ., or --fill KEY), set (set hero.px:idle/0 . 16,10 erases one stray pixel and says erased 1 px; it and fill count what they paint, as the drawing commands do), crop, recolor (optionally within a region; 'a<>b' swaps two keys, quoted for the shell; 'a>b' gives a's pixels a new key b in a's color, a rename when nothing keeps a, and onto a key already in a's color the error says a=b; the key moves of one call apply together, so none feeds another, and a key renamed away is free for another: 'a>b' 'b>a' swaps two names; 'b>t' 't=#1e4548' gives the new key its own color in the same call, and --variant night sets night's color instead of the base), mask (erase outside --keep x,y,w,h or --keep-circle cx,cy,r, with a --dither N edge; --invert erases the inside instead; both flags repeat, and the kept area is the union, so two lamps are one call; works on a rendered PNG too; --keep-keys W,T,t or --drop-keys mask a .px by key), paste (+h/+v mirror the source, as in compose; --under fills only the empty pixels; into an animation, --align shift moves the paste with the body's bob, frame by frame, as anim's best shifts add up, where --align bbox would follow wings that stick out), compose (stack layers into a frame; --dry-run prints all it says, notes, conflicts and --rekey's moves, and the diff of OUT it would write, a new OUT's every line, and writes nothing; a new frame lands after its animation; a new file gets the layers' whole palette, their shared @palette re-pointed when they all import the same one, so a later shade ramp finds its keys, or with --used-keys-only only the keys its frame uses, and the comments on their key and @variant lines, each naming its file when there are several (from keeper.px's @variant night, dusk here; a @variant's names only the files whose variant lines gave it keys, the rest from pal.px's night, and a line # night: also from lamp.px's night credits a file that gave it keys with no comment of its own), with the palette files' headers at the top; a key its file needs (drawn in other frames, named, or kept lit by a variant) that it has to leave out is a WARNING; it reports per source file, one line per reason, with what --rekey moved and why, and an E_KEY_CONFLICT offers the same moves; each variant colors only the layers whose file has it, a note says which stay at their base colors, and --variant-map dusk=night,dark merges several files' variants into one (it adds to the same-name lookup, never replaces it: OUT's night still reads each file's night); a plain OUT that exists keeps its palette, with a note, and --replace starts it as if new; --under draws the layers behind the frame that's there, in its empty pixels; a key a layer has in another color than OUT's is E_KEY_CONFLICT, one line per source file with free keys for it, and --rekey gives it those keys in OUT and leaves the source file alone, as it does for crop, paste and frames --copy-to; --rekey also gives free keys to a key two files have in one color but recolor differently in their variants, into a new file or an existing one (frames keeper.px:walk --copy-to party.px --rekey --variant-map dusk=night reads the keeper's night as party.px's dusk), and in a new OUT to a needed key it would leave out; a key OUT's variants would recolor is a WARNING of its own; --rekey o,r moves only those keys, and --rekey k=j,n=q puts k and n on OUT's own j and q, with a WARNING where their variant colors differ; --rekey girl.px:T=V is for one source file's T only; a color two files share, alike in every variant, keeps the one key it got first), dup (copy a frame; or a group, dup hero.px:walk/right walk/left, with its frames' timing and pivots and its @anim line; flip hero.px:walk/left -o hero.px:walk/right copies a group and flips the copy in one step, as rotate and transpose turn one; onto a group or frame that exists, --replace puts the copy in its place), anim-set (timing), palette --add (with --variant night it sets keys in that variant, making it if needed; --variant night --keep l,g lets keys inherit the base colors; --variant night --derive-from base --darken 0.35 --tint '#10183060' --keep-lit y,W builds a whole night from the base colors, as scene --tint would, with the lamps still lit; --match mossback.px%dusk first maps each channel as another palette's base to dusk does (a fitted gain and offset: warm lights, blue shadows), and a key darker than a quarter is never brightened, by Rec. 709 luma (hue kept), unless --lift-darks; --hoist l,g moves a sprite's own keys into the palette file it imports; --import pal.px adds a @palette pal.px line to a sprite and drops its key lines pal.px has in the same colors, so it renders as before; --order o,t,k puts those key lines first, comments and all; --remove k,n takes out keys no frame draws with (--to j repaints their pixels as j first; an imported key then goes from its palette file only when no other .px under the directory holding both, or --in DIR, uses it); --comment k 'text', --comment @variant night 'text' and --comment-header 'text' write the comment above a key line, a variant or the file; palette FILE alone lists the keys with their comments, then what each variant recolors darker or brighter, relists in its base color and inherits, with its comment: night: recolors (darker) k w; brightens y; relists unchanged: l g; inherits: e E q; --in DIR says how many files under DIR import a palette file and draw with each key; any edit takes -o OUT for an edited copy, and --dry-run prints the diff it would make and writes nothing). Every edit, the drawing commands and new, frames, dup, anim-set and compose too, takes --dry-run: it prints what the edit says and the diff it would make, then (dry run; nothing written). There is no undo: keep sprites in git, or edit a copy with -o. Edits rewrite only the lines that changed, keeping the file's blank lines and comments, and an edit that changes nothing says no change and leaves the file alone. -o OUT always writes the whole file: flip hero.px:walk/0 -o out.px is a copy of hero.px with that frame flipped, and an OUT in another directory gets its @palette lines re-pointed from there, so it imports the same palette file. extract hero.px:walk -o walk.px writes only the selected frames, with the same palette and imports (and their @anim lines, in the order of the frames' groups); --inline-palette copies the imported keys they use (and the variants' colors for them) into the file and drops @palette, so the hand-off renders the same with nothing beside it. extract writes a new file: onto one that exists it is an error (its frames would be lost), unless --replace; frames --copy-to adds to it instead.
  • Drawing (for 32x48 heroes, 64x64 beasts and 256-wide layers, where typing every pixel is the bottleneck; each draws a palette key, or . to erase (ellipse hero.px:idle/0 . 8,8,3,3 --fill cuts a hole, and says erased 37 px), on FILE[:SEL], clipped to the frame, and rewrites only the rows it changed):
    • line hero.px:attack/2 W 3,40 28,12 --width 2: Bresenham, no doubled corners.
    • poly … x,y x,y x,y … [--fill]: a closed polygon from line's pixels (filled by nonzero winding, so a star is solid).
    • rect … x,y,w,h [--fill], ellipse … cx,cy,rx,ry [--fill] (clean, symmetric pixel ellipses; .5 centers and radii for even sizes, or --box x,y,w,h, the pixel box as rect takes it, with no half pixels at all), flood … x,y [--diagonal].
    • arc … cx,cy,r a0,a1 --width 3: a smear or swoosh (degrees, 0 = right, counter-clockwise).
    • shade hero.px:idle/0 --ramp XxcCw --keys c --light nw: re-shades a material with a darkest-to-lightest ramp. Each pixel's tone comes from its outward normal against the light, fading to the base tone --strength px in from the edge: banded, deterministic, no noise (--dither for an ordered blend at band boundaries, --preview p.png to look first; --region repaints only inside it, shaded as part of the whole material, so its border is no edge).
    • outline beast.px:idle --key o --lit m: an outline around the shape (or --inside on its edge), pixel-perfect by default (--corners keeps square corners), and selective: the edges facing the light get the lighter --lit key (--preview p.png as for shade).
    • Both print only the pixels they changed, by the key they got: changed 41 px: 29->C, 12->X.
    • rotate FILE 90|180|270 and transpose turn path tiles into their edges and corners (then re-light with shade and outline --lit, since the light turned too).
  • Converting:
    • export --frames DIR writes one PNG per frame. export town/ --frames out/ exports every .px under town/ (several files and directories, --exclude GLOB as for check); an id two files share, or two that differ only in case (one file on macOS), is an error rather than one PNG replacing another, and --prefix-file ids them roofs/roof-red.
    • export --aseprite x.json writes a sprite sheet and JSON with frameTags (and pivots as meta.slices, the shape Aseprite's own export uses).
    • export --tiled x.tsj writes a tileset with tile animations.
    • export harbor.px:cobble harbor.px:water --tiled t.tsj exports only those frames (selectors of one file add up), so a tileset can leave out the big props.
    • Frame and tile ids count over the exported frames with each animation group contiguous, groups in order of first appearance, and all top-level frames together as one group. A file that already keeps each group together gets ids in file order; otherwise a frame moves up to its group (a/0 b/0 a/1 gives a/0 a/1 b/0).
    • palette --export x.gpl|x.hex writes the palette; palette hero.px --extract-to pal.px writes it as a palette file for @palette, variants included, with the comments on its key and @variant lines (and a palette file's header), and --repoint then makes hero.px import it.
    • from-png converts a PNG to a sprite; from-png Walk.png --grid 16x16 --by cols --names walk/down,walk/up,walk/left,walk/right -o boy.px slices a sheet into frames, a group per column (or row), skipping empty cells. Two PNGs one run would give one id (two packs' tile_0002.png) are an error rather than one replacing the other; --prefix-dir ids them dungeon/tile_0002 and creatures/tile_0002. Loose PNGs take names from --names a,b,... (one per PNG) or from the CSV a pack ships: --labels dungeon/labels.csv names each PNG by its row's proposed_name (--label-col, --file-col pick other columns).
This page is built from README.md in the repository.Edit on GitHub