MineClone2/mods/ITEMS/mcl_maps/pngencoder.lua
2021-05-02 12:55:04 +02:00

191 lines
6.1 KiB
Lua

local Png = {}
Png.__index = Png
local DEFLATE_MAX_BLOCK_SIZE = 65535
local function putBigUint32(val, tbl, index)
for i=0,3 do
tbl[index + i] = bit.band(bit.rshift(val, (3 - i) * 8), 0xFF)
end
end
function Png:writeBytes(data, index, len)
index = index or 1
len = len or #data
for i=index,index+len-1 do
table.insert(self.output, string.char(data[i]))
end
end
function Png:write(pixels)
local count = #pixels -- Byte count
local pixelPointer = 1
while count > 0 do
if self.positionY >= self.height then
error("All image pixels already written")
end
if self.deflateFilled == 0 then -- Start DEFLATE block
local size = DEFLATE_MAX_BLOCK_SIZE;
if (self.uncompRemain < size) then
size = self.uncompRemain
end
local header = { -- 5 bytes long
bit.band((self.uncompRemain <= DEFLATE_MAX_BLOCK_SIZE and 1 or 0), 0xFF),
bit.band(bit.rshift(size, 0), 0xFF),
bit.band(bit.rshift(size, 8), 0xFF),
bit.band(bit.bxor(bit.rshift(size, 0), 0xFF), 0xFF),
bit.band(bit.bxor(bit.rshift(size, 8), 0xFF), 0xFF),
}
self:writeBytes(header)
self:crc32(header, 1, #header)
end
assert(self.positionX < self.lineSize and self.deflateFilled < DEFLATE_MAX_BLOCK_SIZE);
if (self.positionX == 0) then -- Beginning of line - write filter method byte
local b = {0}
self:writeBytes(b)
self:crc32(b, 1, 1)
self:adler32(b, 1, 1)
self.positionX = self.positionX + 1
self.uncompRemain = self.uncompRemain - 1
self.deflateFilled = self.deflateFilled + 1
else -- Write some pixel bytes for current line
local n = DEFLATE_MAX_BLOCK_SIZE - self.deflateFilled;
if (self.lineSize - self.positionX < n) then
n = self.lineSize - self.positionX
end
if (count < n) then
n = count;
end
assert(n > 0);
self:writeBytes(pixels, pixelPointer, n)
-- Update checksums
self:crc32(pixels, pixelPointer, n);
self:adler32(pixels, pixelPointer, n);
-- Increment positions
count = count - n;
pixelPointer = pixelPointer + n;
self.positionX = self.positionX + n;
self.uncompRemain = self.uncompRemain - n;
self.deflateFilled = self.deflateFilled + n;
end
if (self.deflateFilled >= DEFLATE_MAX_BLOCK_SIZE) then
self.deflateFilled = 0; -- End current block
end
if (self.positionX == self.lineSize) then -- Increment line
self.positionX = 0;
self.positionY = self.positionY + 1;
if (self.positionY == self.height) then -- Reached end of pixels
local footer = { -- 20 bytes long
0, 0, 0, 0, -- DEFLATE Adler-32 placeholder
0, 0, 0, 0, -- IDAT CRC-32 placeholder
-- IEND chunk
0x00, 0x00, 0x00, 0x00,
0x49, 0x45, 0x4E, 0x44,
0xAE, 0x42, 0x60, 0x82,
}
putBigUint32(self.adler, footer, 1)
self:crc32(footer, 1, 4)
putBigUint32(self.crc, footer, 5)
self:writeBytes(footer)
self.done = true
end
end
end
end
function Png:crc32(data, index, len)
self.crc = bit.bnot(self.crc)
for i=index,index+len-1 do
local byte = data[i]
for j=0,7 do -- Inefficient bitwise implementation, instead of table-based
local nbit = bit.band(bit.bxor(self.crc, bit.rshift(byte, j)), 1);
self.crc = bit.bxor(bit.rshift(self.crc, 1), bit.band((-nbit), 0xEDB88320));
end
end
self.crc = bit.bnot(self.crc)
end
function Png:adler32(data, index, len)
local s1 = bit.band(self.adler, 0xFFFF)
local s2 = bit.rshift(self.adler, 16)
for i=index,index+len-1 do
s1 = (s1 + data[i]) % 65521
s2 = (s2 + s1) % 65521
end
self.adler = bit.bor(bit.lshift(s2, 16), s1)
end
local function begin(width, height, colorMode)
-- Default to rgb
colorMode = colorMode or "rgb"
-- Determine bytes per pixel and the PNG internal color type
local bytesPerPixel, colorType
if colorMode == "rgb" then
bytesPerPixel, colorType = 3, 2
elseif colorMode == "rgba" then
bytesPerPixel, colorType = 4, 6
else
error("Invalid colorMode")
end
local state = setmetatable({ width = width, height = height, done = false, output = {} }, Png)
-- Compute and check data siezs
state.lineSize = width * bytesPerPixel + 1
-- TODO: check if lineSize too big
state.uncompRemain = state.lineSize * height
local numBlocks = math.ceil(state.uncompRemain / DEFLATE_MAX_BLOCK_SIZE)
-- 5 bytes per DEFLATE uncompressed block header, 2 bytes for zlib header, 4 bytes for zlib Adler-32 footer
local idatSize = numBlocks * 5 + 6
idatSize = idatSize + state.uncompRemain;
-- TODO check if idatSize too big
local header = { -- 43 bytes long
-- PNG header
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A,
-- IHDR chunk
0x00, 0x00, 0x00, 0x0D,
0x49, 0x48, 0x44, 0x52,
0, 0, 0, 0, -- 'width' placeholder
0, 0, 0, 0, -- 'height' placeholder
0x08, colorType, 0x00, 0x00, 0x00,
0, 0, 0, 0, -- IHDR CRC-32 placeholder
-- IDAT chunk
0, 0, 0, 0, -- 'idatSize' placeholder
0x49, 0x44, 0x41, 0x54,
-- DEFLATE data
0x08, 0x1D,
}
putBigUint32(width, header, 17)
putBigUint32(height, header, 21)
putBigUint32(idatSize, header, 34)
state.crc = 0
state:crc32(header, 13, 17)
putBigUint32(state.crc, header, 30)
state:writeBytes(header)
state.crc = 0
state:crc32(header, 38, 6); -- 0xD7245B6B
state.adler = 1
state.positionX = 0
state.positionY = 0
state.deflateFilled = 0
return state
end
return begin