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 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
|
||||
|
|
|
@ -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
|
||||
|
|
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