/*************************************************************************/ /* paint_canvas.cpp */ /*************************************************************************/ /* This file is part of: */ /* PANDEMONIUM ENGINE */ /* https://github.com/Relintai/pandemonium_engine */ /*************************************************************************/ /* Copyright (c) 2022-present Péter Magyar. */ /* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ /* */ /* 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 "../actions/brighten_action.h" #include "../actions/brush_action.h" #include "../actions/bucket_action.h" #include "../actions/cut_action.h" #include "../actions/darken_action.h" #include "../actions/line_action.h" #include "../actions/multiline_action.h" #include "../actions/paint_action.h" #include "../actions/paste_cut_action.h" #include "../actions/pencil_action.h" #include "../actions/rainbow_action.h" #include "../actions/rect_action.h" #include "../bush_prefabs.h" #include "../paint_utilities.h" #include "core/io/image.h" #include "core/io/image_loader.h" #include "core/os/keyboard.h" #include "paint_project.h" #include "scene/resources/texture.h" #include "modules/modules_enabled.gen.h" #ifdef MODULE_LZ4_ENABLED #include "modules/lz4/lz4_compressor.h" #endif bool PaintCanvas::get_symmetry_x() const { return _symmetry_x; } void PaintCanvas::set_symmetry_x(const bool val) { _symmetry_x = val; } bool PaintCanvas::get_symmetry_y() const { return _symmetry_y; } void PaintCanvas::set_symmetry_y(const bool val) { _symmetry_y = val; } bool PaintCanvas::get_alpha_locked() const { return _alpha_locked; } void PaintCanvas::set_alpha_locked(const bool val) { _alpha_locked = val; } int PaintCanvas::get_brush_prefab() const { return _brush_prefab; } void PaintCanvas::set_brush_prefab(const int val) { if (_brush_prefab == val) { return; } _brush_prefab = val; emit_signal("brush_prefab_changed"); } int PaintCanvas::get_brush_size() const { return _brush_size; } void PaintCanvas::set_brush_size(const int val) { if (_brush_size == val) { return; } _brush_size = val; emit_signal("brush_size_changed"); } int PaintCanvas::get_current_tool() const { return _current_tool; } void PaintCanvas::set_current_tool(const int val) { if (_current_tool == val) { return; } _previous_tool = _current_tool; _current_tool = val; emit_signal("current_tool_changed"); } int PaintCanvas::get_previous_tool() const { return _previous_tool; } bool PaintCanvas::validate_pixel_v(const Vector2i &pos) const { if (pos.x < 0 || pos.y < 0 || pos.x >= _image->get_width() || pos.y >= _image->get_height()) { return false; } return true; } bool PaintCanvas::is_inside_canvas(const int x, const int y) { if (x < 0 || y < 0) { return false; } if (x >= get_size().x || y >= get_size().y) { return false; } return true; } 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(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) { if (x < 0 || y < 0 || x >= _image->get_width() || y >= _image->get_height()) { return; } _image->lock(); _image->set_pixel(x, y, color); _image->unlock(); } 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 (x < 0 || y < 0 || x >= _image->get_width() || y >= _image->get_height()) { return Color(); } _image->lock(); Color pixel = _image->get_pixel(x, y); _image->unlock(); return pixel; } 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 (x < 0 || y < 0 || x >= _preview_image->get_width() || y >= _preview_image->get_height()) { return; } _preview_image->lock(); _preview_image->set_pixel(x, y, color); _preview_image->unlock(); } 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) { if (x < 0 || y < 0 || x >= _preview_image->get_width() || y >= _preview_image->get_height()) { return Color(); } _preview_image->lock(); Color pixel = _preview_image->get_pixel(x, y); _preview_image->unlock(); return pixel; } void PaintCanvas::clear() { _image->fill(Color(0.00, 0.00, 0.00, 0.00)); _image_texture->create_from_image(_image, 0); } void PaintCanvas::clear_preview() { _preview_image->fill(Color(0.00, 0.00, 0.00, 0.00)); _preview_image_texture->create_from_image(_preview_image, 0); } void PaintCanvas::update_textures() { if (!_image->empty()) { _image_texture->create_from_image(_image, 0); } if (!_preview_image->empty()) { _preview_image_texture->create_from_image(_preview_image, 0); } update(); } void PaintCanvas::resize_crop(int width, int height) { Vector2i size = get_size(); if (size.x == width && size.y == height) { return; } if (width <= 0 || height <= 0) { _image->clear(); _preview_image->clear(); update_textures(); set_size(Vector2i(width, height)); return; } _image->crop(width, height); _preview_image->crop(width, height); update_textures(); set_size(Vector2i(width, height)); } void PaintCanvas::resize_interpolate(const int width, const int height, Image::Interpolation p_interpolation) { Vector2i size = get_size(); if (size.x == width && size.y == height) { return; } if (width <= 0 || height <= 0) { _image->clear(); _preview_image->clear(); update_textures(); set_size(Vector2i(width, height)); return; } _image->resize(width, height, p_interpolation); _preview_image->resize(width, height, p_interpolation); update_textures(); set_size(Vector2i(width, height)); } 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 < get_size().x; ++x) { for (int y = 0; y < get_size().y; ++y) { Color pixel_color = 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); } // 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; Vector to_check_queue; Vector checked_queue; to_check_queue.push_back(PaintUtilities::to_1D(pos_x, pos_y, get_size().x)); 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, get_size().x); if (checked_queue.find(idx) != -1) { continue; } checked_queue.push_back(idx); if (get_pixel(p.x, p.y) != color) { continue; } // add to result pixels.push_back(p); // check neighbours int x = p.x - 1; int y = p.y; if (is_inside_canvas(x, y)) { idx = PaintUtilities::to_1D(x, y, get_size().x); to_check_queue.push_back(idx); } x = p.x + 1; if (is_inside_canvas(x, y)) { idx = PaintUtilities::to_1D(x, y, get_size().x); to_check_queue.push_back(idx); } x = p.x; y = p.y - 1; if (is_inside_canvas(x, y)) { idx = PaintUtilities::to_1D(x, y, get_size().x); to_check_queue.push_back(idx); } y = p.y + 1; if (is_inside_canvas(x, y)) { idx = PaintUtilities::to_1D(x, y, get_size().x); to_check_queue.push_back(idx); } } return pixels; } Ref PaintCanvas::get_image() { return _image; } Ref PaintCanvas::get_preview_image() { return _preview_image; } Ref PaintCanvas::get_image_texture() { return _image_texture; } Ref PaintCanvas::get_preview_image_texture() { return _preview_image_texture; } Ref PaintCanvas::_get_rendered_image() { return _image; } PoolByteArray PaintCanvas::get_image_data_compressed() { PoolByteArray arr; #ifdef MODULE_LZ4_ENABLED if (_image->empty()) { return arr; } PoolByteArray data = _image->get_data(); int size = data.size(); PoolByteArray::Read dr = data.read(); const uint8_t *ch = dr.ptr(); int bound = LZ4Compressor::LZ4_compressBound(size); int additional_data_size = 3 * sizeof(int); arr.resize(bound + additional_data_size); PoolByteArray::Write w = arr.write(); uint8_t *wptr = w.ptr(); Vector2i node_size = get_size(); reinterpret_cast(wptr)[0] = node_size.x; reinterpret_cast(wptr)[1] = node_size.y; reinterpret_cast(wptr)[2] = data.size(); int ns = LZ4Compressor::LZ4_compress_default(reinterpret_cast(ch), reinterpret_cast(&wptr[additional_data_size]), size, bound); w.release(); arr.resize(ns + additional_data_size); #endif return arr; } void PaintCanvas::set_image_data_compressed(const PoolByteArray &data) { #ifdef MODULE_LZ4_ENABLED int additional_data_size = 3 * sizeof(int); int ds = data.size(); if (ds <= additional_data_size) { set_size(Vector2i()); return; } PoolByteArray::Read dr = data.read(); const uint8_t *ch = dr.ptr(); Vector2i node_size; node_size.x = reinterpret_cast(ch)[0]; node_size.y = reinterpret_cast(ch)[1]; int size = reinterpret_cast(ch)[2]; PoolByteArray arr; arr.resize(size); PoolByteArray::Write w = arr.write(); uint8_t *wptr = w.ptr(); int ns = LZ4Compressor::LZ4_decompress_safe(reinterpret_cast(&ch[additional_data_size]), reinterpret_cast(wptr), ds - additional_data_size, size); if (ns != size) { arr.resize(ns); ERR_PRINT("ns != size"); return; } set_size(node_size); _image->create(node_size.x, node_size.y, false, Image::FORMAT_RGBA8, arr); update_textures(); #endif } void PaintCanvas::handle_draw(const Vector2 &local_position, const Ref &event) { PaintProject *proj = get_paint_project(); if (!proj) { ERR_FAIL_COND(!proj); } set_pixel(local_position.x, local_position.y, proj->get_current_color()); } Color PaintCanvas::get_current_color() { PaintProject *proj = get_paint_project(); if (!proj) { ERR_FAIL_COND_V(!proj, Color(1, 1, 1, 1)); } return proj->get_current_color(); } void PaintCanvas::update_mouse_position(const Vector2 &local_position, const Ref &event) { if (event->get_device() == -1) { _mouse_position = get_global_mouse_position(); _cell_mouse_position = local_position; _last_mouse_position = _mouse_position; _last_cell_mouse_position = local_position; } } void PaintCanvas::handle_left_mouse_button_down(const Vector2 &local_position, const Ref &event) { update_mouse_position(local_position, event); switch (_current_tool) { case TOOL_CUT: { if (!event->is_pressed()) { commit_action(); } } break; case TOOL_BUCKET: { if (!_current_action.is_valid()) { _current_action = get_action(); } Array arr; arr.push_back(_cell_mouse_position); arr.push_back(_last_cell_mouse_position); arr.push_back(get_current_color()); do_action(arr); commit_action(); } break; case TOOL_COLORPICKER: { Color c = get_pixel(_cell_mouse_position.x, _cell_mouse_position.y); if (c.a < 0.00001) { return; } _picked_color = true; PaintProject *project = get_paint_project(); if (project) { project->set_current_color(c); } } break; default: break; } tool_process(local_position, event); } void PaintCanvas::handle_left_mouse_button_up(const Vector2 &local_position, const Ref &event) { update_mouse_position(local_position, event); if (_current_tool == TOOL_COLORPICKER) { if (_picked_color) { set_current_tool(get_previous_tool()); _picked_color = false; } } tool_process(local_position, event); } void PaintCanvas::handle_right_mouse_button_down(const Vector2 &local_position, const Ref &event) { update_mouse_position(local_position, event); switch (_current_tool) { case TOOL_CUT: if (!event->is_pressed()) { commit_action(); } break; case TOOL_COLORPICKER: set_current_tool(get_previous_tool()); break; case TOOL_PASTECUT: commit_action(); set_current_tool(TOOL_PENCIL); break; case TOOL_BUCKET: set_current_tool(get_previous_tool()); break; default: break; } tool_process(local_position, event); } void PaintCanvas::draw_brush_preview() { clear_preview(); switch (_current_tool) { case TOOL_PASTECUT: { for (int idx = 0; idx < _selection_cells.size(); ++idx) { Vector2i pixel = _selection_cells[idx]; Color color = _selection_colors[idx]; pixel -= _cut_pos + _cut_size / 2; pixel.x += _cell_mouse_position.x; pixel.y += _cell_mouse_position.y; set_preview_pixel_v(pixel, color); } update_textures(); } break; case TOOL_BRUSH: { PoolVector2iArray pixels = BrushPrefabs::get_brush(static_cast(_brush_prefab), _brush_size); Color color = get_current_color(); PoolVector2iArray::Read r = pixels.read(); for (int i = 0; i < pixels.size(); ++i) { Vector2i pixel = r[i]; set_preview_pixel(_cell_mouse_position.x + pixel.x, _cell_mouse_position.y + pixel.y, color); //print_error("ad " + String::num(cell_mouse_position.x + pixel.x) + " " + String::num(cell_mouse_position.y + pixel.y)); } r.release(); update_textures(); } break; case TOOL_RAINBOW: { set_preview_pixel(_cell_mouse_position.x, _cell_mouse_position.y, Color(0.46875, 0.446777, 0.446777, 0.3)); update_textures(); } break; case TOOL_COLORPICKER: { set_preview_pixel(_cell_mouse_position.x, _cell_mouse_position.y, Color(0.866667, 0.847059, 0.847059, 0.3)); update_textures(); } break; default: { set_preview_pixel(_cell_mouse_position.x, _cell_mouse_position.y, get_current_color()); update_textures(); } break; } } void PaintCanvas::do_action(const Array &arr) { if (!_current_action.is_valid()) { return; } _current_action->do_action(arr); update_textures(); } void PaintCanvas::commit_action() { if (!_current_action.is_valid()) { return; } _current_action->commit_action(); _actions_history.push_back(_current_action); _redo_history.clear(); update_textures(); if (_current_tool == TOOL_CUT) { Ref ca = _current_action; ERR_FAIL_COND(!ca.is_valid()); _cut_pos = ca->get_mouse_start_pos(); _cut_size = ca->get_mouse_end_pos() - ca->get_mouse_start_pos(); _selection_cells.clear(); _selection_colors.clear(); _selection_cells.append_array(ca->get_redo_cells()); _selection_colors.append_array(ca->get_redo_colors()); set_current_tool(TOOL_PASTECUT); } _current_action.unref(); } void PaintCanvas::redo_action() { if (_redo_history.empty()) { //print("PaintCanvas: nothing to redo"); return; } Ref action = _redo_history[_redo_history.size() - 1]; _redo_history.remove(_redo_history.size() - 1); if (!action.is_valid()) { return; } _actions_history.push_back(action); action->redo_action(); update_textures(); //print("PaintCanvas: redo action"); } void PaintCanvas::undo_action() { if (_actions_history.empty()) { //print("PaintCanvas: nothing to undo"); return; } Ref action = _actions_history[_actions_history.size() - 1]; _actions_history.remove(_actions_history.size() - 1); if (!action.is_valid()) { return; } _redo_history.push_back(action); action->undo_action(); update_textures(); //print("PaintCanvas: undo action") } bool PaintCanvas::has_point(const Vector2 &pos) { Vector2i size = get_size(); if (pos.x < 0 || pos.y < 0 || pos.x > size.x || pos.y > size.y) { return false; } return true; } Ref PaintCanvas::get_action() { Ref action; switch (_current_tool) { case TOOL_PENCIL: action = Ref(memnew(PencilAction)); break; case TOOL_BRUSH: action = Ref(memnew(BrushAction)); break; case TOOL_LINE: action = Ref(memnew(LineAction)); break; case TOOL_RAINBOW: action = Ref(memnew(RainbowAction)); break; case TOOL_BUCKET: action = Ref(memnew(BucketAction)); break; case TOOL_RECT: action = Ref(memnew(RectAction)); break; case TOOL_DARKEN: action = Ref(memnew(DarkenAction)); break; case TOOL_BRIGHTEN: action = Ref(memnew(BrightenAction)); break; case TOOL_CUT: action = Ref(memnew(CutAction)); break; case TOOL_PASTECUT: action = Ref(memnew(PasteCutAction)); break; default: break; } if (action.is_valid()) { action->set_paint_canvas(this); } return action; } void PaintCanvas::_on_tool_changed() { if (_current_tool == TOOL_COLORPICKER) { if (_current_action.is_valid()) { _current_action.unref(); } return; } if (get_previous_tool() == TOOL_CUT) { clear_preview(); } _current_action = get_action(); } void PaintCanvas::tool_process(const Vector2 &local_position, const Ref &event) { if (_current_tool == TOOL_COLORPICKER) { return; } if (!_current_action.is_valid()) { _current_action = get_action(); } if (_current_tool == TOOL_PENCIL || _current_tool == TOOL_LINE || _current_tool == TOOL_RECT) { Array arr; arr.push_back(_cell_mouse_position); arr.push_back(_last_cell_mouse_position); if (_mouse_button_down == BUTTON_LEFT) { arr.push_back(get_current_color()); } else if (_mouse_button_down == BUTTON_RIGHT) { arr.push_back(Color(0, 0, 0, 0)); } do_action(arr); } else if (_current_tool == TOOL_DARKEN || _current_tool == TOOL_BRIGHTEN || _current_tool == TOOL_CUT) { Array arr; arr.push_back(_cell_mouse_position); arr.push_back(_last_cell_mouse_position); arr.push_back(get_current_color()); do_action(arr); } else if (_current_tool == TOOL_BRUSH) { Array arr; arr.push_back(_cell_mouse_position); arr.push_back(_last_cell_mouse_position); if (_mouse_button_down == BUTTON_LEFT) { arr.push_back(get_current_color()); } else if (_mouse_button_down == BUTTON_RIGHT) { arr.push_back(Color(0, 0, 0, 0)); } arr.push_back(_brush_prefab); arr.push_back(_brush_size); do_action(arr); } else if (_current_tool == TOOL_COLORPICKER) { // Nothing to do here } else if (_current_tool == TOOL_PASTECUT) { Array arr; arr.append(_cell_mouse_position); arr.append(_last_cell_mouse_position); arr.append(_selection_cells); arr.append(_selection_colors); arr.append(_cut_pos); arr.append(_cut_size); do_action(arr); } else if (_current_tool == TOOL_RAINBOW) { Array arr; arr.push_back(_cell_mouse_position); arr.push_back(_last_cell_mouse_position); do_action(arr); } } bool PaintCanvas::_forward_canvas_gui_input(const Ref &event) { if (!is_visible_in_tree()) { return false; } Ref iemb = event; if (iemb.is_valid()) { if (_mouse_down && _mouse_button_down != iemb->get_button_index()) { // Ignore it, but consume the event from the editor return true; } if (iemb->get_button_index() != BUTTON_LEFT && iemb->get_button_index() != BUTTON_RIGHT) { return false; } // This seems to be the easiest way to get local mouse position, // even though the event is available Vector2 local_position = get_local_mouse_position(); if (_mouse_down) { if (!iemb->is_pressed()) { _mouse_down = false; _mouse_button_down = -1; if (_mouse_button_down == BUTTON_LEFT) { handle_left_mouse_button_up(local_position, iemb); } commit_action(); } } else { if (has_point(local_position)) { _mouse_down = true; _mouse_button_down = iemb->get_button_index(); clear_preview(); if (_mouse_button_down == BUTTON_LEFT) { handle_left_mouse_button_down(local_position, iemb); } else if (_mouse_button_down == BUTTON_RIGHT) { handle_right_mouse_button_down(local_position, iemb); } return true; } } } Ref iemm = event; if (iemm.is_valid()) { Vector2 local_position = get_local_mouse_position(); _mouse_position = get_global_mouse_position(); _cell_mouse_position = local_position; if (has_point(local_position)) { _was_mouse_outside = false; if (_mouse_down) { //handle_draw(local_position, event) _cell_mouse_position = local_position; tool_process(local_position, event); update_textures(); update(); _last_mouse_position = _mouse_position; _last_cell_mouse_position = local_position; return true; } else { draw_brush_preview(); } } else { if (!_was_mouse_outside) { clear_preview(); _was_mouse_outside = true; } } _last_mouse_position = _mouse_position; _last_cell_mouse_position = local_position; } Ref iek = event; if (iek.is_valid()) { if (iek->is_echo() || !iek->is_pressed()) { return false; } int scancode = iek->get_physical_scancode_with_modifiers(); bool undo = false; if (scancode == (KEY_Z | KEY_MASK_CTRL)) { undo = true; } bool redo = false; if (scancode == (KEY_Z | KEY_MASK_CTRL | KEY_MASK_SHIFT)) { redo = true; } if (!undo && !redo) { return false; } Vector2 local_position = get_local_mouse_position(); if (has_point(local_position)) { if (redo) { redo_action(); return true; } if (undo) { undo_action(); return true; } } } return false; } Error PaintCanvas::load_image(const String &path) { if (path.empty()) { return ERR_FILE_NOT_FOUND; } Error err = ImageLoader::load_image(path, _image); if (err != OK) { return err; } Vector2 size = _image->get_size(); set_size(size); return OK; } PaintCanvas::PaintCanvas() { _symmetry_x = false; _symmetry_y = false; _alpha_locked = false; _brush_prefab = 0; _brush_size = 1; _current_tool = 0; _previous_tool = 0; _was_mouse_outside = true; _image.instance(); _preview_image.instance(); _image_texture.instance(); _preview_image_texture.instance(); _mouse_down = false; _mouse_button_down = -1; _picked_color = false; } PaintCanvas::~PaintCanvas() { } void PaintCanvas::_on_size_changed() { Vector2i size = get_size(); if (size.x <= 0 || size.y <= 0) { _image->clear(); _preview_image->clear(); update_textures(); return; } if (_image->empty()) { _image->create(size.x, size.y, false, Image::FORMAT_RGBA8); } else { _image->crop(size.x, size.y); } if (_preview_image->empty()) { _preview_image->create(size.x, size.y, false, Image::FORMAT_RGBA8); } else { _preview_image->crop(size.x, size.y); } update_textures(); } void PaintCanvas::_notification(int p_what) { switch (p_what) { case NOTIFICATION_POSTINITIALIZE: { connect("size_changed", this, "_on_size_changed"); } break; case NOTIFICATION_READY: { if (!is_connected("current_tool_changed", this, "_on_tool_changed")) { connect("current_tool_changed", this, "_on_tool_changed"); } _on_tool_changed(); } break; case NOTIFICATION_DRAW: { if (!_image->empty()) { draw_texture(_image_texture, Point2()); } if (!_preview_image->empty()) { draw_texture(_preview_image_texture, Point2()); } } break; } } void PaintCanvas::_bind_methods() { ADD_SIGNAL(MethodInfo("brush_prefab_changed")); ADD_SIGNAL(MethodInfo("brush_size_changed")); ADD_SIGNAL(MethodInfo("current_tool_changed")); ClassDB::bind_method(D_METHOD("get_symmetry_x"), &PaintCanvas::get_symmetry_x); ClassDB::bind_method(D_METHOD("set_symmetry_x", "val"), &PaintCanvas::set_symmetry_x); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "symmetry_x"), "set_symmetry_x", "get_symmetry_x"); ClassDB::bind_method(D_METHOD("get_symmetry_y"), &PaintCanvas::get_symmetry_y); ClassDB::bind_method(D_METHOD("set_symmetry_y", "val"), &PaintCanvas::set_symmetry_y); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "symmetry_y"), "set_symmetry_y", "get_symmetry_y"); ClassDB::bind_method(D_METHOD("get_alpha_locked"), &PaintCanvas::get_alpha_locked); ClassDB::bind_method(D_METHOD("set_alpha_locked", "val"), &PaintCanvas::set_alpha_locked); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "alpha_locked"), "set_alpha_locked", "get_alpha_locked"); ClassDB::bind_method(D_METHOD("get_brush_prefab"), &PaintCanvas::get_brush_prefab); ClassDB::bind_method(D_METHOD("set_brush_prefab", "val"), &PaintCanvas::set_brush_prefab); ADD_PROPERTY(PropertyInfo(Variant::INT, "brush_prefab"), "set_brush_prefab", "get_brush_prefab"); ClassDB::bind_method(D_METHOD("get_brush_size"), &PaintCanvas::get_brush_size); ClassDB::bind_method(D_METHOD("set_brush_size", "val"), &PaintCanvas::set_brush_size); ADD_PROPERTY(PropertyInfo(Variant::INT, "brush_size"), "set_brush_size", "get_brush_size"); ClassDB::bind_method(D_METHOD("get_current_tool"), &PaintCanvas::get_current_tool); ClassDB::bind_method(D_METHOD("set_current_tool", "val"), &PaintCanvas::set_current_tool); ADD_PROPERTY(PropertyInfo(Variant::INT, "current_tool"), "set_current_tool", "get_current_tool"); ClassDB::bind_method(D_METHOD("get_previous_tool"), &PaintCanvas::get_previous_tool); ClassDB::bind_method(D_METHOD("get_image_data_compressed"), &PaintCanvas::get_image_data_compressed); ClassDB::bind_method(D_METHOD("set_image_data_compressed", "val"), &PaintCanvas::set_image_data_compressed); ADD_PROPERTY(PropertyInfo(Variant::POOL_BYTE_ARRAY, "image_data_compressed"), "set_image_data_compressed", "get_image_data_compressed"); ClassDB::bind_method(D_METHOD("is_inside_canvas", "x", "y"), &PaintCanvas::is_inside_canvas); ClassDB::bind_method(D_METHOD("set_pixel_arr", "pixels", "color"), &PaintCanvas::set_pixel_arr); ClassDB::bind_method(D_METHOD("set_pixel_v", "pos", "color"), &PaintCanvas::set_pixel_v); ClassDB::bind_method(D_METHOD("set_pixel", "x", "y", "color"), &PaintCanvas::set_pixel); ClassDB::bind_method(D_METHOD("get_pixel_v", "pos"), &PaintCanvas::get_pixel_v); ClassDB::bind_method(D_METHOD("get_pixel", "x", "y"), &PaintCanvas::get_pixel); ClassDB::bind_method(D_METHOD("set_preview_pixel_v", "pos", "color"), &PaintCanvas::set_preview_pixel_v); ClassDB::bind_method(D_METHOD("set_preview_pixel", "x", "y", "color"), &PaintCanvas::set_preview_pixel); ClassDB::bind_method(D_METHOD("get_preview_pixel_v", "pos"), &PaintCanvas::get_preview_pixel_v); ClassDB::bind_method(D_METHOD("get_preview_pixel", "x", "y"), &PaintCanvas::get_preview_pixel); ClassDB::bind_method(D_METHOD("validate_pixel_v", "pos"), &PaintCanvas::validate_pixel_v); ClassDB::bind_method(D_METHOD("clear"), &PaintCanvas::clear); ClassDB::bind_method(D_METHOD("clear_preview"), &PaintCanvas::clear_preview); ClassDB::bind_method(D_METHOD("update_textures"), &PaintCanvas::update_textures); ClassDB::bind_method(D_METHOD("resize_crop", "width", "height"), &PaintCanvas::resize_crop); ClassDB::bind_method(D_METHOD("resize_interpolate", "width", "height", "interpolation"), &PaintCanvas::resize_interpolate, Image::INTERPOLATE_BILINEAR); ClassDB::bind_method(D_METHOD("select_color", "x", "y"), &PaintCanvas::select_color); ClassDB::bind_method(D_METHOD("select_same_color", "x", "y"), &PaintCanvas::select_same_color); ClassDB::bind_method(D_METHOD("get_neighbouring_pixels", "x", "y"), &PaintCanvas::get_neighbouring_pixels); ClassDB::bind_method(D_METHOD("get_image"), &PaintCanvas::get_image); ClassDB::bind_method(D_METHOD("get_preview_image"), &PaintCanvas::get_preview_image); ClassDB::bind_method(D_METHOD("get_image_texture"), &PaintCanvas::get_image_texture); ClassDB::bind_method(D_METHOD("get_preview_image_texture"), &PaintCanvas::get_preview_image_texture); ClassDB::bind_method(D_METHOD("handle_draw", "local_position", "event"), &PaintCanvas::handle_draw); ClassDB::bind_method(D_METHOD("get_current_color"), &PaintCanvas::get_current_color); ClassDB::bind_method(D_METHOD("draw_brush_preview"), &PaintCanvas::draw_brush_preview); ClassDB::bind_method(D_METHOD("do_action", "arr"), &PaintCanvas::do_action); ClassDB::bind_method(D_METHOD("commit_action"), &PaintCanvas::commit_action); ClassDB::bind_method(D_METHOD("redo_action"), &PaintCanvas::redo_action); ClassDB::bind_method(D_METHOD("undo_action"), &PaintCanvas::undo_action); ClassDB::bind_method(D_METHOD("has_point", "pos"), &PaintCanvas::has_point); ClassDB::bind_method(D_METHOD("get_action"), &PaintCanvas::get_action); ClassDB::bind_method(D_METHOD("_on_tool_changed"), &PaintCanvas::_on_tool_changed); ClassDB::bind_method(D_METHOD("tool_process", "local_position", "event"), &PaintCanvas::tool_process); ClassDB::bind_method(D_METHOD("load_image", "path"), &PaintCanvas::load_image); ClassDB::bind_method(D_METHOD("_on_size_changed"), &PaintCanvas::_on_size_changed); BIND_ENUM_CONSTANT(TOOL_PENCIL); BIND_ENUM_CONSTANT(TOOL_BRUSH); BIND_ENUM_CONSTANT(TOOL_BUCKET); BIND_ENUM_CONSTANT(TOOL_RAINBOW); BIND_ENUM_CONSTANT(TOOL_LINE); BIND_ENUM_CONSTANT(TOOL_RECT); BIND_ENUM_CONSTANT(TOOL_DARKEN); BIND_ENUM_CONSTANT(TOOL_BRIGHTEN); BIND_ENUM_CONSTANT(TOOL_COLORPICKER); BIND_ENUM_CONSTANT(TOOL_CUT); BIND_ENUM_CONSTANT(TOOL_PASTECUT); }