login

local ffi = require "ffi"
local bit = require "bit"

local bio = require "bio"
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 write_num = bio.write_lei16

local function write_nums(f, ...)
    local nums = {...}
    for i, n in pairs(nums) do
        write_num(f, n)
    end
end

local function get_depth(n)
    local m, e = math.frexp(n-1)
    return math.max(e, 1)
end

local GIFout = {}
GIFout.__index = GIFout

local function new_gifout(f, w, h, colors)
    if type(f) == "string" then f = io.open(f, "wb") end
    local depth = get_depth(#colors)
    local self = setmetatable({f=f, w=w, h=h, d=depth, gct=colors}, GIFout)
    if depth == 1 then
        self.back = surf.new_bitmap(w, h)
    else
        self.back = surf.new_bytemap(w, h)
    end
    f:write("GIF89a")
    write_nums(f, w, h)
    f:write(string.char(0xF0 + depth - 1, 0, 0)) -- FDSZ, BGINDEX, ASPECT
    local i = 1
    while i <= #colors do
        f:write(string.char(unpack(colors[i])))
        i = i + 1
    end
    while i <= 2^depth do             -- GCT size must be a power of two
        f:write(string.char(0, 0, 0)) -- fill unused colors as black
        i = i + 1
    end
    self.n = 0 -- # of frames added
    return self
end

function GIFout:set_loop(n)
    n = n or 0
    self.f:write("!")
    self.f:write(string.char(0xFF, 0x0B))
    self.f:write("NETSCAPE2.0")
    self.f:write(string.char(0x03, 0x01))
    write_num(self.f, n)
    self.f:write(string.char(0))
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(frame)
    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 back = 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(frame, 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, frame, x, y, w, h) -- IP (Appendix F)
end

function GIFout:add_frame(frame, delay)
    if delay then self:set_delay(delay) end
    local x, y, w, h = self:get_bbox(frame)
    self:put_image(frame, x, y, w, h)
    self.n = self.n + 1
    self.back:blit(x, y, frame, x, y, w, h)
end

function GIFout:close()
    self.f:write(";")
    self.f:close()
end

return {new_gifout=new_gifout}