├── license.txt ├── lua2pico.lua ├── pack.lua ├── pico2lua.lua ├── pico2png.lua ├── pico8.gpl ├── png2pico.lua └── readme.md /license.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Josef N Patoprsty 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /lua2pico.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/lua 2 | 3 | if not arg[1] or not arg[2] then 4 | 5 | print("Usage:\tpico2lua.lua [FILE.lua] [TARGET.p8]") 6 | print("Replaces the __lua__ section of the TARGET with the contents of FILE and then\nprints it to stdout.") 7 | print("e.g.: ./lua2pico.lua code.lua cart.p8 > new_cart.p8") 8 | 9 | else 10 | 11 | local lua_file 12 | 13 | if arg[1] == '-' then 14 | lua_file = io.stdin 15 | else 16 | lua_file = io.open(arg[1]) 17 | end 18 | 19 | local target_file = io.open(arg[2]) 20 | 21 | local found_lua = false 22 | local lua_done = false 23 | local output_lua = false 24 | while true do 25 | local target_line = target_file:read("*line") 26 | 27 | if target_line == nil then break end 28 | 29 | for section,_ in string.gmatch(target_line,"__(%a+)__") do 30 | if section == "lua" then 31 | found_lua = true 32 | break 33 | else 34 | found_lua = false 35 | break 36 | end 37 | end 38 | 39 | if found_lua then 40 | if not lua_done then 41 | lua_done = true 42 | io.write("__lua__\n") 43 | while true do 44 | local lua_line = lua_file:read("*line") 45 | if lua_line == nil then break end 46 | io.write(lua_line.."\n") 47 | end 48 | end 49 | else 50 | io.write(target_line.."\n") 51 | end 52 | 53 | end 54 | 55 | end 56 | -------------------------------------------------------------------------------- /pack.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/lua 2 | 3 | -- Originally released under the the Romantic WTF Public License 4 | -- Original version here: 5 | -- http://getmoai.com/wiki/index.php?title=Concatenate_your_Lua_source_tree 6 | 7 | args = {...} 8 | fs = require"lfs" 9 | files = {} 10 | 11 | root = args[1]:gsub( "/$", "" ) 12 | :gsub( "\\$", "" ) 13 | 14 | function scandir (root, path) 15 | -- adapted from http://keplerproject.github.com/luafilesystem/examples.html 16 | path = path or "" 17 | for file in fs.dir( root..path ) do 18 | if file ~= "." and file ~= ".." then 19 | local f = path..'/'..file 20 | local attr = lfs.attributes( root..f ) 21 | assert (type( attr ) == "table") 22 | if attr.mode == "directory" then 23 | scandir( root, f ) 24 | else 25 | if file:find"%.lua$" then 26 | hndl = (f:gsub( "%.lua$", "" ) 27 | :gsub( "/", "." ) 28 | :gsub( "\\", "." ) 29 | :gsub( "^%.", "" ) 30 | ):gsub( "%.init$", "" ) 31 | files[hndl] = io.open( root..f ):read"*a" 32 | end 33 | end 34 | end 35 | end 36 | end 37 | 38 | scandir( root ) 39 | 40 | acc={"--This file was generated with pack.lua\n__package_preload={}\n"} 41 | 42 | local wrapper = { "\n---file:\n__package_preload['" 43 | , nil, "'] = function (...)\n", nil, "\nend\n" } 44 | for k,v in pairs( files ) do 45 | wrapper[2], wrapper[4] = k, v 46 | table.insert( acc, table.concat(wrapper) ) 47 | end 48 | 49 | table.insert(acc, [[ 50 | --- require/dofile replacements 51 | 52 | __package_load = {} 53 | 54 | function assert(i,s) 55 | if not i then 56 | print(s) 57 | stop() 58 | end 59 | end 60 | 61 | function require(name) 62 | if not __package_load[name] then 63 | assert(__package_preload[name],"Error: module `"..name.."` does not exist.") 64 | __package_load[name] = __package_preload[name]() 65 | end 66 | return __package_load[name] 67 | end 68 | 69 | function dofile(name) 70 | local found 71 | for ppname,_ in pairs(__package_preload) do 72 | if name == ppname..".lua" then 73 | found = ppname 74 | break 75 | end 76 | end 77 | assert(__package_preload[found],"Error: file `"..found..".lua` does not exist.") 78 | return __package_preload[found]() 79 | end 80 | ]]) 81 | if files.main then 82 | table.insert( acc, '\nrequire"main"' ) 83 | end 84 | print( table.concat( acc ) ) 85 | -------------------------------------------------------------------------------- /pico2lua.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/lua 2 | 3 | if not arg[1] then 4 | 5 | print("Usage:\tpico2lua.lua [FILE]") 6 | print("Prints out the lua code in a file pico-8 format file to stdout.") 7 | print("e.g.: ./pico2lua.lua cart.p8 > code.lua") 8 | 9 | else 10 | 11 | local file 12 | 13 | if arg[1] == '-' then 14 | file = io.stdin 15 | else 16 | file = io.open(arg[1]) 17 | end 18 | 19 | local found_lua = false 20 | local section_header = false 21 | while true do 22 | local line = file:read("*line") 23 | 24 | if line == nil then break end 25 | 26 | for section,_ in string.gmatch(line,"__(%a+)__") do 27 | section_header = true 28 | if section == "lua" then 29 | found_lua = true 30 | break 31 | else 32 | found_lua = false 33 | break 34 | end 35 | end 36 | 37 | if found_lua then 38 | if section_header then 39 | section_header = false 40 | else 41 | io.write(line.."\n") 42 | end 43 | end 44 | 45 | end 46 | 47 | end 48 | -------------------------------------------------------------------------------- /pico2png.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/luajit 2 | 3 | local magick = require "magick" 4 | 5 | -- This following code is a big dirty hack that uses os.execute and imagemagick's 6 | -- convert to allow for the `new_image` and `set_pixel` functions. 7 | -- I would normally expect these to exist, but they don't right now. 8 | 9 | -- TODO: ask leafo to extend magick library 10 | __tmp = '/tmp/pico2png.temp.png' 11 | function new_image(w,h) 12 | os.execute('convert -size '..w.."x"..h..' xc:white '..__tmp) 13 | local png,err = magick.load_image(__tmp) 14 | return png,err 15 | end 16 | __points = {} 17 | __count = 0 18 | function set_pixel(x,y,r,g,b) 19 | table.insert(__points,"fill rgb("..r..","..g..","..b..") point "..x..","..y.." ") 20 | if __count > 2^12 then 21 | __flush_pixels() 22 | __count = 0 23 | end 24 | __count = __count + 1 25 | end 26 | function __flush_pixels() 27 | os.execute("convert -draw '"..table.concat(__points).."' "..__tmp.." "..__tmp) 28 | __points = {} 29 | end 30 | function __post() 31 | __flush_pixels() 32 | return magick.load_image(__tmp) 33 | end 34 | 35 | _rgb_colors = { 36 | ["0"] = {0,0,0}, 37 | ["1"] = {29,43,83}, 38 | ["2"] = {126,37,83}, 39 | ["3"] = {0,135,81}, 40 | ["4"] = {171,82,54}, 41 | ["5"] = {95,87,79}, 42 | ["6"] = {194,195,199}, 43 | ["7"] = {255,241,232}, 44 | ["8"] = {255,0,77}, 45 | ["9"] = {255,163,0}, 46 | ["a"] = {255,240,36}, 47 | ["b"] = {0,231,86}, 48 | ["c"] = {41,173,255}, 49 | ["d"] = {131,118,156}, 50 | ["e"] = {255,119,168}, 51 | ["f"] = {255,204,170}, 52 | } 53 | 54 | function pico2rgb(pico) 55 | return _rgb_colors[pico] 56 | end 57 | 58 | if not arg[1] then 59 | 60 | print("Usage:\tpico2png.lua [FILE]") 61 | print("Prints out the pico-8 gfx code in png format file to stdout.") 62 | print("e.g.: ./tpico2png.lua cart.p8 > spritesheet.png") 63 | 64 | else 65 | 66 | local file = io.open(arg[1]) 67 | 68 | local png = new_image(128,128) 69 | local x,y = 0,0 70 | 71 | local found_lua = false 72 | local section_header = false 73 | 74 | while true do 75 | local line = file:read("*line") 76 | 77 | if line == nil then break end 78 | 79 | for section,_ in string.gmatch(line,"__(%a+)__") do 80 | section_header = true 81 | if section == "gfx" then 82 | found_lua = true 83 | break 84 | else 85 | found_lua = false 86 | break 87 | end 88 | end 89 | 90 | if found_lua then 91 | if section_header then 92 | section_header = false 93 | else 94 | 95 | for x = 1, #line do 96 | local c = line:sub(x,x) 97 | local rgb_color = pico2rgb(c) 98 | assert(rgb_color,"Error: Input gfx contains invalid color") 99 | set_pixel(x-1,y,rgb_color[1],rgb_color[2],rgb_color[3]) 100 | end 101 | 102 | y = y + 1 103 | 104 | end 105 | end 106 | 107 | end 108 | 109 | -- TODO: remove once hacks are removed 110 | png = __post() 111 | print(png:get_blob()) 112 | 113 | end 114 | -------------------------------------------------------------------------------- /pico8.gpl: -------------------------------------------------------------------------------- 1 | GIMP Palette 2 | Name: pico8 3 | Columns: 4 4 | # 5 | 0 0 0 black 6 | 29 43 83 dark_blue 7 | 126 37 83 dark_purple 8 | 0 135 81 dark_green 9 | 171 82 54 brown 10 | 95 87 79 dark_gray 11 | 194 195 199 light_gray 12 | 255 241 232 white 13 | 255 0 77 red 14 | 255 163 0 orange 15 | 255 240 36 yellow 16 | 0 231 86 green 17 | 41 173 255 blue 18 | 131 118 156 indigo 19 | 255 119 168 pink 20 | 255 204 170 peach 21 | -------------------------------------------------------------------------------- /png2pico.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/luajit 2 | 3 | local magick = require "magick" 4 | 5 | _rgb_colors = { 6 | ["0,0,0"] = 0, 7 | ["29,43,83"] = 1, 8 | ["126,37,83"] = 2, 9 | ["0,135,81"] = 3, 10 | ["171,82,54"] = 4, 11 | ["95,87,79"] = 5, 12 | ["194,195,199"] = 6, 13 | ["255,241,232"] = 7, 14 | ["255,0,77"] = 8, 15 | ["255,163,0"] = 9, 16 | ["255,240,36"] = 10, 17 | ["0,231,86"] = 11, 18 | ["41,173,255"] = 12, 19 | ["131,118,156"] = 13, 20 | ["255,119,168"] = 14, 21 | ["255,204,170"] = 15, 22 | } 23 | 24 | function rgb2pico(r,g,b) 25 | return _rgb_colors[r..","..g..","..b] 26 | end 27 | 28 | if not arg[1] or not arg[2] then 29 | 30 | print("Usage:\tpico2png.lua [FILE.png] [TARGET.p8]") 31 | print("Replaces the __gfx__ section of the TARGET with the contents of FILE and then\nprints it to stdout.") 32 | print("e.g.: ./png2pico.lua spritesheet.png cart.p8 > new_cart.p8") 33 | 34 | else 35 | 36 | local png_file,png_file_error = magick.load_image(arg[1]) 37 | assert(png_file,png_file_error) 38 | local target_file = io.open(arg[2]) 39 | 40 | local found_png = false 41 | local png_done = false 42 | local output_png = false 43 | while true do 44 | local target_line = target_file:read("*line") 45 | 46 | if target_line == nil then break end 47 | 48 | for section,_ in string.gmatch(target_line,"__(%a+)__") do 49 | if section == "gfx" then 50 | found_png = true 51 | break 52 | else 53 | found_png = false 54 | break 55 | end 56 | end 57 | 58 | if found_png then 59 | if not png_done then 60 | png_done = true 61 | io.write("__gfx__\n") 62 | for y = 0,127 do 63 | for x = 0,127 do 64 | local r,g,b = png_file:get_pixel(x,y) 65 | local pico_color = rgb2pico( 66 | math.floor(r*255), 67 | math.floor(g*255), 68 | math.floor(b*255)) 69 | assert(pico_color,"Error: Input image contains invalid color") 70 | io.write(string.format("%x",pico_color)) 71 | end 72 | io.write("\n") 73 | end 74 | io.write("\n") 75 | end 76 | else 77 | io.write(target_line.."\n") 78 | end 79 | 80 | end 81 | 82 | end 83 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # PICO-8 UTILS 2 | 3 | This is a set of utilities made for the 0.1.2 `.p8` [PICO-8](http://www.lexaloffle.com/pico-8.php) file format. 4 | 5 | They were written against Lua 5.3, but will most likely run correctly against Lua 5.x. 6 | 7 | All of these scripts were made with the unix philosophy. 8 | 9 | For `pico2png.lua` and `png2pico.lua`, you need to use luajit, magick (`luarocks install magick`) and have imagemagick installed. 10 | 11 | For `pack.lua`, you need to have lfs installed (`luarocks install luafilesystem`) 12 | 13 | ## Example usage: 14 | 15 | Extract `foo.p8`'s Lua code from `foo.p8`: 16 | 17 | `lua ./pico2lua.lua foo.p8 > foo.lua` 18 | 19 | Extract `foo.p8`'s gfx as a png from `foo.p8`: 20 | 21 | `lua ./pico2png.lua foo.p8 > foo.png` 22 | 23 | __these examples include backup__ 24 | 25 | Update `foo.p8`'s Lua code with `foo.lua`: 26 | 27 | `cp foo.lua foo.backup.lua && luajit ./png2pico.lua foo.lua foo.backup.p8 > foo.p8` 28 | 29 | Update `foo.p8`'s spritesheet gfx with `foo.png`: 30 | 31 | `cp foo.p8 foo.backup.p8 && luajit ./png2pico.lua foo.png foo.backup.p8 > foo.p8` 32 | 33 | ## Dependencies 34 | 35 | While some of these scripts can use any `5.x` version of `lua`, the ones dependent on `magick` requires `luajit` (which is the drop in for `5.1`). 36 | 37 | If you have instructions for an OS that is not listed here, please open an issue with instructions or make a pull request. 38 | 39 | ### Linux (Arch) 40 | 41 | ``` 42 | sudo pacman -S lua51 luajit luarocks5.1 43 | sudo luarocks-5.1 install magick 44 | luarocks-5.1 list 45 | cd /path/to/repo 46 | ``` 47 | 48 | ### OS X 49 | 50 | ```bash 51 | brew install lua51 52 | brew install luajit 53 | luarocks-5.1 install magick 54 | luarocks-5.1 install luafilesystem 55 | cd /path/to/repo 56 | ``` 57 | --------------------------------------------------------------------------------