diff --git a/README.md b/README.md index 3f9d1cd..5106de3 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,4 @@ lots of different stuff I (Sophie) use for figura -code is all ARR, apart from lib/image2json.js which is MIT licensed +code is all ARR, apart from lib/image2json.js and lua/base64.lua, which are MIT licensed. diff --git a/lua/base64.lua b/lua/base64.lua new file mode 100644 index 0000000..32de332 --- /dev/null +++ b/lua/base64.lua @@ -0,0 +1,201 @@ +--[[ + + base64 -- v1.5.3 public domain Lua base64 encoder/decoder + no warranty implied; use at your own risk + + Needs bit32.extract function. If not present it's implemented using BitOp + or Lua 5.3 native bit operators. For Lua 5.1 fallbacks to pure Lua + implementation inspired by Rici Lake's post: + http://ricilake.blogspot.co.uk/2007/10/iterating-bits-in-lua.html + + author: Ilya Kolbin (iskolbin@gmail.com) + url: github.com/iskolbin/lbase64 + + COMPATIBILITY + + Lua 5.1+, LuaJIT + + LICENSE + + See end of file for license information. + +--]] + + +local base64 = {} + +local extract = _G.bit32 and _G.bit32.extract -- Lua 5.2/Lua 5.3 in compatibility mode +if not extract then + if _G.bit then -- LuaJIT + local shl, shr, band = _G.bit.lshift, _G.bit.rshift, _G.bit.band + extract = function( v, from, width ) + return band( shr( v, from ), shl( 1, width ) - 1 ) + end + elseif _G._VERSION == "Lua 5.1" then + extract = function( v, from, width ) + local w = 0 + local flag = 2^from + for i = 0, width-1 do + local flag2 = flag + flag + if v % flag2 >= flag then + w = w + 2^i + end + flag = flag2 + end + return w + end + else -- Lua 5.3+ + extract = load[[return function( v, from, width ) + return ( v >> from ) & ((1 << width) - 1) + end]]() + end +end + + +function base64.makeencoder( s62, s63, spad ) + local encoder = {} + for b64code, char in pairs{[0]='A','B','C','D','E','F','G','H','I','J', + 'K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y', + 'Z','a','b','c','d','e','f','g','h','i','j','k','l','m','n', + 'o','p','q','r','s','t','u','v','w','x','y','z','0','1','2', + '3','4','5','6','7','8','9',s62 or '+',s63 or'/',spad or'='} do + encoder[b64code] = char:byte() + end + return encoder +end + +function base64.makedecoder( s62, s63, spad ) + local decoder = {} + for b64code, charcode in pairs( base64.makeencoder( s62, s63, spad )) do + decoder[charcode] = b64code + end + return decoder +end + +local DEFAULT_ENCODER = base64.makeencoder() +local DEFAULT_DECODER = base64.makedecoder() + +local char, concat = string.char, table.concat + +function base64.encode( str, encoder, usecaching ) + encoder = encoder or DEFAULT_ENCODER + local t, k, n = {}, 1, #str + local lastn = n % 3 + local cache = {} + for i = 1, n-lastn, 3 do + local a, b, c = str:byte( i, i+2 ) + local v = a*0x10000 + b*0x100 + c + local s + if usecaching then + s = cache[v] + if not s then + s = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[extract(v,0,6)]) + cache[v] = s + end + else + s = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[extract(v,0,6)]) + end + t[k] = s + k = k + 1 + end + if lastn == 2 then + local a, b = str:byte( n-1, n ) + local v = a*0x10000 + b*0x100 + t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[64]) + elseif lastn == 1 then + local v = str:byte( n )*0x10000 + t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[64], encoder[64]) + end + return concat( t ) +end + +function base64.decode( b64, decoder, usecaching ) + decoder = decoder or DEFAULT_DECODER + local pattern = '[^%w%+%/%=]' + if decoder then + local s62, s63 + for charcode, b64code in pairs( decoder ) do + if b64code == 62 then s62 = charcode + elseif b64code == 63 then s63 = charcode + end + end + pattern = ('[^%%w%%%s%%%s%%=]'):format( char(s62), char(s63) ) + end + b64 = b64:gsub( pattern, '' ) + local cache = usecaching and {} + local t, k = {}, 1 + local n = #b64 + local padding = b64:sub(-2) == '==' and 2 or b64:sub(-1) == '=' and 1 or 0 + for i = 1, padding > 0 and n-4 or n, 4 do + local a, b, c, d = b64:byte( i, i+3 ) + local s + if usecaching then + local v0 = a*0x1000000 + b*0x10000 + c*0x100 + d + s = cache[v0] + if not s then + local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d] + s = char( extract(v,16,8), extract(v,8,8), extract(v,0,8)) + cache[v0] = s + end + else + local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d] + s = char( extract(v,16,8), extract(v,8,8), extract(v,0,8)) + end + t[k] = s + k = k + 1 + end + if padding == 1 then + local a, b, c = b64:byte( n-3, n-1 ) + local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + t[k] = char( extract(v,16,8), extract(v,8,8)) + elseif padding == 2 then + local a, b = b64:byte( n-3, n-2 ) + local v = decoder[a]*0x40000 + decoder[b]*0x1000 + t[k] = char( extract(v,16,8)) + end + return concat( t ) +end + +return base64 + +--[[ +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2018 Ilya Kolbin +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +--]] diff --git a/lua/decode.lua b/lua/decode.lua new file mode 100644 index 0000000..eca76dd --- /dev/null +++ b/lua/decode.lua @@ -0,0 +1,89 @@ + +local function getUint16(bytes, idx) + return bytes[idx] * 256 + bytes[idx + 1] +end + +local function getUint8(bytes, idx) + return bytes[idx] +end + +local function componentToHex(c) + return string.format("%02x", c) +end + +local function rgbToHex(r, g, b) + return "#" .. componentToHex(r) .. componentToHex(g) .. componentToHex(b) +end + +local function unpack_block(whole_buffer, startByte) + local paletteIdx = getUint16(whole_buffer, startByte) + local packedInfo = getUint8(whole_buffer, startByte + 2) + local blockAmount = math.floor(packedInfo / 16) + local byteRemoval = packedInfo % 16 + + local textArr = {} + + for i = 1, blockAmount do + local byte = getUint8(whole_buffer, startByte + 2 + i) + for bit = 7, 0, -1 do + table.insert(textArr, ((math.floor(byte / (2 ^ bit)) % 2) == 1) and "\n" or "▇") + end + end + + if byteRemoval > 0 then + for i = 1, byteRemoval do + table.remove(textArr) + end + end + + local endByte = startByte + 2 + blockAmount + return { paletteIdx = paletteIdx, text = table.concat(textArr), endByte = endByte } +end + + +local function unpack_blocks(packed) + local size = getUint16(packed, 1) + + local palette = {} + local idx = 3 + + for i = 1, 300 do + table.insert(palette, { + getUint8(packed, idx + 1), + getUint8(packed, idx + 2), + getUint8(packed, idx + 3) + }) + idx = idx + 3 + end + + local blocks_unpacked = {} + idx = idx + 1 + + while idx <= #packed do + local block = unpack_block(packed, idx) + idx = block.endByte + 1 + block.color = rgbToHex(table.unpack(palette[block.paletteIdx + 1])) + block.paletteIdx = nil + block.endByte = nil + table.insert(blocks_unpacked, block) + end + + return blocks_unpacked +end + +return { unpack_blocks = unpack_blocks } + +--[[ +local base64 = require("base64") +local lz4 = require("lz4") + +local bytes = base64.decode(image) +local byte_table = {} +for num in string.gmatch(bytes, "%d+") do + table.insert(byte_table, tonumber(num)) +end +local decomp = lz4.decompress(byte_table) + +local unpacked = unpack_blocks(decomp) +local json = <..json serializer..>:serialize(unpacked); +]] diff --git a/lua/lz4.lua b/lua/lz4.lua new file mode 100644 index 0000000..8369144 --- /dev/null +++ b/lua/lz4.lua @@ -0,0 +1,146 @@ +-- tiny_lz4_like_bytes.lua +local M = {} + +local MIN_MATCH = 3 +local MAX_OFFSET = 0xFFFF + +local function write_ext_len(out, n) + while n >= 255 do + table.insert(out, 255) + n = n - 255 + end + table.insert(out, n) +end + +local function read_ext_len(input, pos, base) + local add = 0 + while true do + local b = input[pos]; pos = pos + 1 + add = add + b + if b < 255 then break end + end + return base + add, pos +end + +local function find_longest_match(data, pos, window) + local n = #data + local max_off = math.min(window, pos - 1) + local best_len, best_off = 0, 0 + local max_match_len = n - pos + 1 + if max_match_len < MIN_MATCH then return 0, 0 end + + local start = pos - max_off + for j = start, pos - 1 do + if data[j] == data[pos] then + local k = 0 + while k < max_match_len and data[j + k] == data[pos + k] do + k = k + 1 + end + if k >= MIN_MATCH and k > best_len then + best_len = k + best_off = pos - j + if best_len >= 255 + MIN_MATCH then break end + end + end + end + return best_off, best_len +end + +function M.compress(bytes) + assert(type(bytes) == "table", "compress expects table of bytes (0-255)") + local n = #bytes + local i, anchor = 1, 1 + local out = {} + + while i <= n do + local off, match_len = find_longest_match(bytes, i, MAX_OFFSET) + + if match_len >= MIN_MATCH then + local lit_len = i - anchor + local lit_nib = math.min(lit_len, 15) + local match_nib = math.min(match_len - MIN_MATCH, 15) + local token = lit_nib * 16 + match_nib + table.insert(out, token) + + if lit_len >= 15 then + write_ext_len(out, lit_len - 15) + end + + for k = anchor, i - 1 do + table.insert(out, bytes[k]) + end + + -- offset + table.insert(out, off % 256) + table.insert(out, math.floor(off / 256) % 256) + + local rem = match_len - MIN_MATCH + if rem >= 15 then + write_ext_len(out, rem - 15) + end + + i = i + match_len + anchor = i + else + i = i + 1 + end + end + + local final_lit = n - anchor + 1 + local tok_lit = math.min(final_lit, 15) + local token = tok_lit * 16 + table.insert(out, token) + if final_lit >= 15 then + write_ext_len(out, final_lit - 15) + end + for k = anchor, n do + table.insert(out, bytes[k]) + end + + return out +end + +function M.decompress(bytes) + local pos, n = 1, #bytes + local out = {} + + while pos <= n do + local token = bytes[pos]; pos = pos + 1 + if not token then break end + + local lit_nib = math.floor(token / 16) + local match_nib = token % 16 + + local lit_len = lit_nib + if lit_len == 15 then + lit_len, pos = read_ext_len(bytes, pos, 15) + end + + for k = 1, lit_len do + table.insert(out, bytes[pos]); pos = pos + 1 + end + + if pos > n then break end + + local off = bytes[pos] + bytes[pos + 1] * 256 + pos = pos + 2 + + local match_len = match_nib + MIN_MATCH + if match_nib == 15 then + local extra; extra, pos = read_ext_len(bytes, pos, 0) + match_len = match_len + extra + end + + local sofar_len = #out + local match_start = sofar_len - off + 1 + if match_start < 1 then error("invalid offset") end + + for k = 0, match_len - 1 do + table.insert(out, out[match_start + k]) + end + end + + return out +end + +return M