Skip to content

Instantly share code, notes, and snippets.

@pancelor
Last active January 7, 2025 19:06
Show Gist options
  • Save pancelor/43512028087bf34e5d9773a61a03f93a to your computer and use it in GitHub Desktop.
Save pancelor/43512028087bf34e5d9773a61a03f93a to your computer and use it in GitHub Desktop.
aseprite to pico8/picotron exporter

picotron is out! importing sprites is still a bit tricky, so I built this script to help, along with this picotron cart

this script works great for pico8 too -- I use it often

how to use

you'll need Aseprite to use this.

  1. save this script to your aseprite scripts folder (File > Scripts > Open Scripts Folder) then reopen aseprite (or click "Rescan Scripts")
    • (optional) edit local autocopy in the script, based on your operating system. if you set this up, the script will automatically write to your clipboard when it's time to copy something
  2. load an image with the picotron palette (or pico-8 extended palette)
  3. (optional) change the image's color mode to "indexed" (Sprite > Color Mode > Indexed)
    • if you skip this step, picotron will find the "closest" color for each pixel when you import. (but, the "closest" algorithm isn't very smart)
    • be sure aseprite's palette is in the right order! you can load the palette (above) into aseprite and then press the "remap colors" button that pops up
  4. launch this script, press "Copy" to copy to clipboard. (alt-c should work too)
    • if there is a current selection, it will only copy that region
    • leave the script window open to copy again later
    • you can choose an export format: "pod" (for picotron) or "gfx" (for picotron or pico8)
  5. the string in your clipboard should look something like this:
    • gfx format: [gfx]08080000000000000000000000000700707007777007717177700777777000607070[/gfx]. This can be pasted into the pico8 or picotron sprite editors.
    • pod format: --[[pod,pod_type="image"]]userdata("u8",8,8,"00000000000000000000000000000000000000000000000000070000070007000007070707000007070107010707070000070707070707000000060007000700"). This can be pasted into the picotron sprite editor, or pasted into code, or pasted in a text file or a --[[multiline code comment]] (it will be drawn!) For other options, see the picotron alpha release notes.

changelog

  • 2024-12-18: add autocopy, now that aseprite allows popen
  • 2024-10-22: tweak instructions, link to updated palette
  • 2024-04-07: fix bug with merge-all-layers
  • 2024-03-23: add support for pod format (this allows for images larger than 256x256)
  • 2024-03-22: fix a bug where images with empty space around them would not export correctly
  • 2024-03-17: picotron is out! update script and link to sprimp (a tool for importing spritesheets in picotron)
  • 2023-01-01: initial release
-- PICO-8/Picotron Exporter
-- by pancelor -- https://pancelor.com/
-- a tool to export sprites (or current selection)
-- into a format you can paste into pico8/picotron, e.g.:
-- [gfx]08080000000000000000000000000700707007777007717177700777777000607070[/gfx]
-- more info: https://gist.github.com/pancelor/43512028087bf34e5d9773a61a03f93a
-- change this variable! valid options are:
-- "windows" -- if your machine is a windows desktop
-- "osx" -- if your machine is an apple mac desktop
-- "linux" -- if your machine is a linux desktop (uses `xsel --clipboard`)
-- "manual" -- anything else; you must manually press ctrl-c to copy the output
local autocopy = "manual"
--[[
# helpers
]]
if autocopy~="windows" and autocopy~="osx" and autocopy~="linux" then
autocopy="manual"
end
-- 0=>"0", ... 15=>"f", 16=>"g", ... 31=>"v"
local function picohex(val)
if 0<=val and val<=9 then
return string.char(0x30+val)
elseif 10<=val and val<=31 then
return string.char(97+val-10)
else
assert(nil,"bad val for picohex: "..val)
end
end
local function export_pixels(cel,rect, mapper)
local res={}
mapper=mapper or function(...) return ... end
for y=rect.y,rect.y+rect.height-1 do
for x=rect.x,rect.x+rect.width-1 do
local ix=0
if 0<=x and x<cel.image.width and 0<=y and y<cel.image.height then
ix = Color(cel.image:getPixel(x,y)).index -- .index chooses the closest color in the current palette
-- ix = app.pixelColor.tileI(cel.image:getPixel(x,y))
end
table.insert(res,mapper(ix))
end
end
return res
end
-- pico8 format (picotron can read it too)
local function export_gfx(cel,rect)
local build=export_pixels(cel,rect,picohex)
local res=string.format("[gfx]%02x%02x%s[/gfx]",rect.width,rect.height,table.concat(build,""))
return res
end
-- picotron format
local function export_pod(cel,rect)
local build=export_pixels(cel,rect,function(ix) return string.format("%02x",ix) end)
local res=string.format('--[[pod_type="image"]]userdata("u8",%d,%d,"%s")',rect.width,rect.height,table.concat(build,""))
return res
end
-- copy string to host clipboard
-- https://community.aseprite.org/t/solved-copy-string-within-aseprite-extension/16344
local function copy_to_clipboard(str)
-- returns whether the copy command succeeded
-- (this function may fail; use pcall)
local function os_copy(os_name,text)
if os_name=="windows" then
return io.popen('clip','w'):write(text):close()
elseif os_name=="osx" then
return io.popen('pbcopy','w'):write(text):close()
elseif os_name=="linux" then
return io.popen('xsel --clipboard','w'):write(text):close()
end
return nil --failed
end
local pcall_ok,os_copy_ok = pcall(os_copy,autocopy,str)
if not (pcall_ok and os_copy_ok) then
-- autocopy=="manual", or some failure
-- fallback to print() (can be manually copied with ctrl-c)
print(str)
end
end
--[[
# main
]]
local dlg
local function main()
local cel=app.cel or app.activeCel -- https://www.aseprite.org/api/app#appactivecel
if not cel then return app.alert("error: no sprite found") end
-- rect: the subrectangle to export tiles from
local spr=app.sprite or app.activeSprite
local rect=spr.selection.isEmpty and spr.bounds or spr.selection.bounds
rect.x=rect.x-cel.position.x -- the cel's position is different from the absolute xy position: https://www.aseprite.org/api/image#imagegetpixel
rect.y=rect.y-cel.position.y
if dlg.data.format=="gfx" then
if rect.width>=256 or rect.height>=256 then
return app.alert("sprite is too big for gfx format; use pod format instead")
end
copy_to_clipboard(export_gfx(cel,rect))
elseif dlg.data.format=="pod" then
copy_to_clipboard(export_pod(cel,rect))
end
end
local function on_copy_requested()
if dlg.data.merge then
app.transaction(function()
app.command.FlattenLayers{visibleOnly=true}
end)
main()
app.command.Undo()
else
main()
end
end
dlg = Dialog("Pico Export")
dlg:combobox{
id = "format",
label = "Format",
options = {"pod", "gfx"},
option = "gfx", --default
}
dlg:check{
id = "merge",
label = "All Layers",
selected = false, --default
}
dlg:button{text="&Copy", onclick=on_copy_requested}
if autocopy=="manual" then
dlg:label{text="(then ctrl-c)"}
end
dlg:show{wait=false}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment