|
|
@@ -0,0 +1,519 @@ |
|
|
|
--[===================================================================[-- |
|
|
|
Copyright © 2016, 2018 Pedro Gimeno Fortea. All rights reserved. |
|
|
|
|
|
|
|
Permission is hereby granted to everyone to copy and use this file, |
|
|
|
for any purpose, in whole or in part, free of charge, provided this |
|
|
|
single condition is met: The above copyright notice, together with |
|
|
|
this permission grant and the disclaimer below, should be included |
|
|
|
in all copies of this software or of a substantial portion of it. |
|
|
|
|
|
|
|
THIS SOFTWARE COMES WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. |
|
|
|
--]===================================================================]-- |
|
|
|
|
|
|
|
-- GIF(sm) image decoder for the love2d framework, using LuaJIT + FFI. |
|
|
|
-- Includes LZW decompression. |
|
|
|
|
|
|
|
|
|
|
|
local ffi = require 'ffi' |
|
|
|
local bit = require 'bit' |
|
|
|
|
|
|
|
-- We have a "double buffer" coroutine-based consumer-producer system |
|
|
|
-- requiring the consumer to not request large chunks at a time |
|
|
|
-- otherwise the buffer would overflow (this is detected but it will |
|
|
|
-- cause an assertion error). |
|
|
|
|
|
|
|
local bytearray = ffi.typeof('uint8_t[?]') |
|
|
|
local intarray = ffi.typeof('int[?]') |
|
|
|
local int32ptr = ffi.typeof('int32_t *') |
|
|
|
|
|
|
|
-- Interlaced mode table. Format: |
|
|
|
-- {initial value for pass 1, increment for pass 1, |
|
|
|
-- initial value for pass 2, increment for pass 2, ...} |
|
|
|
local intertable = {0, 8, 4, 8, 2, 4, 1, 2, false} |
|
|
|
|
|
|
|
-- Utility function for error propagation |
|
|
|
local function coresume(co, ...) |
|
|
|
local ok, err = coroutine.resume(co, ...) |
|
|
|
if not ok then |
|
|
|
error(err) |
|
|
|
end |
|
|
|
end |
|
|
|
|
|
|
|
-- Consumer |
|
|
|
local function gifread(self, length) |
|
|
|
while self.ptr + length >= self.buflen do |
|
|
|
coroutine.yield() -- wait for more input |
|
|
|
end |
|
|
|
local tmp = self.ptr |
|
|
|
self.ptr = self.ptr + length |
|
|
|
if tmp >= 24576 then -- this leaves 8192 as max read length (768 would probably suffice) |
|
|
|
ffi.copy(self.buffer, self.buffer + tmp, self.buflen - tmp) |
|
|
|
self.buflen = self.buflen - tmp |
|
|
|
self.ptr = self.ptr - tmp |
|
|
|
tmp = 0 |
|
|
|
end |
|
|
|
return tmp, length |
|
|
|
end |
|
|
|
|
|
|
|
-- Producer - prepare the data for the consumer |
|
|
|
local function gifupdate(self, s) |
|
|
|
if #s > 32768 then |
|
|
|
-- Creating a Lua string object is an expensive operation. |
|
|
|
-- Do it as seldom as possible. We split the input data |
|
|
|
-- into 32K chunks. |
|
|
|
for i = 1, #s, 32768 do |
|
|
|
gifupdate(self, s:sub(i, i + 32767)) |
|
|
|
end |
|
|
|
return |
|
|
|
end |
|
|
|
|
|
|
|
if coroutine.status(self.decoder) == "dead" then |
|
|
|
-- feeding data after the decoding is finished, ignore |
|
|
|
return |
|
|
|
end |
|
|
|
assert(self.buflen <= 32768, "Buffer overflow") |
|
|
|
|
|
|
|
ffi.copy(self.buffer + self.buflen, s, #s) |
|
|
|
self.buflen = self.buflen + #s |
|
|
|
coresume(self.decoder) |
|
|
|
return self |
|
|
|
end |
|
|
|
|
|
|
|
local function gifdone(self) |
|
|
|
-- free C memory immediately |
|
|
|
self.buffer = false |
|
|
|
return self |
|
|
|
end |
|
|
|
|
|
|
|
local function giferr(self, msg) |
|
|
|
print(msg) |
|
|
|
end |
|
|
|
|
|
|
|
-- Gif decoding aux functions |
|
|
|
local function gifpalette(palette, source, psize) |
|
|
|
-- Read a palette, inserting alpha |
|
|
|
for i = 0, psize - 1 do |
|
|
|
palette[i*4] = source[i*3] |
|
|
|
palette[i*4 + 1] = source[i*3 + 1] |
|
|
|
palette[i*4 + 2] = source[i*3 + 2] |
|
|
|
palette[i*4 + 3] = 255 |
|
|
|
end |
|
|
|
end |
|
|
|
|
|
|
|
-- Gif decoder proper |
|
|
|
local function gifdecoder(self) |
|
|
|
-- Read file ID and header |
|
|
|
local buffer = self.buffer |
|
|
|
gifread(self, 13) |
|
|
|
if ffi.string(self.buffer, 6) ~= 'GIF87a' |
|
|
|
and ffi.string(self.buffer, 6) ~= 'GIF89a' |
|
|
|
then |
|
|
|
self:err('Invalid GIF file format') |
|
|
|
return |
|
|
|
end |
|
|
|
self.width = buffer[6] + 256*buffer[7] |
|
|
|
self.height = buffer[8] + 256*buffer[9] |
|
|
|
local gpalettesize = buffer[10] >= 128 and bit.lshift(1, bit.band(buffer[10], 7) + 1) or 0 |
|
|
|
local background = buffer[11] |
|
|
|
self.aspect = ((buffer[12] == 0 and 49 or 0) + 15) / 64 |
|
|
|
|
|
|
|
local gpalette = bytearray(256*4) |
|
|
|
local lpalette = bytearray(256*4) |
|
|
|
local lpalettesize |
|
|
|
-- Read palette and set background |
|
|
|
self.background = background -- default value |
|
|
|
if gpalettesize > 0 then |
|
|
|
gifread(self, gpalettesize * 3) |
|
|
|
gifpalette(gpalette, buffer + 13, gpalettesize) |
|
|
|
|
|
|
|
if background < gpalettesize then |
|
|
|
self.background = {gpalette[background*4], gpalette[background*4+1], gpalette[background*4+2]} |
|
|
|
end |
|
|
|
end |
|
|
|
|
|
|
|
local p |
|
|
|
local GCE_trans = false |
|
|
|
local GCE_dispose = 0 |
|
|
|
local GCE_delay = 0 |
|
|
|
|
|
|
|
-- Allocate the buffers in advance, to reuse them for every frame |
|
|
|
local dict = bytearray(4096) |
|
|
|
local dictptrs = intarray(4096) |
|
|
|
local reversebuf = bytearray(4096) |
|
|
|
|
|
|
|
repeat |
|
|
|
-- Get block type |
|
|
|
p = gifread(self, 1) |
|
|
|
local blocktype = 0x3B |
|
|
|
local blocklen |
|
|
|
-- for simplicity (?), we fuse the block type and the extension type into |
|
|
|
-- 'blocktype' |
|
|
|
if buffer[p] == 0x2C then |
|
|
|
-- Image block |
|
|
|
blocktype = 0x2C |
|
|
|
elseif buffer[p] == 0x21 then |
|
|
|
-- Extension block |
|
|
|
p = gifread(self, 1) |
|
|
|
blocktype = buffer[p] |
|
|
|
if blocktype == 0x2C then |
|
|
|
-- there's no extension 2C - terminate |
|
|
|
-- (avoids ambiguity with block type 2C) |
|
|
|
blocktype = 0x3B |
|
|
|
end |
|
|
|
elseif buffer[p] ~= 0x3B then |
|
|
|
self:err(string.format("Unknown block type: 0x%02X", buffer[p])) |
|
|
|
break |
|
|
|
end |
|
|
|
|
|
|
|
if blocktype == 0x3B then |
|
|
|
-- Trailer block or invalid block - terminate |
|
|
|
break |
|
|
|
|
|
|
|
elseif blocktype == 0xFF then |
|
|
|
-- Application extension - may be loop, otherwise skip |
|
|
|
p = gifread(self, 1) |
|
|
|
blocklen = buffer[p] |
|
|
|
p = gifread(self, blocklen + 1) |
|
|
|
if blocklen >= 11 and ffi.string(buffer + p, 11) == 'NETSCAPE2.0' then |
|
|
|
-- these *are* the androids we're looking for |
|
|
|
p = p + blocklen |
|
|
|
while buffer[p] ~= 0 do |
|
|
|
local sblen = buffer[p] |
|
|
|
p = gifread(self, sblen + 1) -- read also the next block length |
|
|
|
if buffer[p] == 1 and sblen >= 3 then |
|
|
|
-- looping subblock - that's for us |
|
|
|
self.loop = buffer[p + 1] + 256 * buffer[p + 2] |
|
|
|
end |
|
|
|
p = p + sblen -- advance to next block |
|
|
|
end |
|
|
|
else |
|
|
|
-- skip entire block |
|
|
|
p = p + blocklen |
|
|
|
while buffer[p] ~= 0 do |
|
|
|
gifread(self, buffer[p]) |
|
|
|
p = gifread(self, 1) |
|
|
|
end |
|
|
|
end |
|
|
|
|
|
|
|
elseif blocktype == 0x01 or blocktype == 0xFE then |
|
|
|
-- Text or Comment Extension - not processed by us, skip |
|
|
|
p = gifread(self, 1) -- read length |
|
|
|
if blocktype < 0x01 then |
|
|
|
-- skip the block header (contains a length field) |
|
|
|
p = gifread(self, buffer[p] + 1) + buffer[p] |
|
|
|
|
|
|
|
-- the text extension "consumes" the GCE, so we clear it |
|
|
|
GCE_trans = false |
|
|
|
GCE_dispose = 0 |
|
|
|
GCE_delay = 0 |
|
|
|
end |
|
|
|
while buffer[p] ~= 0 do |
|
|
|
p = gifread(self, buffer[p] + 1) + buffer[p] |
|
|
|
end |
|
|
|
|
|
|
|
elseif blocktype == 0xF9 then |
|
|
|
-- Graphic Control Extension |
|
|
|
p = gifread(self, 1) |
|
|
|
blocklen = buffer[p] |
|
|
|
p = gifread(self, blocklen + 1) |
|
|
|
if blocklen >= 4 then |
|
|
|
GCE_delay = (buffer[p+1] + 256 * buffer[p+2]) / 100 |
|
|
|
GCE_trans = bit.band(buffer[p], 1) ~= 0 and buffer[p + 3] |
|
|
|
GCE_dispose = bit.rshift(bit.band(buffer[p], 0x1C), 2) |
|
|
|
end |
|
|
|
p = p + blocklen |
|
|
|
while buffer[p] ~= 0 do |
|
|
|
p = gifread(self, buffer[p] + 1) + buffer[p] |
|
|
|
end |
|
|
|
elseif blocktype == 0x2C then |
|
|
|
-- Here be dragons |
|
|
|
p = gifread(self, 9) |
|
|
|
|
|
|
|
local x, y = buffer[p] + 256*buffer[p+1], buffer[p+2] + 256*buffer[p+3] |
|
|
|
local w, h = buffer[p+4] + 256*buffer[p+5], buffer[p+6] + 256*buffer[p+7] |
|
|
|
if w == 0 or h == 0 then |
|
|
|
self:err('Zero size image') |
|
|
|
break |
|
|
|
end |
|
|
|
local img = love.image.newImageData(w, h) |
|
|
|
local dataptr = ffi.cast(int32ptr, img:getPointer()) |
|
|
|
self.imgs[#self.imgs + 1] = GCE_dispose |
|
|
|
self.imgs[#self.imgs + 1] = GCE_delay |
|
|
|
self.imgs[#self.imgs + 1] = img |
|
|
|
self.imgs[#self.imgs + 1] = x |
|
|
|
self.imgs[#self.imgs + 1] = y |
|
|
|
self.nimages = self.nimages + 1 |
|
|
|
|
|
|
|
local flags = buffer[p+8] |
|
|
|
if flags >= 128 then |
|
|
|
-- Has local palette |
|
|
|
lpalettesize = bit.lshift(1, bit.band(flags, 7) + 1) |
|
|
|
p = gifread(self, lpalettesize*3) |
|
|
|
gifpalette(lpalette, buffer + p, lpalettesize) |
|
|
|
else |
|
|
|
-- No local palette - copy the global palette to the local one |
|
|
|
ffi.copy(lpalette, gpalette, gpalettesize*4) |
|
|
|
lpalettesize = gpalettesize |
|
|
|
end |
|
|
|
if GCE_trans and GCE_trans < lpalettesize then |
|
|
|
-- Clear alpha |
|
|
|
lpalette[GCE_trans*4 + 3] = 0 |
|
|
|
end |
|
|
|
local interlace = bit.band(flags, 64) ~= 0 and 1 |
|
|
|
|
|
|
|
-- LZW decoder. |
|
|
|
|
|
|
|
-- This could really use another coroutine for |
|
|
|
-- simplicity, as there's another producer/consumer, |
|
|
|
-- but we won't go there. |
|
|
|
|
|
|
|
p = gifread(self, 2) |
|
|
|
local LZWsize = buffer[p] |
|
|
|
p = p + 1 |
|
|
|
if LZWsize == 0 or LZWsize > 11 then |
|
|
|
self:err("Invalid code size") |
|
|
|
break |
|
|
|
end |
|
|
|
local codebits = LZWsize + 1 |
|
|
|
local clearcode = bit.lshift(1, LZWsize) -- End-of-stream is always clearcode+1 |
|
|
|
local dictlen = clearcode + 2 |
|
|
|
|
|
|
|
local bitstream, bitlen = 0, 0 |
|
|
|
x, y = 0, 0 |
|
|
|
local nextlenptr = p |
|
|
|
local oldcode |
|
|
|
local walkcode |
|
|
|
|
|
|
|
local nrows = 0 -- counts vertical rows, used because interlacing makes the last y invalid |
|
|
|
local row = 0 |
|
|
|
|
|
|
|
repeat |
|
|
|
-- Are there enough bits in curcode? Do we need to read more data? |
|
|
|
if bitlen >= codebits and y then |
|
|
|
-- Extract next code |
|
|
|
local code = bit.band(bitstream, bit.lshift(1, codebits) - 1) |
|
|
|
bitstream = bit.rshift(bitstream, codebits) |
|
|
|
bitlen = bitlen - codebits |
|
|
|
|
|
|
|
if code == clearcode then |
|
|
|
codebits = LZWsize + 1 |
|
|
|
dictlen = clearcode + 2 |
|
|
|
oldcode = false |
|
|
|
elseif code == clearcode + 1 then |
|
|
|
if x ~= 0 or nrows ~= h then |
|
|
|
self:err("Soft EOD before all rows were output") |
|
|
|
end |
|
|
|
-- signal end of processing |
|
|
|
-- (further data won't be read, but we need to follow the blocks) |
|
|
|
y = false |
|
|
|
else |
|
|
|
-- The dictionary is stored as a list of back pointers. |
|
|
|
-- We need to reverse the order to output the entries. |
|
|
|
-- We use a reverse buffer for that. |
|
|
|
local reverseptr = 4095 |
|
|
|
-- Is this code already in the table? |
|
|
|
if code < dictlen then |
|
|
|
-- Already in the table - get the string from the table |
|
|
|
walkcode = code |
|
|
|
while walkcode >= clearcode do |
|
|
|
reversebuf[reverseptr] = dict[walkcode] |
|
|
|
reverseptr = reverseptr - 1 |
|
|
|
walkcode = dictptrs[walkcode] |
|
|
|
end |
|
|
|
reversebuf[reverseptr] = walkcode |
|
|
|
-- Add to the table |
|
|
|
if oldcode then |
|
|
|
if dictlen < 4096 then |
|
|
|
dictptrs[dictlen] = oldcode |
|
|
|
dict[dictlen] = walkcode |
|
|
|
dictlen = dictlen + 1 |
|
|
|
if dictlen ~= 4096 and bit.band(dictlen, dictlen - 1) == 0 then |
|
|
|
-- perfect power of two - increase code size |
|
|
|
codebits = codebits + 1 |
|
|
|
end |
|
|
|
end |
|
|
|
end |
|
|
|
oldcode = code |
|
|
|
else |
|
|
|
-- Not in the table - deal with the special case |
|
|
|
-- The compressor has created a new code, which must be the next |
|
|
|
-- in sequence. We know what it must contain. |
|
|
|
-- It must contain oldcode + first character of oldcode. |
|
|
|
if code > dictlen or not oldcode or not walkcode then |
|
|
|
self:err("Broken LZW") |
|
|
|
break |
|
|
|
end |
|
|
|
|
|
|
|
-- Add to the table |
|
|
|
if oldcode then |
|
|
|
if dictlen < 4096 then |
|
|
|
dictptrs[dictlen] = oldcode |
|
|
|
dict[dictlen] = walkcode |
|
|
|
dictlen = dictlen + 1 |
|
|
|
if dictlen ~= 4096 and bit.band(dictlen, dictlen - 1) == 0 then |
|
|
|
-- perfect power of two - increase code size |
|
|
|
codebits = codebits + 1 |
|
|
|
end |
|
|
|
end |
|
|
|
end |
|
|
|
oldcode = code |
|
|
|
walkcode = oldcode |
|
|
|
|
|
|
|
while walkcode >= clearcode do |
|
|
|
reversebuf[reverseptr] = dict[walkcode] |
|
|
|
reverseptr = reverseptr - 1 |
|
|
|
walkcode = dictptrs[walkcode] |
|
|
|
end |
|
|
|
reversebuf[reverseptr] = walkcode |
|
|
|
end |
|
|
|
|
|
|
|
if y then |
|
|
|
for i = reverseptr, 4095 do |
|
|
|
local c = reversebuf[i] |
|
|
|
if c >= lpalettesize then c = 0 end |
|
|
|
c = ffi.cast(int32ptr, lpalette)[c] |
|
|
|
dataptr[x + row] = c |
|
|
|
if interlace then |
|
|
|
-- The passes 1, 2, 3, 4 correspond to the |
|
|
|
-- values 1, 3, 5, 7 of 'interlace'. |
|
|
|
if self.progressive and interlace < 7 and y + 1 < h then |
|
|
|
-- In any pass but the last, there are at least 2 lines. |
|
|
|
dataptr[x + row + w] = c |
|
|
|
if interlace < 5 and y + 2 < h then |
|
|
|
-- In the first two passes, there are at least 4 lines. |
|
|
|
dataptr[x + row + w*2] = c |
|
|
|
if y + 3 < h then |
|
|
|
dataptr[x + row + w*3] = c |
|
|
|
if interlace < 3 and y + 4 < h then |
|
|
|
-- In the first pass there are 8 lines. |
|
|
|
dataptr[x + row + w*4] = c |
|
|
|
if y + 5 < h then |
|
|
|
dataptr[x + row + w*5] = c |
|
|
|
if y + 6 < h then |
|
|
|
dataptr[x + row + w*6] = c |
|
|
|
if y + 7 < h then |
|
|
|
dataptr[x + row + w*7] = c |
|
|
|
end |
|
|
|
end |
|
|
|
end |
|
|
|
end |
|
|
|
end |
|
|
|
end |
|
|
|
end |
|
|
|
-- Advance pixel |
|
|
|
x = x + 1 |
|
|
|
if x >= w then |
|
|
|
-- Skip to next interlaced row |
|
|
|
x = 0 |
|
|
|
nrows = nrows + 1 |
|
|
|
y = y + intertable[interlace + 1] |
|
|
|
if y >= h then |
|
|
|
interlace = interlace + 2 |
|
|
|
if interlace > 7 then |
|
|
|
y = false |
|
|
|
else |
|
|
|
y = intertable[interlace] |
|
|
|
end |
|
|
|
end |
|
|
|
if y then |
|
|
|
row = y * w |
|
|
|
end |
|
|
|
end |
|
|
|
else |
|
|
|
-- No interlace, just increment y |
|
|
|
x = x + 1 |
|
|
|
if x >= w then |
|
|
|
x = 0 |
|
|
|
y = y + 1 |
|
|
|
nrows = y |
|
|
|
if y >= h then |
|
|
|
y = false |
|
|
|
else |
|
|
|
row = y * w |
|
|
|
end |
|
|
|
end |
|
|
|
end |
|
|
|
end |
|
|
|
|
|
|
|
else |
|
|
|
-- This should not happen. |
|
|
|
self:err('Data past the end of the image') |
|
|
|
end |
|
|
|
end |
|
|
|
else |
|
|
|
-- Not enough bits, grab 8 more |
|
|
|
if p >= nextlenptr then |
|
|
|
-- End of this subblock - read next subblock |
|
|
|
assert(p == nextlenptr) |
|
|
|
local sblen = buffer[nextlenptr] |
|
|
|
|
|
|
|
if sblen == 0 then |
|
|
|
-- no more data |
|
|
|
if y then |
|
|
|
self:err("Hard EOD before the end of the image") |
|
|
|
end |
|
|
|
break |
|
|
|
end |
|
|
|
p = gifread(self, sblen + 1) |
|
|
|
nextlenptr = p + sblen |
|
|
|
end |
|
|
|
if y then |
|
|
|
bitstream = bitstream + bit.lshift(buffer[p], bitlen) |
|
|
|
bitlen = bitlen + 8 |
|
|
|
p = p + 1 |
|
|
|
else |
|
|
|
-- end of data - fast forward to end of block |
|
|
|
p = nextlenptr |
|
|
|
end |
|
|
|
end |
|
|
|
|
|
|
|
until false |
|
|
|
|
|
|
|
GCE_trans = false |
|
|
|
GCE_dispose = 0 |
|
|
|
GCE_delay = 0 |
|
|
|
self.ncomplete = self.nimages |
|
|
|
|
|
|
|
else |
|
|
|
break |
|
|
|
end |
|
|
|
until false |
|
|
|
|
|
|
|
end |
|
|
|
|
|
|
|
local function gifframe(self, n) |
|
|
|
n = (n-1) % self.nimages + 1 |
|
|
|
return self.imgs[n*5-2], self.imgs[n*5-1], self.imgs[n*5], self.imgs[n*5-3], self.imgs[n*5-4] |
|
|
|
end |
|
|
|
|
|
|
|
local function gifnew(retver) |
|
|
|
if retver == "version" then |
|
|
|
return 0x010002 |
|
|
|
-- else just ignore it and create the object |
|
|
|
end |
|
|
|
|
|
|
|
local self = { |
|
|
|
update = gifupdate; |
|
|
|
done = gifdone; |
|
|
|
frame = gifframe; |
|
|
|
err = giferr; |
|
|
|
background = false; |
|
|
|
width = false; |
|
|
|
height = false; |
|
|
|
imgs = {}; |
|
|
|
nimages = 0; |
|
|
|
ncomplete = 0; |
|
|
|
buffer = bytearray(65536); |
|
|
|
buflen = 0; |
|
|
|
ptr = 0; |
|
|
|
progressive = false; |
|
|
|
loop = false; |
|
|
|
aspect = false; |
|
|
|
decoder = coroutine.create(gifdecoder); |
|
|
|
} |
|
|
|
-- pass self to the coroutine (will return immediately for lack of data) |
|
|
|
coresume(self.decoder, self) |
|
|
|
return self |
|
|
|
end |
|
|
|
|
|
|
|
return gifnew |