login

<     >

2021-04-10 23:05:40 (UTC-03:00)

Marcel Rodrigues <marcelgmr@gmail.com>

add GIF encoding

diff --git a/gif.lua b/gif.lua
new file mode 100644
index 0000000..16c48d6
--- /dev/null
+++ b/gif.lua
@@ -0,0 +1,88 @@
+local ffi = require "ffi"
+local bit = require "bit"
+
+local surf = require "surf"
+local lzw = require "lzw"
+
+local bnot = bit.bnot
+local bor, band = bit.bor, bit.band
+local lshift, rshift =  bit.lshift,  bit.rshift
+
+local function write_num(f, n)
+    f:write(string.char(band(n, 0xFF), rshift(n, 8)))
+end
+
+local function write_nums(f, ...)
+    local nums = {...}
+    for i, n in pairs(nums) do
+        write_num(f, n)
+    end
+end
+
+local GIFout = {}
+GIFout.__index = GIFout
+
+local function new_gifout(f, w, h, depth, gct, frame, back)
+    if type(f) == "string" then f = io.open(f, "wb") end
+    local self = setmetatable({f=f, w=w, h=h, d=depth, gct=gct}, GIFout)
+    -- TODO: use ByteMap (NYI) if depth > 1
+    assert(depth == 1)
+    self.frame = frame or surf.new_bitmap(w, h)
+    self.back = back or surf.new_bitmap(w, h)
+    f:write("GIF89a")
+    write_nums(f, w, h)
+    f:write(string.char(0xF0 + depth - 1, 0, 0)) -- FDSZ, BGINDEX, ASPECT
+    f:write(ffi.string(gct, 3 * 2 ^ depth))
+    -- TODO: write Netscape Application Extension (loop) here
+    self.n = 0 -- # of frames added
+    return self
+end
+
+function GIFout:set_delay(d)
+    self.f:write("!")
+    self.f:write(string.char(0xF9, 0x04, 0x04))
+    write_num(self.f, d)
+    self.f:write(string.char(0, 0))
+end
+
+function GIFout:get_bbox()
+    local w, h = self.w, self.h
+    if self.n == 0 then return 0, 0, w, h end
+    local xmin, ymin = w, h
+    local xmax, ymax = 0, 0
+    local frame, back = self.frame, self.back
+    for y = 0, h-1 do
+        for x = 0, w-1 do
+            if frame:pget(x, y) ~= back:pget(x, y) then
+                if x < xmin then xmin = x end
+                if y < ymin then ymin = y end
+                if x > xmax then xmax = x end
+                if y > ymax then ymax = y end
+            end
+        end
+    end
+    if xmin == w or ymin == h then return 0, 0, 1, 1 end
+    return xmin, ymin, xmax-xmin+1, ymax-ymin+1
+end
+
+function GIFout:put_image(x, y, w, h)
+    self.f:write(",")
+    write_nums(self.f, x, y, w, h)
+    self.f:write(string.char(0))
+    lzw.encode(self.f, self.d, self.frame, x, y, w, h) -- IP (Appendix F)
+end
+
+function GIFout:add_frame(delay)
+    if delay then self:set_delay(delay) end
+    local x, y, w, h = self:get_bbox()
+    self:put_image(x, y, w, h)
+    self.n = self.n + 1
+    self.frame, self.back = self.back, self.frame
+end
+
+function GIFout:close()
+    self.f:write(";")
+    self.f:close()
+end
+
+return {new_gifout=new_gifout}

diff --git a/lzw.lua b/lzw.lua
new file mode 100644
index 0000000..07f9ecb
--- /dev/null
+++ b/lzw.lua
@@ -0,0 +1,104 @@
+local ffi = require "ffi"
+local bit = require "bit"
+
+local bnot = bit.bnot
+local bor, band = bit.bor, bit.band
+local lshift, rshift =  bit.lshift,  bit.rshift
+
+local BUFout = {}
+BUFout.__index = BUFout
+
+local function new_buffer(f)
+    local self = setmetatable({f=f, offset=0, partial=0}, BUFout)
+    self.buf = ffi.new("char[255]")
+    return self
+end
+
+function BUFout:put_key(key, size)
+    local offset, partial = self.offset, self.partial
+    local f, buf = self.f, self.buf
+    local byte_offset, bit_offset = math.floor(offset / 8), offset % 8
+    partial = bor(partial, lshift(key, bit_offset))
+    local bits_to_write = bit_offset + size
+    while bits_to_write >= 8 do
+        buf[byte_offset] = band(partial, 0xFF)
+        byte_offset = byte_offset + 1
+        if byte_offset == 0xFF then -- flush
+            f:write(string.char(0xFF))
+            f:write(ffi.string(buf, 0xFF))
+            byte_offset = 0
+        end
+        partial = rshift(partial, 8)
+        bits_to_write = bits_to_write - 8
+    end
+    self.offset = (offset + size) % (0xFF * 8)
+    self.partial = partial
+end
+
+function BUFout:end_key()
+    local offset, partial = self.offset, self.partial
+    local f, buf = self.f, self.buf
+    local byte_offset = math.floor(offset / 8)
+    if offset % 8 ~= 0 then
+        buf[byte_offset] = band(partial, 0xFF)
+        byte_offset = byte_offset + 1
+    end
+    if byte_offset > 0 then
+        f:write(string.char(byte_offset))
+        f:write(ffi.string(buf, byte_offset))
+    end
+    f:write(string.char(0))
+    self.offset, self.partial = 0, 0
+end
+
+
+local function new_trie(degree)
+    local children = {}
+    for key = 0, degree-1 do
+        children[key] = {key=key, children={}}
+    end
+    return {children=children}
+end
+
+local function encode(f, d, s, x, y, w, h)
+    local buf = new_buffer(f)
+    local code_size = math.max(d, 2)
+    f:write(string.char(code_size))
+    local degree = 2 ^ code_size
+    local root = new_trie(degree)
+    local clear, stop = degree, degree + 1
+    local nkeys = degree + 2 -- skip clear code and stop code
+    local node = root
+    local key_size = code_size + 1
+    buf:put_key(clear, key_size)
+    for j = y, y+h-1 do
+        for i = x, x+w-1 do
+            local index = band(s:pget(i, j), degree-1)
+            local child = node.children[index]
+            if child ~= nil then
+                node = child
+            else
+                buf:put_key(node.key, key_size)
+                if nkeys < 0x1000 then
+                    if nkeys == 2 ^ key_size then
+                        key_size = key_size + 1
+                    end
+                    node.children[index] = {key=nkeys, children={}}
+                    nkeys = nkeys + 1
+                else
+                    buf:put_key(clear, key_size)
+                    root = new_trie(degree)
+                    node = root
+                    nkeys = degree + 2
+                    key_size = code_size + 1
+                end
+                node = root.children[index]
+            end
+        end
+    end
+    buf:put_key(node.key, key_size)
+    buf:put_key(stop, key_size)
+    buf:end_key()
+end
+
+return {encode=encode}