Deluxe Pixel scripting reference (Luau)

Written by Deluxe Pixel 2.41 from its own command list, Luau build and examples, so it matches that version exactly.

For everything else, see the Deluxe Pixel guide. Every command is also available as JSON tool definitions (the form AI assistants see through Edit > AI access): deluxepixel_commands.json.

Getting started

Scripts are written in Luau (a fast, typed, sandboxed form of Lua 5.1). Open Edit > Script console, type a script and press Run (Ctrl+Enter). Everything a script does to a picture is one undo step.

-- A red square, then say how big the picture is.
dp.palette.set{ index = 1, colour = "#FF0000" }
dp.draw.rect{ x = 8, y = 8, w = 16, h = 16, colour = 1, filled = true }
print(`The picture is {dp.width()} x {dp.height()}`)
  • Commands: every action is a command, called as dp.<group>.<name>{ settings } - the same commands as the command palette (Ctrl+Shift+P), recorded macros and AI assistants (Edit > AI access) use.
  • Where scripts live: the scripts folder in Deluxe Pixel's settings folder (Edit > Scripts lists them, one click each; Edit > Scripts > Open the scripts folder). Edit > Run a script runs any .luau or .lua file; the command script.run runs one from a macro.
  • Macros as scripts: Edit > Stop recording, saved as a .luau file, writes the recorded steps as a script.
  • Output: print(...) writes to the console's Output (values separated by tabs).
  • Stopping: Escape stops a running script; one that runs for more than 30 seconds is stopped.

Calling commands

dp.draw.rect{ x = 10, y = 20, w = 32, h = 16, colour = 5, filled = true }   -- settings by name
dp.layer.add()                                                            -- no settings
local message = dp.run("draw.pixel", { x = 1, y = 1, colour = 2 })      -- by name
local ok, why = dp.try("layer.delete")                                  -- never stops the script
  • Settings are named (a table: { x = 1, y = 2 }). Every setting is checked before the command runs: an unknown setting, a missing one that's needed, a value of the wrong kind or out of range all stop the script with the reason and the line (script:12: dp.draw.rect needs w (whole number).) - nothing is half done.
  • Values: numbers, true/false, or text. By type:
Type Give Notes
whole number 5, -3 Within the setting's range.
number 2.5 Within the setting's range.
true or false true, false Also the text "yes"/"no", "on"/"off", "1"/"0".
text "Sky" Any text.
choice "Posterise" One of the setting's choices (capitals don't matter).
colour "#FF8800" #RRGGBB.
path "C:/Art/hero.dpx" A file.
  • What a command gives back: its message as text (often empty; the info.* questions give their answer). If it can't run - no picture open, the layer locked, a colour not in the palette - the script stops with the command's own reason. dp.try gives ok, message instead and carries on; pcall catches it too.
  • Undo: a whole script run is one undo step (labelled Script: <name>), however many commands it runs.
  • Names: in Luau a command is dp.<group>.<name>; by name (dp.run, dp.try, macros) it's "group.name"; AI assistants see it as group_name.

The dp helpers

Quick questions about the active picture, answered as Luau values. With no picture open (or a map in the active tab) the picture questions stop the script with "there's no picture open."

