# pxstud.io Lua scripting API — Drawing

> 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.

### Drawing
The editor's tools as commands — the GUI tools dispatch exactly these. Color, brush and tool options you leave out come from the editing context. Painting ignores the selection and symmetry unless you pass respectSelection = true and symmetry = app.getContext().symmetry.

#### `app.blur(layerId, frameId, points, opts?)` — changes, undoable, refused on a locked layer
Blurs the pixels under the brush along the points — exactly the GUI blur tool.

- `layerId` (string)
- `frameId` (string)
- `points` (table) — List of { x = n, y = n } (or { n, n }) points; gaps between consecutive points are interpolated like a real drag.
- `opts` (table, optional) Also takes the **Painting options** and **Brush options** (see "Option groups"), and:
  - `strength` (integer 0–8; default context.blurStrength) — Blur radius in pixels.

Returns nothing.

#### `app.dodgeBurn(layerId, frameId, points, opts?)` — changes, undoable, refused on a locked layer
Lightens (dodge) or darkens (burn) the pixels under the brush along the points, in OKLCH lightness — exactly the GUI dodge/burn tool. Hue and chroma are kept unless `hueShift` is set. Each pixel changes once per call; transparent pixels are untouched.

- `layerId` (string)
- `frameId` (string)
- `points` (table) — List of { x = n, y = n } (or { n, n }) points; gaps between consecutive points are interpolated like a real drag.
- `opts` (table, optional) Also takes the **Painting options** and **Brush options** (see "Option groups"), and:
  - `strength` (integer 1–100; default context.dodgeBurnStrength) — How far each touched pixel's OKLCH lightness moves: 100 = half of black-to-white, 25 = 0.125.
  - `hueShift` (integer 0–60; default context.dodgeBurnHueShift) — Degrees each touched pixel's OKLCH hue turns toward warm yellow (dodge) or cool blue (burn), never past it, with chroma lessened (dodge) or deepened (burn) by 0.5% per degree. 0 keeps hue and chroma.
  - `burn` (boolean; default false) — Darken (burn) instead of lighten (dodge).

Returns nothing.

