From bd74dbe3211909cc326b8d5f82d5f86927ade353 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Sun, 2 May 2021 13:03:18 +0200 Subject: [PATCH] Revert "Use PNG instead of TGA" This reverts commit ca9cd8cbe0fff0ab40121d6067fb075559b12860. The TGA was faster and produced smaller files. --- 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, 119 insertions(+), 305 deletions(-) create mode 100644 mods/CORE/tga_encoder/README.md create mode 100644 mods/CORE/tga_encoder/init.lua create mode 100644 mods/CORE/tga_encoder/mod.conf delete mode 100644 mods/ITEMS/mcl_maps/bit32.lua delete mode 100644 mods/ITEMS/mcl_maps/pngencoder.lua diff --git a/mods/CORE/tga_encoder/README.md b/mods/CORE/tga_encoder/README.md new file mode 100644 index 000000000..9b3293dda --- /dev/null +++ b/mods/CORE/tga_encoder/README.md @@ -0,0 +1,4 @@ +# 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 new file mode 100644 index 000000000..45b9e85a2 --- /dev/null +++ b/mods/CORE/tga_encoder/init.lua @@ -0,0 +1,109 @@ +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 new file mode 100644 index 000000000..e4bfac898 --- /dev/null +++ b/mods/CORE/tga_encoder/mod.conf @@ -0,0 +1,3 @@ +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 deleted file mode 100644 index 7f86d8626..000000000 --- a/mods/ITEMS/mcl_maps/bit32.lua +++ /dev/null @@ -1,98 +0,0 @@ -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 73fdb7b34..357c0f48e 100644 --- a/mods/ITEMS/mcl_maps/init.lua +++ b/mods/ITEMS/mcl_maps/init.lua @@ -7,11 +7,6 @@ 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) @@ -37,7 +32,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 .. ".png" + local texture_file = "mcl_maps_map_texture_" .. id .. ".tga" 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) @@ -126,16 +121,7 @@ function mcl_maps.create_map(pos) end last_heightmap = heightmap end - 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() + tga_encoder.image(pixels):save(texture_path) creating_maps[texture] = false end) return itemstack diff --git a/mods/ITEMS/mcl_maps/mod.conf b/mods/ITEMS/mcl_maps/mod.conf index 617c0f1a3..82c9e61f0 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, tt, mcl_colors +depends = mcl_core, mcl_flowers, tga_encoder, tt, mcl_colors diff --git a/mods/ITEMS/mcl_maps/pngencoder.lua b/mods/ITEMS/mcl_maps/pngencoder.lua deleted file mode 100644 index 015aab542..000000000 --- a/mods/ITEMS/mcl_maps/pngencoder.lua +++ /dev/null @@ -1,190 +0,0 @@ -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