1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-06-27 16:36:03 +00:00

Client: Extend [combine modifier with optional width parameter

This addition makes it possible to combine textures easily without
needing to take care of the texture pack size e.g. by resizing
every texture before combining. This feature automates such labour.
This commit is contained in:
SmallJoker 2024-08-29 23:14:16 +02:00
parent c175046d30
commit f13144a15d
3 changed files with 90 additions and 22 deletions

View file

@ -562,17 +562,22 @@ Example:
default_cobble.png^[crack:10:1 default_cobble.png^[crack:10:1
#### `[combine:<w>x<h>:<x1>,<y1>=<file1>:<x2>,<y2>=<file2>:...` #### `[combine:<w>x<h>:<x1>,<y1>,<w1>=<file1>:<x2>,<y2>,<w2>=<file2>:...`
* `<w>`: width
* `<h>`: height
* `<x>`: x position, negative numbers allowed
* `<y>`: y position, negative numbers allowed
* `<file>`: texture to combine
Creates a texture of size `<w>` times `<h>` and blits the listed files to their Creates a texture of size `<w>` times `<h>` and blits the listed files to their
specified coordinates. specified coordinates.
Note: the output texture size may vary if one or more of `<wN>` is provided.
The aspect ratio is preserved.
* `<xN>`: X offset of insertion, negative numbers allowed
* `<yN>`: Y offset of insertion, negative numbers allowed
* `<wN>` (optional): Expected texture width
* Scales the output image if `<wN> != input texture width` to perform lossless blit.
* Supported since protocol version 46.
* `<file>`: texture to combine
Example: Example:
[combine:16x32:0,0=default_cobble.png:0,16=default_wood.png [combine:16x32:0,0=default_cobble.png:0,16=default_wood.png

View file

@ -1118,21 +1118,78 @@ bool ImageSource::generateImagePart(std::string_view part_of_name,
{ {
Strfnd sf(part_of_name); Strfnd sf(part_of_name);
sf.next(":"); sf.next(":");
// grid size
u32 w0 = stoi(sf.next("x")); u32 w0 = stoi(sf.next("x"));
u32 h0 = stoi(sf.next(":")); u32 h0 = stoi(sf.next(":"));
struct ImagePart {
v2s32 offset;
std::string filename;
video::IImage *img = nullptr;
int expected_width = 0;
~ImagePart()
{
if (img)
img->drop();
}
};
std::list<ImagePart> image_parts;
// fixed point precision to allow textures smaller than the grid size
constexpr int FX_FACTOR = 1024;
// By how much to scale (w0, h0) for the resulting image
u32 scale = 0; // includes FX_FACTOR
while (!sf.at_end()) {
// X,Y(,W)=image_esc(:X,Y...)
auto &it = image_parts.emplace_back();
auto parts = str_split(sf.next("="), ',');
if (parts.size() >= 1)
it.offset.X = stoi(parts[0]);
if (parts.size() >= 2)
it.offset.Y = stoi(parts[1]);
if (parts.size() >= 3)
it.expected_width = stoi(parts[2]);
it.filename = unescape_string(sf.next_esc(":", escape), escape);
it.img = generateImage(it.filename, source_image_names);
if (!it.img) {
errorstream << "generateImagePart(): Failed to load image \""
<< it.filename << "\" for [combine" << std::endl;
image_parts.pop_back();
continue;
}
const auto dim = it.img->getDimension();
if (it.expected_width <= 0) {
// Parameter not specified -> do not scale
it.expected_width = dim.Width;
}
scale = std::max<u32>(scale, FX_FACTOR * dim.Width / it.expected_width);
}
if (!baseimg) { if (!baseimg) {
if (scale > 0) {
w0 = w0 * scale / FX_FACTOR;
h0 = h0 * scale / FX_FACTOR;
}
CHECK_DIM(w0, h0); CHECK_DIM(w0, h0);
// create desired
baseimg = driver->createImage(video::ECF_A8R8G8B8, {w0, h0}); baseimg = driver->createImage(video::ECF_A8R8G8B8, {w0, h0});
baseimg->fill(video::SColor(0,0,0,0)); baseimg->fill(video::SColor(0,0,0,0));
} }
while (!sf.at_end()) { const auto basedim = baseimg->getDimension();
v2s32 pos_base; for (ImagePart &it : image_parts) {
pos_base.X = stoi(sf.next(",")); // Shift insertion offset by the same factor as we scaled `baseimg`
pos_base.Y = stoi(sf.next("=")); const v2s32 pos_base = it.offset * scale / FX_FACTOR;
std::string filename = unescape_string(sf.next_esc(":", escape), escape); const std::string &filename = it.filename;
auto basedim = baseimg->getDimension();
if (pos_base.X > (s32)basedim.Width || pos_base.Y > (s32)basedim.Height) { if (pos_base.X > (s32)basedim.Width || pos_base.Y > (s32)basedim.Height) {
warningstream << "generateImagePart(): Skipping \"" warningstream << "generateImagePart(): Skipping \""
<< filename << "\" as it's out-of-bounds " << pos_base << filename << "\" as it's out-of-bounds " << pos_base
@ -1142,23 +1199,28 @@ bool ImageSource::generateImagePart(std::string_view part_of_name,
infostream << "Adding \"" << filename<< "\" to combined " infostream << "Adding \"" << filename<< "\" to combined "
<< pos_base << std::endl; << pos_base << std::endl;
video::IImage *img = generateImage(filename, source_image_names); auto dim = it.img->getDimension();
if (!img) { u32 wanted_width = it.expected_width * scale / FX_FACTOR;
errorstream << "generateImagePart(): Failed to load image \"" if (dim.Width != wanted_width) {
<< filename << "\" for [combine" << std::endl; // needs resize
continue; video::IImage *newimg = driver->createImage(
baseimg->getColorFormat(),
{ wanted_width, (dim.Height * wanted_width) / dim.Width }
);
it.img->copyToScaling(newimg);
it.img->drop();
it.img = newimg;
dim = it.img->getDimension();
} }
const auto dim = img->getDimension();
if (pos_base.X + dim.Width <= 0 || pos_base.Y + dim.Height <= 0) { if (pos_base.X + dim.Width <= 0 || pos_base.Y + dim.Height <= 0) {
warningstream << "generateImagePart(): Skipping \"" warningstream << "generateImagePart(): Skipping \""
<< filename << "\" as it's out-of-bounds " << pos_base << filename << "\" as it's out-of-bounds " << pos_base
<< " for [combine" << std::endl; << " for [combine" << std::endl;
img->drop();
continue; continue;
} }
blit_with_alpha(img, baseimg, pos_base, dim); blit_with_alpha(it.img, baseimg, pos_base, dim);
img->drop();
} }
} }
/* /*

View file

@ -61,6 +61,7 @@
[scheduled bump for 5.10.0] [scheduled bump for 5.10.0]
PROTOCOL VERSION 47 PROTOCOL VERSION 47
Add particle blend mode "clip" Add particle blend mode "clip"
"[combine:WxH:x1,y1,w1=" 3rd parameter extension
[scheduled bump for 5.11.0] [scheduled bump for 5.11.0]
*/ */