#### `app.drawContour(layerId, frameId, points, opts?)` — changes, undoable, refused on a locked layer
Fills the closed path through the points, the path pixels included (Aseprite's contour) — exactly the GUI contour tool.

- `layerId` (string)
- `frameId` (string)
- `points` (table) — The freehand path as a list of { x = n, y = n } points; it is auto-closed and filled.
- `opts` (table, optional) Also takes the **Painting options** and **Ink options** (see "Option groups"), and:
  - `color` (color ("#rgb", "#rrggbb[aa]" or { r, g, b, a }); default context.foregroundColor) — The paint color.

Returns nothing.

#### `app.drawCurve(layerId, frameId, anchors, opts?)` — changes, undoable, refused on a locked layer
Draws a path of cubic Bezier curves through the anchors — exactly the GUI curve tool. Pass closed = true for a closed shape (and fill = "filled" to fill it).

- `layerId` (string)
- `frameId` (string)
- `anchors` (table) — The path's anchors in order: { x = n, y = n } (or { n, n }), each optionally with Bezier handles inX/inY (the control point of the segment arriving) and outX/outY (of the segment leaving). An anchor without handles is a sharp corner; two in a row are joined by a straight line.
- `opts` (table, optional) Also takes the **Painting options** and **Ink options** (see "Option groups"), and:
  - `color` (color ("#rgb", "#rrggbb[aa]" or { r, g, b, a }); default context.foregroundColor) — The paint color.
  - `closed` (boolean; default false) — Join the last anchor back to the first.
  - `fill` (FillMode: "outline" | "filled"; default context.shapeFillMode) — Closed paths: draw the outline, or fill the inside too.
  - `width` (integer 1–64; default context.lineWidth) — Stroke thickness in pixels (1 = a hard 1px curve); wider strokes get round ends.
  - `pixelPerfect` (boolean; default context.curvePixelPerfect) — Remove the L-shaped corners a 1px curve picks up, for clean pixel-art lines (ignored for wider strokes).

Returns nothing.

#### `app.drawEllipse(layerId, frameId, x0, y0, x1, y1, opts?)` — changes, undoable, refused on a locked layer
Draws an ellipse inscribed in the box spanning the two corners — exactly the GUI ellipse tool.

- `layerId` (string)
- `frameId` (string)
- `x0` (number) — First corner.
- `y0` (number)
- `x1` (number) — Opposite corner (inclusive).
- `y1` (number)
- `opts` (table, optional) Also takes the **Painting options** and **Ink options** (see "Option groups"), and:
  - `color` (color ("#rgb", "#rrggbb[aa]" or { r, g, b, a }); default context.foregroundColor) — The paint color.
  - `fill` (FillMode: "outline" | "filled"; default context.shapeFillMode) — Outline only, or filled.

Returns nothing.

#### `app.drawGradient(layerId, frameId, x0, y0, x1, y1, opts?)` — changes, undoable, refused on a locked layer
Fills the region the bucket would fill from (x0, y0) — the connected area of its colour (or every matching pixel when not contiguous), inside the selection — with a gradient from startColor at (x0, y0) to endColor at (x1, y1), or along `stops` — exactly the GUI gradient tool. Under symmetry the mirrored regions get the same gradient. A zero-length drag, or a start off the canvas or outside the selection, is a no-op.

- `layerId` (string)
- `frameId` (string)
- `x0` (number) — Gradient start (where startColor sits).
- `y0` (number)
- `x1` (number) — Gradient end (where endColor sits).
- `y1` (number)
- `opts` (table, optional) Also takes the **Painting options** and **Ink options** (see "Option groups"), and:
  - `startColor` (color ("#rgb", "#rrggbb[aa]" or { r, g, b, a }); default context.foregroundColor) — The color at the first point.
  - `endColor` (color ("#rgb", "#rrggbb[aa]" or { r, g, b, a }); default context.backgroundColor) — The color at the second point.
  - `stops` ({ color, color, ... } or { { color = c, position = 0-1 }, ... }; default When gradientColors is "stops" and neither startColor nor endColor is given, the gradientStops list.) — A multi-stop ramp of 2-32 colors from the first point to the second, instead of startColor and endColor. Plain colors are spread evenly; positions run 0-1 in order, and two stops at one position make a hard edge.
  - `gradientType` (GradientType: "linear" | "radial"; default context.gradientType) — A straight ramp between the points, or radiating from the first.
  - `dithering` (GradientDithering: "none" | "bayer2" | "bayer4" | "bayer8" | "dots" | "lines-h" | "lines-v" | "diagonal" | "noise"; default context.gradientDithering) — Ordered dithering applied to the ramp (none = smooth).
  - `snap` (GradientSnap: "free" | "palette"; default context.gradientSnap) — Free blends, or only palette colors (dithering then steps between neighbouring palette colors).
  - `contiguous` (boolean; default context.bucketFillMode (true for the context's contiguous mode, false for global)) — Only the connected region (true), or every matching pixel on the canvas (false).
  - `tolerance` (integer 0–255; default context.bucketTolerance) — The largest per-channel color difference that still counts as a match.
  - `diagonal` (boolean; default context.bucketDiagonal) — Contiguous only: also spread to diagonal neighbours (8-connected), so the region leaks through 1px diagonal lines.
  - `stopAtGrid` (boolean; default context.bucketStopAtGrid) — Confine the region to the grid cell the click lands in (an isometric grid's cell is the diamond it draws, with the line pixels of its two upper edges).
  - `closeGap` (integer 0–5; default context.bucketCloseGap) — Contiguous fills: treat gaps in the outline up to this many pixels wide as closed, so the fill stops at them instead of leaking out. 0 = off.

Returns nothing.

#### `app.drawHexagon(layerId, frameId, x0, y0, x1, y1, opts?)` — changes, undoable, refused on a locked layer
Draws a flat-top hexagon filling the box spanning the two corners — exactly the GUI hexagon tool.

- `layerId` (string)
- `frameId` (string)
- `x0` (number) — First corner.
- `y0` (number)
- `x1` (number) — Opposite corner (inclusive).
- `y1` (number)
- `opts` (table, optional) Also takes the **Painting options** and **Ink options** (see "Option groups"), and:
  - `color` (color ("#rgb", "#rrggbb[aa]" or { r, g, b, a }); default context.foregroundColor) — The paint color.
  - `fill` (FillMode: "outline" | "filled"; default context.shapeFillMode) — Outline only, or filled.

Returns nothing.

#### `app.drawIsoShape(layerId, frameId, x, y, width?, depth?, height?, opts?)` — changes, undoable, refused on a locked layer
Draws an isometric solid (box, ramp, stairs, pyramid, prism, cylinder, cone, sphere or dome) in pixel-art projection — exactly the GUI Iso Shape tool. Edges are clean staircases (2 px across per 1 px down at 2:1), faces are filled with the top / left / right colors, inner edges separate them and the silhouette is outlined. The footprint's back corner sits at (x, y); x runs down-right and y down-left on screen, and height raises the top. The round solids (cylinder, cone, sphere, dome) take width as their diameter and ignore depth; the sphere also ignores height.

- `layerId` (string)
- `frameId` (string)
- `x` (number) — Anchor: the leftmost pixel of the cap at the footprint's back (top) corner, on the ground.
- `y` (number)
- `width` (number, optional) — Footprint along x (down-right), in iso units: each unit is one row of the 2:1 edge staircase. nil = the context's isoWidth.
- `depth` (number, optional) — Footprint along y (down-left), in iso units. nil = the context's isoDepth.
- `height` (number, optional) — Height in pixels (0 = a flat diamond). nil = the context's isoHeight.
- `opts` (table, optional) Also takes the **Painting options** and **Ink options** (see "Option groups"), and:
  - `color` (color ("#rgb", "#rrggbb[aa]" or { r, g, b, a }); default context.foregroundColor) — The base color: the top face when auto-shading.
  - `shape` (IsoShape: "box" | "ramp" | "stairs" | "pyramid" | "prism" | "cylinder" | "cone" | "sphere" | "dome"; default context.isoShape) — Which solid to draw.
  - `cap` (integer 2–4; default context.isoCap) — The flat run of pixels at the top and bottom vertices: 2 (even widths), 3 (odd) or 4 (multiples of four). Ratios above 2 have a wider natural cap.
  - `bevel` (integer 0–1024; default context.isoBevel) — Box only: chamfers the four top edges by this many pixels, a 45-degree slope between the top and the sides (0 = square edges). Clamped to the height and half the footprint.
  - `style` (IsoShapeStyle: "filled" | "outline" | "filled-outline"; default context.isoStyle) — Faces, outline, or both.
  - `hiddenEdges` (boolean; default context.isoHiddenEdges) — Also draw the edges behind the solid, in the inner-edge color (a see-through wireframe with style outline).
  - `direction` (IsoDirection: "+x" | "-x" | "+y" | "-y"; default context.isoDirection) — Ramp and stairs: the way they descend. Prism: the way one slope faces (+x and -x give the same roof). Ignored by the other shapes.
  - `ratio` (integer 1–8; default the sprite's isometric grid ratio (run / rise, rounded), else 2) — Pixels across per pixel down along the iso axes.
  - `colors` ({ outline =, innerEdge =, top =, left =, right = } colors; default auto-shaded from color, or the context's isoColors when isoAutoShade is off) — All five colors at once (left is the face toward +y, down-left; right the face toward +x). Overrides auto-shading.
  - `autoShade` (boolean; default context.isoAutoShade) — Derive the colors from `color`: top = color, the sides one and two steps darker along `ramp` (or computed), inner edges darker still, the outline darkest. false uses the context's isoColors. Ignored when colors is given.
  - `ramp` (a list of colors ("#rrggbb[aa]" or { r, g, b, a }); default context.shades) — The ramp auto-shading steps along when it contains `color` (the darker end is found by brightness). Otherwise the shades are computed, and snapped to the palette on an indexed sprite.

Returns nothing.

#### `app.drawLine(layerId, frameId, x0, y0, x1, y1, opts?)` — changes, undoable, refused on a locked layer
Draws a straight line between the two points — exactly the GUI line tool (width thickens it with round ends).

- `layerId` (string)
- `frameId` (string)
- `x0` (number)
- `y0` (number)
- `x1` (number)
- `y1` (number)
- `opts` (table, optional) Also takes the **Painting options** and **Ink options** (see "Option groups"), and:
  - `color` (color ("#rgb", "#rrggbb[aa]" or { r, g, b, a }); default context.foregroundColor) — The paint color.
  - `width` (integer 1–64; default context.lineWidth) — Line thickness in pixels (1 = a hard 1px line); wider lines get round ends.

Returns nothing.

#### `app.drawPolygon(layerId, frameId, vertices, opts?)` — changes, undoable, refused on a locked layer
Draws a closed polygon through the vertices — exactly the GUI polygon tool.

- `layerId` (string)
- `frameId` (string)
- `vertices` (table) — List of { x = n, y = n } (or { n, n }) vertices, at least 2; the polygon closes back to the first automatically.
- `opts` (table, optional) Also takes the **Painting options** and **Ink options** (see "Option groups"), and:
  - `color` (color ("#rgb", "#rrggbb[aa]" or { r, g, b, a }); default context.foregroundColor) — The paint color.
  - `fill` (FillMode: "outline" | "filled"; default context.shapeFillMode) — Outline only, or filled.

Returns nothing.

#### `app.drawRectangle(layerId, frameId, x0, y0, x1, y1, opts?)` — changes, undoable, refused on a locked layer
Draws an axis-aligned rectangle spanning the two corners — exactly the GUI rectangle tool (radius rounds the corners, together or one by one). Ignores the selection unless respectSelection = true.

- `layerId` (string)
- `frameId` (string)
- `x0` (number) — First corner.
- `y0` (number)
- `x1` (number) — Opposite corner (inclusive).
- `y1` (number)
- `opts` (table, optional) Also takes the **Painting options** and **Ink options** (see "Option groups"), and:
  - `color` (color ("#rgb", "#rrggbb[aa]" or { r, g, b, a }); default context.foregroundColor) — The paint color.
  - `fill` (FillMode: "outline" | "filled"; default context.shapeFillMode) — Outline only, or filled.
  - `radius` (an integer 0–1024 for every corner, or a list of four { topLeft, topRight, bottomRight, bottomLeft }; default context.cornerRadius) — Rounds the corners, in pixels (0 = square): one radius for all four, or one per corner going clockwise from the top-left. Each is clamped to half the smaller side.

Returns nothing.

#### `app.drawStar(layerId, frameId, x0, y0, x1, y1, opts?)` — changes, undoable, refused on a locked layer
Draws a 5-point star (point up) whose outer points touch every side of the box spanning the two corners — exactly the GUI star tool.

- `layerId` (string)
- `frameId` (string)
- `x0` (number) — First corner.
- `y0` (number)
- `x1` (number) — Opposite corner (inclusive).
- `y1` (number)
- `opts` (table, optional) Also takes the **Painting options** and **Ink options** (see "Option groups"), and:
  - `color` (color ("#rgb", "#rrggbb[aa]" or { r, g, b, a }); default context.foregroundColor) — The paint color.
  - `fill` (FillMode: "outline" | "filled"; default context.shapeFillMode) — Outline only, or filled.

Returns nothing.

#### `app.drawStroke(layerId, frameId, points, opts?)` — changes, undoable, refused on a locked layer
Freehand brush stroke through the points — exactly the GUI pencil (or, with erase = true, the eraser; with dither, the dither brush).

- `layerId` (string)
- `frameId` (string)
- `points` (table) — List of { x = n, y = n } (or { n, n }) points; gaps between consecutive points are interpolated like a real drag.
- `opts` (table, optional) Also takes the **Painting options**, **Brush options** and **Ink options** (see "Option groups"), and:
  - `color` (color ("#rgb", "#rrggbb[aa]" or { r, g, b, a }); default context.foregroundColor) — The paint color.
  - `erase` (boolean; default false) — Erase to transparent instead of painting (the eraser); `color` is ignored.
  - `pixelPerfect` (boolean; default context.pixelPerfect) — Remove doubled corner steps from the stroke path (any brush size).
  - `matchColor` (color ("#rgb", "#rrggbb[aa]" or { r, g, b, a }); default nil (every pixel)) — Only paint (or erase) pixels that are exactly this color; others are untouched. With color = the background color and matchColor = the foreground color this is Aseprite's right-click eraser.
  - `dither` ({ pattern = "bayer2" | "bayer4" | "bayer8" | "dots" | "lines-h" | "lines-v" | "diagonal" | "noise", density = 0-100 }; default nil (every pixel)) — Only paint (or erase) the pixels of an ordered pattern at a density in percent, leaving the rest untouched: the dither brush (50 with bayer2 is a checkerboard). The pattern tiles from the sprite's top-left corner, so overlapping strokes stay aligned.
  - `dynamics` ({ stabilizer = 0-64, pressure = { size, angle, gradient (booleans), min, max (0-100) }, velocity = { same }, minSize, minAngle, maxAngle, dither, curve = 25-400 }; default built from the context's stabilizer, pressure*/velocity* and dynamics* fields; pass {} for a plain stroke) — Brush dynamics: a stabilizer (moving average of the points) and pressure / velocity sensors that drive the stamp size (from minSize up to the brush size), angle (minAngle to maxAngle) and a color gradient from color to endColor (dither = true picks one or the other per pixel; indexed sprites snap to the nearest palette color). min/max are the sensor range in percent (min > max inverts); curve is the sensitivity in percent (100 = linear). With pixelPerfect and a varying size, elbows are still removed along the center path. Custom brushes only get the stabilizer and gradient.
  - `pressures` (list of numbers 0-1, one per point; default 1 (full pressure) for every point) — Per-point pen pressure read by the pressure sensor. A mouse has none, so it counts as full.
  - `velocities` (list of numbers >= 0, one per point; default 0 for every point) — Per-point pointer speed in sprite pixels per millisecond (0.5 = the sensor's maximum) read by the velocity sensor.
  - `endColor` (color ("#rgb", "#rrggbb[aa]" or { r, g, b, a }); default context.backgroundColor) — The gradient's end color when a sensor drives the gradient; `color` is the start.

Returns nothing.

#### `app.drawTriangle(layerId, frameId, x0, y0, x1, y1, opts?)` — changes, undoable, refused on a locked layer
Draws an isosceles triangle (apex top-centre, base along the bottom) filling the box spanning the two corners — exactly the GUI triangle tool.

- `layerId` (string)
- `frameId` (string)
- `x0` (number) — First corner.
- `y0` (number)
- `x1` (number) — Opposite corner (inclusive).
- `y1` (number)
- `opts` (table, optional) Also takes the **Painting options** and **Ink options** (see "Option groups"), and:
  - `color` (color ("#rgb", "#rrggbb[aa]" or { r, g, b, a }); default context.foregroundColor) — The paint color.
  - `fill` (FillMode: "outline" | "filled"; default context.shapeFillMode) — Outline only, or filled.

Returns nothing.

#### `app.floodFill(layerId, frameId, x, y, opts?)` — changes, undoable, refused on a locked layer
Paint-bucket fill starting at (x, y) — exactly the GUI bucket tool.

- `layerId` (string)
- `frameId` (string)
- `x` (number)
- `y` (number)
- `opts` (table, optional) Also takes the **Painting options** and **Ink options** (see "Option groups"), and:
  - `color` (color ("#rgb", "#rrggbb[aa]" or { r, g, b, a }); default context.foregroundColor) — The paint color.
  - `contiguous` (boolean; default context.bucketFillMode (true for the context's contiguous mode, false for global)) — Only the connected region (true), or every matching pixel on the canvas (false).
  - `tolerance` (integer 0–255; default context.bucketTolerance) — The largest per-channel color difference that still counts as a match.
  - `diagonal` (boolean; default context.bucketDiagonal) — Contiguous only: also spread to diagonal neighbours (8-connected), so the region leaks through 1px diagonal lines.
  - `stopAtGrid` (boolean; default context.bucketStopAtGrid) — Confine the region to the grid cell the click lands in (an isometric grid's cell is the diamond it draws, with the line pixels of its two upper edges).
  - `closeGap` (integer 0–5; default context.bucketCloseGap) — Contiguous fills: treat gaps in the outline up to this many pixels wide as closed, so the fill stops at them instead of leaking out. 0 = off.
  - `pattern` (FillPattern: "solid" | "checker" | "dither25" | "dither75" | "hstripes" | "vstripes" | "diagonal" | "grid" | "dots" | "bricks"; default context.bucketPattern) — Fill with a repeating pattern of `color` and `patternColor`, tiled from the canvas's top-left corner. "solid" = just `color`.
  - `patternColor` (color ("#rgb", "#rrggbb[aa]" or { r, g, b, a }); default context.backgroundColor) — The pattern's second color (ignored when `pattern` is "solid").

Returns nothing.

#### `app.isoProject(layerId, frameId, projection, opts?)` — changes, undoable, refused on a locked layer
Projects the selected pixels (the marquee follows) — or the whole cel when nothing is selected — onto a face of an isometric cube: "left" and "right" shear the columns along the slope, "floor" turns the region into the top face (a square becomes a diamond). Pixel-exact: every edge comes out as a clean staircase (2 across per 1 down at 2:1) and no colors are blended. Content landing outside the canvas and the cel bounds is cut (the Background layer is cut to the canvas).

- `layerId` (string)
- `frameId` (string)
- `projection` (IsoProjection: "left" | "right" | "floor")
- `opts` (table, optional)
  - `run` (integer 1–16; default The sprite's isometric grid ratio, or 2 (2:1) when the grid is rectangular.) — Pixels across per `rise` pixels down (the slope, in lowest terms). Give rise too.
  - `rise` (integer 1–16; default The sprite's isometric grid ratio, or 1 (2:1) when the grid is rectangular.) — Pixels down per `run` pixels across. Give run too.

Returns nothing.

#### `app.jumble(layerId, frameId, points, opts?)` — changes, undoable, refused on a locked layer
Randomized smudge: scrambles pixels under the brush with random neighbors along the points — exactly the GUI jumble tool.

- `layerId` (string)
- `frameId` (string)
- `points` (table) — List of { x = n, y = n } (or { n, n }) points; gaps between consecutive points are interpolated like a real drag.
- `opts` (table, optional) Also takes the **Painting options** and **Brush options** (see "Option groups"), and:
  - `strength` (integer 0–100; default context.jumbleStrength) — Percent chance each touched pixel is scrambled. A 1px brush has no neighbors to scramble with, so use brushSize 2 or more.
  - `seed` (integer 0–4294967295; default random) — Random seed: the same seed and points give the same result.

Returns nothing.

#### `app.movePixels(layerId, frameId, dx, dy)` — changes, undoable, refused on a locked layer
Moves the selected pixels (per the current selection; the marquee follows) — or the whole cel when nothing is selected — by (dx, dy). Exactly the GUI move tool. Without a selection the whole cel moves and keeps everything, past the canvas edge too; moved selection pixels are kept within the canvas and the cel's bounds, and cut beyond. The Background layer is cut to the canvas.

- `layerId` (string)
- `frameId` (string)
- `dx` (number) — Pixels to move right (negative = left).
- `dy` (number) — Pixels to move down (negative = up).

Returns nothing.

#### `app.outline(layerId, frameIds, opts?)` — changes, undoable, refused on a locked layer
Filter > Outline, as in Aseprite: draws an outline around (or just inside) the shapes on a layer's cels, as a single undo step. A pixel is part of a shape unless it is transparent (or matches bgColor). Outside outlines grow the cel, clipped to the canvas. Image layers only.

- `layerId` (string)
- `frameIds` (table) — Array of frame ids whose cels on this layer are outlined (one frame for the active cel, every frame for all of them).
- `opts` (table, optional)
  - `cels` (Array of { layerId = ..., frameId = ... }) — Outline these cels, across layers and frames, instead of layerId/frameIds (pass nil for both).
  - `color` (color ("#rgb", "#rrggbb[aa]" or { r, g, b, a }); default context.foregroundColor) — The outline color. On an indexed sprite it snaps to the nearest palette entry unless colorIndex is given.
  - `colorIndex` (integer 0–255; default Taken from color.) — Indexed sprites: the palette index to paint, used as-is. Not the transparent index.
  - `place` (OutlinePlace: "outside" | "inside"; default "outside") — Outline outside the shape (it grows) or inside it (its edge pixels are repainted).
  - `shape` (OutlineShape: "circle" | "square"; default "circle") — Preset for the neighbor matrix. Ignored when matrix is given.
  - `matrix` (9 booleans, row-major 3x3 (top-left first): which neighbors count as touching. The center entry is ignored.) — A custom neighbor matrix, like Aseprite's 3x3 toggle grid.
  - `bgColor` (color ("#rgb", "#rrggbb[aa]" or { r, g, b, a }); default Only transparent pixels are background.) — An opaque color that also counts as background, besides fully transparent pixels.
  - `bgIndex` (integer 0–255) — Indexed sprites: a palette index that also counts as background.
  - `respectSelection` (boolean; default false) — Pass true to outline only inside the current selection. By default the selection is ignored.

Returns nothing.

#### `app.pastePixels(layerId, frameId, image, opts?)` — changes, undoable, refused on a locked layer
Pastes an image onto a layer/frame's cel like the GUI's floating paste: transparent pixels leave the cel alone, partly transparent ones blend over it, and the pasted pixels become the selection. The optional transform distorts it (corners), then scales, rotates and/or skews the image about its center (unless pivotX/pivotY are given), then shifts it by dx/dy. Needs an editable (unlocked) image layer; a movement lock does not apply. What lands outside the canvas and the cel's bounds is cut (the Background layer is cut to the canvas).

- `layerId` (string)
- `frameId` (string)
- `image` (table) — { x, y, width, height, pixels } — pixels is a list of width*height colors ("#rrggbb[aa]" or { r, g, b, a }), row by row; x, y is where the top-left lands.
- `opts` (table, optional)
  - `scaleX` (number -256–256; default 1) — Horizontal scale about the pivot; negative flips. Not 0.
  - `scaleY` (number -256–256; default 1) — Vertical scale about the pivot; negative flips. Not 0.
  - `rotation` (number; default 0) — Degrees clockwise about the pivot.
  - `skewX` (number -80–80; default 0) — Degrees of horizontal shear (the top leans one way, the bottom the other, about the pivot).
  - `skewY` (number -80–80; default 0) — Degrees of vertical shear.
  - `dx` (number; default 0) — Pixels to shift right after transforming.
  - `dy` (number; default 0) — Pixels to shift down after transforming.
  - `pivotX` (number; default The center of the selection (or of the cel when nothing is selected).) — X of the fixed point for rotate/scale/skew, in canvas pixels.
  - `pivotY` (number; default The center of the selection (or of the cel when nothing is selected).) — Y of the fixed point for rotate/scale/skew, in canvas pixels.
  - `interpolation` (TransformInterpolation: "nearest" | "rotsprite" | "bilinear"; default "nearest") — How pixels are resampled. Use rotsprite for clean rotations of pixel art, bilinear for smooth (blended) results.
  - `corners` ({ {x=,y=}, {x=,y=}, {x=,y=}, {x=,y=} }; default The region's own corners (no distortion).) — Free distort / perspective: where the region's top-left, top-right, bottom-right and bottom-left corners land, in canvas pixels (fractions allowed), before the scale/skew/rotation/shift above. Must form a convex shape. Pinning the two top corners closer together than the bottom ones gives a floor-like perspective.

Returns nothing.

#### `app.scatter(layerId, frameId, points, opts?)` — changes, undoable, refused on a locked layer
Scatter brush: stamps the brush along the points, one step per brush width, each stamp nudged by a random offset — exactly the GUI scatter tool.

- `layerId` (string)
- `frameId` (string)
- `points` (table) — List of { x = n, y = n } (or { n, n }) points; gaps between consecutive points are interpolated like a real drag.
- `opts` (table, optional) Also takes the **Painting options**, **Brush options** and **Ink options** (see "Option groups"), and:
  - `color` (color ("#rgb", "#rrggbb[aa]" or { r, g, b, a }); default context.foregroundColor) — The paint color.
  - `offsetX` (integer 0–64; default context.scatterX) — How far each stamp can land left or right of the path, in pixels.
  - `offsetY` (integer 0–64; default context.scatterY) — How far each stamp can land above or below the path, in pixels.
  - `count` (integer 1–16; default context.scatterCount) — Stamps placed at each step along the path.
  - `seed` (integer 0–4294967295; default random) — Random seed: the same seed and points give the same result.

Returns nothing.

#### `app.spray(layerId, frameId, points, opts?)` — changes, undoable, refused on a locked layer
Airbrush: scatters random dots around each point — exactly the GUI spray tool. Each point is one tick, so repeat a point to keep spraying in place.

- `layerId` (string)
- `frameId` (string)
- `points` (table) — List of { x = n, y = n } (or { n, n }) points; gaps between consecutive points are interpolated like a real drag.
- `opts` (table, optional) Also takes the **Painting options** and **Ink options** (see "Option groups"), and:
  - `color` (color ("#rgb", "#rrggbb[aa]" or { r, g, b, a }); default context.foregroundColor) — The paint color.
  - `radius` (integer 1–128; default twice the context's brushSize, at least 2) — How far from each point the dots scatter, in pixels.
  - `rate` (integer 1–64; default context.sprayRate) — Dots scattered per point.
  - `seed` (integer 0–4294967295; default random) — Random seed: the same seed and points give the same result.

Returns nothing.

#### `app.strokeSelection(layerId, frameId, width, opts?)` — changes, undoable, refused on a locked layer
Edit > Stroke Selection: paints a line of the given width along the current selection's edge (inside, centered or outside it) on one cel, as a single undo step. Does nothing when nothing is selected. Image layers only.

- `layerId` (string)
- `frameId` (string)
- `width` (number) — Pixels, 1 to 256.
- `opts` (table, optional)
  - `position` (StrokePosition: "inside" | "center" | "outside"; default "center") — Where the stroke sits relative to the selection's edge.
  - `color` (color ("#rgb", "#rrggbb[aa]" or { r, g, b, a }); default context.foregroundColor) — The stroke color. On an indexed sprite it snaps to the nearest palette entry unless colorIndex is given.
  - `colorIndex` (integer 0–255; default Taken from color.) — Indexed sprites: the palette index to paint, used as-is. Not the transparent index.
  - `shape` (SelectionModifyShape: "circle" | "square"; default "circle") — How distance from the edge is measured (round or square corners).

Returns nothing.

#### `app.transformPixels(layerId, frameId, opts?)` — changes, undoable, refused on a locked layer
Scales, rotates, skews and/or distorts (perspective, via corners) the selected pixels (the marquee follows) — or the whole cel when nothing is selected — about a pivot, exactly like the GUI move tool's transform handles. Order: scale, skew, rotate, then shift by dx/dy. On a sprite with non-square pixels (Sprite.pixelRatio) the angles and scales are in what you see on screen, so a 90 degree rotation looks like one. Without a selection the whole cel keeps what lands within the box of the canvas and the cel's bounds, both where it is and shifted by dx/dy (so a plain shift loses nothing, past the canvas edge too); transformed selection pixels are kept within the canvas and the cel's bounds and cut beyond. The Background layer is cut to the canvas. A no-op transform changes nothing.

- `layerId` (string)
- `frameId` (string)
- `opts` (table, optional)
  - `scaleX` (number -256–256; default 1) — Horizontal scale about the pivot; negative flips. Not 0.
  - `scaleY` (number -256–256; default 1) — Vertical scale about the pivot; negative flips. Not 0.
  - `rotation` (number; default 0) — Degrees clockwise about the pivot.
  - `skewX` (number -80–80; default 0) — Degrees of horizontal shear (the top leans one way, the bottom the other, about the pivot).
  - `skewY` (number -80–80; default 0) — Degrees of vertical shear.
  - `dx` (number; default 0) — Pixels to shift right after transforming.
  - `dy` (number; default 0) — Pixels to shift down after transforming.
  - `pivotX` (number; default The center of the selection (or of the cel when nothing is selected).) — X of the fixed point for rotate/scale/skew, in canvas pixels.
  - `pivotY` (number; default The center of the selection (or of the cel when nothing is selected).) — Y of the fixed point for rotate/scale/skew, in canvas pixels.
  - `interpolation` (TransformInterpolation: "nearest" | "rotsprite" | "bilinear"; default "nearest") — How pixels are resampled. Use rotsprite for clean rotations of pixel art, bilinear for smooth (blended) results.
  - `corners` ({ {x=,y=}, {x=,y=}, {x=,y=}, {x=,y=} }; default The region's own corners (no distortion).) — Free distort / perspective: where the region's top-left, top-right, bottom-right and bottom-left corners land, in canvas pixels (fractions allowed), before the scale/skew/rotation/shift above. Must form a convex shape. Pinning the two top corners closer together than the bottom ones gives a floor-like perspective.

Returns nothing.
