Merge 3128beba7f
into d2a7875b5b
This commit is contained in:
commit
6e7007590d
@ -569,17 +569,25 @@ 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
|
||||||
|
* 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:
|
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
|
||||||
|
@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
|
@ -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]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user