Merge 3128beba7f
into d2a7875b5b
This commit is contained in:
commit
6e7007590d
@ -569,17 +569,25 @@ Example:
|
||||
|
||||
default_cobble.png^[crack:10:1
|
||||
|
||||
#### `[combine:<w>x<h>:<x1>,<y1>=<file1>:<x2>,<y2>=<file2>:...`
|
||||
|
||||
* `<w>`: width
|
||||
* `<h>`: height
|
||||
* `<x>`: x position, negative numbers allowed
|
||||
* `<y>`: y position, negative numbers allowed
|
||||
* `<file>`: texture to combine
|
||||
#### `[combine:<w>x<h>:<x1>,<y1>,<w1>=<file1>:<x2>,<y2>,<w2>=<file2>:...`
|
||||
|
||||
Creates a texture of size `<w>` times `<h>` and blits the listed files to their
|
||||
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
|
||||
* If the provided texture width is (e.g. 2 times) larger than `<wN>`, it will
|
||||
result in an output texture that is also (2 times) larger.
|
||||
* If the provided texture width is smaller, it will be upscaled proportionally
|
||||
to match the width `<wN>`.
|
||||
* Supported since protocol version 47. Old clients will ignore this parameter.
|
||||
* `<file>`: texture to combine
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
[combine:16x32:0,0=default_cobble.png:0,16=default_wood.png
|
||||
|
@ -1118,21 +1118,78 @@ bool ImageSource::generateImagePart(std::string_view part_of_name,
|
||||
{
|
||||
Strfnd sf(part_of_name);
|
||||
sf.next(":");
|
||||
// grid size
|
||||
u32 w0 = stoi(sf.next("x"));
|
||||
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 (scale > 0) {
|
||||
w0 = w0 * scale / FX_FACTOR;
|
||||
h0 = h0 * scale / FX_FACTOR;
|
||||
}
|
||||
CHECK_DIM(w0, h0);
|
||||
|
||||
// create desired
|
||||
baseimg = driver->createImage(video::ECF_A8R8G8B8, {w0, h0});
|
||||
baseimg->fill(video::SColor(0,0,0,0));
|
||||
}
|
||||
|
||||
while (!sf.at_end()) {
|
||||
v2s32 pos_base;
|
||||
pos_base.X = stoi(sf.next(","));
|
||||
pos_base.Y = stoi(sf.next("="));
|
||||
std::string filename = unescape_string(sf.next_esc(":", escape), escape);
|
||||
const auto basedim = baseimg->getDimension();
|
||||
for (ImagePart &it : image_parts) {
|
||||
// Shift insertion offset by the same factor as we scaled `baseimg`
|
||||
const v2s32 pos_base = it.offset * scale / FX_FACTOR;
|
||||
const std::string &filename = it.filename;
|
||||
|
||||
auto basedim = baseimg->getDimension();
|
||||
if (pos_base.X > (s32)basedim.Width || pos_base.Y > (s32)basedim.Height) {
|
||||
warningstream << "generateImagePart(): Skipping \""
|
||||
<< 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 "
|
||||
<< pos_base << std::endl;
|
||||
|
||||
video::IImage *img = generateImage(filename, source_image_names);
|
||||
if (!img) {
|
||||
errorstream << "generateImagePart(): Failed to load image \""
|
||||
<< filename << "\" for [combine" << std::endl;
|
||||
continue;
|
||||
auto dim = it.img->getDimension();
|
||||
u32 wanted_width = it.expected_width * scale / FX_FACTOR;
|
||||
if (dim.Width != wanted_width) {
|
||||
// needs resize
|
||||
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) {
|
||||
warningstream << "generateImagePart(): Skipping \""
|
||||
<< filename << "\" as it's out-of-bounds " << pos_base
|
||||
<< " for [combine" << std::endl;
|
||||
img->drop();
|
||||
continue;
|
||||
}
|
||||
|
||||
blit_with_alpha(img, baseimg, pos_base, dim);
|
||||
img->drop();
|
||||
blit_with_alpha(it.img, baseimg, pos_base, dim);
|
||||
}
|
||||
}
|
||||
/*
|
||||
|
@ -61,6 +61,7 @@
|
||||
[scheduled bump for 5.10.0]
|
||||
PROTOCOL VERSION 47
|
||||
Add particle blend mode "clip"
|
||||
"[combine:WxH:x1,y1,w1=" 3rd parameter extension
|
||||
[scheduled bump for 5.11.0]
|
||||
*/
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user