From e865e55db07e13fe2b0fe054dfd68cb142187632 Mon Sep 17 00:00:00 2001 From: Relintai Date: Wed, 30 Oct 2019 20:24:50 +0100 Subject: [PATCH] Added TextureLayerMerger. --- README.md | 25 +++ SCsub | 2 + doc_classes/TextureLayerMerger.xml | 27 +++ layers/texture_layer_merger.cpp | 348 +++++++++++++++++++++++++++++ layers/texture_layer_merger.h | 81 +++++++ register_types.cpp | 4 + 6 files changed, 487 insertions(+) create mode 100644 doc_classes/TextureLayerMerger.xml create mode 100644 layers/texture_layer_merger.cpp create mode 100644 layers/texture_layer_merger.h diff --git a/README.md b/README.md index cef065b..cbb1ea1 100644 --- a/README.md +++ b/README.md @@ -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 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. diff --git a/SCsub b/SCsub index 53021f1..9afaead 100644 --- a/SCsub +++ b/SCsub @@ -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") + diff --git a/doc_classes/TextureLayerMerger.xml b/doc_classes/TextureLayerMerger.xml new file mode 100644 index 0000000..a03543e --- /dev/null +++ b/doc_classes/TextureLayerMerger.xml @@ -0,0 +1,27 @@ + + + + A class, that can merge textures as layers together. + + A class, that can merge textures as layers together. + + + + + + + + + + + + + + + + + + + + + diff --git a/layers/texture_layer_merger.cpp b/layers/texture_layer_merger.cpp new file mode 100644 index 0000000..cde711c --- /dev/null +++ b/layers/texture_layer_merger.cpp @@ -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 TextureLayerMerger::get_data() const { + return _image; +} + +void TextureLayerMerger::set_data(const Ref &p_image) { + ERR_FAIL_COND(p_image.is_null()); + + _image = p_image; +} + +Ref TextureLayerMerger::get_result_as_texture() const { + ERR_FAIL_COND_V(!_image.is_valid(), Ref()); + + Ref tex; + tex.instance(); + tex->create_from_image(_image); + + return tex; +} + +void TextureLayerMerger::add_texture(Ref 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 TextureLayerMerger::get_texture(const int p_index) { + ERR_FAIL_INDEX_V(p_index, _entries.size(), Ref()); + + return _entries.get(p_index).texture; +} +void TextureLayerMerger::set_texture(const int p_index, Ref 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 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 altas_texture = e.texture; + Ref input_image; + + if (altas_texture.is_valid()) { + Ref 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 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 &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(); +} diff --git a/layers/texture_layer_merger.h b/layers/texture_layer_merger.h new file mode 100644 index 0000000..e6c96b5 --- /dev/null +++ b/layers/texture_layer_merger.h @@ -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 get_data() const; + void set_data(const Ref &p_image); + + Ref get_result_as_texture() const; + + void add_texture(Ref p_texture, Color p_color = Color(1, 1, 1, 1), Vector2 p_position = Vector2(), Rect2 p_rect = Rect2()); + + Ref get_texture(const int p_index); + void set_texture(const int p_index, Ref 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 &data); + + static void _bind_methods(); + + struct LayerMergerEntry { + Ref texture; + Color color; + Rect2 rect; + Vector2 position; + }; + +private: + int _width; + int _height; + + uint32_t _texture_flags; + + Color _base_color; + + Ref _image; + + Vector _entries; +}; + +#endif diff --git a/register_types.cpp b/register_types.cpp index a24d0f8..0f5ddcf 100644 --- a/register_types.cpp +++ b/register_types.cpp @@ -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(); + ClassDB::register_class(); + #ifdef TOOLS_ENABLED EditorPlugins::add_by_type(); #endif