From 5872a931e1818b98f717f0c2c0226f511d5adbf7 Mon Sep 17 00:00:00 2001 From: hkc Date: Thu, 18 Jan 2024 16:26:57 +0300 Subject: [PATCH] CPIv1 implementation added --- cc-pic.py | 29 ++++++++++++++++--- ccpi.lua | 84 ++++++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 90 insertions(+), 23 deletions(-) diff --git a/cc-pic.py b/cc-pic.py index 85d175f..fc622bc 100644 --- a/cc-pic.py +++ b/cc-pic.py @@ -78,14 +78,35 @@ class Converter: dark_i, bri_i = bri_i, dark_i return out, dark_i, bri_i + @staticmethod + def _write_varint(fp: BinaryIO, value: int): + value &= 0xFFFFFFFF + mask: int = 0xFFFFFF80 + while True: + if (value & mask) == 0: + fp.write(bytes([value & 0xFF])) + return + fp.write(bytes([(value & 0x7F) | 0x80])) + value >>= 7 + def export_binary(self, io: BinaryIO): - io.write(b"CCPI") - io.write(bytes([self._img.width // 2, self._img.height // 3, 0])) - io.write(bytes(self._palette[: 16 * 3])) + if self._img.width <= 510 and self._img.height <= 765: + io.write(b"CCPI") # old format + io.write(bytes([self._img.width // 2, self._img.height // 3, 0])) + io.write(bytes(self._palette[: 16 * 3])) + else: + io.write(b"CPI\x01") # CPIv1 + self._write_varint(io, self._img.width // 2) + self._write_varint(io, self._img.height // 3) + io.write(bytes(self._palette[: 16 * 3])) + written = 0 for y in range(0, self._img.height - 2, 3): + line: bytearray = bytearray() for x in range(0, self._img.width - 1, 2): ch, bg, fg = self._get_block(x, y) - io.write(bytes([(ch + 0x80) & 0xFF, fg << 4 | bg])) + line.extend([(ch + 0x80) & 0xFF, fg << 4 | bg]) + written += io.write(line) + assert written == (self._img.width // 2) * (self._img.height // 3) * 2 def export(self, io: TextIO): io.write("local m = peripheral.find('monitor')\n") diff --git a/ccpi.lua b/ccpi.lua index b230169..f82c820 100644 --- a/ccpi.lua +++ b/ccpi.lua @@ -1,30 +1,20 @@ -local function load(path) - local image = { w = 0, h = 0, scale = 1.0, palette = {}, lines = {} } +local decoders = {} - local fp, err = io.open(path, "rb") - if not fp then return nil, err end - - local magic = fp:read(4) - if magic ~= "CCPI" then - return nil, "Invalid header: expected CCPI got " .. magic - end - - 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 +local function read_palette_full(palette, fp) for i = 1, 16 do - image.palette[i] = bit.blshift(string.byte(fp:read(1)), 16) - image.palette[i] = bit.bor(image.palette[i], bit.blshift(string.byte(fp:read(1)), 8)) - image.palette[i] = bit.bor(image.palette[i], string.byte(fp:read(1))) + 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 - print(image.w, image.h) - +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 == 0 then + if data == nil or #data == 0 then return nil, string.format("Failed to read character at x=%d y=%d", x, y) end @@ -39,9 +29,65 @@ local function load(path) 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