Added TextureLayerMerger.

This commit is contained in:
Relintai 2019-10-30 20:24:50 +01:00
parent 3597ba1e3d
commit e865e55db0
6 changed files with 487 additions and 0 deletions

View File

@ -67,3 +67,28 @@ Useful for textures you only need for baking, as this class will not register it
The module also contains an editor plugin which can import textures as `PackerImageResource` Resource.
To access it, click on a texture, switch to the import tab, and in the "Import As" Dropdown, select "Packer Image Recource".
## TextureLayerMerger
This class can merge together textures as layers. Useful for example to merge together (and color) skin, and clothes for a character.
It can handle both AtlasTextures and normal Textures.
Add the layers from bottom to top with the add_texture() method, when you added everything call merge().
You can set the resulting image's size with the `width`, and `height` properties. If you leave them at 0, they will
change to the first added texture's size.
add_texture looks like this:
```
void add_texture(Ref<Texture> p_texture, Color p_color = Color(1, 1, 1, 1), Vector2 p_position = Vector2(), Rect2 p_rect = Rect2());
```
The color parameter will color the given texture on merge().
With the position parameter you can offset your texture (in the resulted texture), and with the rect parameter, you can crop it.
There are setters to manipulate the added data later.
After the merge, you can either use `get_result_as_texture()` (it creates an ImageTexture on the fly), or the `data` property to
grab the resulting Image.

2
SCsub
View File

