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 parse(fp) 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 return nil, string.format("Invalid CPI version 0x%02x", version) end res, err = decoders[version](image, fp) else return nil, "Invalid header: expected CCPI got " .. magic end if not res then return false, err end return image end local function load(path) local fp, err = io.open(path, "rb") if not fp then return nil, err end local img img, err = parse(fp._handle) fp:close() return img, err 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 and img.scale ~= 1 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 if img.scale ~= 1 then t.setTextScale(img.scale) end 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, parse = parse }