local decoders = {} local function read_palette_full(palette, fp) for i = 1, 16 do palette[i] = bit.blshift(string.byte(fp:read(1)), 16) palette[i] = bit.bor(palette[i], bit.blshift(string.byte(fp:read(1)), 8)) palette[i] = bit.bor(palette[i], string.byte(fp:read(1))) end end local function read_pixeldata_v0(image, fp) for y = 1, image.h do local line = { s = "", bg = "", fg = "" } for x = 1, image.w do local data = fp:read(2) if data == nil or #data == 0 then return nil, string.format("Failed to read character at x=%d y=%d", x, y) end line.s = line.s .. data:sub(1, 1) local color = string.byte(data, 2, 2) if color == nil then return nil, string.format("Failed to read color data for x=%d y=%d", x, y) end line.bg = line.bg .. string.format("%x", bit.band(0xF, color)) line.fg = line.fg .. string.format("%x", bit.band(0xF, bit.brshift(color, 4))) end table.insert(image.lines, line) end return true end local function read_varint(fp) local value = 0 local current = 0 local offset = 0 repeat if offset >= 5 then return nil, "varint too long" end current = string.byte(fp:read(1)) value = bit.bor(value, bit.blshift(bit.band(current, 0x7f), offset * 7)) offset = offset + 1 until bit.band(current, 0x80) == 0 return value end decoders[0] = function(image, fp) image.w, image.h = string.byte(fp:read(1)), string.byte(fp:read(1)) image.scale = 0.5 + string.byte(fp:read(1)) * 5 / 255 read_palette_full(image.palette, fp) local success, err = read_pixeldata_v0(image, fp) if not success then return false, err end return true end decoders[1] = function(image, fp) image.w = read_varint(fp) image.h = read_varint(fp) image.scale = 0.5 -- CPIv1 doesn't have a scale property read_palette_full(image.palette, fp) local success, err = read_pixeldata_v0(image, fp) if not success then return false, err end return true end local function load(path) local fp, err = io.open(path, "rb") if not fp then return nil, err end local res local image = { w = 0, h = 0, scale = 1.0, palette = {}, lines = {} } local magic = fp:read(4) if magic == "CCPI" then res, err = decoders[0](image, fp) elseif magic:sub(1, 3) == "CPI" then local version = magic:byte(4, 4) if decoders[version] == nil then fp:close() return nil, string.format("Invalid CPI version 0x%02x", version) end res, err = decoders[version](image, fp) else fp:close() return nil, "Invalid header: expected CCPI got " .. magic end fp:close() if not res then return false, err end return image end local function draw(img, ox, oy, monitor) -- todo: add expect() local t = monitor or term.current() ox = ox or 1 oy = oy or 1 if not t.setPaletteColor then return nil, "setPaletteColor is not supported on this term" end if not t.setTextScale then return nil, "setTextScale is not supported on this term" end for i = 1, 16 do t.setPaletteColor(bit.blshift(1, i - 1), img.palette[i]) end t.setTextScale(img.scale) for y = 1, img.h do t.setCursorPos(ox, oy + y - 1) t.blit(img.lines[y].s, img.lines[y].fg, img.lines[y].bg) end end return { load = load, draw = draw }