@ -12,3 +12,5 @@ env.add_source_files(env.modules_sources,"texture_resource/packer_image_resource
env.add_source_files(env.modules_sources,"texture_resource/editor_plugin_packer_image_resource.cpp")
env.add_source_files(env.modules_sources,"texture_resource/packer_image_resource_importer.cpp")
env.add_source_files(env.modules_sources,"layers/texture_layer_merger.cpp")

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="TextureLayerMerger" inherits="Reference" category="Addon" version="3.2">
<brief_description>
A class, that can merge textures as layers together.
</brief_description>
A class, that can merge textures as layers together.
<description>
</description>
<tutorials>
</tutorials>
<methods>
<method name="add_texture">
<return type="AtlasTexture">
</return>
<argument index="0" name="texture" type="Texture">
</argument>
<description>
</description>
</method>
</methods>
<members>
<member name="texture_flags" type="int" setter="set_texture_flags" getter="get_texture_flags" default="5">
</member>
</members>
<constants>
</constants>
</class>

View File

@ -0,0 +1,348 @@
#include "texture_layer_merger.h"
int TextureLayerMerger::get_width() const {
return _width;
}
void TextureLayerMerger::set_width(const int p_value) {
_width = p_value;
}
int TextureLayerMerger::get_height() const {
return _height;
}
void TextureLayerMerger::set_height(const int p_value) {
_height = p_value;
}
uint32_t TextureLayerMerger::get_texture_flags() const {
return _texture_flags;
}
void TextureLayerMerger::set_texture_flags(uint32_t p_flags) {
_texture_flags = p_flags;
}
Color TextureLayerMerger::get_base_color() const {
return _base_color;
}
void TextureLayerMerger::set_base_color(const Color p_color) {
_base_color = p_color;
}
Ref<Image> TextureLayerMerger::get_data() const {
return _image;
}
void TextureLayerMerger::set_data(const Ref<Image> &p_image) {
ERR_FAIL_COND(p_image.is_null());
_image = p_image;
}
Ref<ImageTexture> TextureLayerMerger::get_result_as_texture() const {
ERR_FAIL_COND_V(!_image.is_valid(), Ref<ImageTexture>());
Ref<ImageTexture> tex;
tex.instance();
tex->create_from_image(_image);
return tex;
}
void TextureLayerMerger::add_texture(Ref<Texture> p_texture, Color p_color, Vector2 p_position, Rect2 p_rect) {
ERR_FAIL_COND(!p_texture.is_valid());
LayerMergerEntry entry;
entry.texture = p_texture;
entry.color = p_color;
entry.position = p_position;
if (p_rect.size.x <= 0)
p_rect.size.x = p_texture->get_width();
if (p_rect.size.y <= 0)
p_rect.size.y = p_texture->get_height();
entry.rect = p_rect;
if (_width == 0)
_width = p_texture->get_width();
if (_height == 0)
_height = p_texture->get_height();
_entries.push_back(entry);
}
Ref<Texture> TextureLayerMerger::get_texture(const int p_index) {
ERR_FAIL_INDEX_V(p_index, _entries.size(), Ref<Texture>());
return _entries.get(p_index).texture;
}
void TextureLayerMerger::set_texture(const int p_index, Ref<Texture> p_texture) {
ERR_FAIL_INDEX(p_index, _entries.size());
_entries.get(p_index).texture = p_texture;
}
Color TextureLayerMerger::get_color(const int p_index) {
ERR_FAIL_INDEX_V(p_index, _entries.size(), Color());
return _entries.get(p_index).color;
}
void TextureLayerMerger::set_color(const int p_index, Color p_color) {
ERR_FAIL_INDEX(p_index, _entries.size());
_entries.get(p_index).color = p_color;
}
Vector2 TextureLayerMerger::get_position(const int p_index) {
ERR_FAIL_INDEX_V(p_index, _entries.size(), Vector2());
return _entries.get(p_index).position;
}
void TextureLayerMerger::set_position(const int p_index, Vector2 p_position) {
ERR_FAIL_INDEX(p_index, _entries.size());
_entries.get(p_index).position = p_position;
}
Rect2 TextureLayerMerger::get_rect(const int p_index) {
ERR_FAIL_INDEX_V(p_index, _entries.size(), Rect2());
return _entries.get(p_index).rect;
}
void TextureLayerMerger::set_rect(const int p_index, Rect2 p_rect) {
ERR_FAIL_INDEX(p_index, _entries.size());
_entries.get(p_index).rect = p_rect;
}
void TextureLayerMerger::remove_texture(const int p_index) {
ERR_FAIL_INDEX(p_index, _entries.size());
return _entries.remove(p_index);
}
int TextureLayerMerger::get_texture_count() {
return _entries.size();
}
void TextureLayerMerger::clear() {
_entries.clear();
}
void TextureLayerMerger::merge() {
ERR_FAIL_COND(_width <= 0 || _height <= 0);
if (!_image.is_valid())
_image.instance();
PoolVector<uint8_t> data;
data.resize(_width * _height * 4);
write_base_color_to_array(data);
for (int i = 0; i < _entries.size(); ++i) {
LayerMergerEntry &e = _entries.get(i);
ERR_CONTINUE(!e.texture.is_valid());
int rx = e.rect.position.x + 0.1;
int ry = e.rect.position.y + 0.1;
int rw = e.rect.size.x + 0.1;
int rh = e.rect.size.y + 0.1;
int posx = e.position.x + 0.1;
int posy = e.position.y + 0.1;
int atlas_x = 0;
int atlas_y = 0;
if (posx > _width || posy > _height)
continue;
Ref<AtlasTexture> altas_texture = e.texture;
Ref<Image> input_image;
if (altas_texture.is_valid()) {
Ref<Texture> atlas = altas_texture->get_atlas();
ERR_CONTINUE(!atlas.is_valid());
input_image = atlas->get_data();
Rect2 region = altas_texture->get_region();
atlas_x = region.position.x + 0.1;
atlas_y = region.position.y + 0.1;
int atlas_w = region.size.x + 0.1;
int atlas_h = region.size.y + 0.1;
if (rw > atlas_w)
rw = atlas_w;
if (rh > atlas_h)
rh = atlas_h;
} else {
input_image = e.texture->get_data();
}
ERR_CONTINUE(!input_image.is_valid());
int iiw = input_image->get_width();
int iih = input_image->get_height();
PoolVector<uint8_t> input_image_data = input_image->get_data();
Color &blendcolor = e.color;
float blend_arr[] = {
blendcolor.r,
blendcolor.g,
blendcolor.b,
blendcolor.a
};
ERR_CONTINUE(iiw == 0 || iih == 0);
ERR_CONTINUE(rx > iiw || ry > iih);
//clamp width, and height if bigger (for ease of use)
if (atlas_x + rx + rw >= iiw)
rw -= (atlas_x + rx + rw) - iiw;
if (atlas_y + ry + rh >= iih)
rh -= (atlas_y + ry + rh) - iih;
//Let's take position into account
if (rx + rw + posx >= _width)
rw -= (rx + rw + posx) - _width;
if (ry + rh + posy >= _height)
rh -= (ry + rh + posy) - _height;
if (rw <= 0 || rh <= 0)
continue;
int elen = 0;
if (input_image->get_format() == Image::FORMAT_RGBA8) {
elen = 4;
} else if (input_image->get_format() == Image::FORMAT_RGB8) {
elen = 3;
}
ERR_CONTINUE_MSG(elen == 0, "Unsupported image format! Format: " + String::num(input_image->get_format()));
for (int y = 0; y < rh; ++y) {
int img_gen_index = (posx + ((y + posy) * _width)) * 4;
int img_input_index = (rx + atlas_x + ((y + atlas_y + ry) * iiw)) * elen;
for (int x = 0; x < rw; ++x) {
float blend_alpha = blendcolor.a;
if (elen == 4) {
float orig_alpha = input_image_data.get(img_input_index + 3) / 255.0;
blend_alpha -= 1.0 - orig_alpha;
if (blend_alpha < 0)
blend_alpha = 0;
blend_alpha = orig_alpha;
}
for (int sp = 0; sp < elen; ++sp) {
int main_index = img_gen_index + sp;
int main_val = data.get(main_index);
int input_val = input_image_data.get(img_input_index + sp);
main_val = (input_val * blend_arr[sp]) * blend_alpha + (main_val * (1 - blend_alpha));
if (main_val > 255)
main_val = 255;
data.set(main_index, main_val);
}
img_gen_index += 4;
img_input_index += elen;
}
}
}
_image->create(_width, _height, (_texture_flags & Texture::FLAG_MIPMAPS) != 0, Image::FORMAT_RGBA8, data);
}
void TextureLayerMerger::write_base_color_to_array(PoolVector<uint8_t> &data) {
int cr = _base_color.r * 255;
int cg = _base_color.g * 255;
int cb = _base_color.b * 255;
int ca = _base_color.a * 255;
for (int i = 0; i < data.size(); i += 4) {
data.set(i, cr);
data.set(i + 1, cg);
data.set(i + 2, cb);
data.set(i + 3, ca);
}
}
void TextureLayerMerger::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_width"), &TextureLayerMerger::get_width);
ClassDB::bind_method(D_METHOD("set_width", "image"), &TextureLayerMerger::set_width);
ADD_PROPERTY(PropertyInfo(Variant::INT, "width"), "set_width", "get_width");
ClassDB::bind_method(D_METHOD("get_height"), &TextureLayerMerger::get_height);
ClassDB::bind_method(D_METHOD("set_height", "image"), &TextureLayerMerger::set_height);
ADD_PROPERTY(PropertyInfo(Variant::INT, "height"), "set_height", "get_height");
ClassDB::bind_method(D_METHOD("get_texture_flags"), &TextureLayerMerger::get_texture_flags);
ClassDB::bind_method(D_METHOD("set_texture_flags", "image"), &TextureLayerMerger::set_texture_flags);
ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_flags", PROPERTY_HINT_FLAGS, "Mipmaps,Repeat,Filter"), "set_texture_flags", "get_texture_flags");
ClassDB::bind_method(D_METHOD("get_base_color"), &TextureLayerMerger::get_base_color);
ClassDB::bind_method(D_METHOD("set_base_color", "color"), &TextureLayerMerger::set_base_color);
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "base_color"), "set_base_color", "get_base_color");
ClassDB::bind_method(D_METHOD("get_data"), &TextureLayerMerger::get_data);
ClassDB::bind_method(D_METHOD("set_data", "image"), &TextureLayerMerger::set_data);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "data", PROPERTY_HINT_RESOURCE_TYPE, "Image"), "set_data", "get_data");
ClassDB::bind_method(D_METHOD("get_result_as_texture"), &TextureLayerMerger::get_result_as_texture);
ClassDB::bind_method(D_METHOD("add_texture", "texture", "color", "position", "rect"), &TextureLayerMerger::add_texture, DEFVAL(Color(1, 1, 1, 1)), DEFVAL(Vector2()), DEFVAL(Rect2()));
ClassDB::bind_method(D_METHOD("get_texture", "index"), &TextureLayerMerger::get_texture);
ClassDB::bind_method(D_METHOD("set_texture", "index", "texture"), &TextureLayerMerger::set_texture);
ClassDB::bind_method(D_METHOD("get_color", "index"), &TextureLayerMerger::get_color);
ClassDB::bind_method(D_METHOD("set_color", "index", "color"), &TextureLayerMerger::set_color);
ClassDB::bind_method(D_METHOD("get_position", "index"), &TextureLayerMerger::get_position);
ClassDB::bind_method(D_METHOD("set_position", "index", "position"), &TextureLayerMerger::set_position);
ClassDB::bind_method(D_METHOD("get_rect", "index"), &TextureLayerMerger::get_rect);
ClassDB::bind_method(D_METHOD("set_rect", "index", "rect"), &TextureLayerMerger::set_rect);
ClassDB::bind_method(D_METHOD("remove_texture", "index"), &TextureLayerMerger::remove_texture);
ClassDB::bind_method(D_METHOD("get_texture_count"), &TextureLayerMerger::get_texture_count);
ClassDB::bind_method(D_METHOD("clear"), &TextureLayerMerger::clear);
ClassDB::bind_method(D_METHOD("merge"), &TextureLayerMerger::merge);
}
TextureLayerMerger::TextureLayerMerger() {
_width = 0;
_height = 0;
_texture_flags = 0;
}
TextureLayerMerger::~TextureLayerMerger() {
_image.unref();
_entries.clear();
}

