Overview
Aseprite’s scripting API allows you to create custom tools that extend the built-in functionality. You can create procedural generators, special effects, and custom brushes.Custom Brush Tool
Create a script that draws with a custom pattern:-- Custom dotted line tool
local sprite = app.activeSprite
if not sprite then
return app.alert("No active sprite")
end
local dialog = Dialog("Dotted Line Tool")
dialog:color{
id = "color",
label = "Color:",
color = app.fgColor
}
dialog:slider{
id = "spacing",
label = "Dot Spacing:",
min = 2,
max = 20,
value = 5
}
dialog:button{
id = "draw",
text = "Draw",
onclick = function()
local data = dialog.data
local spacing = data.spacing
local color = data.color
-- Draw dotted line from (0,0) to (31,31)
local x1, y1 = 0, 0
local x2, y2 = 31, 31
local dx = x2 - x1
local dy = y2 - y1
local distance = math.sqrt(dx * dx + dy * dy)
local steps = math.floor(distance / spacing)
for i = 0, steps do
local t = i / steps
local x = math.floor(x1 + t * dx)
local y = math.floor(y1 + t * dy)
app.useTool{
tool = 'pencil',
color = color,
points = { Point(x, y) }
}
end
dialog:close()
end
}
dialog:button{ id = "cancel", text = "Cancel" }
dialog:show()
Pattern Fill Tool
Create a tool that fills with a custom pattern:local sprite = app.activeSprite
if not sprite then
return app.alert("No active sprite")
end
local cel = app.activeCel
if not cel then
return app.alert("No active cel")
end
local image = cel.image
-- Define a checkerboard pattern
local color1 = app.pixelColor.rgba(255, 255, 255, 255) -- White
local color2 = app.pixelColor.rgba(0, 0, 0, 255) -- Black
local patternSize = 4
-- Apply pattern
for y = 0, image.height - 1 do
for x = 0, image.width - 1 do
local checkX = math.floor(x / patternSize) % 2
local checkY = math.floor(y / patternSize) % 2
if checkX == checkY then
image:putPixel(x, y, color1)
else
image:putPixel(x, y, color2)
end
end
end
app.refresh()
print("Applied checkerboard pattern")
Gradient Tool
Create custom gradients:local sprite = app.activeSprite
if not sprite then
sprite = Sprite(128, 128)
end
local cel = app.activeCel
if not cel then
return app.alert("No active cel")
end
local image = cel.image
-- Define gradient colors
local color1 = Color(255, 0, 0) -- Red
local color2 = Color(0, 0, 255) -- Blue
-- Create horizontal gradient
for y = 0, image.height - 1 do
for x = 0, image.width - 1 do
local t = x / (image.width - 1)
-- Interpolate colors
local r = math.floor(color1.red * (1 - t) + color2.red * t)
local g = math.floor(color1.green * (1 - t) + color2.green * t)
local b = math.floor(color1.blue * (1 - t) + color2.blue * t)
local color = app.pixelColor.rgba(r, g, b, 255)
image:putPixel(x, y, color)
end
end
app.refresh()
print("Applied gradient")
Noise Generator
Create procedural noise:local sprite = app.activeSprite
if not sprite then
sprite = Sprite(64, 64)
end
local cel = app.activeCel
if not cel then
return app.alert("No active cel")
end
local image = cel.image
-- Simple random noise
math.randomseed(os.time())
for y = 0, image.height - 1 do
for x = 0, image.width - 1 do
local value = math.random(0, 255)
local color = app.pixelColor.rgba(value, value, value, 255)
image:putPixel(x, y, color)
end
end
app.refresh()
print("Generated noise")
Circle Drawing Tool
Draw circles with custom parameters:local sprite = app.activeSprite
if not sprite then
sprite = Sprite(64, 64)
end
local function drawCircle(image, centerX, centerY, radius, color)
for y = 0, image.height - 1 do
for x = 0, image.width - 1 do
local dx = x - centerX
local dy = y - centerY
local distance = math.sqrt(dx * dx + dy * dy)
if distance <= radius then
image:putPixel(x, y, color)
end
end
end
end
local image = app.activeCel.image
local centerX = image.width / 2
local centerY = image.height / 2
local radius = 20
local color = app.pixelColor.rgba(255, 0, 0, 255)
drawCircle(image, centerX, centerY, radius, color)
app.refresh()
print("Drew circle")
Pixel-Perfect Line Tool
Implement pixel-perfect line drawing:-- Based on Aseprite's pixel-perfect algorithm
local sprite = app.activeSprite
if not sprite then
sprite = Sprite(32, 32)
end
local function drawPixelPerfectLine(image, x1, y1, x2, y2, color)
local points = {}
local dx = math.abs(x2 - x1)
local dy = math.abs(y2 - y1)
local sx = x1 < x2 and 1 or -1
local sy = y1 < y2 and 1 or -1
local err = dx - dy
local x, y = x1, y1
while true do
image:putPixel(x, y, color)
if x == x2 and y == y2 then
break
end
local e2 = 2 * err
if e2 > -dy then
err = err - dy
x = x + sx
end
if e2 < dx then
err = err + dx
y = y + sy
end
end
end
local image = app.activeCel.image
local color = app.pixelColor.rgba(255, 255, 255, 255)
drawPixelPerfectLine(image, 2, 2, 29, 29, color)
app.refresh()
print("Drew pixel-perfect line")
Dithering Tool
Apply dithering to an image:local sprite = app.activeSprite
if not sprite then
return app.alert("No active sprite")
end
local cel = app.activeCel
if not cel then
return app.alert("No active cel")
end
local image = cel.image
-- Floyd-Steinberg dithering pattern
local function ditherImage(img)
local newImg = img:clone()
for y = 0, img.height - 1 do
for x = 0, img.width - 1 do
local pixel = img:getPixel(x, y)
local r = app.pixelColor.rgbaR(pixel)
local g = app.pixelColor.rgbaG(pixel)
local b = app.pixelColor.rgbaB(pixel)
-- Threshold to black or white
local gray = (r + g + b) / 3
local newGray = gray > 128 and 255 or 0
local newPixel = app.pixelColor.rgba(newGray, newGray, newGray, 255)
newImg:putPixel(x, y, newPixel)
-- Calculate error
local error = gray - newGray
-- Distribute error to neighboring pixels
-- (Simplified version)
end
end
return newImg
end
local dithered = ditherImage(image)
-- Replace image with dithered version
for y = 0, image.height - 1 do
for x = 0, image.width - 1 do
image:putPixel(x, y, dithered:getPixel(x, y))
end
end
app.refresh()
print("Applied dithering")
Custom Brush from Selection
Create a custom brush from the current selection:local sprite = app.activeSprite
if not sprite then
return app.alert("No active sprite")
end
if sprite.selection.isEmpty then
return app.alert("Make a selection first")
end
local bounds = sprite.selection.bounds
local cel = app.activeCel
if not cel then
return app.alert("No active cel")
end
-- Extract selection as brush
local brushImage = Image(bounds.width, bounds.height, sprite.colorMode)
for y = 0, bounds.height - 1 do
for x = 0, bounds.width - 1 do
local srcX = bounds.x + x
local srcY = bounds.y + y
if sprite.selection:contains(srcX, srcY) then
local color = cel.image:getPixel(srcX - cel.position.x, srcY - cel.position.y)
brushImage:putPixel(x, y, color)
end
end
end
-- Create brush
local brush = Brush(brushImage)
app.activeBrush = brush
print("Created custom brush from selection")
Symmetry Drawing Tool
Draw with symmetry:local sprite = app.activeSprite
if not sprite then
sprite = Sprite(64, 64)
end
local function drawSymmetric(x, y, color)
local image = app.activeCel.image
local centerX = image.width / 2
local centerY = image.height / 2
-- Draw at 4 symmetrical positions
local points = {
{ x = x, y = y },
{ x = image.width - x - 1, y = y },
{ x = x, y = image.height - y - 1 },
{ x = image.width - x - 1, y = image.height - y - 1 }
}
for _, point in ipairs(points) do
if point.x >= 0 and point.x < image.width and
point.y >= 0 and point.y < image.height then
image:putPixel(point.x, point.y, color)
end
end
end
local color = app.pixelColor.rgba(255, 0, 0, 255)
-- Draw a line with symmetry
for i = 0, 20 do
drawSymmetric(10 + i, 10 + i, color)
end
app.refresh()
print("Drew with symmetry")
Color Palette Generator
Generate color palettes procedurally:local sprite = app.activeSprite
if not sprite then
sprite = Sprite(32, 32, ColorMode.INDEXED)
end
-- Generate a rainbow palette
local palette = Palette(16)
for i = 0, 15 do
local hue = (i / 16) * 360
local saturation = 100
local value = 100
-- HSV to RGB conversion
local c = value * saturation / 10000
local x = c * (1 - math.abs((hue / 60) % 2 - 1))
local m = value / 100 - c
local r, g, b
if hue < 60 then
r, g, b = c, x, 0
elseif hue < 120 then
r, g, b = x, c, 0
elseif hue < 180 then
r, g, b = 0, c, x
elseif hue < 240 then
r, g, b = 0, x, c
elseif hue < 300 then
r, g, b = x, 0, c
else
r, g, b = c, 0, x
end
r = math.floor((r + m) * 255)
g = math.floor((g + m) * 255)
b = math.floor((b + m) * 255)
palette:setColor(i, Color(r, g, b))
end
sprite:setPalette(palette)
print("Generated rainbow palette")
Outline Generator
Create outlines around sprites:local sprite = app.activeSprite
if not sprite then
return app.alert("No active sprite")
end
local outlineColor = Color(0, 0, 0) -- Black outline
-- Use built-in outline command
app.command.Outline{
color = outlineColor,
matrix = 'circle', -- 'circle' or 'square'
place = 'outside' -- 'outside' or 'inside'
}
print("Added outline")
Pixel Art Scaler
Implement a simple scaling algorithm:local sprite = app.activeSprite
if not sprite then
return app.alert("No active sprite")
end
local cel = app.activeCel
if not cel then
return app.alert("No active cel")
end
local scale = 2
local srcImage = cel.image
local newWidth = srcImage.width * scale
local newHeight = srcImage.height * scale
-- Create new scaled image
local newImage = Image(newWidth, newHeight, sprite.colorMode)
-- Nearest neighbor scaling
for y = 0, newHeight - 1 do
for x = 0, newWidth - 1 do
local srcX = math.floor(x / scale)
local srcY = math.floor(y / scale)
local color = srcImage:getPixel(srcX, srcY)
newImage:putPixel(x, y, color)
end
end
-- Create new cel with scaled image
local newLayer = sprite:newLayer()
newLayer.name = cel.layer.name .. " (scaled)"
sprite:newCel(newLayer, cel.frame, newImage)
print("Scaled sprite by " .. scale .. "x")
Next Steps
Automation Examples
Automate repetitive tasks
Batch Processing
Process multiple files at once
API Reference
Complete API documentation

