Log In  

I wanted a tool to help me import a large folder of asset .pngs (such as one you might get from https://kenney.nl/) into .gfx files. I know there are tools such as @pancelor 's helpful importpng.p64 (or just click-n-dragging onto the gfx editor), but I didn't want to do that for hundreds of files.

Using importpng.p64 as a foundation, I create a command-line utility for Picotron that will import entire folders of assets into .gfx files! Below is the snippet that you'll put in /appdata/system/utils/simport.lua

-- a tool to import all pngs in a folder into a single .gfx file
-- tool by  @fletch_pico
-- version 1.1
-- 
-- makes use of:
-- importpng.p64 code by     @pancelor
-- https://www.lexaloffle.com/bbs/?tid=141149

cd(env().path)
local argv = env().argv

if (argv[1] == "--help") then
    print("Usage: simport [FOLDER]")
    print("Populate the .gfx file using a folder of PNGs.")
    print("")
    print("OPTIONS")
    print("  -e")
    print("  --empty-sprite-0")
    print("  keep sprite 0 empty on every .gfx file")
    print("  (default is to write data to sprite 0)")
    exit(0)
end

-- check if argv[1] is a folder
local path = fullpath(env().argv[1])
if (not path) then
    print("could not resolve path")
    exit(1)
end

-- loop through items in this folder looking for pngs
local dir_items = ls(path)
local pngs = {}
for i=1,#dir_items do
    local item = dir_items[i]
    if (#item > 4 and sub(item,-4) == ".png") then
        add(pngs,item)
    end
end

if (#pngs < 1) then
    print("no PNGs found, exiting early...")
    exit(1)
end

if (argv[2] == "-e" or argv[2] == "--empty-sprite-0") then
    local i = 0
    while i < #pngs do
        add(pngs, "empty", i+1) -- gotta add 1 because pngs is not 0-indexed here
        i += 256
    end
end
-- ------------------------------------------
--     @pancelor's importpng.p64 code starts here
-- ------------------------------------------
function sprite_from_image(png_path)
    local img_i32 = fetch(png_path)
    if not img_i32 then return nil end

    local rgb = {}
    for i=0,31 do
        rgb[i] = rgb_from_int(peek4(0x5000+i*4))
    end

    local w,h = img_i32:attribs()
    local img_u8 = userdata("u8",w,h)
    for i=0,w*h-1 do
        img_u8[i] = best_match(rgb,img_i32[i])
    end

    return {
        bmp=img_u8,
        flags=0x0,
        pan_x=0,
        pan_y=0,
        zoom=8
    }
end

local _memo={}
function best_match(rgb,now_i32)
    local res=_memo[now_i32]
    if res then return res end

    local now = rgb_from_int(now_i32)
    local v,k = minby0(rgb,function(cvec)
        cvec -= now
        return cvec:dot(cvec) --distance squared to now
    end)

    _memo[now_i32]=k
    return k
end

-- rgb: vec(r,g,b), each in [0,1] range
-- returns: 0xRRGGBB
function int_from_rgb(rgb)
    return (rgb.x*255\1<<16)+(rgb.y*255\1<<8)+(rgb.z*255\1)
end

-- int: 0xRRGGBB
-- returns: vec(r,g,b), each in [0,1] range
function rgb_from_int(int)
    return vec((int>>16&0xff)/255,(int>>8&0xff)/255,(int&0xff)/255)
end

function minby0(arr, fn)
  fn = fn or function(x) return x end
  local best,besti = fn(arr[0]),0
  for i=1,#arr do
    local now = fn(arr[i])
    if now<best then
      best,besti = now,i
    end
  end
  return best,besti
end
-- ------------------------------------------
--     @pancelor's importpng.p64 code stops here
-- ------------------------------------------

-- loop through pngs table and start copying their userdata
local total_files = #pngs
local idx = 0

-- core loop - get a png userdata, copy to .gfx, go next
local gfx_data = {}
for i=0,flr(#pngs/256) do
    -- create a new gfx "page"
    gfx_data[i] = {}

    -- calculate how many sprites are in this page
    local num_sprites = #pngs-(i*256)

    -- loop through each sprite and populate the current page
    for j=0,num_sprites-1    do
        -- check if this is the "empty" sprite
        if (pngs[i*256+j+1] == "empty") then
            gfx_data[i][j] = {
                bmp=userdata("u8", 16, 16), -- empty 16x16 userdata
                flags=0x0,
                pan_x=0,
                pan_y=0,
                zoom=8
            }
        -- this is a real file to attempt to import
        else
            local data = sprite_from_image(path.."/"..pngs[i*256+j+1])
            if (data ~= nil) then
                gfx_data[i][j] = data
                print("imported "..pngs[i*256+j+1].." ("..(i*256+j+1).."/"..total_files..")")
            else
                print("failed to import "..pngs[i].."; continuing")
            end
        end
    end
end

-- write to gfx file(s)
for i=0,flr(#pngs/256) do
    print("storing "..i..".gfx")
    store(path.."/"..i..".gfx", gfx_data[i])
end
P#151555 2024-07-20 20:44 ( Edited 2024-07-21 09:09)

Usage:

simport [FOLDER]
Populate .gfx file(s) using a folder of PNGs.

OPTIONS
  -e
  --empty-sprite-0
  keep sprite 0 empty on every .gfx file
  (default is to write data to sprite 0)
P#151595 2024-07-21 09:10

[Please log in to post a comment]