-- only going to support binary ones -- because the textual ones are annoying -- types: -- P1 ascii pbm -- P2 ascii pgm -- P3 ascii ppm -- P4 binary pbm -- P5 binary pgm -- P6 binary ppm -- function nextpixel(imgf,cur,depth) -> pixel, ncur -- where pixel is {r,g,b}, with r,g,b in [0,1] local pixel_mt = {__tostring=function(s) return string.format("rgb(%f,%f,%f)",s[1],s[2],s[3]) end} local function np_p1(imgf,cur,depth) local val,ncur = imgf:match("([01])%s*()",cur) val = tonumber(val) assert(val,"bad image format at "..cur) local p = (1-val) -- 1 is black (0), 0 is white (1) local pixel = {p,p,p} return pixel,ncur end local function np_p2(imgf,cur,depth) local val,ncur = imgf:match("(%d+)%s+()",cur) val = tonumber(val) assert(val,"bad image format at "..cur) assert(0<=val and val<=depth, "image value out of range at "..cur) local p = val/depth local pixel = {p,p,p} return pixel,ncur end local function np_p3(imgf,cur,depth) local tr,tg,tb,ncur = imgf:match("(%d+)%s+(%d+)%s+(%d+)%s+()",cur) local vr,vg,vb = tonumber(tr),tonumber(tg),tonumber(tb) assert(vr,"bad image format at "..cur) for _,v in ipairs{vr,vg,vb} do assert(0<=v and v<=depth,"image value out of range at "..cur) end local pr,pg,pb = vr/depth,vg/depth,vb/depth local pixel = {pr,pg,pb} return pixel,ncur end -- i will figure this one out later local function np_p4() error("p4 isn't supported yet") end local function np_p5(imgf,cur,depth) local val = imgf:byte(cur) assert(val<=depth,"image value out of range at "..cur) local p = val/depth local pixel = {p,p,p} return pixel,cur+1 end local function np_p6(imgf,cur,depth) local vr,vg,vb = imgf:byte(cur,cur+3) for _,v in ipairs{vr,vg,vb} do assert(0<=v and v<=depth, "image value out of range at "..cur) end local pr,pg,pb = vr/depth,vg/depth,vb/depth local pixel = {pr,pg,pb} return pixel,cur+3 end local nextpixel_fns = { [1]=np_p1, [2]=np_p2, [3]=np_p3, [4]=np_p4, [5]=np_p5, [6]=np_p6, } local function parse(imgf) assert(imgf:sub(1,1) == "P","invalid header") local typech = tonumber(imgf:sub(2,2)) assert(typech and 1<=typech and typech<=6,"invalid header type") local has_depth = not(typech == 1 or typech == 4) local width,height,cur = imgf:match("\x0a(%d+) (%d+)\x0a()",3) width,height = tonumber(width),tonumber(height) assert(width,"couldn't find image dimensions") -- by 'depth' i mean 'maximum value' local depth = 1 if has_depth then local ndepth,ncur = imgf:match("(%d+)\x0a()",cur) ndepth=tonumber(ndepth) assert(ndepth,"couldn't find image depth (max pixel value), in a format that needs it") depth=ndepth cur=ncur end assert(depth<=255,"depth too high (shouldn't be above 255)") -- img[y][x]; 1,1 is top left local img = setmetatable({},{__index=function(t,k) if type(k) ~= "number" then return nil end local r = {} rawset(t,k,r) return r end}) local x,y=1,1 local nextpixel = nextpixel_fns[typech] while cur <= #imgf do local npixel,ncur = nextpixel(imgf,cur,depth) setmetatable(npixel,pixel_mt) img[y][x] = npixel cur = ncur x = x + 1 if x > width then x = 1 y = y + 1 end if y > height then break end end img.width = width img.height = height return img end return {load=parse}