Skip to main content

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