mirror of
https://github.com/minetest/minetest.git
synced 2024-11-27 01:53:45 +01:00
Optimize and improve built-in PNG writer (#14020)
This commit is contained in:
parent
5054918efc
commit
93dfa8a6d8
@ -64,6 +64,13 @@ function core.encode_png(width, height, data, compression)
|
||||
error("Incorrect type for 'height', expected number, got " .. type(height))
|
||||
end
|
||||
|
||||
if width < 1 then
|
||||
error("Incorrect value for 'width', must be at least 1")
|
||||
end
|
||||
if height < 1 then
|
||||
error("Incorrect value for 'height', must be at least 1")
|
||||
end
|
||||
|
||||
local expected_byte_count = width * height * 4
|
||||
|
||||
if type(data) ~= "table" and type(data) ~= "string" then
|
||||
|
@ -5418,9 +5418,8 @@ Utilities
|
||||
* `compression`: Optional zlib compression level, number in range 0 to 9.
|
||||
The data is one-dimensional, starting in the upper left corner of the image
|
||||
and laid out in scanlines going from left to right, then top to bottom.
|
||||
Please note that it's not safe to use string.char to generate raw data,
|
||||
use `colorspec_to_bytes` to generate raw RGBA values in a predictable way.
|
||||
The resulting PNG image is always 32-bit. Palettes are not supported at the moment.
|
||||
You can use `colorspec_to_bytes` to generate raw RGBA values.
|
||||
Palettes are not supported at the moment.
|
||||
You may use this to procedurally generate textures during server init.
|
||||
* `minetest.urlencode(str)`: Encodes non-unreserved URI characters by a
|
||||
percent sign followed by two hex digits. See
|
||||
|
@ -105,6 +105,19 @@ local function gen_checkers(w, h, tile)
|
||||
return r
|
||||
end
|
||||
|
||||
-- The engine should perform color reduction of the generated PNG in certain
|
||||
-- cases, so we have this helper to check the result
|
||||
local function encode_and_check(w, h, ctype, data)
|
||||
local ret = core.encode_png(w, h, data)
|
||||
assert(ret:sub(1, 8) == "\137PNG\r\n\026\n", "missing png signature")
|
||||
assert(ret:sub(9, 16) == "\000\000\000\rIHDR", "didn't find ihdr chunk")
|
||||
local ctype_actual = ret:byte(26) -- Color Type (1 byte)
|
||||
ctype = ({rgba=6, rgb=2, gray=0})[ctype]
|
||||
assert(ctype_actual == ctype, "png should have color type " .. ctype ..
|
||||
" but actually has " .. ctype_actual)
|
||||
return ret
|
||||
end
|
||||
|
||||
local fractal = mandelbrot(512, 512, 128)
|
||||
local frac_emb = mandelbrot(64, 64, 64)
|
||||
local checker = gen_checkers(512, 512, 32)
|
||||
@ -129,17 +142,21 @@ for i=1, #fractal do
|
||||
b = floor(abs(1 - fractal[i]) * 255),
|
||||
a = 255,
|
||||
}
|
||||
data_ck[i] = checker[i] > 0 and "#F80" or "#000"
|
||||
data_ck[i] = checker[i] > 0 and "#888" or "#000"
|
||||
end
|
||||
|
||||
fractal = nil
|
||||
frac_emb = nil
|
||||
checker = nil
|
||||
|
||||
local textures_path = minetest.get_modpath( minetest.get_current_modname() ) .. "/textures/"
|
||||
minetest.safe_file_write(
|
||||
textures_path .. "testnodes_generated_mb.png",
|
||||
minetest.encode_png(512,512,data_mb)
|
||||
encode_and_check(512, 512, "rgb", data_mb)
|
||||
)
|
||||
minetest.safe_file_write(
|
||||
textures_path .. "testnodes_generated_ck.png",
|
||||
minetest.encode_png(512,512,data_ck)
|
||||
encode_and_check(512, 512, "gray", data_ck)
|
||||
)
|
||||
|
||||
minetest.register_node("testnodes:generated_png_mb", {
|
||||
@ -155,7 +172,8 @@ minetest.register_node("testnodes:generated_png_ck", {
|
||||
groups = { dig_immediate = 2 },
|
||||
})
|
||||
|
||||
local png_emb = "[png:" .. minetest.encode_base64(minetest.encode_png(64,64,data_emb))
|
||||
local png_emb = "[png:" .. minetest.encode_base64(
|
||||
encode_and_check(64, 64, "rgba", data_emb))
|
||||
|
||||
minetest.register_node("testnodes:generated_png_emb", {
|
||||
description = S("Generated In-Band Mandelbrot PNG Test Node"),
|
||||
@ -182,6 +200,10 @@ minetest.register_node("testnodes:generated_png_dst_emb", {
|
||||
groups = { dig_immediate = 2 },
|
||||
})
|
||||
|
||||
data_emb = nil
|
||||
data_mb = nil
|
||||
data_ck = nil
|
||||
|
||||
--[[
|
||||
|
||||
The following nodes can be used to demonstrate the TGA format support.
|
||||
|
104
src/util/png.cpp
104
src/util/png.cpp
@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
|
||||
#include "png.h"
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <zlib.h>
|
||||
#include <cassert>
|
||||
@ -26,43 +27,108 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#include "serialization.h"
|
||||
#include "irrlichttypes.h"
|
||||
|
||||
static void writeChunk(std::ostringstream &target, const std::string &chunk_str)
|
||||
enum {
|
||||
COLOR_GRAY = 0,
|
||||
COLOR_RGB = 2,
|
||||
COLOR_RGBA = 6,
|
||||
};
|
||||
|
||||
static void writeChunk(std::string &target, const std::string &chunk_str)
|
||||
{
|
||||
assert(chunk_str.size() >= 4);
|
||||
assert(chunk_str.size() - 4 < U32_MAX);
|
||||
writeU32(target, chunk_str.size() - 4); // Write length minus the identifier
|
||||
target << chunk_str;
|
||||
writeU32(target, crc32(0,(const u8*)chunk_str.data(), chunk_str.size()));
|
||||
u8 tmp[4];
|
||||
target.reserve(target.size() + 4 + chunk_str.size() + 4);
|
||||
|
||||
writeU32(tmp, chunk_str.size() - 4); // Length minus the identifier
|
||||
target.append(reinterpret_cast<char*>(tmp), 4);
|
||||
target.append(chunk_str); // Data
|
||||
const u32 csum = crc32(0, reinterpret_cast<const u8*>(chunk_str.data()),
|
||||
chunk_str.size());
|
||||
writeU32(tmp, csum); // CRC32 checksum
|
||||
target.append(reinterpret_cast<char*>(tmp), 4);
|
||||
}
|
||||
|
||||
static std::optional<u8> reduceColor(const u8 *data, u32 width, u32 height, std::string &new_data)
|
||||
{
|
||||
const u32 npixels = width * height;
|
||||
// check if the alpha channel is all opaque
|
||||
for (u32 i = 0; i < npixels; i++) {
|
||||
if (data[4*i + 3] != 255)
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// check if RGB components are identical
|
||||
bool gray = true;
|
||||
for (u32 i = 0; i < npixels; i++) {
|
||||
const u8 *pixel = &data[4*i];
|
||||
if (pixel[0] != pixel[1] || pixel[1] != pixel[2]) {
|
||||
gray = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (gray) {
|
||||
// convert to grayscale
|
||||
new_data.resize(width * height);
|
||||
u8 *dst = reinterpret_cast<u8*>(new_data.data());
|
||||
for (u32 i = 0; i < npixels; i++)
|
||||
dst[i] = data[4*i];
|
||||
return COLOR_GRAY;
|
||||
} else {
|
||||
// convert to RGB
|
||||
new_data.resize(width * 3 * height);
|
||||
u8 *dst = reinterpret_cast<u8*>(new_data.data());
|
||||
for (u32 i = 0; i < npixels; i++)
|
||||
memcpy(&dst[3*i], &data[4*i], 3);
|
||||
return COLOR_RGB;
|
||||
}
|
||||
}
|
||||
|
||||
std::string encodePNG(const u8 *data, u32 width, u32 height, s32 compression)
|
||||
{
|
||||
std::ostringstream file(std::ios::binary);
|
||||
file << "\x89PNG\r\n\x1a\n";
|
||||
u8 color_type = COLOR_RGBA;
|
||||
std::string new_data;
|
||||
if (compression == Z_DEFAULT_COMPRESSION || compression >= 2) {
|
||||
// try to reduce the image data to grayscale or RGB
|
||||
if (auto ret = reduceColor(data, width, height, new_data); ret.has_value()) {
|
||||
color_type = ret.value();
|
||||
assert(!new_data.empty());
|
||||
data = reinterpret_cast<u8*>(new_data.data());
|
||||
}
|
||||
}
|
||||
|
||||
std::string file;
|
||||
file.append("\x89PNG\r\n\x1a\n");
|
||||
|
||||
{
|
||||
std::ostringstream IHDR(std::ios::binary);
|
||||
IHDR << "IHDR";
|
||||
writeU32(IHDR, width);
|
||||
writeU32(IHDR, height);
|
||||
// 8 bpp, color type 6 (RGBA)
|
||||
IHDR.write("\x08\x06\x00\x00\x00", 5);
|
||||
writeChunk(file, IHDR.str());
|
||||
std::ostringstream header(std::ios::binary);
|
||||
header << "IHDR";
|
||||
writeU32(header, width);
|
||||
writeU32(header, height);
|
||||
writeU8(header, 8); // bpp
|
||||
writeU8(header, color_type);
|
||||
header.write("\x00\x00\x00", 3);
|
||||
writeChunk(file, header.str());
|
||||
}
|
||||
|
||||
{
|
||||
std::ostringstream IDAT(std::ios::binary);
|
||||
IDAT << "IDAT";
|
||||
std::ostringstream scanlines(std::ios::binary);
|
||||
const u32 ps = color_type == COLOR_GRAY ? 1 :
|
||||
(color_type == COLOR_RGB ? 3 : 4);
|
||||
std::string scanlines;
|
||||
scanlines.reserve(width * ps * height + height);
|
||||
for(u32 i = 0; i < height; i++) {
|
||||
scanlines.write("\x00", 1); // Null predictor
|
||||
scanlines.write((const char*) data + width * 4 * i, width * 4);
|
||||
scanlines.append(1, 0); // Null predictor
|
||||
scanlines.append(reinterpret_cast<const char*>(data + width * ps * i),
|
||||
width * ps);
|
||||
}
|
||||
compressZlib(scanlines.str(), IDAT, compression);
|
||||
compressZlib(scanlines, IDAT, compression);
|
||||
writeChunk(file, IDAT.str());
|
||||
}
|
||||
|
||||
file.write("\x00\x00\x00\x00IEND\xae\x42\x60\x82", 12);
|
||||
file.append("\x00\x00\x00\x00IEND\xae\x42\x60\x82", 12);
|
||||
|
||||
return file.str();
|
||||
return file;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user