/* Copyright (c) 2019 Flairieve Copyright (c) 2020-2022 cobrapitz Copyright (c) 2022 Péter Magyar Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "paint_canvas.h" #include "paint_canvas_background.h" #include "paint_canvas_outline.h" #include "paint_utilities.h" #include "paint_visual_grid.h" #include "scene/gui/control.h" #include "scene/gui/texture_rect.h" #include "paint_canvas_layer.h" #include "icons/paint_icons.h" static float scale = 1; template static Ref make_icon(T p_src) { Ref texture(memnew(ImageTexture)); Ref img = memnew(Image(p_src)); if (scale > 1) { Size2 orig_size = Size2(img->get_width(), img->get_height()); img->convert(Image::FORMAT_RGBA8); img->expand_x2_hq2x(); if (scale != 2.0) { img->resize(orig_size.x * scale, orig_size.y * scale); } } else if (scale < 1) { Size2 orig_size = Size2(img->get_width(), img->get_height()); img->convert(Image::FORMAT_RGBA8); img->resize(orig_size.x * scale, orig_size.y * scale); } texture->create_from_image(img, ImageTexture::FLAG_FILTER); return texture; } void PaintCanvas::_process(float delta) { if (!is_visible_in_tree()) { return; } Vector2 mouse_position = get_local_mouse_position(); Rect2 rect = Rect2(Vector2(0, 0), get_size()); mouse_in_region = rect.has_point(mouse_position); } void PaintCanvas::_draw() { for (int i = 0; i < layers.size(); ++i) { Ref layer = layers[i]; ERR_CONTINUE(!layer.is_valid()); layer->update_texture(); } preview_layer->update_texture(); tool_layer->update_texture(); } int PaintCanvas::get_pixel_size() const { return _pixel_size; } void PaintCanvas::set_pixel_size(const int size) { _pixel_size = size; set_grid_size(_grid_size); set_big_grid_size(_big_grid_size); set_canvas_width(_canvas_width); set_canvas_height(_canvas_height); } int PaintCanvas::get_grid_size() const { return _grid_size; } void PaintCanvas::set_grid_size(const int size) { _grid_size = size; if (grid) { int s = size * _pixel_size; grid->set_grid_size(s); } } int PaintCanvas::get_big_grid_size() const { return _big_grid_size; } void PaintCanvas::set_big_grid_size(const int size) { _big_grid_size = size; if (big_grid) { int s = size * _pixel_size; big_grid->set_size(Size2(s, s)); } } int PaintCanvas::get_canvas_width() const { return _canvas_width; } void PaintCanvas::set_canvas_width(const int val) { _canvas_width = val; Size2 s = get_size(); s.x = _canvas_width * _pixel_size; set_size(s); } int PaintCanvas::get_canvas_height() const { return _canvas_height; } void PaintCanvas::set_canvas_height(const int val) { _canvas_height = val; Size2 s = get_size(); s.y = _canvas_height * _pixel_size; set_size(s); } void PaintCanvas::toggle_alpha_locked(const String &layer_name) { Ref layer = find_layer_by_name(layer_name); if (layer.is_valid()) { layer->toggle_alpha_locked(); } } bool PaintCanvas::is_alpha_locked() { if (!active_layer.is_valid()) { return false; } return active_layer->alpha_locked; } Rect2 PaintCanvas::get_content_margin() { Rect2 rect = Rect2(999999, 999999, -999999, -999999); for (int i = 0; i < layers.size(); ++i) { Ref l = layers[i]; ERR_CONTINUE(!l.is_valid()); Rect2 r = l->image->get_used_rect(); if (r.position.x < rect.position.x) { rect.position.x = r.position.x; } if (r.position.y < rect.position.y) { rect.position.y = r.position.y; } if (r.size.x > rect.size.x) { rect.size.x = r.size.x; } if (r.size.y > rect.size.y) { rect.size.y = r.size.y; } } return rect; } void PaintCanvas::crop_to_content() { /* var rect = get_content_margin() #print(rect) for layer in layers: layer.image # set_canvas_width(rect.size.x) # set_canvas_height(rect.size.x) # preview_layer.resize(width, height) # tool_layer.resize(width, height) # for layer in layers: # layer.resize(width, height) */ } Ref PaintCanvas::get_active_layer() { return active_layer; } Ref PaintCanvas::get_preview_layer() { return preview_layer; } void PaintCanvas::clear_active_layer() { if (active_layer.is_valid()) { active_layer->clear(); } } void PaintCanvas::clear_preview_layer() { preview_layer->clear(); } void PaintCanvas::clear_layer(const String &layer_name) { for (int i = 0; i < layers.size(); ++i) { Ref l = layers[i]; ERR_CONTINUE(!l.is_valid()); if (l->name == layer_name) { l->clear(); return; } } } Ref PaintCanvas::remove_layer(const String &layer_name) { // change current layer if the active layer is removed Ref del_layer = find_layer_by_name(layer_name); del_layer->clear(); if (del_layer == active_layer) { if (layers.size() > 1) { for (int i = 0; i < layers.size(); ++i) { Ref layer = layers[i]; ERR_CONTINUE(!layer.is_valid()); if (layer == active_layer) { continue; } active_layer = layer; break; } } else { active_layer.unref(); } } layers.erase(del_layer); return active_layer; } Ref PaintCanvas::add_new_layer(const String &layer_name) { for (int i = 0; i < layers.size(); ++i) { Ref layer = layers[i]; ERR_CONTINUE(!layer.is_valid()); if (layer->name == layer_name) { return layer; } } Ref layer; layer.instance(); layer->name = layer_name; TextureRect *texture_rect = memnew(TextureRect); texture_rect->set_name(layer_name); canvas_layers->add_child(texture_rect, true); texture_rect->set_expand(true); texture_rect->set_anchors_and_margins_preset(Control::PRESET_WIDE); //texture_rect->set_margin(Margin::MARGIN_RIGHT, 0); //texture_rect->set_margin(Margin::MARGIN_BOTTOM, 0); texture_rect->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); layer->create(texture_rect, _canvas_width, _canvas_height); layers.push_back(layer); if (!active_layer.is_valid()) { active_layer = layer; } return layer; } Ref PaintCanvas::duplicate_layer(const String &layer_name, const String &new_layer_name) { for (int i = 0; i < layers.size(); ++i) { Ref layer = layers[i]; ERR_CONTINUE(!layer.is_valid()); if (layer->name == new_layer_name) { return layer; } } Ref dup_layer = find_layer_by_name(layer_name); Ref layer = add_new_layer(new_layer_name); layer->image->copy_internals_from(dup_layer->image); return layer; } void PaintCanvas::toggle_layer_visibility(const String &layer_name) { for (int i = 0; i < layers.size(); ++i) { Ref layer = layers[i]; ERR_CONTINUE(!layer.is_valid()); if (layer->name == layer_name) { layer->set_visible(!layer->get_visible()); return; } } } Ref PaintCanvas::find_layer_by_name(const String &layer_name) { for (int i = 0; i < layers.size(); ++i) { Ref layer = layers[i]; ERR_CONTINUE(!layer.is_valid()); if (layer->name == layer_name) { return layer; } } return Ref(); } void PaintCanvas::toggle_lock_layer(const String &layer_name) { find_layer_by_name(layer_name)->toggle_lock(); } bool PaintCanvas::is_active_layer_locked() { return active_layer->locked; } void PaintCanvas::move_layer_forward(const String &layer_name) { TextureRect *layer = find_layer_by_name(layer_name)->texture_rect_ref; int new_idx = MAX(layer->get_index() - 1, 0); canvas_layers->move_child(layer, new_idx); } void PaintCanvas::move_layer_back(const String &layer_name) { TextureRect *layer = find_layer_by_name(layer_name)->texture_rect_ref; canvas_layers->move_child(layer, layer->get_index() + 1); } void PaintCanvas::select_layer(const String &layer_name) { active_layer = find_layer_by_name(layer_name); } void PaintCanvas::_on_mouse_entered() { mouse_on_top = true; } void PaintCanvas::_on_mouse_exited() { mouse_on_top = false; } bool PaintCanvas::is_inside_canvas(const int x, const int y) { if (x < 0 || y < 0) { return false; } if (x >= _canvas_width || y >= _canvas_height) { return false; } return true; } //Note: Arrays are always passed by reference. To get a copy of an array which // can be modified independently of the original array, use duplicate. // (https://docs.godotengine.org/en/stable/classes/class_array.html) void PaintCanvas::set_pixel_arr(const PoolVector2iArray &pixels, const Color &color) { PoolVector2iArray::Read r = pixels.read(); for (int i = 0; i < pixels.size(); ++i) { const Vector2i &pixel = r[i]; _set_pixel(active_layer, pixel.x, pixel.y, color); } } void PaintCanvas::set_pixel_v(const Vector2i &pos, const Color &color) { set_pixel(pos.x, pos.y, color); } void PaintCanvas::set_pixel(const int x, const int y, const Color &color) { _set_pixel(active_layer, x, y, color); } void PaintCanvas::_set_pixel_v(Ref layer, const Vector2i &v, const Color &color) { _set_pixel(layer, v.x, v.y, color); } void PaintCanvas::_set_pixel(Ref layer, const int x, const int y, const Color &color) { if (!is_inside_canvas(x, y)) { return; } layer->set_pixel(x, y, color); } Color PaintCanvas::get_pixel_v(const Vector2i &pos) { return get_pixel(pos.x, pos.y); } Color PaintCanvas::get_pixel(const int x, const int y) { if (active_layer.is_valid()) { return active_layer->get_pixel(x, y); } return Color(); } void PaintCanvas::set_preview_pixel_v(const Vector2i &pos, const Color &color) { set_preview_pixel(pos.x, pos.y, color); } void PaintCanvas::set_preview_pixel(const int x, const int y, const Color &color) { if (!is_inside_canvas(x, y)) { return; } preview_layer->set_pixel(x, y, color); } Color PaintCanvas::get_preview_pixel_v(const Vector2i &pos) { return get_preview_pixel(pos.x, pos.y); } Color PaintCanvas::get_preview_pixel(const int x, const int y) { return preview_layer->get_pixel(x, y); } bool PaintCanvas::validate_pixel_v(const Vector2i &pos) const { if (active_layer.is_valid()) { return active_layer->validate_pixel_v(pos); } return false; } void PaintCanvas::toggle_grid() { grid->set_visible(!grid->is_visible()); } void PaintCanvas::show_grid() { grid->show(); } void PaintCanvas::hide_grid() { grid->hide(); } PoolVector2iArray PaintCanvas::select_color(const int p_x, const int p_y) { PoolVector2iArray same_color_pixels; Color color = get_pixel(p_x, p_y); for (int x = 0; x < active_layer->layer_width; ++x) { for (int y = 0; y < active_layer->layer_height; ++y) { Color pixel_color = active_layer->get_pixel(x, y); if (pixel_color == color) { same_color_pixels.append(Vector2i(x, y)); } } } return same_color_pixels; } PoolVector2iArray PaintCanvas::select_same_color(const int p_x, const int p_y) { return get_neighbouring_pixels(p_x, p_y); } // returns array of Vector2 // yoinked from // https://www.geeksforgeeks.org/flood-fill-algorithm-implement-fill-paint/ PoolVector2iArray PaintCanvas::get_neighbouring_pixels(const int pos_x, const int pos_y) { PoolVector2iArray pixels; PoolIntArray to_check_queue; PoolIntArray checked_queue; to_check_queue.append(PaintUtilities::to_1D(pos_x, pos_y, _canvas_width)); Color color = get_pixel(pos_x, pos_y); while (!to_check_queue.empty()) { int idx = to_check_queue[0]; to_check_queue.remove(0); Vector2i p = PaintUtilities::to_2D(idx, _canvas_width); if (checked_queue.contains(idx)) { continue; } checked_queue.append(idx); if (get_pixel(p.x, p.y) != color) { continue; } // add to result pixels.append(p); // check neighbours int x = p.x - 1; int y = p.y; if (is_inside_canvas(x, y)) { idx = PaintUtilities::to_1D(x, y, _canvas_width); to_check_queue.append(idx); } x = p.x + 1; if (is_inside_canvas(x, y)) { idx = PaintUtilities::to_1D(x, y, _canvas_width); to_check_queue.append(idx); } x = p.x; y = p.y - 1; if (is_inside_canvas(x, y)) { idx = PaintUtilities::to_1D(x, y, _canvas_width); to_check_queue.append(idx); } y = p.y + 1; if (is_inside_canvas(x, y)) { idx = PaintUtilities::to_1D(x, y, _canvas_width); to_check_queue.append(idx); } } return pixels; } void PaintCanvas::resize(int width, int height) { //if (get_canvas_width() == width && get_canvas_height() == height) { // return; // } if (width < 0) { width = 1; } if (height < 0) { height = 1; } _canvas_width = width; _canvas_height = height; Size2 s; s.x = _canvas_width * _pixel_size; s.y = _canvas_height * _pixel_size; set_size(s); preview_layer->resize(width, height); tool_layer->resize(width, height); for (int i = 0; i < layers.size(); ++i) { Ref layer = layers[i]; ERR_CONTINUE(!layer.is_valid()); layer->resize(width, height); } update(); } void PaintCanvas::_notification(int p_what) { switch (p_what) { case NOTIFICATION_POSTINITIALIZE: { connect("mouse_entered", this, "_on_mouse_entered"); connect("mouse_exited", this, "_on_mouse_exited"); //canvas_size = Vector2(int(rect_size.x / grid_size), int(rect_size.y / grid_size)); //pixel_size = canvas_size; active_layer = add_new_layer("Layer1"); ////hack //_canvas_width = 0; //_canvas_height = 0; set_process(true); } break; case NOTIFICATION_ENTER_TREE: { resize(64, 64); } break; case NOTIFICATION_PROCESS: { _process(get_process_delta_time()); } break; case NOTIFICATION_DRAW: { _draw(); } break; } } PaintCanvas::PaintCanvas() { big_grid = nullptr; _pixel_size = 16; _canvas_width = 64; _canvas_height = 64; _grid_size = 16; _big_grid_size = 10; _can_draw = true; symmetry_x = false; symmetry_y = false; mouse_in_region = false; mouse_on_top = false; canvas_background = memnew(PaintCanvasBackground); canvas_background->set_texture(make_icon(grid_png)); canvas_background->set_expand(true); canvas_background->set_stretch_mode(TextureRect::STRETCH_TILE); //canvas_background->set_draw_behind_parent(true); add_child(canvas_background); canvas_layers = memnew(Control); canvas_layers->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); canvas_layers->set_anchors_and_margins_preset(Control::PRESET_WIDE); add_child(canvas_layers); preview_layer_rect = memnew(TextureRect); preview_layer_rect->set_expand(true); preview_layer_rect->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); preview_layer_rect->set_anchors_and_margins_preset(Control::PRESET_WIDE); add_child(preview_layer_rect); tool_preview_layer_rect = memnew(TextureRect); tool_preview_layer_rect->set_expand(true); tool_preview_layer_rect->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); tool_preview_layer_rect->set_anchors_and_margins_preset(Control::PRESET_WIDE); add_child(tool_preview_layer_rect); grid = memnew(PaintVisualGrid); grid->size = 4; grid->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); add_child(grid); canvas_outline = memnew(PaintCanvasOutline); canvas_outline->color = Color(0, 1, 0, 1); canvas_outline->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); add_child(canvas_outline); preview_layer.instance(); tool_layer.instance(); preview_layer->name = "Preview"; tool_layer->name = "Tool"; preview_layer->create(preview_layer_rect, _canvas_width, _canvas_height); tool_layer->create(tool_preview_layer_rect, _canvas_width, _canvas_height); } PaintCanvas::~PaintCanvas() { } void PaintCanvas::_bind_methods() { ClassDB::bind_method(D_METHOD("_on_mouse_entered"), &PaintCanvas::_on_mouse_entered); ClassDB::bind_method(D_METHOD("_on_mouse_exited"), &PaintCanvas::_on_mouse_exited); }