Helper What it does and gives back
message = dp.run("group.name", { settings }) Runs a command by its name (as dp.group.name{ settings } does). Returns the command's message (text, often empty); if the command can't run, the script stops with the command's own error.
ok, message = dp.try("group.name", { settings }) Runs a command by its name without ever stopping the script. Returns ok (true or false) and a message: the command's message, or why it couldn't run.
dp.width() The picture's width in pixels (a number); a tile map's, cells x tile width.
dp.height() The picture's height in pixels (a number); a tile map's, cells x tile height.
dp.colours() How many colours the palette has (a number).
dp.layers() How many layers the picture has (a number).
dp.active_layer() The active layer's number (0 is the bottom layer).
dp.transparent() The see-through colour's number.
dp.pixel(x, y) The active layer's pixel at x, y. A palette picture: its colour number. A true-colour picture: "#RRGGBB", or nil where it's see-through. Outside the picture: -1.
dp.colour(i) Palette colour i as "#RRGGBB". A number outside the palette stops the script ("the palette has N colours.").
dp.commands() Every command's name ("layer.add"...) as a list, in the order the command palette has them.
buf, w, h = dp.pixels() The whole active layer at once, as a buffer (fast: no command a pixel). A palette picture: 1 byte a pixel, its colour number; a true-colour picture: 4 bytes a pixel, R G B A. Pixel x, y is at offset (y * w + x) (times 4 for true colour). Also gives the width and height.
dp.set_pixels(buf) Writes a buffer like dp.pixels() gives back to the active layer, as one undo step; with a selection, only the pixels inside it change. Stops the script if the size is wrong, a colour number isn't in the palette, or the layer is locked.
id = dp.on("event", function(...) ... end) Calls the function each time the event happens ("frame", "key", "mouse", "picture"... see Events). The script keeps running after its last line until it's stopped. Returns an id for dp.off.
dp.off(id) Stops one handler (or task) that dp.on (or dp.spawn) gave the id of. Returns true if there was one.
id = dp.spawn(function(...) ... end, ...) Runs the function alongside Deluxe Pixel from the next frame, with the arguments given; inside it, dp.wait pauses it. The script keeps running until it's stopped.
waited = dp.wait(seconds) Inside a task (dp.spawn): goes on after that many seconds (left out: the next frame), and gives back how long it was. Deluxe Pixel carries on meanwhile.
dp.stop() Stops this script: its handlers and tasks end (its "stop" handlers run first).
dp.live() How many handlers and tasks this script has (0: it ends after its last line).
p = dp.pad(n) Game controller n (1 if left out) now, or nil if there isn't one: p.lx, p.ly, p.rx, p.ry the sticks (-1..1; y is down, as the picture's pixels), p.lt, p.rt the triggers (0..1), p.a, p.b, p.x, p.y, p.lb, p.rb, p.back, p.start, p.ls, p.rs and the d-pad p.up, p.down, p.left, p.right (true while held), p.pressed and p.released (the buttons that went down or up since the last frame, as p.pressed.a), p.name. Xbox-style pads and most other USB pads.
dp.pads() How many game controllers are connected.
img = dp.image(x, y, w, h, picture) Cuts an image (a character, an enemy, a bullet) from a picture, to show over the canvas with dp.show. picture is an open picture's name; left out, the map's tile set when a map is in front, else the picture in front. Its see-through colour stays see-through. Gone when the script stops.
img = dp.tile_image(n) Tile n (from 0) of the tile set - the map's, when a map is in front - as an image for dp.show.
dp.show(img, x, y, flipx, flipy) Shows an image over the canvas this frame, its top-left at picture (or map) pixel x, y, mirrored if flipx / flipy. Nothing is drawn into the picture: the overlay is cleared every frame, so show things from a frame handler. Positions can be fractions (it's drawn at the pixel it's in).
dp.box(x, y, w, h, colour, filled) A rectangle over the canvas this frame: colour a palette number or "#RRGGBB" ("#RRGGBBAA" see-through); filled = false for just the outline.
dp.say(x, y, text, colour) Text over the canvas this frame, at picture pixel x, y (a score, a message); white if colour is left out.
dp.camera(x, y) Centres the view on picture (or map) pixel x, y, keeping the zoom: the view follows a character. With nothing given, gives back the centre and the size shown: x, y, w, h.
m = dp.map_info() The map in front (nil if it isn't a map): m.width, m.height in cells, m.tile_w, m.tile_h in pixels, m.layers, m.layer (the active one, from 0).
tile, attr, flipx, flipy = dp.cell(cx, cy, layer) A map cell (cx, cy in cells, from 0): its tile number (-1 empty) and attribute bits (solid, ladder... as the map gives them). layer left out: the active layer. nil outside the map.
anim = dp.sprite("name", "animation", picture) One of the sprite system's sprites (Window > Sprite timeline) and one of its animations (the first if left out), as images for dp.show_anim: anim.frames (each image, x, y from the sprite's origin, w, h, time in seconds), anim.length, anim.loop ("loop", "pingpong", "once"). Looked for in the picture named, else the picture in front, the map's tile set, then every open picture.
dp.show_anim(anim, x, y, t, flipx) Shows the frame of a dp.sprite animation that's showing t seconds in, with the sprite's origin (its hot spot) at x, y; flipx mirrors it round the origin (facing left).
dp.sprites() Every sprite in the open pictures, as "picture: sprite (animation, ...)".
dp.show_screen(img, x, y, scale) Shows an image this frame in the view's own pixels rather than the picture's: its top-left x, y from the view's top-left, scale screen pixels an image pixel. With x, y and scale left out it's as big as fits the view, in the middle (a first-person view from dp.raycast or dp.mode7 filling the window); x, y left out alone: centred.
img = dp.raycast(x, y, angle, opts) The map in first person (as Wolfenstein 3D), standing at map pixel x, y and looking along angle (degrees: 0 right, 90 down, as the map is drawn): the cells with a tile are walls drawn with their tiles (opts.solid: only cells whose attribute has one of those bits; opts.open: tile numbers that are never walls, the floor's), the floor from the tiles of the cells that aren't walls. opts: w, h (320 x 200), fov (66), floor, ceiling (colours), ceiling_tile, floor_tiles (true), distance (64 cells), fog (0-1), fog_colour, eye (0.5 of a wall), shade (true), layer, sprites = { {image = img, x = .., y = .., size = 1 (cells high), lift = 0}, ... } standing in it, and into = img to draw into an image made before (do, every frame: view = dp.raycast(x, y, a, {into = view})). Doors: opts.doors = { {x = cell, y = cell, open = 0 to 1, along = "x" or "y" (left out: from the walls beside it)}, ... }: a thin wall across the middle of its cell, drawn with the cell's tile, sliding aside as it opens. Moving walls (push walls): opts.moving = { {x = cell, y = cell, dx = .., dy = ..}, ... }: that cell's wall moved so far, in cells. Keep them in the script and change open / dx / dy each frame. opts.gpu = true draws it on the graphics card (much faster, so it can be as big as the window: w and h up to 3840 x 2160; where there's no graphics card it's drawn as usual - dp.gpu() says which). A view drawn on the graphics card can be shown but can't be a sprite in another view. Show it with dp.show_screen.
dp.gpu() True when views can be drawn on the graphics card here (dp.raycast's gpu option).
dp.wall_at(x, y, opts) True if map pixel x, y is inside a wall as dp.raycast draws them: opts.solid (the attribute bits that make a wall; left out, any tile), opts.open (floor tiles), opts.layer - the same as given to dp.raycast, with its doors (solid until three quarters open) and moving walls (solid where they are now). For walking: don't step into one.
img = dp.raised(x, y, opts) The map raised in 3D, centred on map pixel x, y: each cell a stack of cubes as high as its height (steps), its top drawn with its tile and its sides with the tile shaded; height 0 is flat floor, an empty cell nothing. Heights from opts.height_bits (attribute bits read as a number), opts.heights = { [tile] = height, ... }, or opts.cells = { one per cell, row by row; -1: as the others say }. opts: view = "iso" (a diamond grid: the default) or "angle" (from above, tops and fronts), w, h (320 x 200), cell_w, cell_h (a top's size on screen), step (pixels a height step), background, shade (true), layer, sprites = { {image = img, x = .., y = .., size = 1 (screen pixels an image pixel), lift = 0 (steps)}, ... } standing on the cells, into = img. Show it with dp.show_screen. (Called dp.blocks in 1.70: that name still works.)
sx, sy, height = dp.raised_point(mx, my, x, y, opts) Where map pixel mx, my shows in dp.raised's view centred on x, y (on top of the cell there; opts.lift adds steps), and that cell's height - the same opts as dp.raised. For a cursor, a shadow or clicking on a cell.
img = dp.mode7(x, y, angle, opts) The map laid flat and seen from above it at an angle, as the SNES's Mode 7 (kart racers, flying over a world map): the camera at map pixel x, y, looking along angle (0 right, 90 down). opts: w, h (320 x 200), fov (60), height (48 map pixels above it), horizon (the row: a third of the way down), sky, outside (colours), wrap (the map repeating for ever), fog (0-1), layer, sprites = { {image = img, x = .., y = .., size = 1, lift = 0}, ... } standing on it, into = img. Show it with dp.show_screen.
m = dp.model("name", picture) One of a picture's 3D models (brought in with File > 3D model), to show while playing: m.name, m.anims (its animations' names), m.lengths (seconds), m.w, m.h (its size: a live 3D object's box if the picture has one using it, which also gives its light and look). Looked for in the picture named, else the picture in front, the map's tile set, then every open picture.
img, w, h = dp.model_image(m, opts) A dp.model drawn as an image, turned and posed: opts.facing (the way it's going on the map: 0 right, 90 down, 180 left, 270 up) or opts.yaw (0 facing you), opts.directions (snap to 8 or 16 directions, as pre-rendered sprites), opts.pitch, opts.anim (a name or number), opts.time (seconds into it), opts.w, opts.h. Each pose is drawn once and kept, so call it every frame. For dp.show or as a sprite in dp.raycast and dp.mode7.
dp.show_model(m, x, y, opts) Shows a dp.model over the canvas this frame, centred on picture (map) pixel x, y (opts.feet = true: standing there), turned and posed as dp.model_image says: a 3D character facing the way it walks.
dp.cycle(on) Turns the view's colour cycling on (or off with false): a level's animated water and lights while playing.
tile, attr, cx, cy = dp.cell_at(x, y, layer) The map cell under map pixel x, y (for collisions: what's under a character's feet), and which cell it is. nil outside the map.
dp.key("Left") True while that key is held down (names as the key event gives them: "A", "7", "Space", "Enter", "Left", "LeftShift", "F5"...). For moving things smoothly in a frame handler.
print(...) Writes its values to the Output, separated by tabs.

Commands

Every command, by group: what it does, and every setting (needed ones must be given; the others have the default shown, or are left as they are).

draw

Drawing on the active layer of the active picture with colour numbers (a palette picture). Painting stays inside the selection if there is one. Each command is one undo step (a whole script run is one step too).

dp.draw.pixel{ } - Plot a pixel

Sets one pixel of the active layer to a colour number.

Changes things (one undo step). By name: "draw.pixel". For AI: draw_pixel.

Setting Type Needed / default Range or choices What it is
x whole number needed Across, from the left (0 is the first column).
y whole number needed Down, from the top.
colour whole number needed 0 to 255 The colour number to paint with (the picture's see-through colour, usually 0, paints empty: info.picture says which).

dp.draw.line{ } - Draw a line

A line between two points on the active layer (size: its thickness).

Changes things (one undo step). By name: "draw.line". For AI: draw_line.

Setting Type Needed / default Range or choices What it is
x0 whole number needed Start, across.
y0 whole number needed Start, down.
x1 whole number needed End, across.
y1 whole number needed End, down.
colour whole number needed 0 to 255 The colour number to paint with (the picture's see-through colour, usually 0, paints empty: info.picture says which).
size whole number 1 1 to 64 Line thickness in pixels (1: one pixel; more: a round pen that wide).
pen one of the choices round round, square The pen's shape for thicker lines.

dp.draw.rect{ } - Draw a rectangle

A rectangle on the active layer, outlined (size: the line's thickness, centred on the edge) or filled.

Changes things (one undo step). By name: "draw.rect". For AI: draw_rect.

Setting Type Needed / default Range or choices What it is
x whole number needed Left edge.
y whole number needed Top edge.
w whole number needed 1 to 1,000,000 Width in pixels.
h whole number needed 1 to 1,000,000 Height in pixels.
colour whole number needed 0 to 255 The colour number to paint with (the picture's see-through colour, usually 0, paints empty: info.picture says which).
filled true or false false Filled, or just the outline.
size whole number 1 1 to 64 Line thickness in pixels (1: one pixel; more: a round pen that wide).
pen one of the choices round round, square The pen's shape for thicker lines.

dp.draw.ellipse{ } - Draw an ellipse

An ellipse (or circle) on the active layer, outlined (size: the line's thickness, centred on the edge) or filled, by its centre and radii.

Changes things (one undo step). By name: "draw.ellipse". For AI: draw_ellipse.

Setting Type Needed / default Range or choices What it is
cx whole number needed Centre, across.
cy whole number needed Centre, down.
rx whole number needed 0 to 1,000,000 Radius across.
ry whole number - 0 to 1,000,000 Radius down (left out: the same as rx, a circle).
colour whole number needed 0 to 255 The colour number to paint with (the picture's see-through colour, usually 0, paints empty: info.picture says which).
filled true or false false Filled, or just the outline.
size whole number 1 1 to 64 Line thickness in pixels (1: one pixel; more: a round pen that wide).
pen one of the choices round round, square The pen's shape for thicker lines.

dp.draw.fill{ } - Flood fill

Fills the area of one colour around a point on the active layer (4-connected).

Changes things (one undo step). By name: "draw.fill". For AI: draw_fill.

Setting Type Needed / default Range or choices What it is
x whole number needed Where, across.
y whole number needed Where, down.
colour whole number needed 0 to 255 The colour number to paint with (the picture's see-through colour, usually 0, paints empty: info.picture says which).

palette

The palette: setting colours and making ramps.

dp.palette.set{ } - Set a palette colour

Changes one colour of the palette (every pixel using it changes).

Changes things (one undo step). By name: "palette.set". For AI: palette_set.

Setting Type Needed / default Range or choices What it is
index whole number needed 0 to 255 Which colour number.
colour colour (#RRGGBB) needed The new colour, like #FF8800.
set whole number 0 0 to 255 Which palette set (0: the main palette).

dp.palette.transparent{ } - See-through colour

Chooses which colour number is see-through: pixels in it show as empty (lower layers through them) and export as transparent. For a solid background colour, pick a colour you don't draw with.

Changes things (one undo step). By name: "palette.transparent". For AI: palette_transparent.

Setting Type Needed / default Range or choices What it is
index whole number needed 0 to 255 The colour number to be see-through.

dp.palette.ramp{ } - Make a colour ramp

Blends the colours between two palette entries smoothly from one to the other.

Changes things (one undo step). By name: "palette.ramp". For AI: palette_ramp.

Setting Type Needed / default Range or choices What it is
from whole number needed 0 to 255 The first colour number.
to whole number needed 0 to 255 The last colour number.
set whole number 0 0 to 255 Which palette set (0: the main palette).

dp.palette.lock{ } - Lock the palette to its colours

Locks the palette of the picture in front to the colours it was opened with (or a given number): colours can be changed but none added, pixels in any later colours become the nearest of them, and a true-colour picture paints in them and converts back to them. One undo step.

Changes things (one undo step). By name: "palette.lock". For AI: palette_lock.

Setting Type Needed / default Range or choices What it is
on true or false true Lock (true) or unlock (false).
colours whole number - 2 to 256 How many colours to keep to (left out: the colours the file was opened with, or all it has now).

layer

Layers: adding, choosing, showing, flipping, merging.

dp.layer.select{ } - Choose the active layer

Makes a layer the one painting goes to (0 is the bottom layer).

Changes things (one undo step). By name: "layer.select". For AI: layer_select.

Setting Type Needed / default Range or choices What it is
index whole number needed 0 to 255 The layer's number from the bottom (0 first).

dp.layer.show{ } - Show or hide a layer

Turns a layer's visibility on or off.

Changes things (one undo step). By name: "layer.show". For AI: layer_show.

Setting Type Needed / default Range or choices What it is
index whole number needed 0 to 255 The layer's number from the bottom (0 first).
visible true or false true Shown or hidden.

dp.layer.flip{ } - Flip the layer

Mirrors the active layer's pixels, across (x) or upside down (y), as one undo step.

Changes things (one undo step). By name: "layer.flip". For AI: layer_flip.

Setting Type Needed / default Range or choices What it is
axis one of the choices needed x, y x: mirror left to right; y: upside down.

dp.layer.add{ } - New layer

A new empty layer above the active one (it becomes active).

Changes things (one undo step). By name: "layer.add". For AI: layer_add.

Setting Type Needed / default Range or choices What it is
name text - Its name (left out: Layer 2, 3...).

dp.layer.duplicate() - Duplicate layer

A copy of the active layer above it.

Changes things (one undo step). By name: "layer.duplicate". For AI: layer_duplicate.

No settings.

dp.layer.delete() - Delete layer

Removes the active layer (a picture keeps at least one).

Changes things (one undo step). By name: "layer.delete". For AI: layer_delete.

No settings.

dp.layer.up() - Move layer up

Moves the active layer one place up the stack.

Changes things (one undo step). By name: "layer.up". For AI: layer_up.

No settings.

dp.layer.down() - Move layer down

Moves the active layer one place down the stack.

Changes things (one undo step). By name: "layer.down". For AI: layer_down.

No settings.

dp.layer.rename{ } - Rename layer

Gives a layer a new name.

Changes things (one undo step). By name: "layer.rename". For AI: layer_rename.

Setting Type Needed / default Range or choices What it is
name text needed The new name.
index whole number - 0 to 255 Which layer from the bottom (left out: the active one).

dp.layer.blend{ } - Layer blend and opacity

Sets how a layer's colours go onto the layers beneath: its blend mode and opacity. Palette pictures draw each mix in the nearest palette colour.

Changes things (one undo step). By name: "layer.blend". For AI: layer_blend.

Setting Type Needed / default Range or choices What it is
mode one of the choices - normal, multiply, screen, overlay, add, subtract, darken, lighten, difference The blend mode (left out: as it is).
opacity whole number - 0 to 100 How much shows, 0-100 percent (left out: as it is).
index whole number - 0 to 255 Which layer from the bottom (left out: the active one).

dp.layer.merge_down() - Merge down

Merges the active layer into the one below it.

Changes things (one undo step). By name: "layer.merge_down". For AI: layer_merge_down.

No settings.

frame

Animation frames.

dp.frame.duration{ } - Frame duration

How long the current frame (or every frame) shows, in milliseconds.

Changes things (one undo step). By name: "frame.duration". For AI: frame_duration.

Setting Type Needed / default Range or choices What it is
ms whole number needed 1 to 65,535 Milliseconds.
all true or false false Every frame, not just this one.

dp.frame.add{ } - New frame

Adds an animation frame after the current one and goes to it.

Changes things (one undo step). By name: "frame.add". For AI: frame_add.

Setting Type Needed / default Range or choices What it is
copy true or false true A copy of the current frame (or empty).

dp.frame.delete() - Delete frame

Removes the current frame (a picture keeps at least one).

Changes things (one undo step). By name: "frame.delete". For AI: frame_delete.

No settings.

dp.frame.go{ } - Go to a frame

Shows an animation frame (0 is the first).

Changes things (one undo step). By name: "frame.go". For AI: frame_go.

Setting Type Needed / default Range or choices What it is
index whole number needed 0 to 1,000,000 The frame.

select

Selections (painting then stays inside them).

dp.select.all() - Select all

Selects the whole picture (a map: every cell of the active layer).

Changes things (one undo step). By name: "select.all". For AI: select_all.

No settings.

dp.select.none() - Deselect

Removes the selection.

Changes things (one undo step). By name: "select.none". For AI: select_none.

No settings.

dp.select.invert() - Invert selection

Selects what wasn't, and deselects what was.

Changes things (one undo step). By name: "select.invert". For AI: select_invert.

No settings.

dp.select.wand{ } - Magic wand

Selects the pixel at x, y and the pixels like it: those joined to it (side by side), or that colour anywhere with joined = false. Palette pictures compare colour numbers (with a tolerance, the colours themselves). On a map, x and y are a cell of the active layer, and the cells showing the same tile are selected.

Changes things (one undo step). By name: "select.wand". For AI: select_wand.

Setting Type Needed / default Range or choices What it is
x whole number needed Across.
y whole number needed Down.
tolerance whole number - 0 to 100 How different a colour may be and still count, 0-100% (0: the same colour only).
joined true or false - Only pixels joined to this one (false: that colour anywhere).
layers one of the choices - active, visible Look at the active layer, or at what shows (the visible layers together).
mode one of the choices replace replace, add, take away, overlap Replace the selection, add to it, or take away from it.

dp.select.tile{ } - Select every cell of a tile

A map: selects every cell of the active layer showing this tile (flips and palette bank as given).

Changes things (one undo step). By name: "select.tile". For AI: select_tile.

Setting Type Needed / default Range or choices What it is
tile whole number needed 0 to ... The tile number.
flipx true or false false Mirrored across.
flipy true or false false Mirrored down.
palette whole number 0 0 to 255 Palette bank.
mode one of the choices replace replace, add, take away, overlap Replace the selection, add to it, or take away from it.

dp.select.fill() - Fill the selection

Pictures: paints the selection in the left colour (with the paint mode). Maps: fills the selected cells with the tile brush, tiled from the selection's top-left corner.

Changes things (one undo step). By name: "select.fill". For AI: select_fill.

No settings.

dp.select.colour{ } - Select by colour

Selects every pixel of a colour: a colour number (palette pictures) or a colour (true colour), with the magic wand's tolerance and layers.

Changes things (one undo step). By name: "select.colour". For AI: select_colour.

Setting Type Needed / default Range or choices What it is
index whole number - 0 to 255 The colour number (palette pictures).
colour colour (#RRGGBB) - The colour (true-colour pictures).
tolerance whole number - 0 to 100 How different a colour may be and still count, 0-100% (0: the same colour only).
layers one of the choices - active, visible Look at the active layer, or at what shows (the visible layers together).
mode one of the choices replace replace, add, take away, overlap Replace the selection, add to it, or take away from it.

dp.select.rect{ } - Select a rectangle

Selects a rectangle of the picture (painting then stays inside it); on a map, of the active layer's cells.

Changes things (one undo step). By name: "select.rect". For AI: select_rect.

Setting Type Needed / default Range or choices What it is
x whole number needed Left edge.
y whole number needed Top edge.
w whole number needed 1 to 1,000,000 Width.
h whole number needed 1 to 1,000,000 Height.

filter

Filters and adjustments, as in the Image menu.

dp.filter.apply{ } - Apply a filter

Runs a filter or colour adjustment on the active layer (inside the selection if there is one), as one undo step. Settings left out keep the filter's own defaults.

Changes things (one undo step). By name: "filter.apply". For AI: filter_apply.

Setting Type Needed / default Range or choices What it is
name one of the choices needed Brightness and contrast, Hue and saturation, Colour balance, Posterise, Invert, Greyscale, Fit to palette, Blur, Sharpen, Noise, Pixelate, Outline, Drop shadow, Clean up stray pixels, Grow or shrink, Levels, Curves, Replace colours, Gradient map, Rotate, Auto-shade, Exposure, White balance, Vibrance, Shadows and highlights, Split toning, Sepia, Duotone, Selective colour, Colour look-up (.cube), Film grain, Vignette, Smooth, keeping edges, Oil paint, Toon (cel shading), Pencil or ink sketch, Halftone, Posterise smoothly, Punch for few colours Which filter (info.filters lists them with what a, b, c, n... mean for each).
a number - The filter's first setting (see info.filters).
b number - Its second setting.
c number - Its third setting.
n whole number - Its whole-number setting (levels, size, radius...).
colour whole number - 0 to 255 A colour number it uses (outline, shadow).
dx whole number - Shadow offset across.
dy whole number - Shadow offset down.
flag true or false - Its on/off setting (see info.filters).
dither one of the choices - None, Ordered, Floyd-Steinberg, Atkinson Palette pictures: how the result is fitted back to the palette.
mix number 100 0 to 100 How much of the result: 0 none to 100 all.
to one of the choices pixels pixels, palette Colour adjustments on a palette picture: change the pixels (fitted back), or just the palette's colours.
settings text - Everything at once as the Image menu's recipes save it (overrides the rest).

What each filter's settings mean:

Filter (name) Its settings
Brightness and contrast a = brightness -100..100, b = contrast -100..100, c = gamma 0.2..5.
Hue and saturation a = hue -180..180, b = saturation -100..100, c = lightness -100..100.
Colour balance a, b, c = red, green, blue -100..100.
Posterise n = levels per channel 2..16.
Invert no settings.
Greyscale no settings.
Fit to palette fits to the palette with dithering (dither); nothing else changes.
Blur a = radius 0.5..16.
Sharpen a = amount 0..300 (%), b = radius 0.5..8.
Noise a = amount 0..100, flag = the same noise on every channel.
Pixelate n = the size of the big pixels, 2..64.
Outline colour, n = thickness 1..8, flag = inside the shapes.
Drop shadow colour, dx, dy = offset.
Clean up stray pixels removes stray pixels: n = 0 lone pixels only, 1 also thin spurs.
Grow or shrink n = -8..8 (grow by n, or shrink by -n).
Levels use settings= (channel, inblack, inwhite, c = gamma, outblack, outwhite).
Curves use settings= (channel, curve = x,y pairs 0..255).
Replace colours use settings= (replace = from,to pairs); a = tolerance 0..100.
Gradient map brightness onto colours first..last (settings=); flag = reversed.
Rotate a = degrees; flag = clean (pixel-art rotation).
Auto-shade a = light from (degrees, 0 right, 90 above), b = strength, n = edge width; flag = lighten and darken.
Exposure a = stops -4..4.
White balance a = temperature -100 (cooler)..100 (warmer), b = tint -100 (green)..100 (magenta).
Vibrance a = vibrance -100..100, b = saturation -100..100.
Shadows and highlights a = lift shadows 0..100, b = recover highlights 0..100.
Split toning a = shadow hue, b = highlight hue (degrees), c = balance -100..100, n = strength 0..100.
Sepia a = strength 0..100.
Duotone a = dark hue, b = light hue (degrees), c = saturation 0..100.
Selective colour n = 0 reds, 1 yellows, 2 greens, 3 cyans, 4 blues, 5 magentas; a = hue shift -60..60, b = saturation, c = lightness -100..100.
Colour look-up (.cube) a .cube file (settings= with lut=path); a = strength 0..100.
Film grain a = amount 0..100, b = size 1..8, flag = colour grain.
Vignette a = -100..100 (darken edges if above 0), b = clear middle 0..100, c = softness 0..100.
Smooth, keeping edges n = radius 1..6, a = edge threshold 5..100.
Oil paint n = radius 1..8.
Toon (cel shading) n = tones 2..8, a = outlines 0..100, b = outline darkness 0..100, c = smoothing 0..4.
Pencil or ink sketch n = 0 pencil, 1 ink; a = radius 1..16, b = darkness or detail 0..100; flag = keep the colours.
Halftone n = 0 dots, 1 lines; a = cell size 3..32, b = angle 0..90; flag = colour.
Posterise smoothly n = levels 2..16, c = smoothing radius 0..6, a = edge threshold 5..100; flag = remove specks.
Punch for few colours a = contrast 0..100, b = saturation 0..100, c = de-mud 0..100.

info

Questions: they change nothing and answer as text.

dp.info.filters() - List the filters

Each filter's name and what its settings mean (for filter.apply).

A question: changes nothing. Not written into recorded macros. By name: "info.filters". For AI: info_filters.

No settings.

For example, it answered:

Brightness and contrast: a = brightness -100..100, b = contrast -100..100, c = gamma 0.2..5.
Hue and saturation: a = hue -180..180, b = saturation -100..100, c = lightness -100..100.
Colour balance: a, b, c = red, green, blue -100..100.
Posterise: n = levels per channel 2..16.
Invert: no settings.
Greyscale: no settings.
Fit to palette: fits to the palette with dithering (dither); nothing else changes.
Blur: a = radius 0.5..16.
Sharpen: a = amount 0..300 (%), b = radius 0.5..8.
Noise: a = amount 0..100, flag = the same noise on every channel.
Pixelate: n = the size of the big pixels, 2..64.
Outline: colour, n = thickness 1..8, flag = inside the shapes.
Drop shadow: colour, dx, dy = offset.
Clean up stray pixels: removes stray pixels: n = 0 lone pixels only, 1 also thin spurs.
Grow or shrink: n = -8..8 (grow by n, or shrink by -n).
Levels: use settings= (channel, inblack, inwhite, c = gamma, outblack, outwhite).
Curves: use settings= (channel, curve = x,y pairs 0..255).
Replace colours: use settings= (replace = from,to pairs); a = tolerance 0..100.
Gradient map: brightness onto colours first..last (settings=); flag = reversed.
Rotate: a = degrees; flag = clean (pixel-art rotation).
Auto-shade: a = light from (degrees, 0 right, 90 above), b = strength, n = edge width; flag = lighten and darken.
Exposure: a = stops -4..4.
White balance: a = temperature -100 (cooler)..100 (warmer), b = tint -100 (green)..100 (magenta).
Vibrance: a = vibrance -100..100, b = saturation -100..100.
Shadows and highlights: a = lift shadows 0..100, b = recover highlights 0..100.
Split toning: a = shadow hue, b = highlight hue (degrees), c = balance -100..100, n = strength 0..100.
Sepia: a = strength 0..100.
Duotone: a = dark hue, b = light hue (degrees), c = saturation 0..100.
Selective colour: n = 0 reds, 1 yellows, 2 greens, 3 cyans, 4 blues, 5 magentas; a = hue shift -60..60, b = saturation, c = lightness -100..100.
Colour look-up (.cube): a .cube file (settings= with lut=path); a = strength 0..100.
Film grain: a = amount 0..100, b = size 1..8, flag = colour grain.
Vignette: a = -100..100 (darken edges if above 0), b = clear middle 0..100, c = softness 0..100.
Smooth, keeping edges: n = radius 1..6, a = edge threshold 5..100.
Oil paint: n = radius 1..8.
Toon (cel shading): n = tones 2..8, a = outlines 0..100, b = outline darkness 0..100, c = smoothing 0..4.
Pencil or ink sketch: n = 0 pencil, 1 ink; a = radius 1..16, b = darkness or detail 0..100; flag = keep the colours.
Halftone: n = 0 dots, 1 lines; a = cell size 3..32, b = angle 0..90; flag = colour.
Posterise smoothly: n = levels 2..16, c = smoothing radius 0..6, a = edge threshold 5..100; flag = remove specks.
Punch for few colours: a = contrast 0..100, b = saturation 0..100, c = de-mud 0..100.

dp.info.picture() - About the picture

Its size, colours, layers, frames and active layer.

A question: changes nothing. Not written into recorded macros. By name: "info.picture". For AI: info_picture.

No settings.

For example, it answered:

320 x 200, 256 colours, 1 layers (active 0: Background), 1 frames (current 0), system Generic, mode 320 x 200, 256 colours. Colour 0 is the see-through (transparent) colour: pixels in it show as empty and export as transparent (palette.transparent picks another).

dp.info.palette{ } - List the palette

Every colour number and its colour, as #RRGGBB.

A question: changes nothing. Not written into recorded macros. By name: "info.palette". For AI: info_palette.

Setting Type Needed / default Range or choices What it is
set whole number 0 0 to 255 Which palette set (0: the main palette).

For example, it answered:

0=#000000 1=#000055 2=#0000AA 3=#0000FF 4=#002400 5=#002455 6=#0024AA 7=#0024FF 8=#004900 9=#004955 10=#0049AA 11=#0049FF 12=#006D00 13=#006D55 14=#006DAA 15=#006DFF 16=#009200 17=#009255 18=#0092AA 19=#0092FF 20=#00B600 21=#00B655 22=#00B6AA 23=#00B6FF 24=#00DB00 25=#00DB55 26=#00DBAA 27=#00DBFF 28=#00FF00 29=#00FF55 30=#00FFAA 31=#00FFFF 32=#240000 33=#240055 34=#2400AA 35=#2400FF 36=#242400 37=#242455 38=#2424AA 39=#2424FF 40=#244900 41=#244955 42=#2449AA 43=#2449FF 44=#246D00 45=#246D55 46=#246DAA 47=#246DFF 48=#249200 49=#249255 50=#2492AA 51=#2492FF 52=#24B600 53=#24B655 54=#24B6AA ...

dp.info.layers() - List the layers

Each layer from the bottom: number, name, shown or hidden, locked.

A question: changes nothing. Not written into recorded macros. By name: "info.layers". For AI: info_layers.

No settings.

For example, it answered:

0: Background (active)

dp.info.commands() - List the commands

Every command's id and title (the command palette shows them with their settings).

A question: changes nothing. Not written into recorded macros. By name: "info.commands". For AI: info_commands.

No settings.

For example, it answered:

draw.pixel (Plot a pixel); draw.line (Draw a line); draw.rect (Draw a rectangle); draw.ellipse (Draw an ellipse); draw.fill (Flood fill); palette.set (Set a palette colour); palette.transparent (See-through colour); palette.ramp (Make a colour ramp); layer.select (Choose the active layer); layer.show (Show or hide a layer); frame.duration (Frame duration); filter.apply (Apply a filter); info.filters (List the filters); layer.flip (Flip the layer); info.picture (About the picture); info.palette (List the palette); info.layers (List the layers); file.new (New picture); file.open (Open a file); ...

map

Tile maps (a map open in the active tab): cells, tiles and attributes.

dp.map.info() - About the map

The map's size in cells, its tile size, how many tiles its tile set has, and its layers.

A question: changes nothing. Not written into recorded macros. By name: "map.info". For AI: map_info.

No settings.

dp.map.cell{ } - What's in a cell

A cell of the active map layer: its tile (-1 empty), mirrors, turn, palette bank and attributes.

A question: changes nothing. Not written into recorded macros. By name: "map.cell". For AI: map_cell.

Setting Type Needed / default Range or choices What it is
x whole number needed 0 to 65,535 Column.
y whole number needed 0 to 65,535 Row.

dp.map.set{ } - Set map cells

Puts a tile in a cell, or in every cell of a rectangle, of the active map layer (one undo step; with cells selected, only those change).

Changes things (one undo step). By name: "map.set". For AI: map_set.

Setting Type Needed / default Range or choices What it is
x whole number needed 0 to 65,535 Column.
y whole number needed 0 to 65,535 Row.
w whole number 1 1 to 65,535 Columns wide.
h whole number 1 1 to 65,535 Rows high.
tile whole number - -1 to 1,048,574 The tile number (-1: empty).
flipx true or false false Mirrored across.
flipy true or false false Upside down.
rotate true or false false Turned 90 degrees clockwise.
palette whole number 0 0 to 255 Palette bank.
attr whole number - 0 to 255 Attributes (left out: the cells keep theirs).
random true or false false Instead of tile: each cell one of the random tiles (tiles.random), by weight.
rule text - Instead of tile: an automapping rule tile (empty, ignore, nonempty, other, negate), for rules maps.
stamp text - Instead of tile: a saved stamp (tiles.stamp_save), its top-left at x, y (w and h: placed across that many cells, each placement one of its variations).

dp.map.attr{ } - Set cell attributes

Sets the attributes (the game's own bits) of a cell, or a rectangle of cells, leaving their tiles.

Changes things (one undo step). By name: "map.attr". For AI: map_attr.

Setting Type Needed / default Range or choices What it is
x whole number needed 0 to 65,535 Column.
y whole number needed 0 to 65,535 Row.
w whole number 1 1 to 65,535 Columns wide.
h whole number 1 1 to 65,535 Rows high.
value whole number needed 0 to 255 The attributes, 0-255.

dp.map.save_tiled{ } - Save as a Tiled map

Writes the map in front as a Tiled map (mapeditor.org): .tmx (XML) or .tmj (JSON), by the file's extension, with each layer's tile set as a PNG beside it. (file.open opens Tiled maps.)

Changes things (one undo step). By name: "map.save_tiled". For AI: map_save_tiled.

Setting Type Needed / default Range or choices What it is
path file path needed The .tmx or .tmj file.

dp.map.tile_art{ } - Tall tiles

Sets the size of the active layer's tiles in its tile set, when it isn't the cell's: bigger makes tall tiles (walls, trees, buildings on an isometric map), each drawn whole from its cell's bottom-left, overhanging up (and right), the ones lower down in front, as Tiled draws them. The cell's own size: ordinary tiles.

Changes things (one undo step). By name: "map.tile_art". For AI: map_tile_art.

Setting Type Needed / default Range or choices What it is
w whole number needed 1 to 1,024 The tiles' width in the tile set.
h whole number needed 1 to 1,024 Their height.
layer whole number - 0 to 255 The map layer (from 0; left out: the active one).

dp.map.layer{ } - Map layer settings

Sets a map layer's scroll (the layer pixel shown at the map's top-left) and whether it repeats in every direction (wrap: any shape; isometric maps repeat along their diamond's sides, staggered and hex maps seamlessly with an even number of shifted rows or columns). What's left out stays as it is.

Changes things (one undo step). By name: "map.layer". For AI: map_layer.

Setting Type Needed / default Range or choices What it is
layer whole number - 0 to 255 The map layer (from 0; left out: the active one).
wrap true or false - Repeat in every direction.
scroll_x whole number - -1,000,000 to 1,000,000 Its scroll across.
scroll_y whole number - -1,000,000 to 1,000,000 Its scroll down.
name text - Its name.

dp.map.layer_add{ } - New map layer

A new, empty tile layer above the active one (the same size and tiles), which becomes the active layer.

Changes things (one undo step). By name: "map.layer_add". For AI: map_layer_add.

Setting Type Needed / default Range or choices What it is
name text - Its name (left out: Layer n).

dp.map.save_godot{ } - Save as a Godot scene

Writes the map in front as a Godot 4 scene (.tscn, Godot 4.3 or later): a TileMapLayer for each layer, each with a TileSet of its tile set picture (a PNG beside the scene), in the map's shape (square, isometric, staggered, hexagonal or brick as Godot's half-offset squares), with the flips and turns. Put the scene and its PNGs in the Godot project's folder res ("" its top). (file.open opens Godot 4 scenes: their TileMapLayer and TileMap nodes.)

Changes things (one undo step). By name: "map.save_godot". For AI: map_save_godot.

Setting Type Needed / default Range or choices What it is
path file path needed The .tscn file.
res text - The folder in the Godot project, e.g. levels/ (left out: the top).

dp.map.shape{ } - Map shape

Makes the map in front square, isometric, staggered or hexagonal (all its layers; the cells stay as they are, drawn in the new shape). As Tiled's orientations, stagger axis and index, and hex side length.

Changes things (one undo step). By name: "map.shape". For AI: map_shape.

Setting Type Needed / default Range or choices What it is
shape one of the choices - square, isometric, staggered, hexagonal, brick square, isometric (diamonds), staggered (isometric, every other row or column shifted), hexagonal, or brick (rectangles, every other row or column shifted half: a hex map whose straight edges are the whole tile) (left out: as it is, or square).
stagger one of the choices - rows, columns Staggered and hexagonal: rows shifted (pointy-topped hexes) or columns (flat-topped).
shifted one of the choices - odd, even Staggered and hexagonal: the odd rows (or columns) shifted, or the even ones.
hex_side whole number - 0 to 256 Hexagonal: the straight edge's length in pixels (left out: as it is, or half the tile).

dp.map.new{ } - New tile map

A new, empty tile map painted from the active picture's tiles - or, on a map, from that map's tile set (several maps can share one tile set).

Changes things (one undo step). By name: "map.new". For AI: map_new.

Setting Type Needed / default Range or choices What it is
width whole number 32 1 to 65,535 Columns (tiles across).
height whole number 24 1 to 65,535 Rows (tiles down).
tile_w whole number - 1 to 256 Tile width in pixels (left out: the map's, or the picture's grid).
tile_h whole number - 1 to 256 Tile height in pixels (left out: the map's, or the picture's grid).
shape one of the choices - square, isometric, staggered, hexagonal, brick square, isometric (diamonds), staggered (isometric, every other row or column shifted), hexagonal, or brick (rectangles, every other row or column shifted half: a hex map whose straight edges are the whole tile) (left out: as it is, or square).
stagger one of the choices - rows, columns Staggered and hexagonal: rows shifted (pointy-topped hexes) or columns (flat-topped).
shifted one of the choices - odd, even Staggered and hexagonal: the odd rows (or columns) shifted, or the even ones.
hex_side whole number - 0 to 256 Hexagonal: the straight edge's length in pixels (left out: as it is, or half the tile).

dp.map.object_layer{ } - New object layer

A new object layer above the active map layer (it becomes the active one): game objects - rectangles, ellipses, points, polygons, polylines, tiles placed anywhere, text - with names, classes and properties, shown over the map and saved with it (and in Tiled's maps as an object group).

Changes things (one undo step). By name: "map.object_layer". For AI: map_object_layer.

Setting Type Needed / default Range or choices What it is
name text - Its name (left out: Objects 1, 2...).
colour colour (#RRGGBB) - The colour its objects are drawn in.

dp.map.image_layer{ } - New image layer

A new image layer above the active map layer: one picture (an open one by its name, or a file, which is opened) as a background or foreground, in its own colours, scrolling, linking and repeating (across, down or both) as any layer does; Tiled's image layers.

Changes things (one undo step). By name: "map.image_layer". For AI: map_image_layer.

Setting Type Needed / default Range or choices What it is
picture text - An open picture's name (as on its tab).
path file path - Or a picture file to open.
repeat_x true or false false Repeat it across.
repeat_y true or false false Repeat it down.
name text - The layer's name (left out: the picture's).

dp.map.object_go{ } - Go to an object

Chooses an object (its layer becomes the active one) and brings it into view.

Changes things (one undo step). By name: "map.object_go". For AI: map_object_go.

Setting Type Needed / default Range or choices What it is
id whole number needed 1 to ... The object.

dp.map.template_make{ } - Make a template

Keeps an object as a template of the map: copies of it can be placed anywhere (map.template_place), and changing the template (map.template_set) changes every copy except what a copy has changed itself. The object becomes its first copy. Returns the template's id.

Changes things (one undo step). By name: "map.template_make". For AI: map_template_make.

Setting Type Needed / default Range or choices What it is
object whole number needed 1 to ... The object.
name text - The template's name (left out: the object's).

dp.map.template_place{ } - Place a template's copy

Places a copy of a template on an object layer: it follows the template in everything but its place (and what it's given its own: map.object). Returns its id.

Changes things (one undo step). By name: "map.template_place". For AI: map_template_place.

Setting Type Needed / default Range or choices What it is
template whole number needed 1 to ... The template.
x number 0 Across, in the layer's pixels.
y number 0 Down (a tile's: its bottom-left).
layer whole number - 0 to 255 The object layer (from 0; left out: the active one).

dp.map.template_set{ } - Change a template

Changes a template, and with it every copy (except what a copy has changed itself). What's left out stays as it is.

Changes things (one undo step). By name: "map.template_set". For AI: map_template_set.

Setting Type Needed / default Range or choices What it is
template whole number needed 1 to ... The template.
name text - Its name.
class text - Its class.
w number - 0 to 10,000,000 Width.
h number - 0 to 10,000,000 Height.
rotation number - -360 to 360 Turned, degrees.
tile whole number - 0 to ... Tile templates: the tile.
text text - Text templates: the words.
visible true or false - Shown.

dp.map.templates() - List templates

The map's object templates: a line each, id shape "name" class, size, and how many copies.

A question: changes nothing. Not written into recorded macros. By name: "map.templates". For AI: map_templates.

No settings.

dp.map.object_reset{ } - Back to the template

A template's copy as the template is again: what it changed is dropped (its place kept).

Changes things (one undo step). By name: "map.object_reset". For AI: map_object_reset.

Setting Type Needed / default Range or choices What it is
id whole number needed 1 to ... The copy.

dp.map.object_detach{ } - Cut loose from the template

A template's copy made an ordinary object with what it has now: the template no longer changes it.

Changes things (one undo step). By name: "map.object_detach". For AI: map_object_detach.

Setting Type Needed / default Range or choices What it is
id whole number needed 1 to ... The copy.

dp.map.object{ } - Place or change an object

Places an object on an object layer, or changes one (give its id): what's left out stays as it is (a new one: a rectangle one cell big). Places are the layer's pixels: a rectangle's, ellipse's or text's top-left, a point itself, a polygon's first point (its points from there), a tile object's bottom-left. Returns its id.

Changes things (one undo step). By name: "map.object". For AI: map_object.

Setting Type Needed / default Range or choices What it is
id whole number - 1 to ... The object to change (left out: a new one).
layer whole number - 0 to 255 A new one's layer (from 0; left out: the active one).
shape one of the choices - rectangle, ellipse, point, polygon, polyline, tile, text rectangle, ellipse, point, polygon, polyline, tile or text (a new object; left out: a rectangle).
name text - Its name.
class text - Its class (what kind of thing: Enemy, Door...).
x number - Across, in the layer's pixels.
y number - Down.
w number - 0 to 10,000,000 Width.
h number - 0 to 10,000,000 Height.
rotation number - -360 to 360 Turned clockwise, degrees.
points text - Polygons and polylines: "x,y x,y ..." from its place.
tile whole number - 0 to ... Tile objects: the tile's number.
flip_x true or false - Tile objects: mirrored.
flip_y true or false - Tile objects: upside down.
text text - Text objects: the words.
size whole number - 4 to 512 Text objects: pixels high.
visible true or false - Shown.

dp.map.object_delete{ } - Delete an object

Removes an object from its object layer.

Changes things (one undo step). By name: "map.object_delete". For AI: map_object_delete.

Setting Type Needed / default Range or choices What it is
id whole number needed 1 to ... The object.

dp.map.objects{ } - List objects

The objects on an object layer (or every one): a line each, id shape "name" class at x,y size w x h (and its tile, points or text).

A question: changes nothing. Not written into recorded macros. By name: "map.objects". For AI: map_objects.

Setting Type Needed / default Range or choices What it is
layer whole number - 0 to 255 The layer (from 0; left out: every object layer).

dp.map.collision_layer{ } - Make the map's collision an object layer

The active layer's collision, as an object layer above it: solid full cells merged into as few rectangles as can be, every other cell's shapes as they lie (turned and mirrored as the cell). For games that read a map's collision as objects (Tiled maps, Unity through SuperTiled2Unity). Square maps.

Changes things (one undo step). By name: "map.collision_layer". For AI: map_collision_layer.

Setting Type Needed / default Range or choices What it is
name text Collision The object layer's name.

dp.map.automap{ } - Automap

Applies a rules map (another open map, by its name as on its tab) to the map in front, as Tiled's automapping: its layers input<layer>, inputnot<layer>, output<layer> and output1 ... (one chosen at random), each rule a group of touching cells, the rule tiles Empty, Ignore, NonEmpty, Other, Negate; its properties MatchOutsideMap, WrapBorder, OverflowBorder, MatchInOrder, ModX, ModY, OffsetX, OffsetY, Probability, Disabled; a rule_options object layer. One undo step. x, y, w, h: only rules touching that box of cells (as while drawing). It becomes the rules map the Tiles panel uses.

Changes things (one undo step). By name: "map.automap". For AI: map_automap.

Setting Type Needed / default Range or choices What it is
rules text - The rules map's name (left out: the one chosen in the Tiles panel).
x whole number - -65,536 to 65,536 The box's left cell.
y whole number - -65,536 to 65,536 Its top cell.
w whole number - 1 to 65,536 Its width in cells.
h whole number - 1 to 65,536 Its height in cells.
while_drawing true or false - Also apply the rules after every stroke on this map from now on.

dp.map.brush{ } - Pick up a map brush

Makes the map brush the cells of a rectangle of the active map layer, as a right-drag on the map picks them up (then tiles.stamp_save keeps it, or tiles.quick_stamp).

Changes things (one undo step). By name: "map.brush". For AI: map_brush.

Setting Type Needed / default Range or choices What it is
x whole number needed 0 to 65,535 Column.
y whole number needed 0 to 65,535 Row.
w whole number 1 1 to 256 Columns wide.
h whole number 1 1 to 256 Rows high.

dp.map.layer_lock{ } - Lock a map layer

Locks a map layer (or unlocks it), so painting, filling, map.set and automapping leave it alone (Tiled's layer lock); saved with the map.

Changes things (one undo step). By name: "map.layer_lock". For AI: map_layer_lock.

Setting Type Needed / default Range or choices What it is
layer whole number - 0 to 15 The layer (left out: the active one).
locked true or false true Locked (true) or not.

dp.map.focus{ } - Focus on the layer

Dims the map's other layers so the active one stands out (Ctrl+H on a map), or shows them as they are.

Changes things (one undo step). By name: "map.focus". For AI: map_focus.

Setting Type Needed / default Range or choices What it is
on true or false true Focus (true) or not.

dp.map.save_image{ } - Save the map as an image

Saves the map in front, or part of it, as a PNG of any size, as it's drawn: every chosen layer at its scroll (or laid flat), scaled up if asked; in the map's palette when one palette holds it, otherwise in full colour (layers in their own colours, see-through layers, palette banks past 255). It's written before the command returns.

Changes things (one undo step). By name: "map.save_image". For AI: map_save_image.

Setting Type Needed / default Range or choices What it is
path file path needed The PNG file.
x whole number - 0 to 65,535 The part's first column (the active layer's cells). Left out, with y, w and h: the whole map.
y whole number - 0 to 65,535 Its first row.
w whole number - 1 to 65,535 Columns.
h whole number - 1 to 65,535 Rows.
selected true or false false Only the selected cells (their rectangle; others left empty).
layers text - "visible" (the default), "active", or layer numbers like "0, 2".
scale whole number 1 1 to 8 Each pixel as a block this big.
flat true or false false Laid flat: the layers unscrolled (no scroll, links, column scroll or copper).
view true or false false What the view in front shows of the map.
anim_ms whole number 0 0 to ... Animated tiles: the frame they show this many milliseconds in.
empty text - Empty places: "clear" (see-through, the default) or "colour" (the see-through colour, solid).
colours text - "auto" (a palette PNG when one palette holds it; the default) or "full" (always RGBA).
objects true or false false Draw the chosen object layers' objects over it.
grid true or false false Draw the active layer's cell grid over it.
numbers true or false false Draw the active layer's tile numbers over it.
collisions true or false false Draw the active layer's tiles' collision shapes over it.

dp.map.to_picture{ } - Map to picture

Makes a new picture of the map in front, or part of it, as it's drawn (Tiles > Map to picture): up to 4,096 x 4,096 pixels, in the map's palette (maps one palette can't hold: map.save_image).

Changes things (one undo step). By name: "map.to_picture". For AI: map_to_picture.

Setting Type Needed / default Range or choices What it is
x whole number - 0 to 65,535 The part's first column (the active layer's cells). Left out, with y, w and h: the whole map.
y whole number - 0 to 65,535 Its first row.
w whole number - 1 to 65,535 Columns.
h whole number - 1 to 65,535 Rows.
selected true or false false Only the selected cells (their rectangle; others left empty).
layers text - "visible" (the default), "active", or layer numbers like "0, 2".
scale whole number 1 1 to 8 Each pixel as a block this big.
flat true or false false Laid flat: the layers unscrolled (no scroll, links, column scroll or copper).
view true or false false What the view in front shows of the map.
anim_ms whole number 0 0 to ... Animated tiles: the frame they show this many milliseconds in.

edit

Undo, redo and deleting the selection.

dp.edit.undo() - Undo

Undoes the last change to the active picture.

Changes things (one undo step). By name: "edit.undo". For AI: edit_undo.

No settings.

dp.edit.redo() - Redo

Does again what was last undone.

Changes things (one undo step). By name: "edit.redo". For AI: edit_redo.

No settings.

dp.edit.delete() - Delete selection

Clears the selected part of the active layer to the transparent colour (a map: empties the selected cells).

Changes things (one undo step). By name: "edit.delete". For AI: edit_delete.

No settings.

colour

The colours the mouse buttons paint with.

dp.colour.left{ } - Left colour

The colour the left mouse button paints with.

Changes things (one undo step). By name: "colour.left". For AI: colour_left.

Setting Type Needed / default Range or choices What it is
index whole number needed 0 to 255 Colour number.

dp.colour.right{ } - Right colour

The colour the right mouse button paints with.

Changes things (one undo step). By name: "colour.right". For AI: colour_right.

Setting Type Needed / default Range or choices What it is
index whole number needed 0 to 255 Colour number.

tool

The painting tool.

dp.tool.set{ } - Choose a tool

The tool the mouse paints with.

Changes things (one undo step). By name: "tool.set". For AI: tool_set.

Setting Type Needed / default Range or choices What it is
name one of the choices needed Freehand, Dotted freehand, Airbrush, Line, Curve, Rectangle, Filled rectangle, Circle, Filled circle, Ellipse, Filled ellipse, Polygon, Filled polygon, Fill, Pick colour, Pick up brush, Select rectangle, Select lasso, Text, Pointer, Magic wand The tool.

brush

The brush.

dp.brush.set{ } - Built-in brush

The brush the mouse's tools (and paint.stroke) paint with: round or square, of a size (the custom brush is put aside). draw.line, draw.rect and draw.ellipse don't use it: give them size for a thicker line.

Changes things (one undo step). By name: "brush.set". For AI: brush_set.

Setting Type Needed / default Range or choices What it is
shape one of the choices - round, square A built-in brush's shape (left out: the brush as it is).
size whole number - 1 to 64 Its size in pixels.

paint

Painting as the mouse does (recorded strokes).

dp.paint.stroke{ } - Paint a stroke

Does what dragging the mouse through these points with a tool does (a recorded stroke).

Changes things (one undo step). By name: "paint.stroke". For AI: paint_stroke.

Setting Type Needed / default Range or choices What it is
tool one of the choices needed Freehand, Dotted freehand, Airbrush, Line, Rectangle, Filled rectangle, Circle, Filled circle, Ellipse, Filled ellipse, Fill, Pick up brush, Select rectangle, Select lasso The tool, as the mouse would use it (its current settings: paint mode, brush...).
button one of the choices left left, right The mouse button (left paints with the left colour).
points text needed The points: "x,y x,y ..." (the first is where the button went down).
colour whole number - 0 to 255 The colour number to paint with (left out: the button's colour as it is).
size whole number - 1 to 64 A built-in brush's size (left out: the brush as it is).
shape one of the choices - round, square A built-in brush's shape (left out: the brush as it is).
perfect true or false - Freehand with the 1-pixel brush: take out the doubled corners (pixel-perfect; left out: as the Tools panel has it).

dp.paint.wrap{ } - Wrap around

Turns wrap-around on or off for the picture: it repeats in every direction on the canvas, and paint going over an edge (strokes, lines, shapes, paint.stroke) comes in at the other side, for seamless textures and tiles. Kept with the picture.

Changes things (one undo step). By name: "paint.wrap". For AI: paint_wrap.

Setting Type Needed / default Range or choices What it is
on true or false true On or off.

file

Files: new pictures, opening, saving, exporting.

dp.file.new{ } - New picture

A new picture of any size, in the palette of the system last used.

Changes things (one undo step). By name: "file.new". For AI: file_new.

Setting Type Needed / default Range or choices What it is
width whole number 320 1 to 4,096 Width in pixels.
height whole number 200 1 to 4,096 Height in pixels.
colours whole number 256 2 to 256 Colours in its palette.

dp.file.open{ } - Open a file

Opens a picture, project, map or 3D model (anything File > Open opens).

Changes things (one undo step). By name: "file.open". For AI: file_open.

Setting Type Needed / default Range or choices What it is
path file path needed The file.

dp.file.save{ } - Save

Saves the active picture as a .dpx project (to path, or where it was last saved).

Changes things (one undo step). By name: "file.save". For AI: file_save.

Setting Type Needed / default Range or choices What it is
path file path - Where (a .dpx file). Left out: where it was saved before.

dp.file.export{ } - Export

Writes the active picture (or map) in one of the Export window's formats.

Changes things (one undo step). By name: "file.export". For AI: file_export.

Setting Type Needed / default Range or choices What it is
format text needed The format's name as the Export window lists it, e.g. "PNG" or "IFF ILBM" (any case; enough of it to be unique).
path file path needed The file to write.

macro

Recording and playing macros.

dp.macro.record() - Record a macro

Starts writing down every command run (from the menus, keys, the command palette...) until macro.stop.

Changes things (one undo step). Not written into recorded macros. By name: "macro.record". For AI: macro_record.

No settings.

dp.macro.stop{ } - Stop recording

Stops recording, and saves the macro to path if given (else asks where).

Changes things (one undo step). Not written into recorded macros. By name: "macro.stop". For AI: macro_stop.

Setting Type Needed / default Range or choices What it is
path file path - Where to save it: a .dpmacro file, or a .luau script doing the same.

dp.macro.play{ } - Play a macro

Runs the commands in a macro file, in order, stopping at the first that fails.

Changes things (one undo step). Not written into recorded macros. By name: "macro.play". For AI: macro_play.

Setting Type Needed / default Range or choices What it is
path file path - The macro (.dpmacro). Left out: asks.

script

Running scripts.

dp.script.run{ } - Run a script

Runs a Luau script file: its commands on the active picture, as one undo step.

Changes things (one undo step). By name: "script.run". For AI: script_run.

Setting Type Needed / default Range or choices What it is
path file path needed The script (.luau).

dp.script.console() - Script console

Opens the script console, to write and run Luau scripts.

Changes things (one undo step). Not written into recorded macros. By name: "script.console". For AI: script_console.

No settings.

block

dp.block.make{ } - Make a block map

Makes the active map layer a block map: what's painted cut into blocks of w x h cells, alike ones made one block (mirrored ones too, with flips; ones differing in at most spots cells too, those becoming spot changes). The map's cells don't change. On a block map already: its blocks found afresh.

Changes things (one undo step). By name: "block.make". For AI: block_make.

Setting Type Needed / default Range or choices What it is
w whole number 2 1 to 256 Block width in cells.
h whole number 2 1 to 256 Block height in cells.
flips true or false true Mirrored blocks are the same block.
palettes true or false false Blocks in other palette banks are the same block, placed with a palette add.
spots whole number 0 0 to 65,535 Cells a placement may differ from its block by.

dp.block.clear() - Ordinary layer

The active layer stops being a block map (its cells stay as they are).

Changes things (one undo step). By name: "block.clear". For AI: block_clear.

No settings.

dp.block.info() - About the block map

The active layer's blocks: their size, how many, placements, spot changes, and the bytes they take against the flat map.

A question: changes nothing. Not written into recorded macros. By name: "block.info". For AI: block_info.

No settings.

dp.block.place{ } - Place a block

Places a block at placement (x, y) (in blocks, not cells: x 3 is cells 3 * the block width on), with its flips and palette add; its cells are written (spot changes there go). block -1: the placement becomes plain cells. With chunk: places chunk block at chunk placement (x, y) instead (in chunks), its blocks and their cells written.

Changes things (one undo step). By name: "block.place". For AI: block_place.

Setting Type Needed / default Range or choices What it is
x whole number needed 0 to 65,535 Placement across.
y whole number needed 0 to 65,535 Placement down.
block whole number needed -1 to 1,000,000 The block's number (-1: none).
flipx true or false false Mirrored across.
flipy true or false false Mirrored down.
palette whole number 0 0 to 255 Palette add.
w whole number 1 1 to 65,535 Placements wide (the same block in each).
h whole number 1 1 to 65,535 Placements high.
attr whole number 0 0 to 255 The placement's attributes (Sonic: a block's solidity in its chunk, a chunk's loop flag).
chunk true or false false Place a chunk (x, y, w and h in chunks).

dp.block.break{ } - Break blocks into cells

The placements in a rectangle (in blocks) become plain cells; the cells stay as they are.

Changes things (one undo step). By name: "block.break". For AI: block_break.

Setting Type Needed / default Range or choices What it is
x whole number needed 0 to 65,535 Placement across.
y whole number needed 0 to 65,535 Placement down.
w whole number 1 1 to 65,535 Placements wide.
h whole number 1 1 to 65,535 Placements high.

dp.block.from_cells{ } - New block from cells

A new block from the active layer's cells at cell (x, y) (a block's size of them, snapped to its placement), placed there. Returns its number.

Changes things (one undo step). By name: "block.from_cells". For AI: block_from_cells.

Setting Type Needed / default Range or choices What it is
x whole number needed 0 to 65,535 Cell across.
y whole number needed 0 to 65,535 Cell down.

dp.block.set{ } - Change a block's cell

Changes cell (x, y) of a block itself: every placement of it changes, except where they have spot changes.

Changes things (one undo step). By name: "block.set". For AI: block_set.

Setting Type Needed / default Range or choices What it is
block whole number needed 0 to 1,000,000 The block's number.
x whole number needed 0 to 255 Cell across in the block.
y whole number needed 0 to 255 Cell down in the block.
tile whole number needed -1 to 1,048,574 The tile (-1: empty).
flipx true or false false Mirrored across.
flipy true or false false Mirrored down.
palette whole number 0 0 to 255 Palette bank.
attr whole number 0 0 to 255 Attributes.

dp.block.export{ } - Export block map

Writes the active layer's blocks, layout and spot changes as JSON (documented in the file), for game code.

Changes things (one undo step). By name: "block.export". For AI: block_export.

Setting Type Needed / default Range or choices What it is
path file path needed The .json file.

dp.block.chunks{ } - Group blocks into chunks

Groups the active block map's blocks into chunks of w x h blocks (Sonic's 16 x 16 and 8 x 8, Mega Man's 2 x 2): alike areas made one chunk (mirrored ones too, with flips; in other palettes too, with palettes; ones differing in at most spots blocks too). The cells and the block placements don't change; the layout then places chunks.

Changes things (one undo step). By name: "block.chunks". For AI: block_chunks.

Setting Type Needed / default Range or choices What it is
w whole number 8 1 to 256 Chunk width in blocks.
h whole number 8 1 to 256 Chunk height in blocks.
flips true or false false Mirrored chunks are the same chunk.
palettes true or false false Chunks in other palette banks are the same chunk, placed with a palette add.
spots whole number 0 0 to 65,535 Blocks a chunk placement may differ from its chunk by.

dp.block.unchunk() - Ungroup chunks

The active block map forgets its chunks: its blocks are placed as they are.

Changes things (one undo step). By name: "block.unchunk". For AI: block_unchunk.

No settings.

dp.block.format{ } - Block map format

Sets the machine the active layer's blocks are for (its sizes, limits and files: block.check, block.export_format, block.import_format). The sizes, limits and compression given change the preset's (making it custom).

Changes things (one undo step). By name: "block.format". For AI: block_format.

Setting Type Needed / default Range or choices What it is
preset one of the choices needed free, sonic1, sonic2, sonic3k, nes, nes32, smw, zelda, metroid, custom, saved free (no machine), sonic1, sonic2, sonic3k, nes (Super Mario Bros. style metatiles), nes32 (Mega Man style 32 x 32), smw (SNES Map16), zelda (screens of columns), metroid (rooms of structures), custom (the current options, to change) or saved (one saved with block.format_save: give its name).
name text - preset saved: the saved format's name.
blockw whole number - 1 to 256 Block width in cells.
blockh whole number - 1 to 256 Block height in cells.
chunkw whole number - 0 to 256 Chunk width in blocks (0: none).
chunkh whole number - 0 to 256 Chunk height in blocks.
banks whole number - 0 to 256 Palette banks (0: any).
maxblocks whole number - 0 to 16,777,216 Most blocks (0: any).
maxchunks whole number - 0 to 16,777,216 Most chunks (0: any).
pack one of the choices - none, kosinski, moduled Compress the files: none, kosinski or moduled (moduled Kosinski, Sonic 3 & Knuckles); left out: as the preset has them.

dp.block.format_save{ } - Save block map format

Saves the active layer's format, with all its options, under a name: it's then in the Machine drop-down (and block.format{preset = "saved", name}) for every map. A name already saved is replaced.

Changes things (one undo step). By name: "block.format_save". For AI: block_format_save.

Setting Type Needed / default Range or choices What it is
name text needed The format's name.

dp.block.format_forget{ } - Forget a saved block map format

Takes a saved format out of the Machine drop-down (maps already using it keep its options).

Changes things (one undo step). By name: "block.format_forget". For AI: block_format_forget.

Setting Type Needed / default Range or choices What it is
name text needed The format's name.

dp.block.check() - Check the block map

What in the active block map doesn't fit its format (block.format): one line each, or "It all fits."

A question: changes nothing. Not written into recorded macros. By name: "block.check". For AI: block_check.

No settings.

dp.block.export_format{ } - Export block map for the machine

Writes the active block map as its format's files, beside path (a name without an extension): path.blocks.bin (the cells as the machine's tile words), path.chunks.bin (the chunks' block words), path.layout.bin, and for one-palette blocks path.palettes.bin (Zelda: path.columns.bin, .column_dirs.bin and .screens.bin for the chunks; Metroid: path.structures.bin, .rooms.bin and their _ptrs files, the layout the world map). bg: another layer (from 0) using the same blocks and chunks, written as the background (Sonic 2: each layout row's second half; otherwise path.bg.layout.bin).

Changes things (one undo step). By name: "block.export_format". For AI: block_export_format.

Setting Type Needed / default Range or choices What it is
path file path needed The files' name, without an extension.
bg whole number - 0 to 255 The background layer.

dp.block.import_format{ } - Import block map from the machine

Reads a level in the active layer's format (block.format) from its files beside path (a name without an extension: path.blocks.bin, path.chunks.bin, path.layout.bin, path.palettes.bin, decompressed as the format keeps them) into the active layer: its blocks, chunks and placements, and every cell, the layer sized to the layout. The map's tile set should hold the level's tiles (in order). bg: a layer (from 0) to fill with the level's background too (Sonic).

Changes things (one undo step). By name: "block.import_format". For AI: block_import_format.

Setting Type Needed / default Range or choices What it is
path file path needed The files' name, without an extension.
across whole number - 1 to 65,535 The layout's width in placements, for layouts that don't keep it (a byte or word each).
bg whole number - 0 to 255 The layer for the background.

dp.block.open_level{ } - Open a game level

Opens a level kept as a game keeps it, from its files beside path (a name without an extension: path.blocks.bin, path.chunks.bin, path.layout.bin, and path.art.bin and path.palette.bin if they're there): its art as a tile set picture (without art, each tile a plain square), and a map of its blocks and chunks drawn from it (with a background layer when the level has one).

Changes things (one undo step). By name: "block.open_level". For AI: block_open_level.

Setting Type Needed / default Range or choices What it is
path file path needed The files' name, without an extension.
preset one of the choices needed sonic1, sonic2, sonic3k, nes, nes32, smw, saved The level's format: sonic1, sonic2, sonic3k, nes, nes32, smw, or saved (one saved with block.format_save: give its name).
name text - preset saved: the saved format's name.
across whole number - 1 to 65,535 The layout's width, for layouts that don't keep it (a byte or word each).

dp.block.share{ } - Share blocks with a layer

Makes another layer (from 0) a block map using the active layer's blocks (and chunks): its placements found with them, any new blocks or chunks it needs added to both layers' sets, so the two can be written together (a level's foreground and background).

Changes things (one undo step). By name: "block.share". For AI: block_share.

Setting Type Needed / default Range or choices What it is
layer whole number needed 0 to 255 The other layer.

copper

dp.copper.info() - The copper's changes

Every change the copper makes down the screen, a line each: the line it's from, and what changes (a palette colour and its new colour, or a map layer's scroll or visibility).

A question: changes nothing. Not written into recorded macros. By name: "copper.info". For AI: copper_info.

No settings.

dp.copper.colour{ } - Copper: change a colour

From a line down, a palette colour becomes another colour (until it changes again).

Changes things (one undo step). By name: "copper.colour". For AI: copper_colour.

Setting Type Needed / default Range or choices What it is
line whole number needed 0 to 65,535 The line it changes on (0 is the top).
index whole number needed 0 to 255 The palette colour number.
colour colour (#RRGGBB) needed Its new colour, like #2040A0.

dp.copper.gradient{ } - Copper: a colour gradient

A palette colour ramped from one colour to another down a run of lines (a sky, water, a colour bar), a change every few lines; snapped to the machine's colours.

Changes things (one undo step). By name: "copper.gradient". For AI: copper_gradient.

Setting Type Needed / default Range or choices What it is
from_line whole number needed 0 to 65,535 The first line.
to_line whole number needed 0 to 65,535 The last line.
index whole number needed 0 to 255 The palette colour number it changes.
from colour (#RRGGBB) needed The colour at the first line.
to colour (#RRGGBB) needed The colour at the last line.
every whole number 1 1 to 256 A change every this many lines.
snap true or false true Snap the colours to what the machine can show.

dp.copper.wave{ } - Copper: a wave

A map layer's scroll changed line by line down a run of lines as a sine wave (water, heat haze, a dream sequence).

Changes things (one undo step). By name: "copper.wave". For AI: copper_wave.

Setting Type Needed / default Range or choices What it is
from_line whole number needed 0 to 65,535 The first line.
to_line whole number needed 0 to 65,535 The last line.
layer whole number 0 0 to 255 The map layer (0 is the bottom one).
across true or false true Scroll across (false: up and down).
amplitude whole number 4 0 to 512 How far it moves, in pixels.
period whole number 32 2 to 4,096 Lines for one whole wave.
phase whole number 0 -360 to 360 Where the wave starts, in degrees.

dp.copper.clear() - Copper: clear it

Removes every change the copper makes.

Changes things (one undo step). By name: "copper.clear". For AI: copper_clear.

No settings.

dp.copper.show{ } - Copper: on or off

Shows the copper's changes, or keeps them but doesn't show them.

Changes things (one undo step). By name: "copper.show". For AI: copper_show.

Setting Type Needed / default Range or choices What it is
on true or false true Show them.

history

dp.history.list() - List the undo history

Every undo step, oldest first: its number, name, the layer painted on, when (the clock time), and a * on the current one (steps after it can be redone).

A question: changes nothing. Not written into recorded macros. By name: "history.list". For AI: history_list.

No settings.

dp.history.goto{ } - Go to a step of the history

Undoes or redoes until step steps are done (0: the start of the history), as clicking a step in the History panel.

Changes things (one undo step). Not written into recorded macros. By name: "history.goto". For AI: history_goto.

Setting Type Needed / default Range or choices What it is
step whole number needed 0 to 1,000,000 How many steps done (history.list numbers them).

dp.history.forget{ } - Forget old history steps

Forgets the oldest steps of the undo history (frees their memory; the picture stays as it is). Can't be undone.

Changes things (one undo step). Not written into recorded macros. By name: "history.forget". For AI: history_forget.

Setting Type Needed / default Range or choices What it is
steps whole number needed 1 to 1,000,000 How many of the oldest done steps to forget.

dp.history.clearredo() - Clear the redo steps

Forgets the steps Redo would bring back. Can't be undone.

Changes things (one undo step). Not written into recorded macros. By name: "history.clearredo". For AI: history_clearredo.

No settings.

model

dp.model.open{ } - Open a 3D model

Opens a 3D model (STL, OBJ with its MTL, glTF / GLB, MagicaVoxel .vox) to render as sprites: File > 3D model to sprites shows it.

Changes things (one undo step). By name: "model.open". For AI: model_open.

Setting Type Needed / default Range or choices What it is
path file path needed The model file.

dp.model.info() - About the 3D model

Its parts, colours and animations, and how it's being rendered.

A question: changes nothing. Not written into recorded macros. By name: "model.info". For AI: model_info.

No settings.

dp.model.view{ } - 3D model: view

How the model is seen: the frame size, directions, angles, zoom and perspective (only those given change).

Changes things (one undo step). By name: "model.view". For AI: model_view.

Setting Type Needed / default Range or choices What it is
width whole number - 8 to 512 Frame width in pixels.
height whole number - 8 to 512 Frame height in pixels.
directions one of the choices - 1, 4, 8, 16 Frames turning round the model.
start number - -360 to 360 The first frame's angle, degrees (0: its front towards you).
pitch number - -10 to 90 Looking down on it, degrees (0 level, 90 from above).
zoom number - 0.1 to 8 Zoom (1 fits it).
perspective true or false - Perspective, or straight on (orthographic).

dp.model.light{ } - 3D model: light

Where the light comes from and how much light is everywhere (only those given change).

Changes things (one undo step). By name: "model.light". For AI: model_light.

Setting Type Needed / default Range or choices What it is
yaw number - -180 to 180 From, degrees round (relative to the view).
pitch number - -10 to 90 Its height, degrees.
ambient number - 0 to 1 Light everywhere, 0 to 1.

dp.model.style{ } - 3D model: style

Dithering, the outline and the palette (only those given change).

Changes things (one undo step). By name: "model.style". For AI: model_style.

Setting Type Needed / default Range or choices What it is
dither true or false - Ordered dithering between the shades.
outline one of the choices - none, darkest, left An outline round it.
palette one of the choices - model, picture The picture in front's palette, or one made for the model.
colours whole number - 4 to 256 Colours in a palette made for the model.

dp.model.animation{ } - 3D model: animation

Which of a glTF model's animations to render, in how many frames.

Changes things (one undo step). By name: "model.animation". For AI: model_animation.

Setting Type Needed / default Range or choices What it is
name text - Its name or number (model.info lists them); "none" for the model as it stands.
frames whole number - 1 to 64 Frames for each direction.
loop true or false - It goes round (the last moment isn't made again).

dp.model.colour{ } - 3D model: a colour

Changes one of the model's colours (its materials: model.info lists them).

Changes things (one undo step). By name: "model.colour". For AI: model_colour.

Setting Type Needed / default Range or choices What it is
material text needed Its name or number.
colour colour (#RRGGBB) needed The new colour.

dp.model.pose{ } - 3D model: pose a part

Moves, turns (round its own middle) or scales one of the model's parts - an arm raised, a head turned. What's given is set; the rest of its pose stays.

Changes things (one undo step). By name: "model.pose". For AI: model_pose.

Setting Type Needed / default Range or choices What it is
part text needed The part's name or number (model.info lists them).
x number - Moved across (the model's units).
y number - Moved up.
z number - Moved towards you.
yaw number - Turned round up, degrees.
pitch number - Tipped forward, degrees.
roll number - Rolled, degrees.
scale number - 0.01 to 100 Its size (1 as it is).
pivot one of the choices - middle, top, bottom What it turns round: its middle, top (a shoulder, a hip) or bottom.

dp.model.pose_reset{ } - 3D model: unpose

Puts a part (or every part) back as the model has it.

Changes things (one undo step). By name: "model.pose_reset". For AI: model_pose_reset.

Setting Type Needed / default Range or choices What it is
part text - The part's name or number (left out: every part).

dp.model.frames() - 3D model to frames

Renders the model with the settings now into a new picture: a frame for each direction (and each moment of its animation).

Changes things (one undo step). By name: "model.frames". For AI: model_frames.

No settings.

dp.model.stamp{ } - 3D model into the picture

Renders the model (its first frame: the start angle, the animation's first moment) in the picture in front's colours, onto a new layer at x, y (its top-left): a 3D object put into a scene.

Changes things (one undo step). By name: "model.stamp". For AI: model_stamp.

Setting Type Needed / default Range or choices What it is
x whole number - Where its left edge goes.
y whole number - Where its top goes.

dp.model.layer{ } - 3D model on a live 3D layer

Puts the model itself into the picture in front, on a new layer (its top-left at x, y; left out: centred), with the settings now. It stays 3D: model.object and the Layers panel change how it's turned, lit and posed; flattening the layer makes it pixels.

Changes things (one undo step). By name: "model.layer". For AI: model_layer.

Setting Type Needed / default Range or choices What it is
x whole number - Where its left edge goes (left out: centred).
y whole number - Where its top goes (left out: centred).

dp.model.object{ } - Live 3D object: change it

Turns, lights, animates or moves a live 3D object (model.layer) in the picture in front: the one on the active layer (the topmost, or number n from the bottom). Only those given change; calls in a row are one undo step. A script calling it each frame animates it.

Changes things (one undo step). By name: "model.object". For AI: model_object.

Setting Type Needed / default Range or choices What it is
n whole number - 1 to 4,096 Which model object on the layer, 1 the bottom (left out: the top one).
x whole number - Its left edge.
y whole number - Its top.
width whole number - 1 to 2,048 Its box, pixels.
height whole number - 1 to 2,048 Its box, pixels.
yaw number - -100,000 to 100,000 Turned round, degrees (0: its front towards you).
pitch number - -10 to 90 Looking down on it, degrees.
zoom number - 0.1 to 8 Zoom (1 fits it).
light_yaw number - -180 to 180 The light from, degrees round.
light_pitch number - -10 to 90 The light's height, degrees.
ambient number - 0 to 1 Light everywhere, 0 to 1.
animation whole number - 0 to 1,000 Its animation, 1 the first (0: none, as it stands).
time number - 0 to 100,000 The animation's moment, seconds (it loops).
outline whole number - -1 to 255 A colour number round it (-1: none).
dither true or false - Dither between shades.
perspective true or false - Perspective, or straight on.
visible true or false - Shown.

dp.model.object_pose{ } - Live 3D object: pose a part

Poses one part of a live 3D object (model.layer) on the active layer: moved, turned, scaled. What's given is set; the rest of its pose stays. Calls in a row are one undo step, so a script can animate an arm frame by frame.

Changes things (one undo step). By name: "model.object_pose". For AI: model_object_pose.

Setting Type Needed / default Range or choices What it is
part text needed The part's name or number (1 the first).
n whole number - 1 to 4,096 Which model object on the layer, 1 the bottom (left out: the top one).
x number - Moved across (the model's units).
y number - Moved up.
z number - Moved towards the front.
yaw number - Turned round up, degrees.
pitch number - Tipped forward, degrees.
roll number - Rolled, degrees.
scale number - 0.01 to 100 Its size (1 as it is).
pivot one of the choices - middle, top, bottom What it turns round: its middle, top (a shoulder, a hip) or bottom.
reset true or false - Put the part back as the model has it first.

dp.model.texture{ } - 3D model: a texture

Puts a texture on one of the model's colours (its materials: model.info lists them): the picture in front as it is now (its selection, if it has one) - paint a texture in Deluxe Pixel - or an image file; or takes it off. Models without texture coordinates get a box projection.

Changes things (one undo step). By name: "model.texture". For AI: model_texture.

Setting Type Needed / default Range or choices What it is
material text needed The material's name or number.
from one of the choices picture picture, file, none Where the texture comes from.
path file path - The image file (from = file).

picture

dp.picture.convert{ } - Convert true colour / palette

Changes the picture in front between true colour and a palette, as one undo step: as the Image menu's Machines section does. opened_palette keeps it to the colours of the file as it was opened.

Changes things (one undo step). By name: "picture.convert". For AI: picture_convert.

Setting Type Needed / default Range or choices What it is
to one of the choices needed true_colour, palette, opened_palette true_colour (24-bit, any colour), palette (the colours used, merged to the machine's number), or opened_palette (every pixel one of the colours of the palette the picture was opened with, in its order).
dither one of the choices None None, Ordered, Floyd-Steinberg, Atkinson opened_palette: how colours between the palette's are made.

prop

dp.prop.list{ } - List properties

The properties of the map (or picture), a map layer, a tile or a map object: a line each, name (type) = value; a class's members in braces.

A question: changes nothing. Not written into recorded macros. By name: "prop.list". For AI: prop_list.

Setting Type Needed / default Range or choices What it is
target one of the choices map map, layer, tile, object, template Whose: the map's (or picture's) own, a map layer's, a tile's, or a map object's.
layer whole number - 0 to 255 target = layer: which (from 0, the bottom; left out: the active one).
tile whole number - 0 to ... target = tile: the tile's number (in the active layer's tile set).
object whole number - 1 to ... target = object: the object's id (left out: the chosen one); target = template: the template's.

dp.prop.set{ } - Set a property

Gives the map (or picture), a map layer, a tile or a map object a property, or changes one's value. A class's member: name it "wave.count". type is bool, int, float, string, color, file, or the name of an enum or class (Property types); left out, an existing property keeps its type, and a new one is text.

Changes things (one undo step). By name: "prop.set". For AI: prop_set.

Setting Type Needed / default Range or choices What it is
target one of the choices map map, layer, tile, object, template Whose: the map's (or picture's) own, a map layer's, a tile's, or a map object's.
name text needed The property's name.
value text - Its value (flags: values joined by commas).
type text - Its type (left out: as it is, or text for a new one).
layer whole number - 0 to 255 target = layer: which (from 0, the bottom; left out: the active one).
tile whole number - 0 to ... target = tile: the tile's number (in the active layer's tile set).
object whole number - 1 to ... target = object: the object's id (left out: the chosen one); target = template: the template's.

dp.prop.remove{ } - Remove a property

Takes a property away from the map (or picture), a map layer, a tile or a map object.

Changes things (one undo step). By name: "prop.remove". For AI: prop_remove.

Setting Type Needed / default Range or choices What it is
target one of the choices map map, layer, tile, object, template Whose: the map's (or picture's) own, a map layer's, a tile's, or a map object's.
name text needed The property's name.
layer whole number - 0 to 255 target = layer: which (from 0, the bottom; left out: the active one).
tile whole number - 0 to ... target = tile: the tile's number (in the active layer's tile set).
object whole number - 1 to ... target = object: the object's id (left out: the chosen one); target = template: the template's.

reference

dp.reference.add{ } - Add a reference image

A picture file (PNG, JPEG, GIF, TIFF, BMP...) to look at while drawing: shown beside the picture or over it, colours picked from it with Pick colour, never part of the picture (not a layer; not exported). Kept with the .dpx.

Changes things (one undo step). By name: "reference.add". For AI: reference_add.

Setting Type Needed / default Range or choices What it is
path file path needed The picture file.
place one of the choices beside beside, over Beside the picture (to its right, as tall as it) or over it (fitted, to trace).
opacity whole number 100 5 to 100 How solid it shows, 5-100%.

dp.reference.set{ } - Change a reference image

Moves, sizes, shows or hides a reference image (numbered from 0 in the order added; the last is on top).

Changes things (one undo step). By name: "reference.set". For AI: reference_set.

Setting Type Needed / default Range or choices What it is
index whole number 0 0 to 255 Which one.
x whole number - Its left edge, in picture pixels.
y whole number - Its top edge, in picture pixels.
size number - 1 to 10,000 How big it shows, % (100: one image pixel to one picture pixel).
opacity whole number - 5 to 100 How solid it shows, 5-100%.
visible true or false - Shown or hidden.
place one of the choices - beside, over Put it beside the picture or fit it over it (before x, y and size, which can adjust it).

dp.reference.remove{ } - Remove a reference image

Takes a reference image away (the picture is untouched).

Changes things (one undo step). By name: "reference.remove". For AI: reference_remove.

Setting Type Needed / default Range or choices What it is
index whole number needed 0 to 255 Which one (from 0).

sheet

dp.sheet.open{ } - Open a sprite sheet

Opens a packed sprite sheet by its data file (Gideros .txt, TexturePacker JSON hash / array, Aseprite JSON, a libGDX .atlas, Unity .tpsheet, Godot SpriteFrames .tres, or a TexturePacker .tps project): the sheet picture, with each region as a sprite (walk_0001-style frames and Aseprite tags as animations).

Changes things (one undo step). By name: "sheet.open". For AI: sheet_open.

Setting Type Needed / default Range or choices What it is
path file path needed The data file or .tps.
set true or false false Open every sheet of its multipack set (name0.json, name1.json ... beside it).

dp.sheet.nine{ } - Set 9-patch borders

Makes a sprite of the picture in front a 9-patch (9-slice): the borders in from each edge (of the whole sprite, before any trimming) that stay as drawn while the middle stretches. All 0 makes it an ordinary sprite again. They're saved with the sheet's data (TexturePacker JSON, Aseprite, libGDX, Unity).

Changes things (one undo step). By name: "sheet.nine". For AI: sheet_nine.

Setting Type Needed / default Range or choices What it is
sprite text needed The sprite's name.
cel whole number -1 -1 to 65,535 Which of its cels (from 0); -1: every one.
left whole number 0 0 to 4,096 Pixels in from the left.
top whole number 0 0 to 4,096 Pixels in from the top.
right whole number 0 0 to 4,096 Pixels in from the right.
bottom whole number 0 0 to 4,096 Pixels in from the bottom.

dp.sheet.save{ } - Save a sprite sheet's data

Writes the picture in front's sprites as a sheet's data file, and the sheet as a PNG beside it. A sheet of a multipack set saves the whole set (phaser and libgdx: one data file; the others: name0, name1 ...).

Changes things (one undo step). By name: "sheet.save". For AI: sheet_save.

Setting Type Needed / default Range or choices What it is
path file path needed The data file to write.
format one of the choices gideros gideros, json-hash, json-array, aseprite, libgdx, unity, godot, phaser, tps The data format (tps: a TexturePacker project, with a JSON hash beside it).
png true or false true Also save the sheet picture as a PNG beside it.

dp.sheet.pack{ } - Pack a sprite sheet

Packs the picture in front's frames, layers or sprites, or a folder of pictures, into a new sheet picture (its regions as sprites).

Changes things (one undo step). By name: "sheet.pack". For AI: sheet_pack.

Setting Type Needed / default Range or choices What it is
from one of the choices frames frames, layers, sprites, folder What to pack.
folder file path - The folder (from = folder).
trim true or false true Trim see-through edges.
aliases true or false true Identical sprites stored once.
padding whole number 1 0 to 64 Pixels between sprites.
border whole number 0 0 to 64 Pixels round the sheet.
extrude whole number 0 0 to 8 Edge pixels repeated round each sprite.
pot true or false true A power-of-two size.
square true or false false Square.
max whole number 2048 16 to 16,384 The largest the sheet may be, each way.
rotate true or false false Sprites may be turned 90 degrees clockwise to fit better.
multipack true or false false More than one sheet when they don't fit on one.
polygons true or false false Packed by their outlines instead of their rectangles (no rotate or extrude; TexturePacker JSON only).

target

dp.target.add{ } - Add a machine target

Adds a machine target to the picture in front: a computer it's made for, shown as that machine would show it (fitted to its screen unless fit = false), kept up to date as the picture changes, and exported in its formats (target.export). Saved with the picture.

Changes things (one undo step). By name: "target.add". For AI: target_add.

Setting Type Needed / default Range or choices What it is
machine text needed The machine (a system profile's name, e.g. "ZX Spectrum", "C64", "ST").
name text - The target's name (left out: the machine's, made unique).
mode text - The screen mode (as the system profile names it; left out: its first).
dither text - none, ordered, floyd-steinberg (the default) or atkinson.
strength number - 0 to 100 Dithering strength, 0-100.
fix_clashes true or false - Modes with colour cells: choose each cell's colours so nothing clashes.
contrast number - 0 to 100 Before converting: contrast, 0-100.
saturation number - 0 to 100 Saturation, 0-100.
demud number - 0 to 100 De-mud, 0-100.
fit true or false - Fitted to the machine's screen (true, the default) or the picture's own size.
sprites text - Sprites: the machine's sprite hardware (as the Timeline's hardware check names it, e.g. "C64 (multicolour)", "NES", "Spectrum Next"; "none" for none): every frame, the sprites kept to its colours and checked.
split_sprites true or false - Sprites: cels that are no size the sprite hardware has shown as poses of its sprites (pieces of one of its sizes).
tiles text - Tile maps: the machine's tile hardware (e.g. "Mega Drive", "SNES", "Spectrum Next", "NES"; "none" for none): its palette banks, mirrors and tile limit.
reduce_tiles true or false - Tile maps: merge the most alike tiles until they fit the tile hardware's limit.
show true or false true Show it in a canvas of its own.

dp.target.set{ } - Change a machine target

Changes a machine target's settings (the ones given): its canvas follows. Targets can be named by part of their name.

Changes things (one undo step). By name: "target.set". For AI: target_set.

Setting Type Needed / default Range or choices What it is
name text needed The target.
machine text - Another machine.
rename text - A new name.
mode text - The screen mode (as the system profile names it; left out: its first).
dither text - none, ordered, floyd-steinberg (the default) or atkinson.
strength number - 0 to 100 Dithering strength, 0-100.
fix_clashes true or false - Modes with colour cells: choose each cell's colours so nothing clashes.
contrast number - 0 to 100 Before converting: contrast, 0-100.
saturation number - 0 to 100 Saturation, 0-100.
demud number - 0 to 100 De-mud, 0-100.
fit true or false - Fitted to the machine's screen (true, the default) or the picture's own size.
sprites text - Sprites: the machine's sprite hardware (as the Timeline's hardware check names it, e.g. "C64 (multicolour)", "NES", "Spectrum Next"; "none" for none): every frame, the sprites kept to its colours and checked.
split_sprites true or false - Sprites: cels that are no size the sprite hardware has shown as poses of its sprites (pieces of one of its sizes).
tiles text - Tile maps: the machine's tile hardware (e.g. "Mega Drive", "SNES", "Spectrum Next", "NES"; "none" for none): its palette banks, mirrors and tile limit.
reduce_tiles true or false - Tile maps: merge the most alike tiles until they fit the tile hardware's limit.

dp.target.remove{ } - Remove a machine target

Removes a machine target from the picture (and closes its canvas).

Changes things (one undo step). By name: "target.remove". For AI: target_remove.

Setting Type Needed / default Range or choices What it is
name text needed The target.

dp.target.list() - List machine targets

The picture's machine targets: name, machine, mode and settings.

Changes things (one undo step). By name: "target.list". For AI: target_list.

No settings.

dp.target.freeze{ } - Freeze a machine target

Freezes a machine target (frozen = false: unfreezes it). Frozen, it no longer follows the picture: its picture as it is now (touch-ups and all) is the finished art for that machine, painted on freely and saved with the picture. Unfrozen, it's made again from the picture, and every pixel the frozen picture had that the new conversion doesn't becomes a touch-up.

Changes things (one undo step). By name: "target.freeze". For AI: target_freeze.

Setting Type Needed / default Range or choices What it is
name text needed The target.
frozen true or false true Frozen (true) or following the picture again.

dp.target.clear_touches{ } - Clear a target's touch-ups

Forgets a machine target's touch-ups (pixels fixed by hand): it's made again as converted.

Changes things (one undo step). By name: "target.clear_touches". For AI: target_clear_touches.

Setting Type Needed / default Range or choices What it is
name text needed The target.

dp.target.export_all{ } - Export all machine targets

Exports every machine target of the picture again, each the way it was last exported (its format and file; into folder, if given, keeping the files' names). Targets never exported are listed: export each once (the Export window, or target.export).

Changes things (one undo step). By name: "target.export_all". For AI: target_export_all.

Setting Type Needed / default Range or choices What it is
folder file path - Write them all into this folder instead.

dp.target.show{ } - Show a machine target

Shows a machine target in its own canvas (in front), made from the picture as it is now.

Changes things (one undo step). By name: "target.show". For AI: target_show.

Setting Type Needed / default Range or choices What it is
name text needed The target.

dp.target.export{ } - Export a machine target

Writes a machine target in one of the Export window's formats (as file.export does), made from the picture as it is now.

Changes things (one undo step). By name: "target.export". For AI: target_export.

Setting Type Needed / default Range or choices What it is
name text needed The target.
format text needed The format's name as the Export window lists it.
path file path needed The file to write.

terrain

dp.terrain.list() - List terrain sets

The tile set's terrain sets (on a map, the active layer's tile set): a line each, number, name, kind, its terrains (1 up) and how many tiles take part.

A question: changes nothing. Not written into recorded macros. By name: "terrain.list". For AI: terrain_list.

No settings.

dp.terrain.new{ } - New terrain set

Makes a terrain set on the tile set (on a map, the active layer's), and chooses it.

Changes things (one undo step). By name: "terrain.new". For AI: terrain_new.

Setting Type Needed / default Range or choices What it is
name text Terrains Its name.
kind one of the choices corner corner, edge, mixed corner: transitions (16 tiles); edge: paths, roads, fences (16); mixed: the 47-tile blob set.
terrains text Grass Its terrains' names, joined by commas.

dp.terrain.remove{ } - Remove a terrain set

Takes a terrain set away from the tile set.

Changes things (one undo step). By name: "terrain.remove". For AI: terrain_remove.

Setting Type Needed / default Range or choices What it is
set text - The terrain set: its name or number from 0 (left out: the one chosen in Terrains).

dp.terrain.layout{ } - Set up terrain tiles from a layout

Sets the terrains of 16 or 47 tiles one after another, laid out in a ready-made order, terrain a over b (b none: empty).

Changes things (one undo step). By name: "terrain.layout". For AI: terrain_layout.

Setting Type Needed / default Range or choices What it is
set text - The terrain set: its name or number from 0 (left out: the one chosen in Terrains).
layout one of the choices corner16 corner16, edge16, blob47 corner16: tile n has terrain a at the corners of n's bits (1 top-right, 2 bottom-right, 4 bottom-left, 8 top-left); edge16: n's bits are the sides (1 top, 2 right, 4 bottom, 8 left); blob47: the 47 blob masks in order, smallest first.
first whole number 0 0 to 1,048,574 The first tile's number.
a text 1 The terrain (name or number from 1).
b text none The other terrain (or none).

dp.terrain.tile{ } - Set a tile's terrains

Which terrain is at each of a tile's 8 places, in Tiled's order: top, top-right, right, bottom-right, bottom, bottom-left, left, top-left; numbers from 1 (0 none), joined by commas. Places the set's kind doesn't use are kept as 0. places left out takes the tile out of the set.

Changes things (one undo step). By name: "terrain.tile". For AI: terrain_tile.

Setting Type Needed / default Range or choices What it is
set text - The terrain set: its name or number from 0 (left out: the one chosen in Terrains).
tile whole number needed 0 to 1,048,574 The tile's number.
places text - 8 numbers, joined by commas.

dp.terrain.paint{ } - Paint a terrain

Paints a terrain into a cell, or every cell of a rectangle, of the active map layer; the tiles there and round about are chosen from the set (one undo step; with cells selected, only those change). terrain none takes it away.

Changes things (one undo step). By name: "terrain.paint". For AI: terrain_paint.

Setting Type Needed / default Range or choices What it is
set text - The terrain set: its name or number from 0 (left out: the one chosen in Terrains).
terrain text needed The terrain: its name, or number from 1 (none: erase).
x whole number needed 0 to 65,535 Column.
y whole number needed 0 to 65,535 Row.
w whole number 1 1 to 65,535 Columns wide.
h whole number 1 1 to 65,535 Rows high.

tiles

dp.tiles.shape_template{ } - New tile set for a map shape

A new picture of tiles to paint for a map of this shape: each tile's cell shape (diamond, hex, brick, square) outlined in the left colour, the corners outside it see-through. Left out: the map in front's shape and tile size.

Changes things (one undo step). By name: "tiles.shape_template". For AI: tiles_shape_template.

Setting Type Needed / default Range or choices What it is
shape one of the choices - square, isometric, staggered, hexagonal, brick square, isometric, staggered, hexagonal or brick (left out: the map in front's shape).
stagger one of the choices - rows, columns Hexagonal and brick: rows shifted or columns.
tile_w whole number - 2 to 256 Tile width in pixels.
tile_h whole number - 2 to 256 Tile height in pixels.
hex_side whole number - 0 to 256 Hexagonal: the straight edge's length in pixels (left out: half the tile).
count whole number 64 1 to 4,096 How many tiles.
columns whole number 8 1 to 256 Tiles across.

dp.tiles.blocks3d{ } - 3D cubes from tiles

Each tile of the picture in front on the top and sides of a 3D cube, rendered in the same colours into a new picture of cubes, in the same order: isometric (2:1) or three-quarter view tiles.

Changes things (one undo step). By name: "tiles.blocks3d". For AI: tiles_blocks3d.

Setting Type Needed / default Range or choices What it is
tile_width whole number 16 1 to 512 The tiles' width.
tile_height whole number 16 1 to 512 The tiles' height.
view one of the choices isometric isometric, three-quarter, custom How the cubes are seen.
yaw number 45 -180 to 180 Custom view: turned, degrees.
pitch number 30 0 to 90 Custom view: looking down, degrees.
height number 1 0.05 to 8 The cube's height, as a fraction of its width.
sides one of the choices same same, below, plain The sides' tile.
width whole number 0 0 to 1,024 Each cube's cell width (0: automatic).
cell_height whole number 0 0 to 1,024 Each cube's cell height (0: automatic).
outline whole number -1 -1 to 255 A colour number round each cube (-1: none).
dither true or false - Dither between shades.
top_as_drawn true or false - The top exactly as the tile, not shaded (the default).

dp.tiles.animate{ } - Animate a tile

Makes a tile of the active map layer's tile set an animated tile (Tiled's tile animations): wherever it's on a map, it shows the frames in turn, each for its time, round and round. Give frames as "tile:ms, tile:ms, ...", or count: that many tiles from it on. Replaces any animation it had. One undo step (on the tile set).

Changes things (one undo step). By name: "tiles.animate". For AI: tiles_animate.

Setting Type Needed / default Range or choices What it is
tile whole number needed 0 to 1,048,575 The tile animated (its number in the set).
frames text - Its frames: "12:100, 13:100, 14:200" (a frame without :ms lasts ms).
count whole number - 2 to 4,096 Or this many tiles from it on (2 or more).
ms whole number 150 1 to 600,000 How long each frame lasts when not given.

dp.tiles.animation_remove{ } - Stop animating a tile

The tile shows just itself again (on every map using its tile set).

Changes things (one undo step). By name: "tiles.animation_remove". For AI: tiles_animation_remove.

Setting Type Needed / default Range or choices What it is
tile whole number needed 0 to 1,048,575 The animated tile's number.

dp.tiles.animations() - List animated tiles

The active map layer's tile set's animated tiles, a line each: "tile: frame:ms, frame:ms, ...".

A question: changes nothing. Not written into recorded macros. By name: "tiles.animations". For AI: tiles_animations.

No settings.

dp.tiles.animation_play{ } - Play animated tiles

Animated tiles play on every map (on), or each shows its own tile (off).

Changes things (one undo step). By name: "tiles.animation_play". For AI: tiles_animation_play.

Setting Type Needed / default Range or choices What it is
on true or false true Play or stop.
restart true or false false Every animation from its first frame.

dp.tiles.random{ } - Random tiles

Sets the random tiles the map brush paints with (Tiled's random mode): every cell painted - brush, line, box, fill, Fill selection, map.set with random = true - is one of them, by weight, optionally mirrored or turned at random. Give tiles as "3, 4, 5" or with weights "3:4, 4:1, 5:1"; on = false turns it off (the brush paints as it is).

Changes things (one undo step). By name: "tiles.random". For AI: tiles_random.

Setting Type Needed / default Range or choices What it is
tiles text - The tiles, "3, 4, 5" or "3:4, 4:1" (tile:weight). Left out: as they are.
on true or false true Paint random tiles (true) or not.
flip_x true or false - Mirror across at random.
flip_y true or false - Mirror down at random.
rotate true or false - Turn a quarter at random (square tiles).

dp.tiles.collision{ } - Set a tile's collision

Gives a tile of the active map layer's tile set collision shapes, in its own pixels (Tiled's tile collision editor): a ready-made shape (full, top_half, bottom_half, left_half, right_half, slope_up, slope_down, platform), boxes "x, y, w, h; x, y, w, h", or a polygon "x, y, x, y, x, y...". Replaces what it had unless add = true. Every cell holding the tile is solid there (turned and mirrored as the cell). Saved with the tile set, and in Tiled's maps and Godot's scenes. One undo step.

Changes things (one undo step). By name: "tiles.collision". For AI: tiles_collision.

Setting Type Needed / default Range or choices What it is
tile whole number needed 0 to 1,048,575 The tile's number in the set.
preset text - A ready-made shape: full, top_half, bottom_half, left_half, right_half, slope_up, slope_down, platform.
boxes text - Boxes: "x, y, w, h" each, separated by ;.
polygon text - A polygon's corners: "x, y, x, y, x, y...".
add true or false false Add to the shapes it has (false: replace them).

dp.tiles.collision_clear{ } - Clear a tile's collision

The tile has no collision shapes (nothing solid).

Changes things (one undo step). By name: "tiles.collision_clear". For AI: tiles_collision_clear.

Setting Type Needed / default Range or choices What it is
tile whole number needed 0 to 1,048,575 The tile's number in the set.

dp.tiles.collisions() - List tiles' collision

Each tile of the active map layer's tile set with collision shapes, a line each: its shapes in its pixels.

A question: changes nothing. Not written into recorded macros. By name: "tiles.collisions". For AI: tiles_collisions.

No settings.

dp.tiles.stamp_save{ } - Save a stamp

Keeps the map brush (picked up from a map, or a block of tiles) as a named stamp (Tiled's stamps), saved for every map in the person's own folder (stamps.json). A stamp of that name already: the brush replaces it.

Changes things (one undo step). By name: "tiles.stamp_save". For AI: tiles_stamp_save.

Setting Type Needed / default Range or choices What it is
name text needed Its name.

dp.tiles.stamp_add_variation{ } - Add a stamp variation

Adds the map brush to a stamp as another variation: wherever the stamp is placed, one of its variations is, at random (rock clusters, trees, cracks that don't repeat).

Changes things (one undo step). By name: "tiles.stamp_add_variation". For AI: tiles_stamp_add_variation.

Setting Type Needed / default Range or choices What it is
name text needed The stamp.

dp.tiles.stamp_use{ } - Use a stamp

Makes a saved stamp the map brush (with its variations, one at random per placement).

Changes things (one undo step). By name: "tiles.stamp_use". For AI: tiles_stamp_use.

Setting Type Needed / default Range or choices What it is
name text needed The stamp.

dp.tiles.stamp_delete{ } - Forget a stamp

Forgets a saved stamp.

Changes things (one undo step). By name: "tiles.stamp_delete". For AI: tiles_stamp_delete.

Setting Type Needed / default Range or choices What it is
name text needed The stamp.

dp.tiles.stamps() - List stamps

The saved stamps: each one's name, size and how many variations.

Changes things (one undo step). By name: "tiles.stamps". For AI: tiles_stamps.

No settings.

dp.tiles.quick_stamp{ } - Quick stamp

Keeps the map brush as quick stamp 1-9 (as Ctrl+1-9 on a map), or with use = true makes quick stamp 1-9 the brush (as 1-9).

Changes things (one undo step). By name: "tiles.quick_stamp". For AI: tiles_quick_stamp.

Setting Type Needed / default Range or choices What it is
slot whole number needed 1 to 9 1 to 9.
use true or false false Bring it back (true) rather than keep the brush.

types

dp.types.enum{ } - Make an enum

Makes (or changes) an enum for properties: one of a list of values, or with flags = true, any of them at once (as bits). Kept with the map or picture in front.

Changes things (one undo step). By name: "types.enum". For AI: types_enum.

Setting Type Needed / default Range or choices What it is
name text needed Its name.
values text needed Its values, joined by commas.
flags true or false false Any of the values at once.
attributes true or false false (Maps, flags) name the cells' 8 attribute bits with it.

dp.types.class{ } - Make a class

Makes (or changes) a class for properties: named members with their default values, written "count:int=3; kind:Kind=Enemy; first:Spawn" (a member of a class type takes that class's members).

Changes things (one undo step). By name: "types.class". For AI: types_class.

Setting Type Needed / default Range or choices What it is
name text needed Its name.
members text needed Its members (see above).

dp.types.remove{ } - Remove an enum or class

Takes an enum or class away (properties of that type keep their values).

Changes things (one undo step). By name: "types.remove". For AI: types_remove.

Setting Type Needed / default Range or choices What it is
name text needed Its name.

voxel

dp.voxel.open_layers{ } - Open voxels as layers

Opens a MagicaVoxel .vox model as a new picture, a layer for each slice (the bottom layer the lowest; up to 32), in its colours. (Opening a .vox otherwise shows it as a 3D model.)

Changes things (one undo step). By name: "voxel.open_layers". For AI: voxel_open_layers.

Setting Type Needed / default Range or choices What it is
path file path needed The .vox file.

dp.voxel.save_layers{ } - Save layers as voxels

Saves the picture in front's layers (those shown) as a MagicaVoxel .vox model, a layer a slice (the bottom layer the lowest), up to 256 x 256: palette pictures, the colours kept.

Changes things (one undo step). By name: "voxel.save_layers". For AI: voxel_save_layers.

Setting Type Needed / default Range or choices What it is
path file path needed The .vox file to write.

dp.voxel.layers_to_model() - Layers as a 3D model

The picture in front's layers (those shown) as a voxel model in the 3D window, a layer a slice: turn and light it, render frames, or put it on a live 3D layer.

Changes things (one undo step). By name: "voxel.layers_to_model". For AI: voxel_layers_to_model.

No settings.

Luau in Deluxe Pixel

  • Luau: luau-lang/luau commit 1eca9fda3e4753a1592000f6cfdf659aaa778b7d (6 October 2026): Common, Ast, Bytecode, Compiler, VM and CodeGen. MIT licence (Roblox; parts Lua.org, PUC-Rio).
  • Native code: Run (Ctrl+Enter) compiles a script to the computer's own machine code (Luau's code generator) when the processor supports it (x64 with SSE4.1, or ARM64), so a script's own maths, loops, tables and buffers run several times faster than interpreted; commands take the same time either way. Script console > Options > Native code turns it off. Debugging always uses the interpreter (breakpoints and stepping need it).
  • Compiled with: optimisation level 1 (level 0 when debugging, so every line is where it's written), full debug information (line numbers and local variables' names, for errors and the debugger).
  • Language: all of Luau: continue, compound assignment (+=, -=, *=, /=, //=, %=, ^=, ..=), if ... then ... else expressions, string interpolation with backticks (`x is {x}`), floor division //, generalised iteration (for k, v in t do), numbers with _ separators and 0b binary, type annotations (local n: number, type Point = { x: number }; the console's checks read them, they don't slow anything).

Libraries

What a script can use - everything listed is there in this build:

  • bit32: arshift, band, bnot, bor, btest, bxor, byteswap, countlz, countrz, extract, lrotate, lshift, replace, rrotate, rshift
  • buffer: copy, create, fill, fromstring, len, readbits, readf32, readf64, readi16, readi32, readi8, readinteger, readstring, readu16, readu32, readu8, tostring, writebits, writef32, writef64, writei16, writei32, writei8, writeinteger, writestring, writeu16, writeu32, writeu8
  • coroutine: close, create, isyieldable, resume, running, status, wrap, yield
  • debug (only looking: no hooks or changing variables): info, traceback
  • integer: add, arshift, band, bnot, bor, bswap, btest, bxor, clamp, countlz, countrz, create, div, extract, fromstring, ge, gt, idiv, le, lrotate, lshift, lt, max, maxsigned, min, minsigned, mod, mul, neg, rem, replace, rrotate, rshift, sub, tonumber, udiv, uge, ugt, ule, ult, urem
  • math: abs, acos, asin, atan, atan2, ceil, clamp, cos, cosh, deg, e, exp, floor, fmod, frexp, huge, isfinite, isinf, isnan, ldexp, lerp, log, log10, map, max, min, modf, nan, noise, phi, pi, pow, rad, random, randomseed, round, sign, sin, sinh, sqrt, sqrt2, tan, tanh, tau
  • os (no files, programs or environment: only the time): clock, date, difftime, time
  • string: byte, char, find, format, gmatch, gsub, len, lower, match, pack, packsize, rep, reverse, split, sub, unpack, upper
  • table: clear, clone, concat, create, find, foreach, foreachi, freeze, getn, insert, isfrozen, maxn, move, pack, remove, sort, unpack
  • utf8: char, charpattern, codepoint, codes, len, offset
  • vector: abs, angle, ceil, clamp, create, cross, dot, floor, lerp, magnitude, max, min, normalize, one, sign, zero
  • Built in (globals): _G, _VERSION, assert, error, gcinfo, getfenv, getmetatable, ipairs, newproxy, next, pairs, pcall, print, rawequal, rawget, rawlen, rawset, select, setfenv, setmetatable, tonumber, tostring, type, typeof, unpack, xpcall

Left out on purpose

  • Not there: io, require, load, loadstring, dofile, loadfile, module, package, collectgarbage.
  • The sandbox: scripts can't read or write files, use the network, start programs, or load other code. The libraries and the dp table are read-only (a script can't replace math.floor or dp.width). Each run starts with fresh globals: nothing carries over from one run to the next.
  • Limits: a run is stopped after 30 seconds (time paused in the debugger doesn't count), or when Escape is pressed.
  • Not built in: Luau's type checker and linter (Analysis) - the console's own checks cover the mistakes that matter for Deluxe Pixel's scripts - and require (loading other script files).

Luau's optional features in this build

Luau keeps new features and fixes behind switches ("fast flags"), off unless the program using it turns them on. Deluxe Pixel turns on every one that Luau's own tools do: all but the experimental ones. In this build:

58 of 60 on.

Feature On
LuauAllowGlobalDeclarationToBeCalledClass yes
LuauBackedgeHeapCheck yes
LuauBufferCage yes
LuauBytecodeCostModel yes
LuauCallFeedback yes
LuauCallLuauTm yes
LuauCodeGenFastpcall yes
LuauCodegenA64ForgLoopArray yes
LuauCodegenBufferInteger yes
LuauCodegenConstPropMinOffset yes
LuauCodegenInteger3 yes
LuauCodegenIntegerCompare yes
LuauCodegenLimitVersions yes
LuauCodegenNoLinearFastpcall yes
LuauCodegenNopPadding yes
LuauCodegenPropagateFallbackTags yes
LuauCodegenX64IntSpillRestore yes
LuauCompileCleanBlockDeadClose yes
LuauCompileConcatTargetTop yes
LuauCompileEmitVectorDouble yes
LuauCompileFastpcall yes
LuauCompileLoopUnrollZero yes
LuauCompileMoveElision yes
LuauCompileNoFoldVectorEqW yes
LuauCompileRecursiveAliases yes
LuauCompileRefactorFeedback yes
LuauCompileReuseLocalRegs yes
LuauCompileUndoEmitAdjust yes
LuauCostModel yes
LuauEasyStateInit yes
LuauEmitCallFeedback yes
LuauEnumMoreEdges yes
LuauExperimentalIfLocalSyntax no (experimental)
LuauExportValueSyntax yes
LuauExportedTypesParticipateInScc yes
LuauFastpcall yes
LuauFastpcallInterrupt yes
LuauFrozenMetaButterfly yes
LuauGcHeapShrinkFix yes
LuauGcTraceUdata yes
LuauIntegerBufferFastcalls yes
LuauIntegerFastcalls yes
LuauIntegerLibrary yes
LuauIntegerType2 yes
LuauLoadRemapOptionalUserdata yes
LuauNewPointerEncode yes
LuauOptimizeExportTable yes
LuauOptimizeStringGsub yes
LuauOptimizeStringSplit yes
LuauPcallOptimize yes
LuauPromoteProto yes
LuauSandboxFreezesVectorMetatable yes
LuauSingleTypeOptionalPackReturnsAttributeParens yes
LuauSolverV2 no (experimental)
LuauSplitTableLookups yes
LuauTableArrayAdjustCheck yes
LuauTableArrayShrinkOrder yes
LuauTableMoveTimeoutFix yes
LuauTableRobustOom yes
LuauVirtualBcBuilder yes

The script console

  • Editor: Luau colours, line numbers, bracket matching, find and replace (Ctrl+F), several cursors (Ctrl+D selects the next match), comment lines (Ctrl+/), indent (Ctrl+] / Ctrl+[), undo and redo.
  • Checks as you type: syntax errors (Luau's own parser), and the mistakes that would only show when run: a misspelt command or group ("dp.draw.rec isn't a command. Did you mean dp.draw.rect?"), a setting a command doesn't have, settings it needs, a value of the wrong kind or out of range, a misspelt library function (math.flor), a global that's never set (pirnt), a local that's never used. Underlined, marked by the line number, listed under Problems.
  • Autocomplete (as you type, or Ctrl+Space): after dp. the groups and helpers, after dp.draw. its commands, inside a command's { } its settings, after math. (any library) its functions, otherwise keywords, globals and the script's own names.
  • Hover help: a command's description and settings, a setting's meaning inside { }, a helper's use; while paused, a variable's value.
  • Errors: the line is highlighted red with the message; the Output shows where it was called from, each line clickable.
  • Layout: the editor and the Output, Problems, Variables and Call stack panels are docked windows: drag the split, reorder the tabs, or drag a panel anywhere (Options > Reset the console's layout puts them back).

The debugger

Key
Ctrl+Enter, Ctrl+F5 Run (native code, no stopping)
F5 Debug: run, stopping at breakpoints; Continue when paused
F9, or click left of a line number Breakpoint on or off
F10 Step over (when nothing's running: start, stopped at the first line)
F11 Step into
Shift+F11 Step out
Shift+F5 Stop the paused script
Pause, or F6 Pause a debugged script where it is (even an endless loop)
Escape Stop any running script

While paused: the line is highlighted, Variables shows the local variables (and the globals the script made, tables shown in brief), Call stack shows the functions it's in (click one for its variables), and hovering a variable shows its value. The picture can be looked at meanwhile; each stretch the script runs between pauses is one undo step.

Events: scripts that keep running

Most scripts run once, top to bottom. A script can instead give Deluxe Pixel a function to call when something happens - every frame, a key, the mouse over the picture - with dp.on, or start a task that runs alongside with dp.spawn. Then it keeps running after its last line: the menu bar says Running: name (Stop), and Escape, the script console's Stop, Edit > Stop running scripts, dp.stop() or running it again ends it. Plugins use plugin.on (the same) and their handlers last while the plugin is loaded.

local x = 0
dp.on("frame", function(dt, t)            -- every frame, about 60 a second
    x = (x + 1) % dp.width()
    dp.draw.pixel{ x = x, y = 10, colour = 2 }
end)
dp.on("key", function(e)
    if e.key == "Space" then print("jump!") return true end   -- true: used up (the tools don't see it)
end)
dp.spawn(function()                         -- a task: it can wait without holding anything up
    for i = 1, 3 do dp.wait(1) print("tick", i) end
end)
  • Each call has a quarter of a second at most; a handler that fails or takes longer stops its script (the error goes to the console's Output).
  • What handlers change is one undo step, joined frame after frame while nothing else happens: one Undo takes back a whole run.
  • The events, and what the handler is given:
Event The handler gets When
frame dt, t Every frame (about 60 a second): dt is the seconds since the last frame, t the seconds since the script started.
key e: key, ctrl, shift, alt A key went down (not while typing in a box). e.key is its name: "A", "7", "Space", "Enter", "Left", "F5"... Return true and Deluxe Pixel doesn't act on it.
keyup e: key, ctrl, shift, alt A key was let go.
mouse e: x, y, left, right, middle, pressed, released, wheel The pointer moved over the picture, a button went down or up, or the wheel turned. x, y are the picture's pixels; pressed / released name the button ("left", "right", "middle"). Return true on a press and the tool doesn't paint.
pad e: pad, button, pressed, connected A game controller's button went down (pressed = true) or up (false), or a controller was plugged in or out (connected = true / false; button is nil). e.pad is its number for dp.pad.
picture e: name, width, height, map Another picture (or map) became the active one.
layer e: index, name Another layer became the active one (0 is the bottom).
colour e: left, right The left or right colour changed.
palette e: colours The active picture's palette changed.
selection e: empty, x, y, w, h The selection changed (x, y, w, h: the box round it).
open e: path A file was opened.
save e: path The active picture was saved.
stop nothing This script (or plugin) is stopping: tidy up. (Its last chance: it can't start anything new.)

Plugins

A plugin is Luau that adds to Deluxe Pixel itself - menu items, panels and filters - rather than running once. Plugins > Manage plugins turns them on and off, reloads them after editing, makes a new one from a template and adds the examples; Plugins > Open the plugins folder shows where they live (plugins in the settings folder).

A plugin's files

Each plugin is a folder with two files:

plugins/star-tools/plugin.json
plugins/star-tools/main.luau

plugin.json says who it is (all optional; main defaults to main.luau):

{
    "name": "Star tools",
    "version": "1.0",
    "author": "You",
    "description": "What it does.",
    "main": "main.luau"
}

main.luau runs once when the plugin loads. Unlike a script, what it makes lives on while the plugin is loaded: its local variables and functions keep their values between calls (a panel's settings, say). It has everything a script has (dp.*, the libraries, the same sandbox) plus the plugin table. Saving main.luau in the script console reloads the plugin; so does Reload in Manage plugins.

plugin.menu

plugin.menu{ label = "Scatter stars", help = "Scatters stars on the active layer.", run = function() ... end }

A menu item in the Plugins menu (a plugin with several is a submenu named after it). It's a command too, named plugin.<plugin>_<label> (plugin.star_tools_scatter_stars), so the command palette, macros, scripts (dp.plugin.star_tools_scatter_stars()) and AI assistants can run it. Each run is one undo step; it may run for up to 30 seconds (Escape stops it).

plugin.panel

local count = 200
plugin.panel{
    title = "Stars",
    draw = function(ui)
        count = ui.slider_int("How many", count, 10, 2000)
        if ui.button("Scatter stars") then scatter() end
    end,
}

A window of its own (dockable anywhere; Plugins > Panels shows or hides it). draw runs every frame and lays out the panel with ui's widgets, which give back their values: keep them in the plugin's variables. A draw that fails, or takes over half a second, stops the panel and shows why (Try again, or reload the plugin).

Widget Gives back
ui.text(...) - (text, wrapped)
ui.button(label) true when pressed
ui.checkbox(label, value) the value (true or false)
ui.slider(label, value, min, max) the value (a number)
ui.slider_int(label, value, min, max) the value (a whole number)
ui.colour(label, index) a palette colour number (a swatch that opens the palette)
ui.choice(label, index, { "a", "b", ... }) the chosen item's number (1 first)
ui.input(label, text) the text
ui.separator() - (a line)
ui.same_line() - (the next widget goes on the same line)

plugin.filter

plugin.filter{
    name = "Scanlines",
    help = "Every few rows in one colour.",
    settings = {
        { name = "colour", label = "Line colour", type = "colour", default = 0 },
        { name = "every", label = "Every", type = "int", min = 2, max = 8, default = 2 },
    },
    apply = function(px, w, h, s, info)
        for y = 0, h - 1, s.every do
            for x = 0, w - 1 do buffer.writeu8(px, y * w + x, s.colour) end
        end
    end,
}

A filter in Image > Plugin filters (and Plugins > Filters): a dialog with its settings and a live preview, then Apply (one undo step) or Cancel. It's a command too, its settings as the command's (checked the same way).

  • Settings: each { name, label, help, type, min, max, default }; type is number, int, bool, colour (a palette colour number, 0 to 255) or choice (with choices = { "...", ... }, the default by its text).
  • apply(px, w, h, s, info): px is the active layer as a buffer, laid out as dp.pixels() gives it (1 byte a pixel, the colour number; 4 for true colour, R G B A); w, h its size; s the settings by name (numbers, true/false, or a choice's text); info.truecolour, info.colours (how many in the palette), info.bytes (1 or 4). Change px in place, or give back a new buffer the same size. Deluxe Pixel writes the result inside the selection, if there is one. error("...") stops it with that message.

The example plugins

Manage plugins > Add the example plugins puts these in the plugins folder:

star-tools/plugin.json

{
    "name": "Star tools",
    "version": "1.0",
    "author": "Kage Games",
    "description": "A Stars panel to scatter stars in a range of colours, and the same as a menu item (and a command)."
}

star-tools/main.luau

-- Star tools: a panel and a menu item. This file runs once when the plugin loads; these locals keep the
-- panel's settings for as long as it's loaded.
local count = 200
local first, last = 13, 15
local crosses = true

local function scatter()
    local w, h = dp.width(), dp.height()
    local lo, hi = math.min(first, last), math.max(first, last)
    for _ = 1, count do
        local x, y = math.random(0, w - 1), math.random(0, h - 1)
        local c = math.random(lo, hi)
        dp.draw.pixel{ x = x, y = y, colour = c }
        -- now and then a bigger one: a little cross
        if crosses and math.random() < 0.08 and x > 0 and y > 0 and x < w - 1 and y < h - 1 then
            dp.draw.line{ x0 = x - 1, y0 = y, x1 = x + 1, y1 = y, colour = c }
            dp.draw.line{ x0 = x, y0 = y - 1, x1 = x, y1 = y + 1, colour = c }
        end
    end
end

plugin.panel{
    title = "Stars",
    draw = function(ui)
        count = ui.slider_int("How many", count, 10, 2000)
        first = ui.colour("From colour", first)
        last = ui.colour("To colour", last)
        crosses = ui.checkbox("Some as little crosses", crosses)
        if ui.button("Scatter stars") then scatter() end
        ui.same_line()
        ui.text("(on the active layer)")
    end,
}

plugin.menu{
    label = "Scatter stars",
    help = "Scatters stars on the active layer, with the Stars panel's settings.",
    run = scatter,
}

retro-filters/plugin.json

{
    "name": "Retro filters",
    "version": "1.0",
    "author": "Kage Games",
    "description": "Scanlines and a colour shift, written in Luau: each gets the whole layer at once as a buffer."
}

retro-filters/main.luau

-- Retro filters: filters get the active layer as a buffer (as dp.pixels gives it: 1 byte a pixel, the colour
-- number, or 4 for a true-colour picture, R G B A), its size, the settings and a little about the picture.
-- They change the buffer (or give back a new one); Deluxe Pixel writes it back as one undo step, inside the
-- selection if there is one. Image > Plugin filters shows them with their settings and a live preview.

plugin.filter{
    name = "Scanlines",
    help = "Every few rows in one colour (or darker, in true colour), like an old TV.",
    settings = {
        { name = "colour", label = "Line colour", type = "colour", default = 0 },
        { name = "every", label = "Every", type = "int", min = 2, max = 8, default = 2 },
        { name = "darken", label = "True colour: darker by %", type = "int", min = 0, max = 100, default = 50 },
    },
    apply = function(px, w, h, s, info)
        local keep = 1 - s.darken / 100
        for y = 0, h - 1, s.every do
            for x = 0, w - 1 do
                if info.truecolour then
                    local o = (y * w + x) * 4
                    for c = 0, 2 do
                        buffer.writeu8(px, o + c, math.floor(buffer.readu8(px, o + c) * keep))
                    end
                else
                    buffer.writeu8(px, y * w + x, s.colour)
                end
            end
        end
    end,
}

plugin.filter{
    name = "Colour shift",
    help = "Shifts the colour numbers in a range round by an amount (palette pictures): frames for colour cycling.",
    settings = {
        { name = "first", label = "First colour", type = "colour", default = 16 },
        { name = "last", label = "Last colour", type = "colour", default = 31 },
        { name = "by", label = "Shift by", type = "int", min = -32, max = 32, default = 1 },
    },
    apply = function(px, w, h, s, info)
        if info.truecolour then error("Colour shift is for palette pictures.") end
        local lo, hi = math.min(s.first, s.last), math.max(s.first, s.last)
        local span = hi - lo + 1
        for i = 0, w * h - 1 do
            local c = buffer.readu8(px, i)
            if c >= lo and c <= hi then buffer.writeu8(px, i, lo + (c - lo + s.by) % span) end
        end
    end,
}

layer-tools/plugin.json

{
    "name": "Layer tools",
    "version": "1.0",
    "author": "Kage Games",
    "description": "Menu items that work on the whole layer at once with dp.pixels and dp.set_pixels."
}

layer-tools/main.luau

-- Layer tools: dp.pixels() gives the whole active layer as a buffer; dp.set_pixels() writes one back (one undo
-- step, inside the selection). Much faster than a command a pixel.

plugin.menu{
    label = "Mirror the left half",
    help = "Copies the left half of the active layer onto the right, mirrored: symmetry.",
    run = function()
        local px, w, h = dp.pixels()
        local bytes = buffer.len(px) // (w * h)
        for y = 0, h - 1 do
            for x = 0, w // 2 - 1 do
                buffer.copy(px, (y * w + (w - 1 - x)) * bytes, px, (y * w + x) * bytes, bytes)
            end
        end
        dp.set_pixels(px)
    end,
}

plugin.menu{
    label = "Colour usage",
    help = "Prints how many pixels use each colour on the active layer (the script console's Output).",
    run = function()
        local px, w, h = dp.pixels()
        if buffer.len(px) ~= w * h then
            print("Colour usage is for palette pictures.")
            return
        end
        local counts = table.create(256, 0)
        for i = 0, w * h - 1 do
            local c = buffer.readu8(px, i) + 1
            counts[c] += 1
        end
        for c = 1, 256 do
            if counts[c] > 0 then print(`colour {c - 1} {dp.colour(c - 1)}: {counts[c]} pixels`) end
        end
    end,
}

Examples

The console's examples (File > Examples), by category. Those marked needs a map want a tile map in the active tab.

Basics

Hello, picture

-- What's open: the picture, its palette and layers. dp.info.* answer as text;
-- dp.width() and friends answer as numbers.
print(dp.info.picture())
print(`{dp.width()} x {dp.height()} pixels, {dp.colours()} colours, {dp.layers()} layers`)
print(`Colour 1 is {dp.colour(1)}; the pixel at 0, 0 is colour {dp.pixel(0, 0)}`)

Shapes

-- Rectangles, ellipses and a line, outlined and filled.
local w, h = dp.width(), dp.height()
dp.draw.rect{ x = 2, y = 2, w = w // 3, h = h // 3, colour = 1 }
dp.draw.rect{ x = 6, y = 6, w = w // 3 - 8, h = h // 3 - 8, colour = 2, filled = true }
dp.draw.ellipse{ cx = w // 2, cy = h // 2, rx = h // 4, colour = 3 }
dp.draw.ellipse{ cx = w * 3 // 4, cy = h * 3 // 4, rx = w // 8, ry = h // 10, colour = 4, filled = true }
dp.draw.line{ x0 = 0, y0 = h - 1, x1 = w - 1, y1 = 0, colour = 5 }

Starburst

-- Lines from the middle out past every edge, cycling through colours 1 to 15.
local cx, cy = dp.width() // 2, dp.height() // 2
local reach = math.max(dp.width(), dp.height())
local n = 48
for i = 0, n - 1 do
    local a = i / n * math.pi * 2
    dp.draw.line{ x0 = cx, y0 = cy, x1 = cx + math.floor(math.cos(a) * reach), y1 = cy + math.floor(math.sin(a) * reach), colour = 1 + i % 15 }
end

Rings

-- Filled circles from the biggest down, each a different colour: a target.
local cx, cy = dp.width() // 2, dp.height() // 2
for r = math.min(cx, cy) - 1, 1, -3 do
    dp.draw.ellipse{ cx = cx, cy = cy, rx = r, colour = 1 + (r // 3) % 15, filled = true }
end

Flood fill

-- Outlines, then a flood fill inside each: a fill takes the area of one colour around a point.
local w, h = dp.width(), dp.height()
dp.draw.rect{ x = 2, y = 2, w = w // 3, h = h // 2, colour = 1 }
dp.draw.ellipse{ cx = w * 2 // 3, cy = h // 2, rx = w // 6, ry = h // 4, colour = 2 }
dp.draw.fill{ x = 4, y = 4, colour = 3 }
dp.draw.fill{ x = w * 2 // 3, y = h // 2, colour = 4 }

Checkerboard

-- A checkerboard of colours 1 and 2 in 8 x 8 squares, on a new layer.
dp.layer.add{ name = "Checkerboard" }
for y = 0, dp.height() - 1, 8 do
    for x = 0, dp.width() - 1, 8 do
        local c = ((x + y) // 8) % 2 + 1
        dp.draw.rect{ x = x, y = y, w = 8, h = 8, colour = c, filled = true }
    end
end

Patterns

Diagonal stripes

-- Stripes 4 pixels wide at 45 degrees, in colours 1 to 4.
local w, h = dp.width(), dp.height()
for i = -h, w - 1 do
    dp.draw.line{ x0 = i, y0 = 0, x1 = i + h - 1, y1 = h - 1, colour = 1 + (i // 4) % 4 }
end

Dithered blend

-- Colour 1 blending into colour 2 across the picture with a 4 x 4 Bayer (ordered) dither:
-- the way 8-bit games faked gradients with two colours.
local bayer = { 0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5 }
local w, h = dp.width(), dp.height()
for y = 0, h - 1 do
    for x = 0, w - 1 do
        local level = x / math.max(1, w - 1) * 16
        local threshold = bayer[(y % 4) * 4 + x % 4 + 1]
        dp.draw.pixel{ x = x, y = y, colour = if level > threshold then 2 else 1 }
    end
end

Bricks

-- A brick wall: mortar first, then bricks with every other row offset by half a brick.
local bw, bh = 16, 8
local w, h = dp.width(), dp.height()
dp.draw.rect{ x = 0, y = 0, w = w, h = h, colour = 2, filled = true }
for row = 0, h // bh do
    local offset = if row % 2 == 0 then 0 else bw // 2
    for col = -1, w // bw do
        dp.draw.rect{ x = col * bw + offset + 1, y = row * bh + 1, w = bw - 1, h = bh - 1, colour = 3 + (row + col) % 2, filled = true }
    end
end

Polka dots

-- Dots on a staggered grid.
local step = 12
local row = 0
for y = step // 2, dp.height() - 1, step do
    local start = if row % 2 == 0 then step // 2 else step
    for x = start, dp.width() - 1, step do
        dp.draw.ellipse{ cx = x, cy = y, rx = 3, colour = 5, filled = true }
    end
    row += 1
end

Tartan

-- Bands across and down, woven: where they cross the colours alternate pixel by pixel.
local bands = { { at = 0, size = 10, colour = 4 }, { at = 14, size = 4, colour = 2 }, { at = 22, size = 8, colour = 6 } }
local period = 32
local function band(p: number): number
    for _, b in bands do
        if p % period >= b.at and p % period < b.at + b.size then return b.colour end
    end
    return 1
end
for y = 0, dp.height() - 1 do
    for x = 0, dp.width() - 1 do
        dp.draw.pixel{ x = x, y = y, colour = if (x + y) % 2 == 0 then band(x) else band(y) }
    end
end

Stars

-- Scatter 200 single-pixel stars in colours 13 to 15.
for _ = 1, 200 do
    dp.draw.pixel{ x = math.random(0, dp.width() - 1), y = math.random(0, dp.height() - 1), colour = math.random(13, 15) }
end

Palette

Colour ramp

-- Blend colours 16 to 31 from dark blue to pale yellow, then show them as bars.
dp.palette.set{ index = 16, colour = "#102040" }
dp.palette.set{ index = 31, colour = "#F0E8A0" }
dp.palette.ramp{ from = 16, to = 31 }
local bw = math.max(1, dp.width() // 16)
for i = 0, 15 do
    dp.draw.rect{ x = i * bw, y = 0, w = bw, h = dp.height(), colour = 16 + i, filled = true }
end

Rainbow

-- Colours 16 to 31 all the way round the colour wheel (hue, saturation, value to #RRGGBB).
local function hsv(hue: number, sat: number, val: number): string
    local i = math.floor(hue * 6)
    local f = hue * 6 - i
    local p, q, t = val * (1 - sat), val * (1 - f * sat), val * (1 - (1 - f) * sat)
    local r, g, b = val, t, p
    i %= 6
    if i == 1 then r, g, b = q, val, p
    elseif i == 2 then r, g, b = p, val, t
    elseif i == 3 then r, g, b = p, q, val
    elseif i == 4 then r, g, b = t, p, val
    elseif i == 5 then r, g, b = val, p, q end
    return string.format("#%02X%02X%02X", math.floor(r * 255 + 0.5), math.floor(g * 255 + 0.5), math.floor(b * 255 + 0.5))
end
for i = 0, 15 do
    dp.palette.set{ index = 16 + i, colour = hsv(i / 16, 0.8, 1) }
end
local bw = math.max(1, dp.width() // 16)
for i = 0, 15 do
    dp.draw.rect{ x = i * bw, y = 0, w = bw, h = dp.height(), colour = 16 + i, filled = true }
end

Sunset sky

-- A sky gradient from several key colours: ramps between them, then one band of each colour down
-- the picture.
local keys = { { 48, "#1A0B2E" }, { 54, "#5B2A6E" }, { 58, "#C2456B" }, { 61, "#F2865A" }, { 63, "#FAD47A" } }
for i, k in keys do
    dp.palette.set{ index = k[1], colour = k[2] }
    if i > 1 then dp.palette.ramp{ from = keys[i - 1][1], to = k[1] } end
end
local h = dp.height()
for y = 0, h - 1 do
    dp.draw.line{ x0 = 0, y0 = y, x1 = dp.width() - 1, y1 = y, colour = 48 + math.floor(y / h * 16) }
end

Grey ramp

-- Colours 32 to 47 as greys from black to white.
dp.palette.set{ index = 32, colour = "#000000" }
dp.palette.set{ index = 47, colour = "#FFFFFF" }
dp.palette.ramp{ from = 32, to = 47 }
print(dp.info.palette())

Show the palette

-- Every palette colour as a swatch, 16 to a row.
local n = dp.colours()
local size = math.max(2, math.min(dp.width() // 16, dp.height() // math.ceil(n / 16)))
for i = 0, n - 1 do
    dp.draw.rect{ x = (i % 16) * size, y = (i // 16) * size, w = size, h = size, colour = i, filled = true }
end

Count colours

-- How many pixels of each colour the active layer uses.
local counts = {}
for y = 0, dp.height() - 1 do
    for x = 0, dp.width() - 1 do
        local c = dp.pixel(x, y)
        counts[c] = (counts[c] or 0) + 1
    end
end
for c, n in counts do print(`colour {c} {dp.colour(c)}: {n} pixels`) end

Unused colours

-- The palette colours the active layer doesn't use (free for something else).
local used = {}
for y = 0, dp.height() - 1 do
    for x = 0, dp.width() - 1 do
        used[dp.pixel(x, y)] = true
    end
end
local unused = {}
for i = 0, dp.colours() - 1 do
    if not used[i] then table.insert(unused, i) end
end
print(`{#unused} of {dp.colours()} colours aren't used on the active layer:`)
print(table.concat(unused, ", "))

Swap two colours

-- Colours 1 and 2 swap places on the active layer.
local a, b = 1, 2
for y = 0, dp.height() - 1 do
    for x = 0, dp.width() - 1 do
        local c = dp.pixel(x, y)
        if c == a then dp.draw.pixel{ x = x, y = y, colour = b }
        elseif c == b then dp.draw.pixel{ x = x, y = y, colour = a } end
    end
end

Generative

String art

-- Straight lines between points round a circle make a curve: point i to point 2i is a cardioid.
local cx, cy = dp.width() // 2, dp.height() // 2
local r = math.min(cx, cy) - 2
local n = 120
local function at(i: number): (number, number)
    local a = i / n * math.pi * 2
    return cx + math.floor(math.cos(a) * r + 0.5), cy + math.floor(math.sin(a) * r + 0.5)
end
for i = 0, n - 1 do
    local x0, y0 = at(i)
    local x1, y1 = at(i * 2)
    dp.draw.line{ x0 = x0, y0 = y0, x1 = x1, y1 = y1, colour = 1 + i % 15 }
end

Sierpinski triangle

-- The chaos game: jump halfway to a random corner, again and again, and a fractal appears.
local w, h = dp.width(), dp.height()
local corners = { { w // 2, 0 }, { 0, h - 1 }, { w - 1, h - 1 } }
local x, y = w / 2, h / 2
for i = 1, 20000 do
    local c = corners[math.random(1, 3)]
    x, y = (x + c[1]) / 2, (y + c[2]) / 2
    if i > 10 then dp.draw.pixel{ x = math.floor(x), y = math.floor(y), colour = 1 + i % 3 } end
end

Mandelbrot

-- The Mandelbrot set, coloured by how quickly each point escapes. The maths runs as native code
-- (Run, Ctrl+Enter): try Debug to see the difference the interpreter makes.
local w, h = dp.width(), dp.height()
local maxIter = 32
for py = 0, h - 1 do
    for px = 0, w - 1 do
        local cr = px / w * 3.5 - 2.5
        local ci = py / h * 2.4 - 1.2
        local zr, zi, n = 0, 0, 0
        while zr * zr + zi * zi < 4 and n < maxIter do
            zr, zi = zr * zr - zi * zi + cr, 2 * zr * zi + ci
            n += 1
        end
        dp.draw.pixel{ x = px, y = py, colour = if n == maxIter then 0 else 1 + n % 15 }
    end
end

Plasma

-- Old-school demo plasma: sine waves added together, mapped onto colours 1 to 15.
local w, h = dp.width(), dp.height()
for y = 0, h - 1 do
    for x = 0, w - 1 do
        local v = math.sin(x / 8) + math.sin(y / 6) + math.sin((x + y) / 10) + math.sin(math.sqrt(x * x + y * y) / 8)
        dp.draw.pixel{ x = x, y = y, colour = 1 + math.floor((v + 4) / 8 * 15) % 15 }
    end
end

Maze

-- A maze carved by a random walk that backs up when it's stuck (recursive backtracking),
-- on a grid of 4-pixel cells.
local cell = 4
local cols, rows = (dp.width() - 1) // (cell * 2), (dp.height() - 1) // (cell * 2)
dp.draw.rect{ x = 0, y = 0, w = dp.width(), h = dp.height(), colour = 1, filled = true }
local seen = {}
local function key(c: number, r: number): number return r * cols + c end
local function open(c: number, r: number)
    dp.draw.rect{ x = (c * 2 + 1) * cell, y = (r * 2 + 1) * cell, w = cell, h = cell, colour = 2, filled = true }
end
local stack = { { 0, 0 } }
seen[key(0, 0)] = true
open(0, 0)
while #stack > 0 do
    local top = stack[#stack]
    local c, r = top[1], top[2]
    local choices = {}
    for _, d in { { 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 } } do
        local nc, nr = c + d[1], r + d[2]
        if nc >= 0 and nc < cols and nr >= 0 and nr < rows and not seen[key(nc, nr)] then
            table.insert(choices, { nc, nr })
        end
    end
    if #choices == 0 then
        table.remove(stack)
    else
        local n = choices[math.random(1, #choices)]
        seen[key(n[1], n[2])] = true
        -- knock through the wall between the two cells, then step in
        dp.draw.rect{ x = (c + n[1] + 1) * cell, y = (r + n[2] + 1) * cell, w = cell, h = cell, colour = 2, filled = true }
        open(n[1], n[2])
        table.insert(stack, n)
    end
end

Mountains

-- Four ranges of hills, back to front, each made by midpoint displacement: halve the gaps,
-- nudge each new point up or down a bit less each time.
local w, h = dp.width(), dp.height()
local function ridge(base: number, rough: number): { number }
    local size = 1
    while size < w do size *= 2 end
    local pts = table.create(size + 1, base)
    pts[1], pts[size + 1] = base + math.random(-rough, rough), base + math.random(-rough, rough)
    local step, amount = size, rough
    while step > 1 do
        local half = step // 2
        for i = 1, size, step do
            pts[i + half] = (pts[i] + pts[i + step]) / 2 + math.random(-amount, amount)
        end
        step = half
        amount = math.max(1, amount // 2)
    end
    return pts
end
for range = 0, 3 do
    local pts = ridge(h * (0.35 + range * 0.15), math.max(1, h // 5))
    for x = 0, w - 1 do
        local top = math.clamp(math.floor(pts[x + 1]), 0, h - 1)
        dp.draw.line{ x0 = x, y0 = top, x1 = x, y1 = h - 1, colour = 4 + range }
    end
end

Lissajous

-- A Lissajous figure: x and y follow sine waves at different speeds (3 : 4).
local cx, cy = dp.width() / 2, dp.height() / 2
local px, py = -1, -1
for i = 0, 2000 do
    local t = i / 2000 * math.pi * 2
    local x = math.floor(cx + math.sin(3 * t + math.pi / 2) * (cx - 2))
    local y = math.floor(cy + math.sin(4 * t) * (cy - 2))
    if i > 0 then dp.draw.line{ x0 = px, y0 = py, x1 = x, y1 = y, colour = 1 + (i // 200) % 15 } end
    px, py = x, y
end

Voronoi cells

-- Each pixel takes the colour of its nearest seed point: cells like cracked mud or stained glass.
-- (Uses Luau's vector type for the distances.)
local w, h = dp.width(), dp.height()
local seeds = {}
for i = 1, 12 do seeds[i] = vector.create(math.random(0, w - 1), math.random(0, h - 1), 0) end
for y = 0, h - 1 do
    for x = 0, w - 1 do
        local p = vector.create(x, y, 0)
        local best, bestDistance = 1, math.huge
        for i, s in seeds do
            local d = vector.magnitude(p - s)
            if d < bestDistance then best, bestDistance = i, d end
        end
        dp.draw.pixel{ x = x, y = y, colour = best }
    end
end

Game of Life

-- One step of Conway's Game of Life on the active layer: any colour but 0 is alive. Run it
-- again for the next generation (the picture wraps round at the edges).
local w, h = dp.width(), dp.height()
local alive = {}
for y = 0, h - 1 do
    for x = 0, w - 1 do alive[y * w + x] = dp.pixel(x, y) ~= 0 end
end
for y = 0, h - 1 do
    for x = 0, w - 1 do
        local n = 0
        for dy = -1, 1 do
            for dx = -1, 1 do
                if (dx ~= 0 or dy ~= 0) and alive[((y + dy) % h) * w + (x + dx) % w] then n += 1 end
            end
        end
        local now = alive[y * w + x]
        local live = n == 3 or (now and n == 2)
        if live ~= now then dp.draw.pixel{ x = x, y = y, colour = if live then 1 else 0 } end
    end
end

Spiral

-- Points round a widening spiral, each a dot of the next colour.
local cx, cy = dp.width() / 2, dp.height() / 2
for i = 0, 600 do
    local a = i / 20
    local r = i / 600 * math.min(cx, cy)
    dp.draw.pixel{ x = math.floor(cx + math.cos(a) * r), y = math.floor(cy + math.sin(a) * r), colour = 1 + i % 15 }
end

Layers and animation

A scene in layers

-- Three layers, each with its own part of a scene: hide or move them afterwards.
local w, h = dp.width(), dp.height()
dp.layer.add{ name = "Sky" }
dp.draw.rect{ x = 0, y = 0, w = w, h = h, colour = 6, filled = true }
dp.layer.add{ name = "Hills" }
dp.draw.ellipse{ cx = w // 4, cy = h, rx = w // 3, ry = h // 3, colour = 5, filled = true }
dp.draw.ellipse{ cx = w * 3 // 4, cy = h, rx = w // 3, ry = h // 4, colour = 4, filled = true }
dp.layer.add{ name = "Sun" }
dp.draw.ellipse{ cx = w * 4 // 5, cy = h // 5, rx = math.max(2, h // 10), colour = 7, filled = true }
print(dp.info.layers())

Mirror the layer

-- Left-right symmetry: a copy of the active layer, flipped across and merged back down.
-- Draw on the left half first, then run it.
dp.layer.duplicate()
dp.layer.flip{ axis = "x" }
dp.layer.merge_down()

Every other layer

-- Shows the layers with even numbers and hides the odd ones.
for i = 0, dp.layers() - 1 do
    dp.layer.show{ index = i, visible = i % 2 == 0 }
end
print(dp.info.layers())

Bouncing ball

-- A 12-frame animation of a ball bouncing on the floor (play it with the Frames panel).
local w, h = dp.width(), dp.height()
local frames = 12
local r = math.max(3, h // 10)
for f = 0, frames - 1 do
    if f > 0 then dp.frame.add{ copy = false } end
    local up = math.abs(math.sin(f / frames * math.pi))
    local y = math.floor(h - r - 2 - up * (h - 2 * r - 4))
    dp.draw.rect{ x = 0, y = h - 1, w = w, h = 1, colour = 3, filled = true }
    dp.draw.ellipse{ cx = w // 2, cy = y, rx = r, ry = if f == 0 then r * 3 // 4 else r, colour = 2, filled = true }
    dp.frame.duration{ ms = 60 }
end
dp.frame.go{ index = 0 }

Spinning bar

-- An 8-frame loop of a bar turning round its middle.
local cx, cy = dp.width() // 2, dp.height() // 2
local r = math.min(cx, cy) - 2
for f = 0, 7 do
    if f > 0 then dp.frame.add{ copy = false } end
    local a = f / 8 * math.pi
    local dx, dy = math.floor(math.cos(a) * r), math.floor(math.sin(a) * r)
    dp.draw.line{ x0 = cx - dx, y0 = cy - dy, x1 = cx + dx, y1 = cy + dy, colour = 1 + f }
    dp.frame.duration{ ms = 80 }
end
dp.frame.go{ index = 0 }

Paint inside a selection

-- Painting stays inside the selection: a big circle only fills the selected rectangle.
local w, h = dp.width(), dp.height()
dp.select.rect{ x = w // 4, y = h // 4, w = w // 2, h = h // 2 }
dp.draw.ellipse{ cx = w // 4, cy = h // 4, rx = w // 3, colour = 9, filled = true }
dp.select.none()

Filters

Posterise and outline

-- Filters by name; dp.info.filters() says what each one's settings mean.
dp.filter.apply{ name = "Posterise", n = 4 }
dp.filter.apply{ name = "Outline", colour = 1, n = 1 }

Old photo

-- Sepia, film grain and darkened corners.
dp.filter.apply{ name = "Sepia", a = 80 }
dp.filter.apply{ name = "Film grain", a = 25, b = 1 }
dp.filter.apply{ name = "Vignette", a = 60, b = 30, c = 50 }

Cartoon

-- Flat cel-shaded tones with dark outlines.
dp.filter.apply{ name = "Toon (cel shading)", n = 4, a = 60, b = 80, c = 1 }

Sprite outline and shadow

-- A one-pixel outline in colour 1 round everything that isn't see-through, and a shadow
-- in colour 2 two pixels down and right.
dp.filter.apply{ name = "Outline", colour = 1, n = 1 }
dp.filter.apply{ name = "Drop shadow", colour = 2, dx = 2, dy = 2 }

Retro look

-- Fewer levels per channel, then fitted back to the palette with an ordered dither.
dp.filter.apply{ name = "Posterise", n = 3 }
dp.filter.apply{ name = "Fit to palette", dither = "Ordered" }

List the filters

-- Every filter and what its settings (a, b, c, n, colour, flag...) mean.
print(dp.info.filters())

Tile maps

About the map (needs a map)

-- The map's size, tile size and layers, and what's in the top-left cell (open a map first).
print(dp.map.info())
print(dp.map.cell{ x = 0, y = 0 })

Border of tiles (needs a map)

-- Tile 1 all round the edge of the active map layer, tile 0 inside.
local a, b = string.match(dp.map.info(), "(%d+) x (%d+) cells")
local cols, rows = tonumber(a) or 0, tonumber(b) or 0
dp.map.set{ x = 0, y = 0, w = cols, h = rows, tile = 0 }
dp.map.set{ x = 0, y = 0, w = cols, h = 1, tile = 1 }
dp.map.set{ x = 0, y = rows - 1, w = cols, h = 1, tile = 1 }
dp.map.set{ x = 0, y = 0, w = 1, h = rows, tile = 1 }
dp.map.set{ x = cols - 1, y = 0, w = 1, h = rows, tile = 1 }

Random ground (needs a map)

-- Every cell a random tile from 2 to 5, sometimes mirrored, so the ground doesn't repeat.
local a, b = string.match(dp.map.info(), "(%d+) x (%d+) cells")
local cols, rows = tonumber(a) or 0, tonumber(b) or 0
for y = 0, rows - 1 do
    for x = 0, cols - 1 do
        dp.map.set{ x = x, y = y, tile = math.random(2, 5), flipx = math.random() < 0.5 }
    end
end

Mark solid cells (needs a map)

-- Attribute 1 (say, "solid") on every cell that has a tile, 0 on empty ones: the game's own bits.
local a, b = string.match(dp.map.info(), "(%d+) x (%d+) cells")
local cols, rows = tonumber(a) or 0, tonumber(b) or 0
local solid = 0
for y = 0, rows - 1 do
    for x = 0, cols - 1 do
        local tile = tonumber(string.match(dp.map.cell{ x = x, y = y }, "tile=(%-?%d+)"))
        local value = if tile and tile >= 0 then 1 else 0
        dp.map.attr{ x = x, y = y, value = value }
        solid += value
    end
end
print(`{solid} of {cols * rows} cells marked solid`)

Events and games

Bouncing square

-- dp.on("frame", ...) runs every frame, so the script keeps going after its last line:
-- a square bounces across the picture. Up and Down move it (the key is used up, so the tools
-- don't see it). Escape, Stop, or the "Running" note on the menu bar stops it; one Undo
-- takes back everything it drew.
local x, y, dx, size = 4, dp.height() // 3, 1, 8
local under = dp.pixel(0, 0)   -- (the colour to rub out with: the background's)
local function square(colour)
    dp.draw.rect{ x = x, y = y, w = size, h = size, colour = colour, filled = true }
end
dp.on("frame", function(dt, t)
    square(under)
    x += dx * 2
    if x < 0 or x + size >= dp.width() then dx = -dx end
    square(2)
end)
dp.on("key", function(e)
    if e.key == "Up" or e.key == "Down" then
        square(under)
        y = math.clamp(y + (if e.key == "Up" then -size else size), 0, dp.height() - size)
        return true
    end
end)
dp.on("stop", function() square(under) end)   -- tidy up when it's stopped

Move with the keys or a pad

-- Smooth movement for a game: a frame handler reads the keys held down (dp.key) and a game
-- controller (dp.pad), and moves by speed x dt, so it's the same speed however fast the
-- frames come. Arrow keys, the left stick or the d-pad move the square; A (or Space) jumps it.
-- (It rubs itself out with the background's colour, so it scratches what it crosses: drawing
-- moving things over the picture without touching it is what's coming next.)
local x, y, size, speed = dp.width() / 2, dp.height() / 2, 6, 80   -- speed: pixels a second
local under = dp.pixel(0, 0)
local function square(colour)
    dp.draw.rect{ x = math.floor(x), y = math.floor(y), w = size, h = size, colour = colour, filled = true }
end
dp.on("frame", function(dt)
    local dx, dy = 0, 0
    if dp.key("Left") then dx -= 1 end
    if dp.key("Right") then dx += 1 end
    if dp.key("Up") then dy -= 1 end
    if dp.key("Down") then dy += 1 end
    local p = dp.pad(1)
    if p then
        dx += p.lx + (if p.left then -1 elseif p.right then 1 else 0)
        dy += p.ly + (if p.up then -1 elseif p.down then 1 else 0)
    end
    if dx ~= 0 or dy ~= 0 then
        square(under)
        x = math.clamp(x + dx * speed * dt, 0, dp.width() - size)
        y = math.clamp(y + dy * speed * dt, 0, dp.height() - size)
    end
    square(3)
end)
local function jump()
    square(under)
    y = math.max(0, y - size * 3)
end
dp.on("key", function(e) if e.key == "Space" then jump() return true end end)
dp.on("pad", function(e)
    if e.button == "a" and e.pressed then jump() end
    if e.button == nil then print(`pad {e.pad} {if e.connected then "plugged in" else "unplugged"}`) end
end)
dp.on("stop", function() square(under) end)
print(`{dp.pads()} game controller(s) connected. Escape stops it.`)

Sprites over the picture

-- dp.image cuts a piece of the picture as an image; dp.show shows it over the canvas each frame
-- without drawing into the picture - nothing to rub out, nothing to undo. Three copies of the
-- top-left corner bounce about; dp.say writes over it. Escape stops it, and the picture is as it was.
local size = math.min(16, dp.width() // 4, dp.height() // 4)
local img = dp.image(0, 0, size, size)
local balls = {}
for i = 1, 3 do
    table.insert(balls, { x = math.random(0, dp.width() - size), y = math.random(0, dp.height() - size),
                          dx = math.random(30, 90) * (if i % 2 == 0 then -1 else 1), dy = math.random(30, 90) })
end
local bounces = 0
dp.on("frame", function(dt)
    for _, b in balls do
        b.x += b.dx * dt
        b.y += b.dy * dt
        if b.x < 0 or b.x > dp.width() - size then b.dx = -b.dx; bounces += 1 end
        if b.y < 0 or b.y > dp.height() - size then b.dy = -b.dy; bounces += 1 end
        b.x = math.clamp(b.x, 0, dp.width() - size)
        b.y = math.clamp(b.y, 0, dp.height() - size)
        dp.show(img, b.x, b.y, b.dx < 0)          -- mirrored when it's going left
    end
    dp.box(0, 0, dp.width(), dp.height(), "#FFFFFF80", false)   -- a frame round the picture
    dp.say(2, 2, `bounces: {bounces}`)
end)

Platform game (needs a map)

-- A tiny platform game over the tile map in front - make one first (open a picture of a level,
-- then Tiles > Make tile set from this image). Arrow keys or a pad move, Space / Up / A jump; the
-- view follows. Solid cells: those with attribute bit 1 set or, on a map without attributes, any
-- tile but the one in the top-left corner (the sky). Nothing is drawn into the map. A sprite
-- called "Hero" (Window > Sprite timeline, in any open picture) is the hero if there is one; its
-- origin is put at its feet. Tiles > Play this level runs it; F9 plays again.
local m = dp.map_info()
assert(m, "Put a tile map in front first (Tiles > Make tile set from this image).")
local tw, th = m.tile_w, m.tile_h
local sky = dp.cell(0, 0)
local useAttr = false
for cy = 0, math.min(m.height, 64) - 1 do
    for cx = 0, math.min(m.width, 64) - 1 do
        local _, a = dp.cell(cx, cy)
        if a ~= 0 then useAttr = true end
    end
end
local function solid(px, py)
    local tile, attr = dp.cell_at(px, py)
    if tile == nil then return false end                 -- outside the map: open (fall off the bottom)
    if useAttr then return bit32.btest(attr, 1) end
    return tile ~= -1 and tile ~= sky
end
local w, h = math.max(4, tw // 2), math.max(4, th - 2)   -- the hero: a box (dp.image + dp.show for a sprite)
local function hits(px, py)
    return solid(px, py) or solid(px + w - 1, py) or solid(px, py + h - 1) or solid(px + w - 1, py + h - 1)
        or solid(px, py + h // 2) or solid(px + w - 1, py + h // 2)
end
local hasSprite, heroAnim = pcall(dp.sprite, "Hero")
dp.cycle(true)                                           -- the level's colour cycling, if it has any
local startX, startY = tw * 2, 0
local x, y, vy, onGround, facing = startX, startY, 0, false, 1
local gravity, jumpSpeed, runSpeed = 700, 280, 100      -- pixels a second (a second squared)

dp.on("frame", function(dt, t)
    dt = math.min(dt, 1 / 30)
    local dir = 0
    if dp.key("Left") then dir -= 1 end
    if dp.key("Right") then dir += 1 end
    local p = dp.pad(1)
    if p then dir += p.lx + (if p.left then -1 elseif p.right then 1 else 0) end
    dir = math.clamp(dir, -1, 1)
    if dir ~= 0 then facing = math.sign(dir) end
    -- across: only where it's clear
    local nx = x + dir * runSpeed * dt
    if not hits(nx, y) then x = nx end
    -- up and down: falling, landing on what's solid, bumping heads
    vy = math.min(vy + gravity * dt, 500)
    local ny = y + vy * dt
    if hits(x, ny) then
        local step = math.sign(vy)
        for _ = 1, 32 do                                  -- (close the gap, a pixel at a time)
            if hits(x, y + step) then break end
            y += step
        end
        onGround = vy > 0
        vy = 0
    else
        y = ny
        onGround = false
    end
    if y > m.height * th + 64 then x, y, vy = startX, startY, 0 end   -- fell off: start again
    if hasSprite then
        dp.show_anim(heroAnim, x + w / 2, y + h, if dir ~= 0 then t else 0, facing < 0)
    else
        dp.box(x, y, w, h, "#E04040")
        dp.box(x + (if facing > 0 then w - 3 else 1), y + 2, 2, 2, "#FFFFFF")   -- an eye, the way it faces
    end
    dp.camera(x + w / 2, y + h / 2)
end)
local function jump()
    if onGround then vy = -jumpSpeed; onGround = false end
end
dp.on("key", function(e)
    if e.key == "Space" or e.key == "Up" then jump() return true end
    if e.key == "Left" or e.key == "Right" or e.key == "Down" then return true end   -- (not the tools')
end)
dp.on("pad", function(e) if e.button == "a" and e.pressed then jump() end end)
print("Arrow keys or a pad to move, Space or A to jump. Escape stops it.")

Walk round the map in 3D (needs a map)

-- The map in first person, as Wolfenstein 3D: the cells are walls drawn with their own tiles,
-- the floor is the most common tile (or, if the map has attributes, the cells with bit 1 set are
-- the walls). Arrow keys or a pad: up and down walk, left and right turn. M: the map from above.
local m = dp.map_info()
local tw, th = m.tile_w, m.tile_h
-- What's a wall: cells with attribute bit 1 if any have it, else every tile but the floor's.
local count, floor, attrs = {}, -1, false
for cy = 0, math.min(m.height, 128) - 1 do
    for cx = 0, math.min(m.width, 128) - 1 do
        local tile, a = dp.cell(cx, cy)
        if a and bit32.btest(a, 1) then attrs = true end
        if tile and tile >= 0 then
            count[tile] = (count[tile] or 0) + 1
            if floor < 0 or count[tile] > count[floor] then floor = tile end
        end
    end
end
local walls = if attrs then { solid = 1 } else { open = { floor } }
-- Start in the middle of the first open cell.
local x, y
for cy = 0, m.height - 1 do
    for cx = 0, m.width - 1 do
        if not x and not dp.wall_at((cx + 0.5) * tw, (cy + 0.5) * th, walls) then x, y = (cx + 0.5) * tw, (cy + 0.5) * th end
    end
end
if not x then error("every cell is a wall: paint some floor (the most common tile) first.") end
local angle, view, fromAbove = 0, nil, false
local gpu = dp.gpu()
local walk, turn = 3 * tw, 120                       -- cells a second (as pixels), degrees a second
local function clear(px, py)                         -- a little room round the player, so walls aren't touched
    local r = tw * 0.2
    return not (dp.wall_at(px - r, py - r, walls) or dp.wall_at(px + r, py - r, walls)
        or dp.wall_at(px - r, py + r, walls) or dp.wall_at(px + r, py + r, walls))
end
dp.on("frame", function(dt)
    dt = math.min(dt, 1 / 20)
    local go, spin = 0, 0
    if dp.key("Up") then go += 1 end
    if dp.key("Down") then go -= 1 end
    if dp.key("Left") then spin -= 1 end
    if dp.key("Right") then spin += 1 end
    local p = dp.pad(1)
    if p then go -= p.ly; spin += p.lx end
    angle = (angle + math.clamp(spin, -1, 1) * turn * dt) % 360
    local r = math.rad(angle)
    local step = math.clamp(go, -1, 1) * walk * dt
    local nx, ny = x + math.cos(r) * step, y + math.sin(r) * step
    if clear(nx, y) then x = nx end                   -- (sliding along walls: each way on its own)
    if clear(x, ny) then y = ny end
    if fromAbove then
        view = dp.mode7(x, y, angle, { w = 320, h = 200, height = th * 3, fog = 0.6, into = view })
    else
        -- (on the graphics card when there is one: three times as sharp, as fast)
        local opts = { w = if gpu then 960 else 320, h = if gpu then 600 else 200, gpu = gpu,
                       fog = 0.7, distance = 24, ceiling = "#202838", into = view }
        for k, v in walls do opts[k] = v end
        view = dp.raycast(x, y, angle, opts)
    end
    dp.show_screen(view)
    dp.camera(x, y)
end)
dp.on("key", function(e)
    if e.key == "M" then fromAbove = not fromAbove return true end
    if e.key == "Up" or e.key == "Down" or e.key == "Left" or e.key == "Right" then return true end
end)
print("Up and down walk, left and right turn, M flies over it. Escape stops it.")

Doors and push walls in 3D (needs a map)

-- Walking round the map in first person with doors that slide open and walls that can be pushed,
-- as Wolfenstein 3D. Walls are the cells with attribute bit 1; doors the cells with bit 2; push
-- walls the cells with bits 1 and 4 (set them in the Map layers panel's attributes). Arrow keys
-- walk and turn; Space opens the door or pushes the wall in front.
local m = dp.map_info()
local tw, th = m.tile_w, m.tile_h
local doors, pushes, byCell = {}, {}, {}
local x, y
for cy = 0, m.height - 1 do
    for cx = 0, m.width - 1 do
        local tile, a = dp.cell(cx, cy)
        a = a or 0
        if bit32.btest(a, 2) then
            local d = { x = cx, y = cy, open = 0, target = 0 }
            table.insert(doors, d)
            byCell[cy * m.width + cx] = d
        elseif bit32.btest(a, 1) and bit32.btest(a, 4) then
            local p = { x = cx, y = cy, dx = 0, dy = 0, ex = 0, ey = 0 }
            table.insert(pushes, p)
            byCell[cy * m.width + cx] = p
        elseif not bit32.btest(a, 1) and tile and tile >= 0 and not x then
            x, y = (cx + 0.5) * tw, (cy + 0.5) * th
        end
    end
end
if not x then error("there's no open cell (one with a tile and without attribute bit 1) to start in.") end
local angle, view = 0, nil
local function opts() return { solid = 1, doors = doors, moving = pushes } end
local function clear(px, py)
    local r, o = tw * 0.2, opts()
    return not (dp.wall_at(px - r, py - r, o) or dp.wall_at(px + r, py - r, o)
        or dp.wall_at(px - r, py + r, o) or dp.wall_at(px + r, py + r, o))
end
dp.on("frame", function(dt)
    dt = math.min(dt, 1 / 20)
    local go, spin = 0, 0
    if dp.key("Up") then go += 1 end
    if dp.key("Down") then go -= 1 end
    if dp.key("Left") then spin -= 1 end
    if dp.key("Right") then spin += 1 end
    angle = (angle + spin * 120 * dt) % 360
    local r = math.rad(angle)
    local nx, ny = x + math.cos(r) * go * 3 * tw * dt, y + math.sin(r) * go * 3 * th * dt
    if clear(nx, y) then x = nx end
    if clear(x, ny) then y = ny end
    for _, d in doors do                                -- doors slide at one cell a second
        if d.open < d.target then d.open = math.min(d.target, d.open + dt) end
        if d.open > d.target then d.open = math.max(d.target, d.open - dt) end
    end
    for _, p in pushes do                               -- push walls move at half a cell a second, two cells at most
        if p.ex ~= 0 or p.ey ~= 0 then
            local fx, fy = p.dx + p.ex * dt * 0.5, p.dy + p.ey * dt * 0.5
            local lead = math.ceil(math.max(math.abs(fx), math.abs(fy)) - 0.000001)   -- the cells it reaches into
            local cx, cy = p.x + p.ex * lead, p.y + p.ey * lead
            if lead > 2 or (lead > 0 and dp.wall_at((cx + 0.5) * tw, (cy + 0.5) * th, { solid = 1 })) then
                p.dx, p.dy, p.ex, p.ey = p.ex * (lead - 1), p.ey * (lead - 1), 0, 0   -- stopped: against a wall, or far enough
            else
                p.dx, p.dy = fx, fy
            end
        end
    end
    local o = opts()
    o.fog, o.distance, o.ceiling, o.into = 0.6, 24, "#202838", view
    o.gpu = dp.gpu()                                    -- (sharper on the graphics card)
    o.w, o.h = if o.gpu then 960 else 320, if o.gpu then 600 else 200
    view = dp.raycast(x, y, angle, o)
    dp.show_screen(view)
end)
dp.on("key", function(e)
    if e.key == "Space" then
        local r = math.rad(angle)
        local ax, ay = x / tw + math.cos(r), y / th + math.sin(r)   -- a cell ahead: the nearest door or push wall to there
        local thing, best = nil, 1.0
        for cell, t in byCell do
            local d = math.sqrt((t.x + 0.5 - ax) ^ 2 + (t.y + 0.5 - ay) ^ 2)
            if d < best then thing, best = t, d end
        end
        if thing and thing.target ~= nil then
            thing.target = if thing.target > 0 then 0 else 1
        elseif thing and thing.ex == 0 and thing.ey == 0 and thing.dx == 0 and thing.dy == 0 then
            local ax, ay = math.cos(r), math.sin(r)            -- pushed the way the player faces, straight along a row
            if math.abs(ax) > math.abs(ay) then thing.ex = if ax > 0 then 1 else -1 else thing.ey = if ay > 0 then 1 else -1 end
        end
        return true
    end
    if e.key == "Up" or e.key == "Down" or e.key == "Left" or e.key == "Right" then return true end
end)
print("Arrow keys walk and turn, Space opens doors and pushes walls. Escape stops it.")

The map raised in 3D (needs a map)

-- The map drawn as stacked cubes of different heights, seen isometrically (Tab: from above at an angle).
-- Heights come from the attribute bits 1, 2 and 4 read as a number (0-7) if the map has any;
-- otherwise every tile but the most common (the floor) is 2 high. Arrow keys move round.
local m = dp.map_info()
local tw, th = m.tile_w, m.tile_h
local count, floor, attrs = {}, -1, false
for cy = 0, math.min(m.height, 128) - 1 do
    for cx = 0, math.min(m.width, 128) - 1 do
        local tile, a = dp.cell(cx, cy)
        if a and bit32.band(a, 7) ~= 0 then attrs = true end
        if tile and tile >= 0 then
            count[tile] = (count[tile] or 0) + 1
            if floor < 0 or count[tile] > count[floor] then floor = tile end
        end
    end
end
local heights = {}
if not attrs then for tile in count do if tile ~= floor then heights[tile] = 2 end end end
local x, y = m.width * tw / 2, m.height * th / 2
local iso, view = true, nil
local player = dp.image(0, 0, tw, th)                  -- the tile set's first tile stands in for a player
dp.on("frame", function(dt)
    local dx, dy = 0, 0
    if dp.key("Left") then dx -= 1 end
    if dp.key("Right") then dx += 1 end
    if dp.key("Up") then dy -= 1 end
    if dp.key("Down") then dy += 1 end
    x = math.clamp(x + dx * 4 * tw * dt, 0, m.width * tw - 1)
    y = math.clamp(y + dy * 4 * th * dt, 0, m.height * th - 1)
    local o = { w = 320, h = 200, view = if iso then "iso" else "angle", background = "#101820", into = view,
                sprites = { { image = player, x = x, y = y, size = 1 } } }
    if attrs then o.height_bits = 7 else o.heights = heights end
    view = dp.raised(x, y, o)
    dp.show_screen(view)
    local _, _, h = dp.raised_point(x, y, x, y, o)
    dp.say(4, 4, `cell {math.floor(x / tw)}, {math.floor(y / th)}  height {h}`)
end)
dp.on("key", function(e)
    if e.key == "Tab" then iso = not iso return true end
    if e.key == "Up" or e.key == "Down" or e.key == "Left" or e.key == "Right" then return true end
end)
print("Arrow keys move, Tab switches between isometric and from above at an angle. Escape stops it.")

Fly over the map (Mode 7) (needs a map)

-- The map laid flat and flown over, as the SNES's Mode 7 (a kart track, a world map):
-- left and right steer, up speeds up, down slows down. It wraps round at the edges.
local m = dp.map_info()
local x, y = m.width * m.tile_w / 2, m.height * m.tile_h / 2
local angle, speed, view = 270, 40, nil
dp.on("frame", function(dt)
    dt = math.min(dt, 1 / 20)
    if dp.key("Left") then angle -= 90 * dt end
    if dp.key("Right") then angle += 90 * dt end
    if dp.key("Up") then speed = math.min(speed + 80 * dt, 300) end
    if dp.key("Down") then speed = math.max(speed - 80 * dt, 0) end
    local r = math.rad(angle)
    x += math.cos(r) * speed * dt
    y += math.sin(r) * speed * dt
    view = dp.mode7(x, y, angle, { w = 320, h = 200, height = 24 + speed / 6, wrap = true, fog = 0.5, sky = "#5080D0", into = view })
    dp.show_screen(view)
    dp.say(4, 4, `speed {math.floor(speed)}`)
end)
dp.on("key", function(e)
    if e.key == "Up" or e.key == "Down" or e.key == "Left" or e.key == "Right" then return true end
end)
print("Left and right steer, up and down change speed. Escape stops it.")

A 3D character on the map (needs a map)

-- A 3D model (File > 3D model, brought into any open picture) walking round the map from above,
-- turned to face the way it goes and playing its first animation while it moves - drawn live, as
-- pre-rendered sprites were, in 8 directions. Arrow keys or a pad.
local name = "robot"                                  -- the model's name (as File > 3D model gave it)
local ok, model = pcall(dp.model, name)
if not ok then error(model .. " Change `name` at the top to one of yours.") end
local m = dp.map_info()
local x, y = m.width * m.tile_w / 2, m.height * m.tile_h / 2
local facing, t = 90, 0
dp.on("frame", function(dt)
    local dx, dy = 0, 0
    if dp.key("Left") then dx -= 1 end
    if dp.key("Right") then dx += 1 end
    if dp.key("Up") then dy -= 1 end
    if dp.key("Down") then dy += 1 end
    local p = dp.pad(1)
    if p then dx += p.lx; dy += p.ly end
    local moving = math.abs(dx) + math.abs(dy) > 0.2
    if moving then
        facing = math.deg(math.atan2(dy, dx))
        x += dx * 60 * dt
        y += dy * 60 * dt
        t += dt
    end
    dp.show_model(model, x, y, { facing = facing, directions = 8, feet = true,
        anim = if #model.anims > 0 then 1 else nil, time = if moving then t else 0 })
    dp.camera(x, y)
end)
dp.on("key", function(e)
    if e.key == "Up" or e.key == "Down" or e.key == "Left" or e.key == "Right" then return true end
end)
print("Arrow keys or a pad to walk. Escape stops it.")

Paint with the keys

-- A cursor the arrow keys move a pixel at a time (Shift: 8); Space paints with the left
-- colour, and the number keys pick colours 1 to 9. A tiny "etch a sketch".
local x, y, colour = dp.width() // 2, dp.height() // 2, 1
local function show() print(`cursor {x}, {y}, colour {colour}`) end
dp.on("key", function(e)
    local step = if e.shift then 8 else 1
    if e.key == "Left" then x -= step
    elseif e.key == "Right" then x += step
    elseif e.key == "Up" then y -= step
    elseif e.key == "Down" then y += step
    elseif e.key == "Space" then dp.draw.pixel{ x = x, y = y, colour = colour }
    elseif tonumber(e.key) and tonumber(e.key) > 0 then colour = math.min(tonumber(e.key), dp.colours() - 1)
    else return false end            -- anything else: Deluxe Pixel's as usual
    x = math.clamp(x, 0, dp.width() - 1)
    y = math.clamp(y, 0, dp.height() - 1)
    show()
    return true
end)
show()

Mouse trail

-- The mouse event: hold the left button over the picture for a rainbow trail (the press
-- is used up, so the tool doesn't paint too). The right button paints as usual.
local colour = 1
dp.on("mouse", function(e)
    if e.left then
        dp.draw.ellipse{ cx = e.x, cy = e.y, rx = 2, colour = colour, filled = true }
        colour = colour % (dp.colours() - 1) + 1
    end
    if e.pressed == "left" then return true end
end)
print("Hold the left button over the picture. Escape stops it.")

Tasks and waiting

-- dp.spawn runs a function alongside Deluxe Pixel; inside it, dp.wait(seconds) pauses it
-- without holding anything up. Two at once: rain falling, and a clock ticking.
dp.spawn(function()
    for i = 1, 40 do
        local x = math.random(0, dp.width() - 1)
        dp.draw.line{ x0 = x, y0 = 0, x1 = x, y1 = math.random(2, dp.height() // 3), colour = 4 }
        dp.wait(0.1)
    end
    print("rain: done")      -- a task that finishes simply ends
end)
dp.spawn(function()
    for second = 1, 5 do
        dp.wait(1)
        print(`tick {second}`)
    end
end)

Watching what changes

-- Events for what the person does: another picture, layer, colour, palette or selection.
-- Try them while it runs (Escape stops it).
dp.on("picture", function(e) print(`picture: {e.name} ({e.width} x {e.height}){if e.map then ", a map" else ""}`) end)
dp.on("layer", function(e) print(`layer {e.index}: {e.name}`) end)
dp.on("colour", function(e) print(`colours: left {e.left}, right {e.right}`) end)
dp.on("palette", function(e) print(`the palette changed ({e.colours} colours)`) end)
dp.on("selection", function(e)
    if e.empty then print("nothing selected") else print(`selected {e.w} x {e.h} at {e.x}, {e.y}`) end
end)
dp.on("save", function(e) print("saved " .. e.path) end)
print("Watching...")

Learning Luau

Debugging

-- Click left of a line number for a breakpoint (the red dot), then Debug (F5).
-- When it stops: the variables are below; F10 steps over a line, F11 into a function,
-- Shift+F11 out of it, F5 carries on. Hover a variable to see its value.
local function spiral(cx: number, cy: number, turns: number)
    local points = {}
    for i = 0, turns * 32 do
        local a = i / 32 * math.pi * 2
        local r = i / 4
        table.insert(points, vector.create(cx + math.cos(a) * r, cy + math.sin(a) * r, 0))
    end
    return points
end

local pts = spiral(dp.width() / 2, dp.height() / 2, 3)
for i, p in pts do
    dp.draw.pixel{ x = math.floor(p.x), y = math.floor(p.y), colour = 1 + i % 15 }
end
print(`{#pts} points`)

Errors and dp.try

-- A command that can't run stops the script with why; dp.try runs one without stopping.
local tooBig = 9999
local ok, why = dp.try("draw.pixel", { x = 0, y = 0, colour = tooBig })
print("ok:", ok, "why:", why)
-- pcall catches any error, a command's included:
local none = 0
local fine, err = pcall(function() dp.draw.rect{ x = 0, y = 0, w = none, h = 4, colour = 1 } end)
print("fine:", fine, "error:", err)
-- error() stops the script with your own message: take the -- off the next line and Run
-- (the line goes red, and the Output says where it was called from).
-- error("stopped on purpose")

Tables and strings

-- A quick tour: tables as lists and as records, string functions, interpolation.
local colours = { "red", "green", "blue" }
table.insert(colours, "yellow")
print(#colours, table.concat(colours, ", "))
local sprite = { name = "Hero", w = 16, h = 24, frames = 4 }
for k, v in sprite do print(k, v) end
print(string.upper(sprite.name), string.rep("=", 10))
print(`{sprite.name} is {sprite.w} x {sprite.h}, {sprite.frames} frames`)
local words = string.split("one two three", " ")
print(#words, words[2])
local sorted = { 5, 3, 9, 1 }
table.sort(sorted)
print(table.concat(sorted, " < "))

Functions and types

-- Functions are values; type annotations are optional (the checks use them, the VM ignores them).
local function clamp(v: number, lo: number, hi: number): number
    return math.max(lo, math.min(hi, v))
end
local function twice(f: (number) -> number): (number) -> number
    return function(x) return f(f(x)) end
end
local addTen = function(x: number) return x + 10 end
print(clamp(300, 0, 255), twice(addTen)(1))
type Point = { x: number, y: number }
local p: Point = { x = 3, y = 4 }
print(`distance: {math.sqrt(p.x ^ 2 + p.y ^ 2)}`)

Buffers

-- buffer: a block of bytes, compact and fast. The active layer read into one, then counted.
local w, h = dp.width(), dp.height()
local b = buffer.create(w * h)
for y = 0, h - 1 do
    for x = 0, w - 1 do
        local c = dp.pixel(x, y)
        buffer.writeu8(b, y * w + x, if type(c) == "number" and c >= 0 then c else 0)
    end
end
local counts = table.create(256, 0)
for i = 0, w * h - 1 do
    local c = buffer.readu8(b, i) + 1
    counts[c] += 1
end
print(`{buffer.len(b)} bytes; colour 0 is used {counts[1]} times`)

Whole layer at once

-- dp.pixels() gives the whole active layer as a buffer (1 byte a pixel: its colour number; 4 for true colour);
-- dp.set_pixels() writes it back as one undo step (inside the selection). Far faster than a command a pixel:
-- here every colour number goes up by one, the last wrapping round to 0.
local px, w, h = dp.pixels()
if buffer.len(px) ~= w * h then error("This example is for palette pictures.") end
local top = dp.colours() - 1
for i = 0, w * h - 1 do
    local c = buffer.readu8(px, i)
    buffer.writeu8(px, i, if c == top then 0 else c + 1)
end
dp.set_pixels(px)
print(`{w * h} pixels in one go`)

Vectors (3D)

-- vector: 3D maths built into Luau (x, y, z), fast as native code. A cube's corners turned
-- and projected onto the picture, edges drawn as lines.
local cx, cy = dp.width() / 2, dp.height() / 2
local size = math.min(cx, cy) / 2
local corners = {}
for i = 0, 7 do
    corners[i + 1] = vector.create(if i % 2 == 0 then -1 else 1, if (i // 2) % 2 == 0 then -1 else 1, if i // 4 == 0 then -1 else 1)
end
local function turn(v: vector, a: number): vector
    local x = v.x * math.cos(a) - v.z * math.sin(a)
    local z = v.x * math.sin(a) + v.z * math.cos(a)
    local y = v.y * math.cos(a * 0.7) - z * math.sin(a * 0.7)
    return vector.create(x, y, v.y * math.sin(a * 0.7) + z * math.cos(a * 0.7))
end
local function project(v: vector): (number, number)
    local p = 3 / (v.z + 4)
    return math.floor(cx + v.x * size * p), math.floor(cy + v.y * size * p)
end
local edges = { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 }, { 1, 3 }, { 2, 4 }, { 5, 7 }, { 6, 8 }, { 1, 5 }, { 2, 6 }, { 3, 7 }, { 4, 8 } }
for _, e in edges do
    local x0, y0 = project(turn(corners[e[1]], 0.6))
    local x1, y1 = project(turn(corners[e[2]], 0.6))
    dp.draw.line{ x0 = x0, y0 = y0, x1 = x1, y1 = y1, colour = 1 }
end
print(`corner 1 is {vector.magnitude(corners[1])} from the middle`)

Native speed

-- Luau compiles scripts to native code (Run, Ctrl+Enter): maths and loops run fast.
-- Debug (F5) uses the interpreter instead: compare the times.
local t = os.clock()
local sum = 0
for i = 1, 10_000_000 do
    sum += math.sqrt(i) * 0.5
end
print(`sum {math.floor(sum)} in {math.floor((os.clock() - t) * 1000)} ms`)

Every command

-- Every command a script can use (dp.<group>.<name>; hover one in the editor for its help).
for _, id in dp.commands() do print(id) end