From ca9cd8cbe0fff0ab40121d6067fb075559b12860 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Sun, 2 May 2021 12:55:04 +0200 Subject: [PATCH] Use PNG instead of TGA --- mods/CORE/tga_encoder/README.md | 4 - mods/CORE/tga_encoder/init.lua | 109 ----------------- mods/CORE/tga_encoder/mod.conf | 3 - mods/ITEMS/mcl_maps/bit32.lua | 98 +++++++++++++++ mods/ITEMS/mcl_maps/init.lua | 18 ++- mods/ITEMS/mcl_maps/mod.conf | 2 +- mods/ITEMS/mcl_maps/pngencoder.lua | 190 +++++++++++++++++++++++++++++ 7 files changed, 305 insertions(+), 119 deletions(-) delete mode 100644 mods/CORE/tga_encoder/README.md delete mode 100644 mods/CORE/tga_encoder/init.lua delete mode 100644 mods/CORE/tga_encoder/mod.conf create mode 100644 mods/ITEMS/mcl_maps/bit32.lua create mode 100644 mods/ITEMS/mcl_maps/pngencoder.lua diff --git a/mods/CORE/tga_encoder/README.md b/mods/CORE/tga_encoder/README.md deleted file mode 100644 index 9b3293ddab..0000000000 --- a/mods/CORE/tga_encoder/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# tga_encoder -A TGA Encoder written in Lua without the use of external Libraries. - -May be used as a Minetest mod. diff --git a/mods/CORE/tga_encoder/init.lua b/mods/CORE/tga_encoder/init.lua deleted file mode 100644 index 45b9e85a23..0000000000 --- a/mods/CORE/tga_encoder/init.lua +++ /dev/null @@ -1,109 +0,0 @@ -tga_encoder = {} - -local LUA_ARGS_LIMIT = 1000 - -local image = setmetatable({}, { - __call = function(self, ...) - local t = setmetatable({}, {__index = self}) - t:constructor(...) - return t - end, -}) - -function image:constructor(pixels) - self.bytes = {} - self.chunks = {self.bytes} - self.pixels = pixels - self.width = #pixels[1] - self.height = #pixels - - self:encode() -end - -function image:insert(byte) - table.insert(self.bytes, byte) - if #self.bytes == LUA_ARGS_LIMIT then - self.bytes = {} - table.insert(self.chunks, self.bytes) - end -end - -function image:littleendian(size, value) - for i = 1, size do - local byte = value % 256 - value = value - byte - value = value / 256 - self:insert(byte) - end -end - -function image:encode_colormap_spec() - -- first entry index - self:littleendian(2, 0) - -- number of entries - self:littleendian(2, 0) - -- number of bits per pixel - self:insert(0) -end - -function image:encode_image_spec() - -- X- and Y- origin - self:littleendian(2, 0) - self:littleendian(2, 0) - -- width and height - self:littleendian(2, self.width) - self:littleendian(2, self.height) - -- pixel depth - self:insert(24) - -- image descriptor - self:insert(0) -end - -function image:encode_header() - -- id length - self:insert(0) -- no image id info - -- color map type - self:insert(0) -- no color map - -- image type - self:insert(2) -- uncompressed true-color image - -- color map specification - self:encode_colormap_spec() - -- image specification - self:encode_image_spec() -end - -function image:encode_data() - for _, row in ipairs(self.pixels) do - for _, pixel in ipairs(row) do - self:insert(pixel[3]) - self:insert(pixel[2]) - self:insert(pixel[1]) - end - end -end - -function image:encode() - -- encode header - self:encode_header() - -- no color map and image id data - -- encode data - self:encode_data() - -- no extension area -end - -function image:get_data() - local data = "" - for _, bytes in ipairs(self.chunks) do - data = data .. string.char(unpack(bytes)) - end - return data .. string.char(0, 0, 0, 0) .. string.char(0, 0, 0, 0) .. "TRUEVISION-XFILE." .. string.char(0) -end - -function image:save(filename) - self.data = self.data or self:get_data() - local f = assert(io.open(filename, "w")) - f:write(self.data) - f:close() -end - -tga_encoder.image = image diff --git a/mods/CORE/tga_encoder/mod.conf b/mods/CORE/tga_encoder/mod.conf deleted file mode 100644 index e4bfac8985..0000000000 --- a/mods/CORE/tga_encoder/mod.conf +++ /dev/null @@ -1,3 +0,0 @@ -name = tga_encoder -author = Fleckenstein -description = A TGA Encoder written in Lua without the use of external Libraries. diff --git a/mods/ITEMS/mcl_maps/bit32.lua b/mods/ITEMS/mcl_maps/bit32.lua new file mode 100644 index 0000000000..7f86d86260 --- /dev/null +++ b/mods/ITEMS/mcl_maps/bit32.lua @@ -0,0 +1,98 @@ +bit32 = {} + +local N = 32 +local P = 2^N + +function bit32.bnot(x) + x = x % P + return P - 1 - x +end + +function bit32.band(x, y) + -- Common usecases, they deserve to be optimized + if y == 0xff then return x % 0x100 end + if y == 0xffff then return x % 0x10000 end + if y == 0xffffffff then return x % 0x100000000 end + + x, y = x % P, y % P + local r = 0 + local p = 1 + for i = 1, N do + local a, b = x % 2, y % 2 + x, y = math.floor(x / 2), math.floor(y / 2) + if a + b == 2 then + r = r + p + end + p = 2 * p + end + return r +end + +function bit32.bor(x, y) + -- Common usecases, they deserve to be optimized + if y == 0xff then return x - (x%0x100) + 0xff end + if y == 0xffff then return x - (x%0x10000) + 0xffff end + if y == 0xffffffff then return 0xffffffff end + + x, y = x % P, y % P + local r = 0 + local p = 1 + for i = 1, N do + local a, b = x % 2, y % 2 + x, y = math.floor(x / 2), math.floor(y / 2) + if a + b >= 1 then + r = r + p + end + p = 2 * p + end + return r +end + +function bit32.bxor(x, y) + x, y = x % P, y % P + local r = 0 + local p = 1 + for i = 1, N do + local a, b = x%2, y%2 + x, y = math.floor(x/2), math.floor(y/2) + if a + b == 1 then + r = r + p + end + p = 2 * p + end + return r +end + +function bit32.lshift(x, s_amount) + if math.abs(s_amount) >= N then return 0 end + x = x % P + if s_amount < 0 then + return math.floor(x * (2 ^ s_amount)) + else + return (x * (2 ^ s_amount)) % P + end +end + +function bit32.rshift(x, s_amount) + if math.abs(s_amount) >= N then return 0 end + x = x % P + if s_amount > 0 then + return math.floor(x * (2 ^ - s_amount)) + else + return (x * (2 ^ -s_amount)) % P + end +end + +function bit32.arshift(x, s_amount) + if math.abs(s_amount) >= N then return 0 end + x = x % P + if s_amount > 0 then + local add = 0 + if x >= P/2 then + add = P - 2 ^ (N - s_amount) + end + return math.floor(x * (2 ^ -s_amount)) + add + else + return (x * (2 ^ -s_amount)) % P + end +end diff --git a/mods/ITEMS/mcl_maps/init.lua b/mods/ITEMS/mcl_maps/init.lua index 357c0f48ee..73fdb7b348 100644 --- a/mods/ITEMS/mcl_maps/init.lua +++ b/mods/ITEMS/mcl_maps/init.lua @@ -7,6 +7,11 @@ local worldpath = minetest.get_worldpath() local map_textures_path = worldpath .. "/mcl_maps/" local last_finished_id = storage:get_int("next_id") - 1 +dofile(modpath .. "/bit32.lua") -- taken from http://gitea.minetest.one/minetest-mods/turtle/src/branch/master/bit32.lua + +bit = bit32 +pngencoder = dofile(modpath .. "/pngencoder.lua") -- taken from https://github.com/wyozi/lua-pngencoder/blob/master/pngencoder.lua + minetest.mkdir(map_textures_path) local function load_json_file(name) @@ -32,7 +37,7 @@ function mcl_maps.create_map(pos) local meta = itemstack:get_meta() local id = storage:get_int("next_id") storage:set_int("next_id", id + 1) - local texture_file = "mcl_maps_map_texture_" .. id .. ".tga" + local texture_file = "mcl_maps_map_texture_" .. id .. ".png" local texture_path = map_textures_path .. texture_file local texture = "[combine:140x140:0,0=mcl_maps_map_background.png:6,6=" .. texture_file meta:set_int("mcl_maps:id", id) @@ -121,7 +126,16 @@ function mcl_maps.create_map(pos) end last_heightmap = heightmap end - tga_encoder.image(pixels):save(texture_path) + local image = pngencoder(128, 128, "rgb") + for _, row in ipairs(pixels) do + for _, pixel in ipairs(row) do + image:write(pixel) + end + end + assert(image.done) + local f = assert(io.open(texture_path, "w")) + f:write(table.concat(image.output)) + f:close() creating_maps[texture] = false end) return itemstack diff --git a/mods/ITEMS/mcl_maps/mod.conf b/mods/ITEMS/mcl_maps/mod.conf index 82c9e61f03..617c0f1a33 100644 --- a/mods/ITEMS/mcl_maps/mod.conf +++ b/mods/ITEMS/mcl_maps/mod.conf @@ -1,2 +1,2 @@ name = mcl_maps -depends = mcl_core, mcl_flowers, tga_encoder, tt, mcl_colors +depends = mcl_core, mcl_flowers, tt, mcl_colors diff --git a/mods/ITEMS/mcl_maps/pngencoder.lua b/mods/ITEMS/mcl_maps/pngencoder.lua new file mode 100644 index 0000000000..015aab5423 --- /dev/null +++ b/mods/ITEMS/mcl_maps/pngencoder.lua @@ -0,0 +1,190 @@ +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