View File

@ -0,0 +1,81 @@
#ifndef TEXTURE_LAYER_MERGER_H
#define TEXTURE_LAYER_MERGER_H
#include "core/reference.h"
#include "core/math/rect2.h"
#include "core/vector.h"
#include "core/image.h"
#include "scene/resources/texture.h"
class TextureLayerMerger : public Reference {
GDCLASS(TextureLayerMerger, Reference);
public:
int get_width() const;
void set_width(const int p_value);
int get_height() const;
void set_height(const int p_value);
uint32_t get_texture_flags() const;
void set_texture_flags(uint32_t p_flags);
Color get_base_color() const;
void set_base_color(const Color p_color);
Ref<Image> get_data() const;
void set_data(const Ref<Image> &p_image);
Ref<ImageTexture> get_result_as_texture() const;
void add_texture(Ref<Texture> p_texture, Color p_color = Color(1, 1, 1, 1), Vector2 p_position = Vector2(), Rect2 p_rect = Rect2());
Ref<Texture> get_texture(const int p_index);
void set_texture(const int p_index, Ref<Texture> p_texture);
Color get_color(const int p_index);
void set_color(const int p_index, Color p_color);
Vector2 get_position(const int p_index);
void set_position(const int p_index, Vector2 p_position);
Rect2 get_rect(const int p_index);
void set_rect(const int p_index, Rect2 p_rect);
void remove_texture(const int p_index);
int get_texture_count();
void clear();
void merge();
TextureLayerMerger();
~TextureLayerMerger();
protected:
void write_base_color_to_array(PoolVector<uint8_t> &data);
static void _bind_methods();
struct LayerMergerEntry {
Ref<Texture> texture;
Color color;
Rect2 rect;
Vector2 position;
};
private:
int _width;
int _height;
uint32_t _texture_flags;
Color _base_color;
Ref<Image> _image;
Vector<LayerMergerEntry> _entries;
};
#endif

View File

@ -5,6 +5,8 @@
#include "texture_resource/packer_image_resource.h"
#include "layers/texture_layer_merger.h"
#ifdef TOOLS_ENABLED
#include "editor/editor_plugin.h"
@ -17,6 +19,8 @@ void register_texture_packer_types() {
ClassDB::register_class<PackerImageResource>();
ClassDB::register_class<TextureLayerMerger>();
#ifdef TOOLS_ENABLED
EditorPlugins::add_by_type<EditorPluginPackerImageResource>();
#endif