# pxstud.io Lua scripting API — Document

> Part of the pxstud.io Lua scripting reference, whose overview (https://pxstud.io/docs/scripting.md) explains its conventions. The option groups and types it refers to are in https://pxstud.io/docs/scripting/option-groups.md and https://pxstud.io/docs/scripting/types.md. Everything in one file: https://pxstud.io/llms-full.txt. The whole site's index: https://pxstud.io/llms.txt.

### Document
The sprite as a whole: start here with app.describe(), then size, crop, rotate and color mode.

#### `app.convertColorMode(colorMode, opts?)` — changes, undoable
Converts the sprite between color modes (rgba, grayscale, indexed), optionally dithering and generating a palette when going to indexed.

- `colorMode` (ColorMode: "rgba" | "grayscale" | "indexed") — Conversions out of rgba (and into grayscale) are lossy: color quantizes to its nearest palette entry or its Rec. 709 luma (Aseprite's, truncated: red is 54). Into grayscale the palette becomes 256 grays, black to white (per-frame palettes and palette groups go, the transparent index becomes 0). No-op if the sprite is already in that mode.
- `opts` (table, optional)
  - `dithering` (DitherMethod: "none" | "bayer2" | "bayer4" | "bayer8" | "floydSteinberg" | "atkinson"; default "none") — Into indexed only: how colours between palette entries are approximated.
  - `ditherAmount` (integer 0–100; default 100) — Into indexed only: dithering strength (0 = nearest colour only).
  - `paletteColors` (integer 1–256; default keep the current palette) — Into indexed only: replace the palette with this many colours generated from the sprite (plus a transparent entry at index 0 unless the Background is the only layer; per-frame palettes are dropped).

Returns nothing.

#### `app.cropToSelection()` — changes, undoable
Crops the canvas to the current selection's bounds; a selection reaching past the canvas grows it there. No-op if nothing is selected.

Returns nothing.

#### `app.describe()` — reads
Everything about the document's structure in one call (no pixel data): { sprite, activeLayerId, activeFrameId, layers, frames, cels, tags, slices, selection (bounds or nil), context } — each part in the shape of its own query (app.getSprite, app.getLayers, ...). Start here to see what you're working with.

Returns (table): Document table.

#### `app.flip(axis, layerId?)` — changes, undoable, refused on a locked layer
Mirrors the whole canvas, or one layer (a group's descendants too) across every frame: cel pixels and positions, tilemap tiles, reference images. Unlike app.flipCelRegion it ignores the selection.

- `axis` (FlipAxis: "horizontal" | "vertical")
- `layerId` (string, optional) — The layer (or group) to flip on every frame. Omit to flip the whole canvas: every layer, every frame, the slices and the selection.

Returns nothing.

#### `app.getCanvasHeight()` — reads
The sprite's canvas height in pixels.

Returns (number): Height in pixels.

#### `app.getCanvasWidth()` — reads
The sprite's canvas width in pixels.

Returns (number): Width in pixels.

#### `app.getColorMode()` — reads
The sprite's color mode: "rgba", "grayscale", or "indexed".

Returns (string): "rgba", "grayscale" or "indexed".

#### `app.getSprite()` — reads
The sprite: { id, width, height, colorMode ("rgba" | "grayscale" | "indexed"), palette (hex strings), transparentIndex (indexed sprites: the palette index whose pixels are transparent), pixelRatio ({ w, h }; 1:1 = square pixels), colorDepth (levels per channel: { kind = "rgb", red, green, blue } or { kind = "gray", gray }, with profile; nil for full color), layerCount, frameCount }.

Returns (table): Sprite table.

#### `app.newSprite(width, height, background?)` — changes, undoable
Replaces the whole document with a brand new sprite (one layer, one frame; a Background layer when a background colour is given). Discards the current document's content.

- `width` (number) — Canvas width in pixels.
- `height` (number) — Canvas height in pixels.
- `background` (color, optional) — Fill colour of a Background layer. Omitted = a transparent "Layer 1" instead.

Returns (table): { layerId, frameId } of the new sprite's one layer and frame.

#### `app.rotateCanvas(degrees)` — changes, undoable
Rotates the whole canvas (every layer, every frame) clockwise (90), counter-clockwise (-90), or 180 degrees.

- `degrees` (number) — 90, -90, or 180.

Returns nothing.

#### `app.setCanvasSize(width, height, anchor)` — changes, undoable
Resizes the canvas, repositioning (not resampling) existing content relative to `anchor`. Content left past the new edge is kept there (only the Background layer is cut), so growing back restores it.

- `width` (number)
- `height` (number)
- `anchor` (CanvasAnchor: "top-left" | "top" | "top-right" | "left" | "center" | "right" | "bottom-left" | "bottom" | "bottom-right")

Returns nothing.

#### `app.setPixelRatio(w, h)` — changes, undoable
Sets the sprite's pixel aspect ratio (Aseprite's Sprite Properties > Pixel Ratio), e.g. 2, 1 for pixels twice as wide as tall. Only display and ratio-aware exports change, not the pixels.

- `w` (number) — Pixel width, a positive integer (1:1 = square).
- `h` (number) — Pixel height, a positive integer.

Returns nothing.

#### `app.setSpriteSize(width, height)` — changes, undoable
Resamples every cel (nearest-neighbor) to fit a new sprite size.

- `width` (number)
- `height` (number)

Returns nothing.

#### `app.setUserData(target, userData?)` — changes, undoable
Sets the user data (colour tag, text and custom properties, as in Aseprite) of a sprite, layer, cel, tag, slice or tileset. A cel target may be a tilemap frame. Linked cels share one user data, like their image. Changes only what you give, so setting the text keeps the colour, and writing your extension's properties namespace keeps everyone else's (as Aseprite's layer.data, layer.color and layer.properties(ns) are separate). Read it back from app.getSprite, getLayers, getCels, getTags, getSlices or getTileset (the userData field). Allowed on locked layers.

- `target` (table) — What to annotate: { kind = "sprite" }, { kind = "layer", layerId }, { kind = "cel", layerId, frameId }, { kind = "tag", tagId }, { kind = "slice", sliceId } or { kind = "tileset", layerId }.
  - `kind` (UserDataKind: "sprite" | "layer" | "cel" | "tag" | "slice" | "tileset") — Which kind of thing.
  - `layerId` (string) — For layer, cel and tileset targets.
  - `frameId` (string) — For cel targets.
  - `tagId` (string) — For tag targets.
  - `sliceId` (string) — For slice targets.
- `userData` (table, optional) — What to change: only the parts you give. text = "" or color = "" clears that part; in properties, each namespace you name is replaced and one set to {} is removed, while the others stay. nil removes all the user data.
  - `color` (string) — The colour tag as "#rrggbb" or "#rrggbbaa". Not accepted for tags (their colour is Tag.color).
  - `text` (string) — Free text (Aseprite's user data text).
  - `properties` (namespaces to properties: { [""] = { score = 3 }, ["my-extension"] = { ... } }. Values are plain JSON: nil-free, finite numbers, strings, booleans, lists and tables. properties[""] is your own namespace (Aseprite's user properties).) — Custom properties by namespace. Aseprite's typed values (uuid, fixed, point, ...) appear as tables with a $type key, e.g. { ["$type"] = "uuid", value = "..." }, and are written back as the Aseprite type.

Returns nothing.

#### `app.trim()` — changes, undoable
Crops the canvas to what's drawn across every frame, as Aseprite's Trim: the visible layers as shown, shrunk against transparency or a solid border colour (such as an opaque Background). No-op when nothing is left to cut.

Returns nothing.
