From 02292e03e42e5c3be0aa09a329dabd9c8dad5571 Mon Sep 17 00:00:00 2001 From: hecks <42101236+hecktest@users.noreply.github.com> Date: Wed, 13 Oct 2021 17:51:37 +0200 Subject: [PATCH] Add embedded PNG texture modifier (#11498) --- doc/lua_api.txt | 17 ++++ games/devtest/mods/testnodes/textures.lua | 31 ++++++ src/client/tile.cpp | 113 +++++++++++++++------- 3 files changed, 126 insertions(+), 35 deletions(-) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index e6cabb68e..69ac55493 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -628,6 +628,23 @@ Result is more like what you'd expect if you put a color on top of another color, meaning white surfaces get a lot of your new color while black parts don't change very much. +#### `[png:` + +Embed a base64 encoded PNG image in the texture string. +You can produce a valid string for this by calling +`minetest.encode_base64(minetest.encode_png(tex))`, +refer to the documentation of these functions for details. +You can use this to send disposable images such as captchas +to individual clients, or render things that would be too +expensive to compose with `[combine:`. + +IMPORTANT: Avoid sending large images this way. +This is not a replacement for asset files, do not use it to do anything +that you could instead achieve by just using a file. +In particular consider `minetest.dynamic_add_media` and test whether +using other texture modifiers could result in a shorter string than +embedding a whole image, this may vary by use case. + Hardware coloring ----------------- diff --git a/games/devtest/mods/testnodes/textures.lua b/games/devtest/mods/testnodes/textures.lua index 4652007d9..dc581b0c7 100644 --- a/games/devtest/mods/testnodes/textures.lua +++ b/games/devtest/mods/testnodes/textures.lua @@ -102,12 +102,22 @@ local function gen_checkers(w, h, tile) end local fractal = mandelbrot(512, 512, 128) +local frac_emb = mandelbrot(64, 64, 64) local checker = gen_checkers(512, 512, 32) local floor = math.floor local abs = math.abs +local data_emb = {} local data_mb = {} local data_ck = {} +for i=1, #frac_emb do + data_emb[i] = { + r = floor(abs(frac_emb[i] * 2 - 1) * 255), + g = floor(abs(1 - frac_emb[i]) * 255), + b = floor(frac_emb[i] * 255), + a = frac_emb[i] < 0.95 and 255 or 0, + } +end for i=1, #fractal do data_mb[i] = { r = floor(fractal[i] * 255), @@ -140,3 +150,24 @@ 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)) + +minetest.register_node("testnodes:generated_png_emb", { + description = S("Generated In-Band Mandelbrot PNG Test Node"), + tiles = { png_emb }, + + groups = { dig_immediate = 2 }, +}) +minetest.register_node("testnodes:generated_png_src_emb", { + description = S("Generated In-Band Source Blit Mandelbrot PNG Test Node"), + tiles = { png_emb .. "^testnodes_damage_neg.png" }, + + groups = { dig_immediate = 2 }, +}) +minetest.register_node("testnodes:generated_png_dst_emb", { + description = S("Generated In-Band Dest Blit Mandelbrot PNG Test Node"), + tiles = { "testnodes_generated_ck.png^" .. png_emb }, + + groups = { dig_immediate = 2 }, +}) diff --git a/src/client/tile.cpp b/src/client/tile.cpp index 091e546c6..2f57503d3 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -33,6 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "imagefilters.h" #include "guiscalingfilter.h" #include "renderingengine.h" +#include "util/base64.h" /* A cache from texture name to texture path @@ -1059,6 +1060,45 @@ static std::string unescape_string(const std::string &str, const char esc = '\\' return out; } +void blitBaseImage(video::IImage* &src, video::IImage* &dst) +{ + //infostream<<"Blitting "< dim = src->getDimension(); + //core::dimension2d dim(16,16); + // Position to copy the blitted to in the base image + core::position2d pos_to(0,0); + // Position to copy the blitted from in the blitted image + core::position2d pos_from(0,0); + // Blit + /*image->copyToWithAlpha(baseimg, pos_to, + core::rect(pos_from, dim), + video::SColor(255,255,255,255), + NULL);*/ + + core::dimension2d dim_dst = dst->getDimension(); + if (dim == dim_dst) { + blit_with_alpha(src, dst, pos_from, pos_to, dim); + } else if (dim.Width * dim.Height < dim_dst.Width * dim_dst.Height) { + // Upscale overlying image + video::IImage *scaled_image = RenderingEngine::get_video_driver()-> + createImage(video::ECF_A8R8G8B8, dim_dst); + src->copyToScaling(scaled_image); + + blit_with_alpha(scaled_image, dst, pos_from, pos_to, dim_dst); + scaled_image->drop(); + } else { + // Upscale base image + video::IImage *scaled_base = RenderingEngine::get_video_driver()-> + createImage(video::ECF_A8R8G8B8, dim); + dst->copyToScaling(scaled_base); + dst->drop(); + dst = scaled_base; + + blit_with_alpha(src, dst, pos_from, pos_to, dim); + } +} + bool TextureSource::generateImagePart(std::string part_of_name, video::IImage *& baseimg) { @@ -1122,41 +1162,7 @@ bool TextureSource::generateImagePart(std::string part_of_name, // Else blit on base. else { - //infostream<<"Blitting "< dim = image->getDimension(); - //core::dimension2d dim(16,16); - // Position to copy the blitted to in the base image - core::position2d pos_to(0,0); - // Position to copy the blitted from in the blitted image - core::position2d pos_from(0,0); - // Blit - /*image->copyToWithAlpha(baseimg, pos_to, - core::rect(pos_from, dim), - video::SColor(255,255,255,255), - NULL);*/ - - core::dimension2d dim_dst = baseimg->getDimension(); - if (dim == dim_dst) { - blit_with_alpha(image, baseimg, pos_from, pos_to, dim); - } else if (dim.Width * dim.Height < dim_dst.Width * dim_dst.Height) { - // Upscale overlying image - video::IImage *scaled_image = RenderingEngine::get_video_driver()-> - createImage(video::ECF_A8R8G8B8, dim_dst); - image->copyToScaling(scaled_image); - - blit_with_alpha(scaled_image, baseimg, pos_from, pos_to, dim_dst); - scaled_image->drop(); - } else { - // Upscale base image - video::IImage *scaled_base = RenderingEngine::get_video_driver()-> - createImage(video::ECF_A8R8G8B8, dim); - baseimg->copyToScaling(scaled_base); - baseimg->drop(); - baseimg = scaled_base; - - blit_with_alpha(image, baseimg, pos_from, pos_to, dim); - } + blitBaseImage(image, baseimg); } //cleanup image->drop(); @@ -1784,6 +1790,43 @@ bool TextureSource::generateImagePart(std::string part_of_name, baseimg->drop(); baseimg = img; } + /* + [png:base64 + Decodes a PNG image in base64 form. + Use minetest.encode_png and minetest.encode_base64 + to produce a valid string. + */ + else if (str_starts_with(part_of_name, "[png:")) { + Strfnd sf(part_of_name); + sf.next(":"); + std::string png; + { + std::string blob = sf.next(""); + if (!base64_is_valid(blob)) { + errorstream << "generateImagePart(): " + << "malformed base64 in '[png'" + << std::endl; + return false; + } + png = base64_decode(blob); + } + + auto *device = RenderingEngine::get_raw_device(); + auto *fs = device->getFileSystem(); + auto *vd = device->getVideoDriver(); + auto *memfile = fs->createMemoryReadFile(png.data(), png.size(), "__temp_png"); + video::IImage* pngimg = vd->createImageFromFile(memfile); + memfile->drop(); + + if (baseimg) { + blitBaseImage(pngimg, baseimg); + } else { + core::dimension2d dim = pngimg->getDimension(); + baseimg = driver->createImage(video::ECF_A8R8G8B8, dim); + pngimg->copyTo(baseimg); + } + pngimg->drop(); + } else { errorstream << "generateImagePart(): Invalid "