packpico/gif.lua

194 lines
3.9 KiB
Lua

-- GIF encoder specialized for PICO-8
-- by gamax92.
local palmap={}
for i=1, 16 do
local palette=pico8.palette[i]
local value=bit.lshift(palette[1], 16)+bit.lshift(palette[2], 8)+palette[3]
palmap[i-1]=value
palmap[value]=i-1
end
local function num2str(data)
return string.char(bit.band(data, 0xFF), bit.rshift(data, 8))
end
local gif={}
function gif:frame(data)
self.file:write("\33\249\4\4\3\0\0\0")
local last=self.last
local x0, y0, x1, y1=0, nil, pico8.resolution[1]*2-1, pico8.resolution[2]*2-1
if self.first then
y0=0
self.first=nil
else
for y=0, y1 do
local kill=false
for x=x0, x1 do
local r1, g1, b1=last:getPixel(x, y)
local r2, g2, b2=data:getPixel(x, y)
if r1~=r2 or g1~=g2 or b1~=b2 then
y0=y
kill=true
break
end
end
if kill then
break
end
end
if y0==nil then
-- TODO: Output longer delay instead of bogus frame
x0, y0, x1, y1=0, 0, 0, 0
end
for x=x0, x1 do
local kill=false
for y=y0, y1 do
local r1, g1, b1=last:getPixel(x, y)
local r2, g2, b2=data:getPixel(x, y)
if r1~=r2 or g1~=g2 or b1~=b2 then
x0=x
kill=true
break
end
end
if kill then
break
end
end
for y=y1, y0, -1 do
local kill=false
for x=x0, x1 do
local r1, g1, b1=last:getPixel(x, y)
local r2, g2, b2=data:getPixel(x, y)
if r1~=r2 or g1~=g2 or b1~=b2 then
y1=y
kill=true
break
end
end
if kill then
break
end
end
for x=x1, x0, -1 do
local kill=false
for y=y0, y1 do
local r1, g1, b1=last:getPixel(x, y)
local r2, g2, b2=data:getPixel(x, y)
if r1~=r2 or g1~=g2 or b1~=b2 then
x1=x
kill=true
break
end
end
if kill then
break
end
end
end
self.file:write("\44"..num2str(x0)..num2str(y0)..num2str(x1-x0+1)..num2str(y1-y0+1).."\0\4")
local codetbl={}
for i=0, 15 do
codetbl[string.char(i)]=i
end
local last=17
local buffer=""
local stream={16}
for y=y0, y1 do
for x=x0, x1 do
local r, g, b=data:getPixel(x, y)
r, g, b=r*255, g*255, b*255
local index=string.char(palmap[bit.lshift(r, 16)+bit.lshift(g, 8)+b])
local temp=buffer..index
if codetbl[temp] then
buffer=temp
else
stream[#stream+1]=codetbl[buffer]
last=last+1
if last<4095 then
codetbl[temp]=last
else
stream[#stream+1]=16
codetbl={}
for i=0, 15 do
codetbl[string.char(i)]=i
end
last=17
end
buffer=tostring(index)
end
end
end
stream[#stream+1]=codetbl[buffer]
stream[#stream+1]=17
local output={}
local size=5
local bits=0
local pack=0
local base=-16
for i=1, #stream do
pack=pack+bit.lshift(stream[i], bits)
bits=bits+size
while bits>=8 do
bits=bits-8
output[#output+1]=string.char(bit.band(pack, 0xFF))
pack=bit.rshift(pack, 8)
end
if i-base>=2^size then
size=size+1
end
if stream[i]==16 then
base=i-17
size=5
end
end
while bits>0 do
bits=bits-8
output[#output+1]=string.char(bit.band(pack, 0xFF))
pack=bit.rshift(pack, 8)
end
output=table.concat(output)
while #output>0 do
self.file:write(string.char(math.min(#output, 255))..output:sub(1, 255))
output=output:sub(256)
end
self.file:write("\0")
self.last=data
end
function gif:close()
self.file:write("\59")
self.file:close()
self.file=nil
self.last=nil
end
local gifmt={
__index=function(t, k)
return gif[k]
end
}
local giflib={}
function giflib.new(filename)
local file, err=love.filesystem.newFile(filename, "w")
if not file then
return nil, err
end
file:write("GIF89a"..num2str(pico8.resolution[1]*2)..num2str(pico8.resolution[2]*2).."\243\0\0")
for i=1, 16 do
local palette=pico8.palette[i]
file:write(string.char(palette[1], palette[2], palette[3]))
end
file:write("\33\255\11NETSCAPE2.0\3\1\0\0\0")
local last=love.image.newImageData(pico8.resolution[1]*2, pico8.resolution[2]*2)
return setmetatable({filename=filename, file=file, last=last, first=true}, gifmt)
end
return giflib