mirror of
https://git.minetest.land/VoxeLibre/VoxeLibre.git
synced 2024-12-18 06:19:34 +01:00
Use PNG instead of TGA
This commit is contained in:
parent
d9a670dcb8
commit
ca9cd8cbe0
7 changed files with 305 additions and 119 deletions
|
@ -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.
|
|
|
@ -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
|
|
|
@ -1,3 +0,0 @@
|
||||||
name = tga_encoder
|
|
||||||
author = Fleckenstein
|
|
||||||
description = A TGA Encoder written in Lua without the use of external Libraries.
|
|
98
mods/ITEMS/mcl_maps/bit32.lua
Normal file
98
mods/ITEMS/mcl_maps/bit32.lua
Normal file
|
@ -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
|
|
@ -7,6 +7,11 @@ local worldpath = minetest.get_worldpath()
|
||||||
local map_textures_path = worldpath .. "/mcl_maps/"
|
local map_textures_path = worldpath .. "/mcl_maps/"
|
||||||
local last_finished_id = storage:get_int("next_id") - 1
|
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)
|
minetest.mkdir(map_textures_path)
|
||||||
|
|
||||||
local function load_json_file(name)
|
local function load_json_file(name)
|
||||||
|
@ -32,7 +37,7 @@ function mcl_maps.create_map(pos)
|
||||||
local meta = itemstack:get_meta()
|
local meta = itemstack:get_meta()
|
||||||
local id = storage:get_int("next_id")
|
local id = storage:get_int("next_id")
|
||||||
storage:set_int("next_id", id + 1)
|
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_path = map_textures_path .. texture_file
|
||||||
local texture = "[combine:140x140:0,0=mcl_maps_map_background.png:6,6=" .. texture_file
|
local texture = "[combine:140x140:0,0=mcl_maps_map_background.png:6,6=" .. texture_file
|
||||||
meta:set_int("mcl_maps:id", id)
|
meta:set_int("mcl_maps:id", id)
|
||||||
|
@ -121,7 +126,16 @@ function mcl_maps.create_map(pos)
|
||||||
end
|
end
|
||||||
last_heightmap = heightmap
|
last_heightmap = heightmap
|
||||||
end
|
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
|
creating_maps[texture] = false
|
||||||
end)
|
end)
|
||||||
return itemstack
|
return itemstack
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
name = mcl_maps
|
name = mcl_maps
|
||||||
depends = mcl_core, mcl_flowers, tga_encoder, tt, mcl_colors
|
depends = mcl_core, mcl_flowers, tt, mcl_colors
|
||||||
|
|
190
mods/ITEMS/mcl_maps/pngencoder.lua
Normal file
190
mods/ITEMS/mcl_maps/pngencoder.lua
Normal file
|
@ -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
|
Loading…
Reference in a new issue