diff --git a/project.godot b/project.godot index bd4662a82..e3d60239b 100644 --- a/project.godot +++ b/project.godot @@ -488,6 +488,26 @@ right_ellipsetool_tool={ "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":true,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":67,"unicode":0,"echo":false,"script":null) ] } +left_move_tool={ +"deadzone": 0.5, +"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":84,"unicode":0,"echo":false,"script":null) + ] +} +right_move_tool={ +"deadzone": 0.5, +"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":true,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":84,"unicode":0,"echo":false,"script":null) + ] +} +select_all={ +"deadzone": 0.5, +"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":true,"meta":false,"command":true,"pressed":false,"scancode":65,"unicode":0,"echo":false,"script":null) + ] +} +invert_selection={ +"deadzone": 0.5, +"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":true,"meta":false,"command":true,"pressed":false,"scancode":73,"unicode":0,"echo":false,"script":null) + ] +} [locale] diff --git a/src/Autoload/DrawingAlgos.gd b/src/Autoload/DrawingAlgos.gd index 4a8ace066..6e62a92fe 100644 --- a/src/Autoload/DrawingAlgos.gd +++ b/src/Autoload/DrawingAlgos.gd @@ -5,7 +5,7 @@ enum GradientDirection {TOP, BOTTOM, LEFT, RIGHT} func scale3X(sprite : Image, tol : float = 50) -> Image: - var scaled = Image.new() + var scaled := Image.new() scaled.create(sprite.get_width()*3, sprite.get_height()*3, false, Image.FORMAT_RGBA8) scaled.lock() sprite.lock() @@ -62,163 +62,142 @@ func scale3X(sprite : Image, tol : float = 50) -> Image: return scaled -func rotxel(sprite : Image, angle : float, pixels : Array) -> void: +func rotxel(sprite : Image, angle : float, pivot : Vector2) -> void: # If angle is simple, then nn rotation is the best if angle == 0 || angle == PI/2 || angle == PI || angle == 2*PI: - nn_rotate(sprite, angle, pixels) + nn_rotate(sprite, angle, pivot) return var aux : Image = Image.new() aux.copy_from(sprite) - var selection_rectangle := Rect2(pixels[0].x, pixels[0].y, pixels[-1].x - pixels[0].x + 1, pixels[-1].y - pixels[0].y + 1) - var center : Vector2 = selection_rectangle.position + ((selection_rectangle.end - selection_rectangle.position) / 2) var ox : int var oy : int var p : Color aux.lock() sprite.lock() - for pix in pixels: - var x = pix.x - var y = pix.y - var dx = 3*(x - center.x) - var dy = 3*(y - center.y) - var found_pixel : bool = false - for k in range(9): - var i = -1 + k % 3 + for x in sprite.get_size().x: + for y in sprite.get_size().y: + var dx = 3*(x - pivot.x) + var dy = 3*(y - pivot.y) + var found_pixel : bool = false + for k in range(9): + var i = -1 + k % 3 # warning-ignore:integer_division - var j = -1 + int(k / 3) - var dir = atan2(dy + j, dx + i) - var mag = sqrt(pow(dx + i, 2) + pow(dy + j, 2)) - dir -= angle - ox = round(center.x*3 + 1 + mag*cos(dir)) - oy = round(center.y*3 + 1 + mag*sin(dir)) + var j = -1 + int(k / 3) + var dir = atan2(dy + j, dx + i) + var mag = sqrt(pow(dx + i, 2) + pow(dy + j, 2)) + dir -= angle + ox = round(pivot.x*3 + 1 + mag*cos(dir)) + oy = round(pivot.y*3 + 1 + mag*sin(dir)) - if (sprite.get_width() % 2 != 0): - ox += 1 - oy += 1 + if (sprite.get_width() % 2 != 0): + ox += 1 + oy += 1 - if (ox >= 0 && ox < sprite.get_width()*3 - && oy >= 0 && oy < sprite.get_height()*3): - found_pixel = true - break + if (ox >= 0 && ox < sprite.get_width()*3 + && oy >= 0 && oy < sprite.get_height()*3): + found_pixel = true + break - if !found_pixel: - sprite.set_pixel(x, y, Color(0,0,0,0)) - continue + if !found_pixel: + sprite.set_pixel(x, y, Color(0,0,0,0)) + continue - var fil : int = oy % 3 - var col : int = ox % 3 - var index : int = col + 3*fil + var fil : int = oy % 3 + var col : int = ox % 3 + var index : int = col + 3*fil - ox = round((ox - 1)/3.0); - oy = round((oy - 1)/3.0); - var a : Color - var b : Color - var c : Color - var d : Color - var e : Color - var f : Color - var g : Color - var h : Color - var i : Color - if (ox == 0 || ox == sprite.get_width() - 1 || - oy == 0 || oy == sprite.get_height() - 1): - p = aux.get_pixel(ox, oy) - else: - a = aux.get_pixel(ox-1,oy-1); - b = aux.get_pixel(ox,oy-1); - c = aux.get_pixel(ox+1,oy-1); - d = aux.get_pixel(ox-1,oy); - e = aux.get_pixel(ox,oy); - f = aux.get_pixel(ox+1,oy); - g = aux.get_pixel(ox-1,oy+1); - h = aux.get_pixel(ox,oy+1); - i = aux.get_pixel(ox+1,oy+1); + ox = round((ox - 1)/3.0); + oy = round((oy - 1)/3.0); + var a : Color + var b : Color + var c : Color + var d : Color + var e : Color + var f : Color + var g : Color + var h : Color + var i : Color + if (ox == 0 || ox == sprite.get_width() - 1 || + oy == 0 || oy == sprite.get_height() - 1): + p = aux.get_pixel(ox, oy) + else: + a = aux.get_pixel(ox-1,oy-1); + b = aux.get_pixel(ox,oy-1); + c = aux.get_pixel(ox+1,oy-1); + d = aux.get_pixel(ox-1,oy); + e = aux.get_pixel(ox,oy); + f = aux.get_pixel(ox+1,oy); + g = aux.get_pixel(ox-1,oy+1); + h = aux.get_pixel(ox,oy+1); + i = aux.get_pixel(ox+1,oy+1); - match(index): - 0: - p = d if (similarColors(d,b) && !similarColors(d,h) - && !similarColors(b,f)) else e; - 1: - p = b if ((similarColors(d,b) && !similarColors(d,h) && - !similarColors(b,f) && !similarColors(e,c)) || - (similarColors(b,f) && !similarColors(d,b) && - !similarColors(f,h) && !similarColors(e,a))) else e; - 2: - p = f if (similarColors(b,f) && !similarColors(d,b) && - !similarColors(f,h)) else e; - 3: - p = d if ((similarColors(d,h) && !similarColors(f,h) && - !similarColors(d,b) && !similarColors(e,a)) || - (similarColors(d,b) && !similarColors(d,h) && - !similarColors(b,f) && !similarColors(e,g))) else e; - 4: - p = e - 5: - p = f if((similarColors(b,f) && !similarColors(d,b) && - !similarColors(f,h) && !similarColors(e,i)) - || (similarColors(f,h) && !similarColors(b,f) && - !similarColors(d,h) && !similarColors(e,c))) else e; - 6: - p = d if (similarColors(d,h) && !similarColors(f,h) && - !similarColors(d,b)) else e; - 7: - p = h if ((similarColors(f,h) && !similarColors(f,b) && - !similarColors(d,h) && !similarColors(e,g)) - || (similarColors(d,h) && !similarColors(f,h) && - !similarColors(d,b) && !similarColors(e,i))) else e; - 8: - p = f if (similarColors(f,h) && !similarColors(f,b) && - !similarColors(d,h)) else e; - sprite.set_pixel(x, y, p) + match(index): + 0: + p = d if (similarColors(d,b) && !similarColors(d,h) + && !similarColors(b,f)) else e; + 1: + p = b if ((similarColors(d,b) && !similarColors(d,h) && + !similarColors(b,f) && !similarColors(e,c)) || + (similarColors(b,f) && !similarColors(d,b) && + !similarColors(f,h) && !similarColors(e,a))) else e; + 2: + p = f if (similarColors(b,f) && !similarColors(d,b) && + !similarColors(f,h)) else e; + 3: + p = d if ((similarColors(d,h) && !similarColors(f,h) && + !similarColors(d,b) && !similarColors(e,a)) || + (similarColors(d,b) && !similarColors(d,h) && + !similarColors(b,f) && !similarColors(e,g))) else e; + 4: + p = e + 5: + p = f if((similarColors(b,f) && !similarColors(d,b) && + !similarColors(f,h) && !similarColors(e,i)) + || (similarColors(f,h) && !similarColors(b,f) && + !similarColors(d,h) && !similarColors(e,c))) else e; + 6: + p = d if (similarColors(d,h) && !similarColors(f,h) && + !similarColors(d,b)) else e; + 7: + p = h if ((similarColors(f,h) && !similarColors(f,b) && + !similarColors(d,h) && !similarColors(e,g)) + || (similarColors(d,h) && !similarColors(f,h) && + !similarColors(d,b) && !similarColors(e,i))) else e; + 8: + p = f if (similarColors(f,h) && !similarColors(f,b) && + !similarColors(d,h)) else e; + sprite.set_pixel(x, y, p) sprite.unlock() aux.unlock() -func fake_rotsprite(sprite : Image, angle : float, pixels : Array) -> void: - var selection_rectangle := Rect2(pixels[0].x, pixels[0].y, pixels[-1].x - pixels[0].x + 1, pixels[-1].y - pixels[0].y + 1) +func fake_rotsprite(sprite : Image, angle : float, pivot : Vector2) -> void: var selected_sprite := Image.new() - selected_sprite = sprite.get_rect(selection_rectangle) + selected_sprite.copy_from(sprite) selected_sprite.copy_from(scale3X(selected_sprite)) - nn_rotate(selected_sprite, angle, []) + nn_rotate(selected_sprite, angle, pivot * 3) # warning-ignore:integer_division # warning-ignore:integer_division selected_sprite.resize(selected_sprite.get_width() / 3, selected_sprite.get_height() / 3, 0) - sprite.blit_rect(selected_sprite, Rect2(Vector2.ZERO, selected_sprite.get_size()), selection_rectangle.position) + sprite.blit_rect(selected_sprite, Rect2(Vector2.ZERO, selected_sprite.get_size()), Vector2.ZERO) -func nn_rotate(sprite : Image, angle : float, pixels : Array) -> void: +func nn_rotate(sprite : Image, angle : float, pivot : Vector2) -> void: var aux : Image = Image.new() aux.copy_from(sprite) sprite.lock() aux.lock() var ox: int var oy: int - var center : Vector2 - if pixels: - var selection_rectangle := Rect2(pixels[0].x, pixels[0].y, pixels[-1].x - pixels[0].x + 1, pixels[-1].y - pixels[0].y + 1) - center = selection_rectangle.position + ((selection_rectangle.end - selection_rectangle.position) / 2) - for pix in pixels: - var x = pix.x - var y = pix.y - ox = (x - center.x)*cos(angle) + (y - center.y)*sin(angle) + center.x - oy = -(x - center.x)*sin(angle) + (y - center.y)*cos(angle) + center.y + for x in range(sprite.get_width()): + for y in range(sprite.get_height()): + ox = (x - pivot.x)*cos(angle) + (y - pivot.y)*sin(angle) + pivot.x + oy = -(x - pivot.x)*sin(angle) + (y - pivot.y)*cos(angle) + pivot.y if ox >= 0 && ox < sprite.get_width() && oy >= 0 && oy < sprite.get_height(): sprite.set_pixel(x, y, aux.get_pixel(ox, oy)) else: sprite.set_pixel(x, y, Color(0,0,0,0)) - else: -# warning-ignore:integer_division -# warning-ignore:integer_division - center = Vector2(sprite.get_width() / 2, sprite.get_height() / 2) - for x in range(sprite.get_width()): - for y in range(sprite.get_height()): - ox = (x - center.x)*cos(angle) + (y - center.y)*sin(angle) + center.x - oy = -(x - center.x)*sin(angle) + (y - center.y)*cos(angle) + center.y - if ox >= 0 && ox < sprite.get_width() && oy >= 0 && oy < sprite.get_height(): - sprite.set_pixel(x, y, aux.get_pixel(ox, oy)) - else: - sprite.set_pixel(x, y, Color(0,0,0,0)) sprite.unlock() aux.unlock() @@ -256,6 +235,7 @@ func scale_image(width : int, height : int, interpolation : int) -> void: func centralize() -> void: + Global.canvas.selection.move_content_confirm() # Find used rect of the current frame (across all of the layers) var used_rect := Rect2() for cel in Global.current_project.frames[Global.current_project.current_frame].cels: @@ -277,6 +257,7 @@ func centralize() -> void: func crop_image(image : Image) -> void: + Global.canvas.selection.move_content_confirm() # Use first cel as a starting rectangle var used_rect : Rect2 = image.get_used_rect() @@ -325,82 +306,101 @@ func resize_canvas(width : int, height : int, offset_x : int, offset_y : int) -> func general_do_scale(width : int, height : int) -> void: - var x_ratio = Global.current_project.size.x / width - var y_ratio = Global.current_project.size.y / height - var new_x_symmetry_point = Global.current_project.x_symmetry_point / x_ratio - var new_y_symmetry_point = Global.current_project.y_symmetry_point / y_ratio - var new_x_symmetry_axis_points = Global.current_project.x_symmetry_axis.points - var new_y_symmetry_axis_points = Global.current_project.y_symmetry_axis.points + var project := Global.current_project + var size := Vector2(width, height).floor() + var x_ratio = project.size.x / width + var y_ratio = project.size.y / height + + var bitmap : BitMap + bitmap = project.resize_bitmap(project.selection_bitmap, size) + + var new_x_symmetry_point = project.x_symmetry_point / x_ratio + var new_y_symmetry_point = project.y_symmetry_point / y_ratio + var new_x_symmetry_axis_points = project.x_symmetry_axis.points + var new_y_symmetry_axis_points = project.y_symmetry_axis.points new_x_symmetry_axis_points[0].y /= y_ratio new_x_symmetry_axis_points[1].y /= y_ratio new_y_symmetry_axis_points[0].x /= x_ratio new_y_symmetry_axis_points[1].x /= x_ratio - Global.current_project.undos += 1 - Global.current_project.undo_redo.create_action("Scale") - Global.current_project.undo_redo.add_do_property(Global.current_project, "size", Vector2(width, height).floor()) - Global.current_project.undo_redo.add_do_property(Global.current_project, "x_symmetry_point", new_x_symmetry_point) - Global.current_project.undo_redo.add_do_property(Global.current_project, "y_symmetry_point", new_y_symmetry_point) - Global.current_project.undo_redo.add_do_property(Global.current_project.x_symmetry_axis, "points", new_x_symmetry_axis_points) - Global.current_project.undo_redo.add_do_property(Global.current_project.y_symmetry_axis, "points", new_y_symmetry_axis_points) + project.undos += 1 + project.undo_redo.create_action("Scale") + project.undo_redo.add_do_property(project, "size", size) + project.undo_redo.add_do_property(project, "selection_bitmap", bitmap) + project.undo_redo.add_do_property(project, "x_symmetry_point", new_x_symmetry_point) + project.undo_redo.add_do_property(project, "y_symmetry_point", new_y_symmetry_point) + project.undo_redo.add_do_property(project.x_symmetry_axis, "points", new_x_symmetry_axis_points) + project.undo_redo.add_do_property(project.y_symmetry_axis, "points", new_y_symmetry_axis_points) func general_undo_scale() -> void: - Global.current_project.undo_redo.add_undo_property(Global.current_project, "size", Global.current_project.size) - Global.current_project.undo_redo.add_undo_property(Global.current_project, "x_symmetry_point", Global.current_project.x_symmetry_point) - Global.current_project.undo_redo.add_undo_property(Global.current_project, "y_symmetry_point", Global.current_project.y_symmetry_point) - Global.current_project.undo_redo.add_undo_property(Global.current_project.x_symmetry_axis, "points", Global.current_project.x_symmetry_axis.points) - Global.current_project.undo_redo.add_undo_property(Global.current_project.y_symmetry_axis, "points", Global.current_project.y_symmetry_axis.points) - Global.current_project.undo_redo.add_undo_method(Global, "undo") - Global.current_project.undo_redo.add_do_method(Global, "redo") - Global.current_project.undo_redo.commit_action() + var project := Global.current_project + project.undo_redo.add_undo_property(project, "size", project.size) + project.undo_redo.add_undo_property(project, "selection_bitmap", project.selection_bitmap) + project.undo_redo.add_undo_property(project, "x_symmetry_point", project.x_symmetry_point) + project.undo_redo.add_undo_property(project, "y_symmetry_point", project.y_symmetry_point) + project.undo_redo.add_undo_property(project.x_symmetry_axis, "points", project.x_symmetry_axis.points) + project.undo_redo.add_undo_property(project.y_symmetry_axis, "points", project.y_symmetry_axis.points) + project.undo_redo.add_undo_method(Global, "undo") + project.undo_redo.add_do_method(Global, "redo") + project.undo_redo.commit_action() func general_do_centralize() -> void: - Global.current_project.undos += 1 - Global.current_project.undo_redo.create_action("Centralize") + var project := Global.current_project + project.undos += 1 + project.undo_redo.create_action("Centralize") func general_undo_centralize() -> void: - Global.current_project.undo_redo.add_undo_method(Global, "undo") - Global.current_project.undo_redo.add_do_method(Global, "redo") - Global.current_project.undo_redo.commit_action() + var project := Global.current_project + project.undo_redo.add_undo_method(Global, "undo") + project.undo_redo.add_do_method(Global, "redo") + project.undo_redo.commit_action() -func invert_image_colors(image : Image, pixels : Array, red := true, green := true, blue := true, alpha := false) -> void: +func invert_image_colors(image : Image, affect_selection : bool, project : Project, red := true, green := true, blue := true, alpha := false) -> void: image.lock() - for i in pixels: - var px_color := image.get_pixelv(i) - # Manually invert each color channel - if red: - px_color.r = 1.0 - px_color.r - if green: - px_color.g = 1.0 - px_color.g - if blue: - px_color.b = 1.0 - px_color.b - if alpha: - px_color.a = 1.0 - px_color.a - image.set_pixelv(i, px_color) + for x in project.size.x: + for y in project.size.y: + var pos := Vector2(x, y) + if affect_selection and !project.can_pixel_get_drawn(pos): + continue + var px_color := image.get_pixelv(pos) + # Manually invert each color channel + if red: + px_color.r = 1.0 - px_color.r + if green: + px_color.g = 1.0 - px_color.g + if blue: + px_color.b = 1.0 - px_color.b + if alpha: + px_color.a = 1.0 - px_color.a + image.set_pixelv(pos, px_color) -func desaturate_image(image : Image, pixels : Array, red := true, green := true, blue := true, alpha := false) -> void: +func desaturate_image(image : Image, affect_selection : bool, project : Project, red := true, green := true, blue := true, alpha := false) -> void: image.lock() - for i in pixels: - var px_color := image.get_pixelv(i) - var gray = px_color.v - if red: - px_color.r = gray - if green: - px_color.g = gray - if blue: - px_color.b = gray - if alpha: - px_color.a = gray + for x in project.size.x: + for y in project.size.y: + var pos := Vector2(x, y) + if affect_selection and !project.can_pixel_get_drawn(pos): + continue + var px_color := image.get_pixelv(pos) + var gray = px_color.v + if red: + px_color.r = gray + if green: + px_color.g = gray + if blue: + px_color.b = gray + if alpha: + px_color.a = gray - image.set_pixelv(i, px_color) + image.set_pixelv(pos, px_color) -func generate_outline(image : Image, pixels : Array, outline_color : Color, thickness : int, diagonal : bool, inside_image : bool) -> void: +func generate_outline(image : Image, affect_selection : bool, project : Project, outline_color : Color, thickness : int, diagonal : bool, inside_image : bool) -> void: if image.is_invisible(): return var new_image := Image.new() @@ -408,169 +408,175 @@ func generate_outline(image : Image, pixels : Array, outline_color : Color, thic new_image.lock() image.lock() - for pos in pixels: - var current_pixel := image.get_pixelv(pos) - if current_pixel.a == 0: - continue + for x in project.size.x: + for y in project.size.y: + var pos := Vector2(x, y) + var current_pixel := image.get_pixelv(pos) + if affect_selection and !project.can_pixel_get_drawn(pos): + continue + if current_pixel.a == 0: + continue - for i in range(1, thickness + 1): - if inside_image: - var outline_pos : Vector2 = pos + Vector2.LEFT # Left - if outline_pos.x < 0 || image.get_pixelv(outline_pos).a == 0: - var new_pos : Vector2 = pos + Vector2.RIGHT * (i - 1) - if new_pos.x < Global.current_project.size.x: - var new_pixel = image.get_pixelv(new_pos) - if new_pixel.a > 0: - new_image.set_pixelv(new_pos, outline_color) + for i in range(1, thickness + 1): + if inside_image: + var outline_pos : Vector2 = pos + Vector2.LEFT # Left + if outline_pos.x < 0 || image.get_pixelv(outline_pos).a == 0: + var new_pos : Vector2 = pos + Vector2.RIGHT * (i - 1) + if new_pos.x < Global.current_project.size.x: + var new_pixel = image.get_pixelv(new_pos) + if new_pixel.a > 0: + new_image.set_pixelv(new_pos, outline_color) - outline_pos = pos + Vector2.RIGHT # Right - if outline_pos.x >= Global.current_project.size.x || image.get_pixelv(outline_pos).a == 0: - var new_pos : Vector2 = pos + Vector2.LEFT * (i - 1) + outline_pos = pos + Vector2.RIGHT # Right + if outline_pos.x >= Global.current_project.size.x || image.get_pixelv(outline_pos).a == 0: + var new_pos : Vector2 = pos + Vector2.LEFT * (i - 1) + if new_pos.x >= 0: + var new_pixel = image.get_pixelv(new_pos) + if new_pixel.a > 0: + new_image.set_pixelv(new_pos, outline_color) + + outline_pos = pos + Vector2.UP # Up + if outline_pos.y < 0 || image.get_pixelv(outline_pos).a == 0: + var new_pos : Vector2 = pos + Vector2.DOWN * (i - 1) + if new_pos.y < Global.current_project.size.y: + var new_pixel = image.get_pixelv(new_pos) + if new_pixel.a > 0: + new_image.set_pixelv(new_pos, outline_color) + + outline_pos = pos + Vector2.DOWN # Down + if outline_pos.y >= Global.current_project.size.y || image.get_pixelv(outline_pos).a == 0: + var new_pos : Vector2 = pos + Vector2.UP * (i - 1) + if new_pos.y >= 0: + var new_pixel = image.get_pixelv(new_pos) + if new_pixel.a > 0: + new_image.set_pixelv(new_pos, outline_color) + + if diagonal: + outline_pos = pos + (Vector2.LEFT + Vector2.UP) # Top left + if (outline_pos.x < 0 && outline_pos.y < 0) || image.get_pixelv(outline_pos).a == 0: + var new_pos : Vector2 = pos + (Vector2.RIGHT + Vector2.DOWN) * (i - 1) + if new_pos.x < Global.current_project.size.x && new_pos.y < Global.current_project.size.y: + var new_pixel = image.get_pixelv(new_pos) + if new_pixel.a > 0: + new_image.set_pixelv(new_pos, outline_color) + + outline_pos = pos + (Vector2.LEFT + Vector2.DOWN) # Bottom left + if (outline_pos.x < 0 && outline_pos.y >= Global.current_project.size.y) || image.get_pixelv(outline_pos).a == 0: + var new_pos : Vector2 = pos + (Vector2.RIGHT + Vector2.UP) * (i - 1) + if new_pos.x < Global.current_project.size.x && new_pos.y >= 0: + var new_pixel = image.get_pixelv(new_pos) + if new_pixel.a > 0: + new_image.set_pixelv(new_pos, outline_color) + + outline_pos = pos + (Vector2.RIGHT + Vector2.UP) # Top right + if (outline_pos.x >= Global.current_project.size.x && outline_pos.y < 0) || image.get_pixelv(outline_pos).a == 0: + var new_pos : Vector2 = pos + (Vector2.LEFT + Vector2.DOWN) * (i - 1) + if new_pos.x >= 0 && new_pos.y < Global.current_project.size.y: + var new_pixel = image.get_pixelv(new_pos) + if new_pixel.a > 0: + new_image.set_pixelv(new_pos, outline_color) + + outline_pos = pos + (Vector2.RIGHT + Vector2.DOWN) # Bottom right + if (outline_pos.x >= Global.current_project.size.x && outline_pos.y >= Global.current_project.size.y) || image.get_pixelv(outline_pos).a == 0: + var new_pos : Vector2 = pos + (Vector2.LEFT + Vector2.UP) * (i - 1) + if new_pos.x >= 0 && new_pos.y >= 0: + var new_pixel = image.get_pixelv(new_pos) + if new_pixel.a > 0: + new_image.set_pixelv(new_pos, outline_color) + + else: + var new_pos : Vector2 = pos + Vector2.LEFT * i # Left if new_pos.x >= 0: var new_pixel = image.get_pixelv(new_pos) - if new_pixel.a > 0: + if new_pixel.a == 0: new_image.set_pixelv(new_pos, outline_color) - outline_pos = pos + Vector2.UP # Up - if outline_pos.y < 0 || image.get_pixelv(outline_pos).a == 0: - var new_pos : Vector2 = pos + Vector2.DOWN * (i - 1) - if new_pos.y < Global.current_project.size.y: + new_pos = pos + Vector2.RIGHT * i # Right + if new_pos.x < Global.current_project.size.x: var new_pixel = image.get_pixelv(new_pos) - if new_pixel.a > 0: + if new_pixel.a == 0: new_image.set_pixelv(new_pos, outline_color) - outline_pos = pos + Vector2.DOWN # Down - if outline_pos.y >= Global.current_project.size.y || image.get_pixelv(outline_pos).a == 0: - var new_pos : Vector2 = pos + Vector2.UP * (i - 1) + new_pos = pos + Vector2.UP * i # Up if new_pos.y >= 0: var new_pixel = image.get_pixelv(new_pos) - if new_pixel.a > 0: + if new_pixel.a == 0: new_image.set_pixelv(new_pos, outline_color) - if diagonal: - outline_pos = pos + (Vector2.LEFT + Vector2.UP) # Top left - if (outline_pos.x < 0 && outline_pos.y < 0) || image.get_pixelv(outline_pos).a == 0: - var new_pos : Vector2 = pos + (Vector2.RIGHT + Vector2.DOWN) * (i - 1) - if new_pos.x < Global.current_project.size.x && new_pos.y < Global.current_project.size.y: - var new_pixel = image.get_pixelv(new_pos) - if new_pixel.a > 0: - new_image.set_pixelv(new_pos, outline_color) + new_pos = pos + Vector2.DOWN * i # Down + if new_pos.y < Global.current_project.size.y: + var new_pixel = image.get_pixelv(new_pos) + if new_pixel.a == 0: + new_image.set_pixelv(new_pos, outline_color) - outline_pos = pos + (Vector2.LEFT + Vector2.DOWN) # Bottom left - if (outline_pos.x < 0 && outline_pos.y >= Global.current_project.size.y) || image.get_pixelv(outline_pos).a == 0: - var new_pos : Vector2 = pos + (Vector2.RIGHT + Vector2.UP) * (i - 1) - if new_pos.x < Global.current_project.size.x && new_pos.y >= 0: - var new_pixel = image.get_pixelv(new_pos) - if new_pixel.a > 0: - new_image.set_pixelv(new_pos, outline_color) - - outline_pos = pos + (Vector2.RIGHT + Vector2.UP) # Top right - if (outline_pos.x >= Global.current_project.size.x && outline_pos.y < 0) || image.get_pixelv(outline_pos).a == 0: - var new_pos : Vector2 = pos + (Vector2.LEFT + Vector2.DOWN) * (i - 1) - if new_pos.x >= 0 && new_pos.y < Global.current_project.size.y: - var new_pixel = image.get_pixelv(new_pos) - if new_pixel.a > 0: - new_image.set_pixelv(new_pos, outline_color) - - outline_pos = pos + (Vector2.RIGHT + Vector2.DOWN) # Bottom right - if (outline_pos.x >= Global.current_project.size.x && outline_pos.y >= Global.current_project.size.y) || image.get_pixelv(outline_pos).a == 0: - var new_pos : Vector2 = pos + (Vector2.LEFT + Vector2.UP) * (i - 1) + if diagonal: + new_pos = pos + (Vector2.LEFT + Vector2.UP) * i # Top left if new_pos.x >= 0 && new_pos.y >= 0: var new_pixel = image.get_pixelv(new_pos) - if new_pixel.a > 0: + if new_pixel.a == 0: new_image.set_pixelv(new_pos, outline_color) - else: - var new_pos : Vector2 = pos + Vector2.LEFT * i # Left - if new_pos.x >= 0: - var new_pixel = image.get_pixelv(new_pos) - if new_pixel.a == 0: - new_image.set_pixelv(new_pos, outline_color) + new_pos = pos + (Vector2.LEFT + Vector2.DOWN) * i # Bottom left + if new_pos.x >= 0 && new_pos.y < Global.current_project.size.y: + var new_pixel = image.get_pixelv(new_pos) + if new_pixel.a == 0: + new_image.set_pixelv(new_pos, outline_color) - new_pos = pos + Vector2.RIGHT * i # Right - if new_pos.x < Global.current_project.size.x: - var new_pixel = image.get_pixelv(new_pos) - if new_pixel.a == 0: - new_image.set_pixelv(new_pos, outline_color) + new_pos = pos + (Vector2.RIGHT + Vector2.UP) * i # Top right + if new_pos.x < Global.current_project.size.x && new_pos.y >= 0: + var new_pixel = image.get_pixelv(new_pos) + if new_pixel.a == 0: + new_image.set_pixelv(new_pos, outline_color) - new_pos = pos + Vector2.UP * i # Up - if new_pos.y >= 0: - var new_pixel = image.get_pixelv(new_pos) - if new_pixel.a == 0: - new_image.set_pixelv(new_pos, outline_color) - - new_pos = pos + Vector2.DOWN * i # Down - if new_pos.y < Global.current_project.size.y: - var new_pixel = image.get_pixelv(new_pos) - if new_pixel.a == 0: - new_image.set_pixelv(new_pos, outline_color) - - if diagonal: - new_pos = pos + (Vector2.LEFT + Vector2.UP) * i # Top left - if new_pos.x >= 0 && new_pos.y >= 0: - var new_pixel = image.get_pixelv(new_pos) - if new_pixel.a == 0: - new_image.set_pixelv(new_pos, outline_color) - - new_pos = pos + (Vector2.LEFT + Vector2.DOWN) * i # Bottom left - if new_pos.x >= 0 && new_pos.y < Global.current_project.size.y: - var new_pixel = image.get_pixelv(new_pos) - if new_pixel.a == 0: - new_image.set_pixelv(new_pos, outline_color) - - new_pos = pos + (Vector2.RIGHT + Vector2.UP) * i # Top right - if new_pos.x < Global.current_project.size.x && new_pos.y >= 0: - var new_pixel = image.get_pixelv(new_pos) - if new_pixel.a == 0: - new_image.set_pixelv(new_pos, outline_color) - - new_pos = pos + (Vector2.RIGHT + Vector2.DOWN) * i # Bottom right - if new_pos.x < Global.current_project.size.x && new_pos.y < Global.current_project.size.y: - var new_pixel = image.get_pixelv(new_pos) - if new_pixel.a == 0: - new_image.set_pixelv(new_pos, outline_color) + new_pos = pos + (Vector2.RIGHT + Vector2.DOWN) * i # Bottom right + if new_pos.x < Global.current_project.size.x && new_pos.y < Global.current_project.size.y: + var new_pixel = image.get_pixelv(new_pos) + if new_pixel.a == 0: + new_image.set_pixelv(new_pos, outline_color) image.unlock() new_image.unlock() image.copy_from(new_image) -func adjust_hsv(img: Image, delta_h : float, delta_s : float, delta_v : float, pixels : Array) -> void: +func adjust_hsv(img: Image, delta_h : float, delta_s : float, delta_v : float, affect_selection : bool, project : Project) -> void: img.lock() - for i in pixels: - var c : Color = img.get_pixelv(i) - # Hue - var hue = range_lerp(c.h,0,1,-180,180) - hue = hue + delta_h + for x in project.size.x: + for y in project.size.y: + var pos := Vector2(x, y) + if affect_selection and !project.can_pixel_get_drawn(pos): + continue + var c : Color = img.get_pixelv(pos) + # Hue + var hue = range_lerp(c.h,0,1,-180,180) + hue = hue + delta_h - while(hue >= 180): - hue -= 360 - while(hue < -180): - hue += 360 + while(hue >= 180): + hue -= 360 + while(hue < -180): + hue += 360 - # Saturation - var sat = c.s - if delta_s > 0: - sat = range_lerp(delta_s,0,100,c.s,1) - elif delta_s < 0: - sat = range_lerp(delta_s,-100,0,0,c.s) + # Saturation + var sat = c.s + if delta_s > 0: + sat = range_lerp(delta_s,0,100,c.s,1) + elif delta_s < 0: + sat = range_lerp(delta_s,-100,0,0,c.s) - # Value - var val = c.v - if delta_v > 0: - val = range_lerp(delta_v,0,100,c.v,1) - elif delta_v < 0: - val = range_lerp(delta_v,-100,0,0,c.v) + # Value + var val = c.v + if delta_v > 0: + val = range_lerp(delta_v,0,100,c.v,1) + elif delta_v < 0: + val = range_lerp(delta_v,-100,0,0,c.v) - c.h = range_lerp(hue,-180,180,0,1) - c.s = sat - c.v = val - img.set_pixelv(i,c) - - img.unlock() + c.h = range_lerp(hue,-180,180,0,1) + c.s = sat + c.v = val + img.set_pixelv(pos, c) -func generate_gradient(image : Image, colors : Array, steps := 2, direction : int = GradientDirection.TOP, pixels = Global.current_project.selected_pixels) -> void: +func generate_gradient(image : Image, colors : Array, steps : int, direction : int, affect_selection : bool, project : Project) -> void: if colors.size() < 2: return @@ -584,8 +590,13 @@ func generate_gradient(image : Image, colors : Array, steps := 2, direction : in if direction == GradientDirection.BOTTOM or direction == GradientDirection.RIGHT: colors.invert() - var selection_rectangle := Rect2(pixels[0].x, pixels[0].y, pixels[-1].x - pixels[0].x + 1, pixels[-1].y - pixels[0].y + 1) - var size := selection_rectangle.size + var draw_rectangle := Rect2() + var selection := affect_selection and project.has_selection + if selection: + draw_rectangle = project.get_selection_rectangle() + else: + draw_rectangle = Rect2(Vector2.ZERO, project.size) + var size := draw_rectangle.size image.lock() var gradient_size @@ -596,7 +607,9 @@ func generate_gradient(image : Image, colors : Array, steps := 2, direction : in var start = i * gradient_size var end = (i + 1) * gradient_size for yy in range(start, end): - var pos : Vector2 = Vector2(xx, yy) + pixels[0] + var pos : Vector2 = Vector2(xx, yy) + draw_rectangle.position + if selection and !project.selection_bitmap.get_bit(pos): + continue image.set_pixelv(pos, colors[i]) else: @@ -606,5 +619,7 @@ func generate_gradient(image : Image, colors : Array, steps := 2, direction : in var start = i * gradient_size var end = (i + 1) * gradient_size for xx in range(start, end): - var pos : Vector2 = Vector2(xx, yy) + pixels[0] + var pos : Vector2 = Vector2(xx, yy) + draw_rectangle.position + if selection and !project.selection_bitmap.get_bit(pos): + continue image.set_pixelv(pos, colors[i]) diff --git a/src/Autoload/Export.gd b/src/Autoload/Export.gd index 607895e02..e41884795 100644 --- a/src/Autoload/Export.gd +++ b/src/Autoload/Export.gd @@ -196,7 +196,7 @@ func export_processed_images(ignore_overwrites: bool, export_dialog: AcceptDialo # Store settings for quick export and when the dialog is opened again was_exported = true Global.current_project.was_exported = true - Global.file_menu.get_popup().set_item_text(6, tr("Export") + " %s" % (file_name + file_format_string(file_format))) + Global.top_menu_container.file_menu.set_item_text(6, tr("Export") + " %s" % (file_name + file_format_string(file_format))) # Only show when not exporting gif - gif export finishes in thread if not (current_tab == ExportTab.ANIMATION and animation_type == AnimationType.ANIMATED): diff --git a/src/Autoload/Global.gd b/src/Autoload/Global.gd index 125a3939e..fbfb781b7 100644 --- a/src/Autoload/Global.gd +++ b/src/Autoload/Global.gd @@ -114,16 +114,10 @@ var small_preview_viewport : ViewportContainer var camera : Camera2D var camera2 : Camera2D var camera_preview : Camera2D -var selection_rectangle : Polygon2D var horizontal_ruler : BaseButton var vertical_ruler : BaseButton var transparent_checker : ColorRect -var file_menu : MenuButton -var edit_menu : MenuButton -var view_menu : MenuButton -var image_menu : MenuButton -var help_menu : MenuButton var cursor_position_label : Label var zoom_level_label : Label @@ -201,7 +195,7 @@ func _ready() -> void: # XDGDataDirs depends on it nyaa directory_module = XDGDataPaths.new() image_clipboard = Image.new() - Input.set_custom_mouse_cursor(Global.cursor_image, Input.CURSOR_CROSS, Vector2(15, 15)) + Input.set_custom_mouse_cursor(cursor_image, Input.CURSOR_CROSS, Vector2(15, 15)) var root = get_tree().get_root() control = find_node_by_name(root, "Control") @@ -218,16 +212,10 @@ func _ready() -> void: camera = find_node_by_name(main_viewport, "Camera2D") camera2 = find_node_by_name(root, "Camera2D2") camera_preview = find_node_by_name(root, "CameraPreview") - selection_rectangle = find_node_by_name(root, "SelectionRectangle") horizontal_ruler = find_node_by_name(root, "HorizontalRuler") vertical_ruler = find_node_by_name(root, "VerticalRuler") transparent_checker = find_node_by_name(root, "TransparentChecker") - file_menu = find_node_by_name(root, "FileMenu") - edit_menu = find_node_by_name(root, "EditMenu") - view_menu = find_node_by_name(root, "ViewMenu") - image_menu = find_node_by_name(root, "ImageMenu") - help_menu = find_node_by_name(root, "HelpMenu") cursor_position_label = find_node_by_name(root, "CursorPosition") zoom_level_label = find_node_by_name(root, "ZoomLevel") @@ -362,7 +350,7 @@ func general_redo(project : Project = current_project) -> void: func undo(_frame_index := -1, _layer_index := -1, project : Project = current_project) -> void: general_undo(project) var action_name : String = project.undo_redo.get_current_action_name() - if action_name == "Draw" or action_name == "Draw Shape" or action_name == "Rectangle Select" or action_name == "Scale" or action_name == "Centralize" or action_name == "Merge Layer" or action_name == "Link Cel" or action_name == "Unlink Cel": + if action_name == "Draw" or action_name == "Draw Shape" or action_name == "Rectangle Select" or action_name == "Move Selection" or action_name == "Scale" or action_name == "Centralize" or action_name == "Merge Layer" or action_name == "Link Cel" or action_name == "Unlink Cel": if _layer_index > -1 and _frame_index > -1: canvas.update_texture(_layer_index, _frame_index, project) else: @@ -370,11 +358,12 @@ func undo(_frame_index := -1, _layer_index := -1, project : Project = current_pr for j in project.layers.size(): canvas.update_texture(j, i, project) + canvas.selection.update() if action_name == "Scale": canvas.camera_zoom() - Global.canvas.grid.update() - Global.canvas.pixel_grid.update() - Global.cursor_position_label.text = "[%s×%s]" % [project.size.x, project.size.y] + canvas.grid.update() + canvas.pixel_grid.update() + cursor_position_label.text = "[%s×%s]" % [project.size.x, project.size.y] elif "Frame" in action_name: # This actually means that frames.size is one, but it hasn't been updated yet @@ -396,7 +385,7 @@ func undo(_frame_index := -1, _layer_index := -1, project : Project = current_pr func redo(_frame_index := -1, _layer_index := -1, project : Project = current_project) -> void: general_redo(project) var action_name : String = project.undo_redo.get_current_action_name() - if action_name == "Draw" or action_name == "Draw Shape" or action_name == "Rectangle Select" or action_name == "Scale" or action_name == "Centralize" or action_name == "Merge Layer" or action_name == "Link Cel" or action_name == "Unlink Cel": + if action_name == "Draw" or action_name == "Draw Shape" or action_name == "Rectangle Select" or action_name == "Move Selection" or action_name == "Scale" or action_name == "Centralize" or action_name == "Merge Layer" or action_name == "Link Cel" or action_name == "Unlink Cel": if _layer_index > -1 and _frame_index > -1: canvas.update_texture(_layer_index, _frame_index, project) else: @@ -404,11 +393,12 @@ func redo(_frame_index := -1, _layer_index := -1, project : Project = current_pr for j in project.layers.size(): canvas.update_texture(j, i, project) + canvas.selection.update() if action_name == "Scale": canvas.camera_zoom() - Global.canvas.grid.update() - Global.canvas.pixel_grid.update() - Global.cursor_position_label.text = "[%s×%s]" % [project.size.x, project.size.y] + canvas.grid.update() + canvas.pixel_grid.update() + cursor_position_label.text = "[%s×%s]" % [project.size.x, project.size.y] elif "Frame" in action_name: if project.frames.size() == 1: # Stop animating @@ -432,6 +422,7 @@ func title_changed(value : String) -> void: func project_changed(value : int) -> void: + canvas.selection.move_content_confirm() current_project_index = value current_project = projects[value] current_project.change_project() @@ -477,9 +468,10 @@ func change_button_texturerect(texture_button : TextureRect, new_file_name : Str func update_hint_tooltips() -> void: - var root = get_tree().get_root() + var root = control + var tool_buttons = root.find_node("ToolButtons") - var rect_select : BaseButton = find_node_by_name(root, "RectSelect") + var rect_select : BaseButton = tool_buttons.find_node("RectSelect") rect_select.hint_tooltip = tr("""Rectangular Selection %s for left mouse button @@ -487,6 +479,13 @@ func update_hint_tooltips() -> void: Press %s to move the content""") % [InputMap.get_action_list("left_rectangle_select_tool")[0].as_text(), InputMap.get_action_list("right_rectangle_select_tool")[0].as_text(), "Shift"] + var move_select : BaseButton = tool_buttons.find_node("Move") + move_select.hint_tooltip = tr("""Move + +%s for left mouse button +%s for right mouse button""") % [InputMap.get_action_list("left_move_tool")[0].as_text(), InputMap.get_action_list("right_move_tool")[0].as_text()] + + var zoom_tool : BaseButton = find_node_by_name(root, "Zoom") zoom_tool.hint_tooltip = tr("""Zoom @@ -616,7 +615,7 @@ func save_project_to_recent_list(path : String) -> void: func update_recent_projects_submenu() -> void: - for project in Global.recent_projects: + for project in recent_projects: recent_projects_submenu.add_item(project.get_file()) func use_osx_shortcuts() -> void: diff --git a/src/Autoload/OpenSave.gd b/src/Autoload/OpenSave.gd index 9fabf9730..a073c3649 100644 --- a/src/Autoload/OpenSave.gd +++ b/src/Autoload/OpenSave.gd @@ -119,8 +119,8 @@ func open_pxo_file(path : String, untitled_backup : bool = false, replace_empty new_project.directory_path = Export.directory_path new_project.file_name = Export.file_name Export.was_exported = false - Global.file_menu.get_popup().set_item_text(4, tr("Save") + " %s" % path.get_file()) - Global.file_menu.get_popup().set_item_text(6, tr("Export")) + Global.top_menu_container.file_menu.set_item_text(4, tr("Save") + " %s" % path.get_file()) + Global.top_menu_container.file_menu.set_item_text(6, tr("Export")) Global.save_project_to_recent_list(path) @@ -339,7 +339,7 @@ func save_pxo_file(path : String, autosave : bool, use_zstd_compression := true, Export.directory_path = path.get_base_dir() Export.was_exported = false project.was_exported = false - Global.file_menu.get_popup().set_item_text(4, tr("Save") + " %s" % path.get_file()) + Global.top_menu_container.file_menu.set_item_text(4, tr("Save") + " %s" % path.get_file()) Global.save_project_to_recent_list(path) diff --git a/src/Autoload/Palettes.gd b/src/Autoload/Palettes.gd index d3c986286..30bdca114 100644 --- a/src/Autoload/Palettes.gd +++ b/src/Autoload/Palettes.gd @@ -124,7 +124,12 @@ func create_new_palette_from_current_palette(name: String, comment: String) -> v func create_new_palette_from_current_selection(name: String, comment: String, width: int, height: int, add_alpha_colors: bool, get_colors_from: int): var new_palette: Palette = Palette.new(name, width, height, comment) var current_project = Global.current_project - var pixels = current_project.selected_pixels.duplicate() + var pixels := [] + for x in current_project.size.x: + for y in current_project.size.y: + var pos := Vector2(x, y) + if current_project.selection_bitmap.get_bit(pos): + pixels.append(pos) fill_new_palette_with_colors(pixels, new_palette, add_alpha_colors, get_colors_from) diff --git a/src/Autoload/Tools.gd b/src/Autoload/Tools.gd index d503550db..4bd43109a 100644 --- a/src/Autoload/Tools.gd +++ b/src/Autoload/Tools.gd @@ -40,6 +40,7 @@ signal color_changed(color, button) var _tools = { "RectSelect" : "res://src/Tools/RectSelect.tscn", + "Move" : "res://src/Tools/Move.tscn", "Zoom" : "res://src/Tools/Zoom.tscn", "Pan" : "res://src/Tools/Pan.tscn", "ColorPicker" : "res://src/Tools/ColorPicker.tscn", diff --git a/src/Classes/Drawers.gd b/src/Classes/Drawers.gd index 3be9ca9ed..83da1c2e6 100644 --- a/src/Classes/Drawers.gd +++ b/src/Classes/Drawers.gd @@ -75,19 +75,9 @@ func set_pixel(image: Image, position: Vector2, color: Color) -> void: var mirror_y = project.y_symmetry_point - position.y var mirror_x_inside : bool var mirror_y_inside : bool - var entire_image_selected : bool = project.selected_pixels.empty() - if entire_image_selected: - mirror_x_inside = mirror_x >= 0 and mirror_x < project.size.x - mirror_y_inside = mirror_y >= 0 and mirror_y < project.size.y - else: - var selected_pixels_x := [] - var selected_pixels_y := [] - for i in project.selected_pixels: - selected_pixels_x.append(i.x) - selected_pixels_y.append(i.y) + mirror_x_inside = project.can_pixel_get_drawn(Vector2(mirror_x, position.y)) + mirror_y_inside = project.can_pixel_get_drawn(Vector2(position.x, mirror_y)) - mirror_x_inside = mirror_x in selected_pixels_x - mirror_y_inside = mirror_y in selected_pixels_y if horizontal_mirror and mirror_x_inside: drawers[1].set_pixel(image, Vector2(mirror_x, position.y), color, color_op) diff --git a/src/Classes/ImageEffect.gd b/src/Classes/ImageEffect.gd index 9f4d711b0..58cae2158 100644 --- a/src/Classes/ImageEffect.gd +++ b/src/Classes/ImageEffect.gd @@ -6,7 +6,6 @@ class_name ImageEffect extends AcceptDialog enum {CEL, FRAME, ALL_FRAMES, ALL_PROJECTS} var affect : int = CEL -var pixels := [] var current_cel : Image var current_frame : Image var preview_image : Image @@ -33,6 +32,7 @@ func _ready() -> void: func _about_to_show() -> void: + Global.canvas.selection.move_content_confirm() current_cel = Global.current_project.frames[Global.current_project.current_frame].cels[Global.current_project.current_layer].image current_frame.resize(Global.current_project.size.x, Global.current_project.size.y) current_frame.fill(Color(0, 0, 0, 0)) @@ -48,39 +48,31 @@ func _about_to_show() -> void: func _confirmed() -> void: if affect == CEL: Global.canvas.handle_undo("Draw") - commit_action(current_cel, pixels) + commit_action(current_cel) Global.canvas.handle_redo("Draw") elif affect == FRAME: Global.canvas.handle_undo("Draw", Global.current_project, -1) for cel in Global.current_project.frames[Global.current_project.current_frame].cels: - commit_action(cel.image, pixels) + commit_action(cel.image) Global.canvas.handle_redo("Draw", Global.current_project, -1) elif affect == ALL_FRAMES: Global.canvas.handle_undo("Draw", Global.current_project, -1, -1) for frame in Global.current_project.frames: for cel in frame.cels: - commit_action(cel.image, pixels) + commit_action(cel.image) Global.canvas.handle_redo("Draw", Global.current_project, -1, -1) elif affect == ALL_PROJECTS: for project in Global.projects: - var _pixels := [] - if selection_checkbox.pressed and project.selected_pixels: - _pixels = project.selected_pixels.duplicate() - else: - for x in project.size.x: - for y in project.size.y: - _pixels.append(Vector2(x, y)) - Global.canvas.handle_undo("Draw", project, -1, -1) for frame in project.frames: for cel in frame.cels: - commit_action(cel.image, _pixels, project) + commit_action(cel.image, project) Global.canvas.handle_redo("Draw", project, -1, -1) -func commit_action(_cel : Image, _pixels : Array, _project : Project = Global.current_project) -> void: +func commit_action(_cel : Image, _project : Project = Global.current_project) -> void: pass @@ -88,15 +80,7 @@ func set_nodes() -> void: pass -func _on_SelectionCheckBox_toggled(button_pressed : bool) -> void: - pixels.clear() - if button_pressed and Global.current_project.selected_pixels: - pixels = Global.current_project.selected_pixels.duplicate() - else: - for x in Global.current_project.size.x: - for y in Global.current_project.size.y: - pixels.append(Vector2(x, y)) - +func _on_SelectionCheckBox_toggled(_button_pressed : bool) -> void: update_preview() @@ -111,7 +95,8 @@ func update_preview() -> void: preview_image.copy_from(current_cel) _: preview_image.copy_from(current_frame) - commit_action(preview_image, pixels) + commit_action(preview_image) + preview_image.unlock() preview_texture.create_from_image(preview_image, 0) preview.texture = preview_texture diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index f62219cb0..ca0b6b1a4 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -1,7 +1,6 @@ class_name Project extends Reference # A class for project properties. - var name := "" setget name_changed var size : Vector2 setget size_changed var undo_redo : UndoRedo @@ -23,8 +22,10 @@ var y_symmetry_point var x_symmetry_axis : SymmetryGuide var y_symmetry_axis : SymmetryGuide -var selected_pixels := [] -var selected_rect := Rect2(0, 0, 0, 0) setget _set_selected_rect +var selection_bitmap := BitMap.new() +# This is useful for when the selection is outside of the canvas boundaries, on the left and/or above (negative coords) +var selection_offset := Vector2.ZERO setget _selection_offset_changed +var has_selection := false # For every camera (currently there are 3) var cameras_zoom := [Vector2(0.15, 0.15), Vector2(0.15, 0.15), Vector2(0.15, 0.15)] # Array of Vector2 @@ -41,6 +42,7 @@ func _init(_frames := [], _name := tr("untitled"), _size := Vector2(64, 64)) -> frames = _frames name = _name size = _size + selection_bitmap.create(size) update_tile_mode_rects() undo_redo = UndoRedo.new() @@ -74,20 +76,33 @@ func _init(_frames := [], _name := tr("untitled"), _size := Vector2(64, 64)) -> directory_path = OS.get_system_dir(OS.SYSTEM_DIR_DESKTOP) -func select_all_pixels() -> void: - clear_selection() - for x in size.x: - for y in size.y: - selected_pixels.append(Vector2(x, y)) +func commit_undo() -> void: + if Global.canvas.selection.is_moving_content: + Global.canvas.selection.move_content_cancel() + else: + undo_redo.undo() -func clear_selection() -> void: - selected_pixels.clear() +func commit_redo() -> void: + Global.control.redone = true + undo_redo.redo() + Global.control.redone = false -func _set_selected_rect(value : Rect2) -> void: - selected_rect = value - Global.selection_rectangle.set_rect(value) +func selection_bitmap_changed() -> void: + var image := Image.new() + var image_texture := ImageTexture.new() + has_selection = selection_bitmap.get_true_bit_count() > 0 + if has_selection: + image = bitmap_to_image(selection_bitmap) + image_texture.create_from_image(image, 0) + Global.canvas.selection.marching_ants_outline.texture = image_texture + + +func _selection_offset_changed(value : Vector2) -> void: + selection_offset = value + Global.canvas.selection.marching_ants_outline.offset = selection_offset + Global.canvas.selection.update_on_zoom(Global.camera.zoom.x) func change_project() -> void: @@ -146,9 +161,6 @@ func change_project() -> void: self.animation_tags = animation_tags - # Change the selection rectangle - Global.selection_rectangle.set_rect(selected_rect) - # Change the guides for guide in Global.canvas.get_children(): if guide is Guide: @@ -167,21 +179,12 @@ func change_project() -> void: for brush in brushes: Brushes.add_project_brush(brush) - var cameras = [Global.camera, Global.camera2, Global.camera_preview] - var i := 0 - for camera in cameras: - camera.zoom = cameras_zoom[i] - camera.offset = cameras_offset[i] - i += 1 - Global.zoom_level_label.text = str(round(100 / Global.camera.zoom.x)) + " %" Global.canvas.update() Global.canvas.grid.update() - Global.canvas.pixel_grid.update() Global.transparent_checker._ready() Global.animation_timeline.fps_spinbox.value = fps Global.horizontal_ruler.update() Global.vertical_ruler.update() - Global.preview_zoom_slider.value = -Global.camera_preview.zoom.x Global.cursor_position_label.text = "[%s×%s]" % [size.x, size.y] Global.window_title = "%s - Pixelorama %s" % [name, Global.current_version] @@ -192,9 +195,9 @@ func change_project() -> void: if save_path != "": Global.open_sprites_dialog.current_path = save_path Global.save_sprites_dialog.current_path = save_path - Global.file_menu.get_popup().set_item_text(4, tr("Save") + " %s" % save_path.get_file()) + Global.top_menu_container.file_menu.set_item_text(4, tr("Save") + " %s" % save_path.get_file()) else: - Global.file_menu.get_popup().set_item_text(4, tr("Save")) + Global.top_menu_container.file_menu.set_item_text(4, tr("Save")) Export.directory_path = directory_path Export.file_name = file_name @@ -202,13 +205,27 @@ func change_project() -> void: Export.was_exported = was_exported if !was_exported: - Global.file_menu.get_popup().set_item_text(6, tr("Export")) + Global.top_menu_container.file_menu.set_item_text(6, tr("Export")) else: - Global.file_menu.get_popup().set_item_text(6, tr("Export") + " %s" % (file_name + Export.file_format_string(file_format))) + Global.top_menu_container.file_menu.set_item_text(6, tr("Export") + " %s" % (file_name + Export.file_format_string(file_format))) for j in Global.TileMode.values(): Global.tile_mode_submenu.set_item_checked(j, j == tile_mode) + # Change selection effect & bounding rectangle + Global.canvas.selection.marching_ants_outline.offset = selection_offset + selection_bitmap_changed() + Global.canvas.selection.big_bounding_rectangle = get_selection_rectangle() + Global.canvas.selection.big_bounding_rectangle.position += selection_offset + Global.canvas.selection.update() + + var i := 0 + for camera in [Global.camera, Global.camera2, Global.camera_preview]: + camera.zoom = cameras_zoom[i] + camera.offset = cameras_offset[i] + camera.zoom_changed() + i += 1 + func serialize() -> Dictionary: var layer_data := [] @@ -363,7 +380,6 @@ func name_changed(value : String) -> void: func size_changed(value : Vector2) -> void: size = value update_tile_mode_rects() - Global.selection_rectangle.set_rect(Global.selection_rectangle.get_rect()) func frames_changed(value : Array) -> void: @@ -582,3 +598,135 @@ func update_tile_mode_rects() -> void: func is_empty() -> bool: return frames.size() == 1 and layers.size() == 1 and frames[0].cels[0].image.is_invisible() and animation_tags.size() == 0 + + +func can_pixel_get_drawn(pixel : Vector2) -> bool: + if pixel.x < 0 or pixel.y < 0 or pixel.x >= size.x or pixel.y >= size.y: + return false + var selection_position : Vector2 = Global.canvas.selection.big_bounding_rectangle.position + if selection_position.x < 0: + pixel.x -= selection_position.x + if selection_position.y < 0: + pixel.y -= selection_position.y + if has_selection: + return selection_bitmap.get_bit(pixel) + else: + return true + + +func invert_bitmap(bitmap : BitMap) -> void: + for x in bitmap.get_size().x: + for y in bitmap.get_size().y: + var pos := Vector2(x, y) + bitmap.set_bit(pos, !bitmap.get_bit(pos)) + + +# Unexposed BitMap class function - https://github.com/godotengine/godot/blob/master/scene/resources/bit_map.cpp#L605 +func resize_bitmap(bitmap : BitMap, new_size : Vector2) -> BitMap: + if new_size == bitmap.get_size(): + return bitmap + var new_bitmap := BitMap.new() + new_bitmap.create(new_size) + var lw = min(bitmap.get_size().x, new_size.x) + var lh = min(bitmap.get_size().y, new_size.y) + for x in lw: + for y in lh: + new_bitmap.set_bit(Vector2(x, y), bitmap.get_bit(Vector2(x, y))) + + return new_bitmap + + +# Unexposed BitMap class function - https://github.com/godotengine/godot/blob/master/scene/resources/bit_map.cpp#L622 +func bitmap_to_image(bitmap : BitMap) -> Image: + var image := Image.new() + var width := bitmap.get_size().x + var height := bitmap.get_size().y + var square_size = max(width, height) + image.create(square_size, square_size, false, Image.FORMAT_LA8) + image.lock() + for x in width: + for y in height: + var pos := Vector2(x, y) + var color = Color(1, 1, 1, 1) if bitmap.get_bit(pos) else Color(0, 0, 0, 0) + image.set_pixelv(pos, color) + image.unlock() + return image + + +func get_selection_rectangle(bitmap : BitMap = selection_bitmap) -> Rect2: + var rect := Rect2(Vector2.ZERO, Vector2.ZERO) + if bitmap.get_true_bit_count() > 0: + var image : Image = bitmap_to_image(bitmap) + rect = image.get_used_rect() + return rect + + +func move_bitmap_values(bitmap : BitMap) -> void: + var selection_node = Global.canvas.selection + var selection_position : Vector2 = selection_node.big_bounding_rectangle.position + var selection_end : Vector2 = selection_node.big_bounding_rectangle.end + + var image : Image = bitmap_to_image(bitmap) + var selection_rect := image.get_used_rect() + var smaller_image := image.get_rect(selection_rect) + image.lock() + image.fill(Color(0)) + var dst := selection_position + var x_diff = selection_end.x - size.x + var y_diff = selection_end.y - size.y + var nw = max(size.x, size.x + x_diff) + var nh = max(size.y, size.y + y_diff) + + if selection_position.x < 0: + nw -= selection_position.x + self.selection_offset.x = selection_position.x + dst.x = 0 + else: + self.selection_offset.x = 0 + if selection_position.y < 0: + nh -= selection_position.y + self.selection_offset.y = selection_position.y + dst.y = 0 + else: + self.selection_offset.y = 0 + + if nw <= image.get_size().x: + nw = image.get_size().x + if nh <= image.get_size().y: + nh = image.get_size().y + + image.crop(nw, nh) + image.blit_rect(smaller_image, Rect2(Vector2.ZERO, Vector2(nw, nh)), dst) + bitmap.create_from_image_alpha(image) + + +func resize_bitmap_values(bitmap : BitMap, new_size : Vector2, flip_x : bool, flip_y : bool) -> BitMap: + var selection_node = Global.canvas.selection + var selection_position : Vector2 = selection_node.big_bounding_rectangle.position + var dst := selection_position + var new_bitmap_size := size + new_bitmap_size.x = max(size.x, abs(selection_position.x) + new_size.x) + new_bitmap_size.y = max(size.y, abs(selection_position.y) + new_size.y) + var new_bitmap := BitMap.new() + var image : Image = bitmap_to_image(bitmap) + var selection_rect := image.get_used_rect() + var smaller_image := image.get_rect(selection_rect) + if selection_position.x <= 0: + self.selection_offset.x = selection_position.x + dst.x = 0 + if selection_position.y <= 0: + self.selection_offset.y = selection_position.y + dst.y = 0 + image.lock() + image.fill(Color(0)) + smaller_image.resize(new_size.x, new_size.y, Image.INTERPOLATE_NEAREST) + if flip_x: + smaller_image.flip_x() + if flip_y: + smaller_image.flip_y() + if new_bitmap_size != size: + image.crop(new_bitmap_size.x, new_bitmap_size.y) + image.blit_rect(smaller_image, Rect2(Vector2.ZERO, new_bitmap_size), dst) + new_bitmap.create_from_image_alpha(image) + + return new_bitmap diff --git a/src/Main.gd b/src/Main.gd index 28074af4f..91ffd5c48 100644 --- a/src/Main.gd +++ b/src/Main.gd @@ -162,24 +162,21 @@ func _input(event : InputEvent) -> void: # The section of code below is reserved for Undo and Redo! Do not place code for Input below, but above. if !event.is_echo(): # Checks if the action is pressed down - if event.is_action_pressed("redo_secondary"): # Done, so that "redo_secondary" hasn't - redone = true # a slight delay before it starts. The "redo" and "undo" action don't have a slight delay, - Global.current_project.undo_redo.redo() # The "redo" and "undo" action don't have a slight delay, - redone = false # because they get called as an accelerator once pressed (TopMenuContainer.gd / Line 152). + if event.is_action_pressed("redo_secondary"): + # Done, so that "redo_secondary" hasn't a slight delay before it starts. + # The "redo" and "undo" action don't have a slight delay, + # because they get called as an accelerator once pressed (TopMenuContainer.gd / Line 152). + Global.current_project.commit_redo() return if event.is_action("redo"): # Ctrl + Y - redone = true - Global.current_project.undo_redo.redo() - redone = false + Global.current_project.commit_redo() if event.is_action("redo_secondary"): # Shift + Ctrl + Z - redone = true - Global.current_project.undo_redo.redo() - redone = false + Global.current_project.commit_redo() if event.is_action("undo") and !event.shift: # Ctrl + Z and check if shift isn't pressed - Global.current_project.undo_redo.undo() # so "undo" isn't accidentaly triggered while using "redo_secondary" + Global.current_project.commit_undo() # so "undo" isn't accidentaly triggered while using "redo_secondary" func setup_application_window_size() -> void: @@ -349,8 +346,8 @@ func _on_BackupConfirmation_confirmed(project_paths : Array, backup_paths : Arra Export.file_name = OpenSave.current_save_paths[0].get_file().trim_suffix(".pxo") Export.directory_path = OpenSave.current_save_paths[0].get_base_dir() Export.was_exported = false - Global.file_menu.get_popup().set_item_text(4, tr("Save") + " %s" % OpenSave.current_save_paths[0].get_file()) - Global.file_menu.get_popup().set_item_text(6, tr("Export")) + Global.top_menu_container.file_menu.set_item_text(4, tr("Save") + " %s" % OpenSave.current_save_paths[0].get_file()) + Global.top_menu_container.file_menu.set_item_text(6, tr("Export")) func _on_BackupConfirmation_delete(project_paths : Array, backup_paths : Array) -> void: diff --git a/src/SelectionRectangle.gd b/src/SelectionRectangle.gd deleted file mode 100644 index 479c4536d..000000000 --- a/src/SelectionRectangle.gd +++ /dev/null @@ -1,192 +0,0 @@ -extends Polygon2D - - -var _selected_rect := Rect2(0, 0, 0, 0) -var _clipped_rect := Rect2(0, 0, 0, 0) -var _move_image := Image.new() -var _move_texture := ImageTexture.new() -var _clear_image := Image.new() -var _move_pixel := false -var _clipboard := Image.new() -var _undo_data := {} - - -func _ready() -> void: - _clear_image.create(1, 1, false, Image.FORMAT_RGBA8) - _clear_image.fill(Color(0, 0, 0, 0)) - - -func _draw() -> void: - if _move_pixel: - draw_texture(_move_texture, _clipped_rect.position, Color(1, 1, 1, 0.5)) - - -func has_point(position : Vector2) -> bool: - return _selected_rect.has_point(position) - - -func get_rect() -> Rect2: - return _selected_rect - - -func set_rect(rect : Rect2) -> void: - _selected_rect = rect - polygon[0] = rect.position - polygon[1] = Vector2(rect.end.x, rect.position.y) - polygon[2] = rect.end - polygon[3] = Vector2(rect.position.x, rect.end.y) - visible = not rect.has_no_area() - - var project : Project = Global.current_project - if rect.has_no_area(): - project.selected_pixels = [] - else: - project.clear_selection() - for x in range(rect.position.x, rect.end.x): - for y in range(rect.position.y, rect.end.y): - if x < 0 or x >= project.size.x: - continue - if y < 0 or y >= project.size.y: - continue - project.selected_pixels.append(Vector2(x, y)) - - -func move_rect(move : Vector2) -> void: - _selected_rect.position += move - _clipped_rect.position += move - set_rect(_selected_rect) - - -func select_rect() -> void: - var undo_data = _get_undo_data(false) - Global.current_project.selected_rect = _selected_rect - commit_undo("Rectangle Select", undo_data) - - -func move_start(move_pixel : bool) -> void: - if not move_pixel: - return - - _undo_data = _get_undo_data(true) - var project := Global.current_project - var image : Image = project.frames[project.current_frame].cels[project.current_layer].image - - var rect = Rect2(Vector2.ZERO, project.size) - _clipped_rect = rect.clip(_selected_rect) - _move_image = image.get_rect(_clipped_rect) - _move_texture.create_from_image(_move_image, 0) - - var size := _clipped_rect.size - rect = Rect2(Vector2.ZERO, size) - _clear_image.resize(size.x, size.y, Image.INTERPOLATE_NEAREST) - image.blit_rect(_clear_image, rect, _clipped_rect.position) - Global.canvas.update_texture(project.current_layer) - - _move_pixel = true - update() - - -func move_end() -> void: - var undo_data = _undo_data if _move_pixel else _get_undo_data(false) - - if _move_pixel: - var project := Global.current_project - var image : Image = project.frames[project.current_frame].cels[project.current_layer].image - var size := _clipped_rect.size - var rect = Rect2(Vector2.ZERO, size) - image.blit_rect_mask(_move_image, _move_image, rect, _clipped_rect.position) - _move_pixel = false - update() - - Global.current_project.selected_rect = _selected_rect - commit_undo("Rectangle Select", undo_data) - _undo_data.clear() - - -func copy() -> void: - if _selected_rect.has_no_area(): - return - - var project := Global.current_project - var image : Image = project.frames[project.current_frame].cels[project.current_layer].image - _clipboard = image.get_rect(_selected_rect) - if _clipboard.is_invisible(): - return - var brush = _clipboard.get_rect(_clipboard.get_used_rect()) - project.brushes.append(brush) - Brushes.add_project_brush(brush) - -func cut() -> void: # This is basically the same as copy + delete - if _selected_rect.has_no_area(): - return - - var undo_data = _get_undo_data(true) - var project := Global.current_project - var image : Image = project.frames[project.current_frame].cels[project.current_layer].image - var size := _selected_rect.size - var rect = Rect2(Vector2.ZERO, size) - _clipboard = image.get_rect(_selected_rect) - if _clipboard.is_invisible(): - return - - _clear_image.resize(size.x, size.y, Image.INTERPOLATE_NEAREST) - var brush = _clipboard.get_rect(_clipboard.get_used_rect()) - project.brushes.append(brush) - Brushes.add_project_brush(brush) - move_end() # The selection_rectangle can be used while is moving, this prevents malfunctioning - image.blit_rect(_clear_image, rect, _selected_rect.position) - commit_undo("Draw", undo_data) - -func paste() -> void: - if _clipboard.get_size() <= Vector2.ZERO: - return - - var undo_data = _get_undo_data(true) - var project := Global.current_project - var image : Image = project.frames[project.current_frame].cels[project.current_layer].image - var size := _selected_rect.size - var rect = Rect2(Vector2.ZERO, size) - image.blend_rect(_clipboard, rect, _selected_rect.position) - move_end() # The selection_rectangle can be used while is moving, this prevents malfunctioning - commit_undo("Draw", undo_data) - - -func delete() -> void: - var undo_data = _get_undo_data(true) - var project := Global.current_project - var image : Image = project.frames[project.current_frame].cels[project.current_layer].image - var size := _selected_rect.size - var rect = Rect2(Vector2.ZERO, size) - _clear_image.resize(size.x, size.y, Image.INTERPOLATE_NEAREST) - image.blit_rect(_clear_image, rect, _selected_rect.position) - move_end() # The selection_rectangle can be used while is moving, this prevents malfunctioning - commit_undo("Draw", undo_data) - - -func commit_undo(action : String, undo_data : Dictionary) -> void: - var redo_data = _get_undo_data("image_data" in undo_data) - var project := Global.current_project - - project.undos += 1 - project.undo_redo.create_action(action) - project.undo_redo.add_do_property(project, "selected_rect", redo_data["selected_rect"]) - project.undo_redo.add_undo_property(project, "selected_rect", undo_data["selected_rect"]) - if "image_data" in undo_data: - var image : Image = project.frames[project.current_frame].cels[project.current_layer].image - project.undo_redo.add_do_property(image, "data", redo_data["image_data"]) - project.undo_redo.add_undo_property(image, "data", undo_data["image_data"]) - project.undo_redo.add_do_method(Global, "redo", project.current_frame, project.current_layer) - project.undo_redo.add_undo_method(Global, "undo", project.current_frame, project.current_layer) - project.undo_redo.commit_action() - - -func _get_undo_data(undo_image : bool) -> Dictionary: - var data = {} - var project := Global.current_project - data["selected_rect"] = Global.current_project.selected_rect - if undo_image: - var image : Image = project.frames[project.current_frame].cels[project.current_layer].image - image.unlock() - data["image_data"] = image.data - image.lock() - return data diff --git a/src/Shaders/MarchingAntsOutline.shader b/src/Shaders/MarchingAntsOutline.shader new file mode 100644 index 000000000..24970fbd9 --- /dev/null +++ b/src/Shaders/MarchingAntsOutline.shader @@ -0,0 +1,52 @@ +// Taken and modified from https://godotshaders.com/shader/2d-outline-inline/ +// Also thanks to https://andreashackel.de/tech-art/stripes-shader-1/ for the stripe tutorial +shader_type canvas_item; + +uniform vec4 first_color : hint_color = vec4(1.0); +uniform vec4 second_color : hint_color = vec4(0.0, 0.0, 0.0, 1.0); +uniform bool animated = true; +uniform float width : hint_range(0, 2) = 0.2; +uniform float frequency = 50.0; +uniform float stripe_direction : hint_range(0, 1) = 0.5; + + +bool hasContraryNeighbour(vec2 uv, vec2 texture_pixel_size, sampler2D texture) { + for (float i = -ceil(width); i <= ceil(width); i++) { + float x = abs(i) > width ? width * sign(i) : i; + float offset = width; + + for (float j = -ceil(offset); j <= ceil(offset); j++) { + float y = abs(j) > offset ? offset * sign(j) : j; + vec2 xy = uv + texture_pixel_size * vec2(x, y); + + if ((xy != clamp(xy, vec2(0.0), vec2(1.0)) || texture(texture, xy).a == 0.0) == true) { + return true; + } + } + } + + return false; +} + +void fragment() { + vec2 uv = UV; + COLOR = texture(TEXTURE, uv); + + if ((COLOR.a > 0.0) == true && hasContraryNeighbour(uv, TEXTURE_PIXEL_SIZE, TEXTURE)) { + vec4 final_color = first_color; + // Generate diagonal stripes + if(animated) + uv -= TIME / frequency; + float pos = mix(uv.x, uv.y, stripe_direction) * frequency; + float value = floor(fract(pos) + 0.5); + if (mod(value, 2.0) == 0.0) + final_color = second_color; + + COLOR.rgb = mix(COLOR.rgb, final_color.rgb, final_color.a); + COLOR.a += (1.0 - COLOR.a) * final_color.a; + } + else { + // Erase the texture's pixels in order to only keep the outline visible + COLOR.a = 0.0; + } +} \ No newline at end of file diff --git a/src/Tools/BaseTool.gd b/src/Tools/BaseTool.gd index 66fd6dcc5..ae832e098 100644 --- a/src/Tools/BaseTool.gd +++ b/src/Tools/BaseTool.gd @@ -81,11 +81,10 @@ func draw_preview() -> void: func _get_draw_rect() -> Rect2: - if Global.current_project.selected_pixels.empty(): - return Global.current_project.tile_mode_rects[Global.TileMode.NONE] + if Global.current_project.has_selection: + return Global.current_project.get_selection_rectangle() else: - var selected_pixels = Global.current_project.selected_pixels - return Rect2(selected_pixels[0].x, selected_pixels[0].y, selected_pixels[-1].x - selected_pixels[0].x + 1, selected_pixels[-1].y - selected_pixels[0].y + 1) + return Global.current_project.tile_mode_rects[Global.TileMode.NONE] func _get_draw_image() -> Image: diff --git a/src/Tools/Bucket.gd b/src/Tools/Bucket.gd index 3d37d7c71..af61e54cd 100644 --- a/src/Tools/Bucket.gd +++ b/src/Tools/Bucket.gd @@ -94,9 +94,10 @@ func update_pattern() -> void: func draw_start(position : Vector2) -> void: + Global.canvas.selection.move_content_confirm() if Global.current_project.layers[Global.current_project.current_layer].locked or !Global.current_project.tile_mode_rects[Global.TileMode.NONE].has_point(position): return - if Global.current_project.selected_pixels and not position in Global.current_project.selected_pixels: + if Global.current_project.has_selection and not Global.current_project.can_pixel_get_drawn(position): return var undo_data = _get_undo_data() if _fill_area == 0: @@ -123,17 +124,14 @@ func fill_in_color(position : Vector2) -> void: return image.lock() - var pixels := [] - if project.selected_pixels: - pixels = project.selected_pixels.duplicate() - else: - for x in Global.current_project.size.x: - for y in Global.current_project.size.y: - pixels.append(Vector2(x, y)) - for i in pixels: - if image.get_pixelv(i).is_equal_approx(color): - _set_pixel(image, i.x, i.y, tool_slot.color) + for x in Global.current_project.size.x: + for y in Global.current_project.size.y: + var pos := Vector2(x, y) + if project.has_selection and not project.can_pixel_get_drawn(pos): + continue + if image.get_pixelv(pos).is_equal_approx(color): + _set_pixel(image, x, y, tool_slot.color) func fill_in_area(position : Vector2) -> void: @@ -145,19 +143,9 @@ func fill_in_area(position : Vector2) -> void: var mirror_y = project.y_symmetry_point - position.y var mirror_x_inside : bool var mirror_y_inside : bool - var entire_image_selected : bool = project.selected_pixels.empty() - if entire_image_selected: - mirror_x_inside = mirror_x >= 0 and mirror_x < project.size.x - mirror_y_inside = mirror_y >= 0 and mirror_y < project.size.y - else: - var selected_pixels_x := [] - var selected_pixels_y := [] - for i in project.selected_pixels: - selected_pixels_x.append(i.x) - selected_pixels_y.append(i.y) - mirror_x_inside = mirror_x in selected_pixels_x - mirror_y_inside = mirror_y in selected_pixels_y + mirror_x_inside = project.can_pixel_get_drawn(Vector2(mirror_x, position.y)) + mirror_y_inside = project.can_pixel_get_drawn(Vector2(position.x, mirror_y)) if tool_slot.horizontal_mirror and mirror_x_inside: _flood_fill(Vector2(mirror_x, position.y)) @@ -202,13 +190,8 @@ func _flood_fill(position : Vector2) -> void: func _set_pixel(image : Image, x : int, y : int, color : Color) -> void: var project : Project = Global.current_project - var entire_image_selected : bool = project.selected_pixels.empty() - if entire_image_selected: - if not _get_draw_rect().has_point(Vector2(x, y)): - return - else: - if not Vector2(x, y) in project.selected_pixels: - return + if !project.can_pixel_get_drawn(Vector2(x, y)): + return if _fill_with == 0 or _pattern == null: image.set_pixel(x, y, color) diff --git a/src/Tools/Draw.gd b/src/Tools/Draw.gd index 776fc9b08..4a8919a38 100644 --- a/src/Tools/Draw.gd +++ b/src/Tools/Draw.gd @@ -100,7 +100,6 @@ func update_brush() -> void: else: var random = randi() % _brush.random.size() _brush_image = _create_blended_brush_image(_brush.random[random]) - _brush_image.lock() _brush_texture.create_from_image(_brush_image, 0) update_mirror_brush() _indicator = _create_brush_indicator() @@ -114,7 +113,6 @@ func update_random_image() -> void: return var random = randi() % _brush.random.size() _brush_image = _create_blended_brush_image(_brush.random[random]) - _brush_image.lock() _brush_texture.create_from_image(_brush_image, 0) _indicator = _create_brush_indicator() update_mirror_brush() @@ -273,34 +271,43 @@ func draw_tool_brush(position : Vector2) -> void: return var src_rect := Rect2(dst_rect.position - dst, dst_rect.size) dst = dst_rect.position - - _draw_brush_image(_brush_image, src_rect, dst) + var brush_image : Image = remove_unselected_parts_of_brush(_brush_image, dst) + _draw_brush_image(brush_image, src_rect, dst) # Handle Mirroring var mirror_x = (project.x_symmetry_point + 1) - dst.x - src_rect.size.x var mirror_y = (project.y_symmetry_point + 1) - dst.y - src_rect.size.y - var mirror_x_inside : bool - var mirror_y_inside : bool - var entire_image_selected : bool = project.selected_pixels.empty() - if entire_image_selected: - mirror_x_inside = mirror_x >= 0 and mirror_x < project.size.x - mirror_y_inside = mirror_y >= 0 and mirror_y < project.size.y - else: - var selected_pixels_x := [] - var selected_pixels_y := [] - for i in project.selected_pixels: - selected_pixels_x.append(i.x) - selected_pixels_y.append(i.y) - mirror_x_inside = mirror_x in selected_pixels_x - mirror_y_inside = mirror_y in selected_pixels_y + if tool_slot.horizontal_mirror: + var x_dst := Vector2(mirror_x, dst.y) + var mirror_brush_x : Image = remove_unselected_parts_of_brush(_mirror_brushes.x, x_dst) + _draw_brush_image(mirror_brush_x, _flip_rect(src_rect, size, true, false), x_dst) + if tool_slot.vertical_mirror: + var xy_dst := Vector2(mirror_x, mirror_y) + var mirror_brush_xy : Image = remove_unselected_parts_of_brush(_mirror_brushes.xy, xy_dst) + _draw_brush_image(mirror_brush_xy, _flip_rect(src_rect, size, true, true), xy_dst) + if tool_slot.vertical_mirror: + var y_dst := Vector2(dst.x, mirror_y) + var mirror_brush_y : Image = remove_unselected_parts_of_brush(_mirror_brushes.y, y_dst) + _draw_brush_image(mirror_brush_y, _flip_rect(src_rect, size, false, true), y_dst) - if tool_slot.horizontal_mirror and mirror_x_inside: - _draw_brush_image(_mirror_brushes.x, _flip_rect(src_rect, size, true, false), Vector2(mirror_x, dst.y)) - if tool_slot.vertical_mirror and mirror_y_inside: - _draw_brush_image(_mirror_brushes.xy, _flip_rect(src_rect, size, true, true), Vector2(mirror_x, mirror_y)) - if tool_slot.vertical_mirror and mirror_y_inside: - _draw_brush_image(_mirror_brushes.y, _flip_rect(src_rect, size, false, true), Vector2(dst.x, mirror_y)) + +func remove_unselected_parts_of_brush(brush : Image, dst : Vector2) -> Image: + var project : Project = Global.current_project + var size := brush.get_size() + var new_brush := Image.new() + new_brush.copy_from(_mirror_brushes.x) + if !project.has_selection: + return new_brush + + new_brush.lock() + for x in size.x: + for y in size.y: + var pos := Vector2(x, y) + dst + if !project.selection_bitmap.get_bit(pos): + new_brush.set_pixel(x, y, Color(0)) + new_brush.unlock() + return new_brush func draw_indicator() -> void: @@ -337,13 +344,8 @@ func _set_pixel(position : Vector2) -> void: if project.tile_mode and project.get_tile_mode_rect().has_point(position): position = position.posmodv(project.size) - var entire_image_selected : bool = project.selected_pixels.empty() - if entire_image_selected: - if not _get_draw_rect().has_point(position): - return - else: - if not position in project.selected_pixels: - return + if !project.can_pixel_get_drawn(position): + return var image := _get_draw_image() var i := int(position.x + position.y * image.get_size().x) diff --git a/src/Tools/Eraser.gd b/src/Tools/Eraser.gd index b73ff86a9..f24d2adb2 100644 --- a/src/Tools/Eraser.gd +++ b/src/Tools/Eraser.gd @@ -24,6 +24,7 @@ func _init() -> void: func draw_start(position : Vector2) -> void: + Global.canvas.selection.move_content_confirm() update_mask() _changed = false _drawer.color_op.changed = false diff --git a/src/Tools/LightenDarken.gd b/src/Tools/LightenDarken.gd index a5452eb6c..acccf4155 100644 --- a/src/Tools/LightenDarken.gd +++ b/src/Tools/LightenDarken.gd @@ -200,6 +200,7 @@ func update_strength() -> void: func draw_start(position : Vector2) -> void: + Global.canvas.selection.move_content_confirm() update_mask(false) _changed = false _drawer.color_op.changed = false diff --git a/src/Tools/Move.gd b/src/Tools/Move.gd new file mode 100644 index 000000000..e1cfd464a --- /dev/null +++ b/src/Tools/Move.gd @@ -0,0 +1,40 @@ +extends BaseTool + + +var _starting_pos : Vector2 +var _offset : Vector2 + + +func draw_start(position : Vector2) -> void: + _starting_pos = position + _offset = position + if Global.current_project.has_selection: + Global.canvas.selection.move_content_start() + + +func draw_move(position : Vector2) -> void: + if Global.current_project.has_selection: + Global.canvas.selection.move_content(position - _offset) + _offset = position + else: + Global.canvas.move_preview_location = position - _starting_pos + _offset = position + + +func draw_end(position : Vector2) -> void: + if _starting_pos != Vector2.INF: + var pixel_diff : Vector2 = position - _starting_pos + var project : Project = Global.current_project + var image : Image = _get_draw_image() + + if !project.has_selection: + Global.canvas.move_preview_location = Vector2.ZERO + var image_copy := Image.new() + image_copy.copy_from(image) + Global.canvas.handle_undo("Draw") + image.fill(Color(0, 0, 0, 0)) + image.blit_rect(image_copy, Rect2(Vector2.ZERO, project.size), pixel_diff) + + Global.canvas.handle_redo("Draw") + + _starting_pos = Vector2.INF diff --git a/src/Tools/Move.tscn b/src/Tools/Move.tscn new file mode 100644 index 000000000..82037f1ba --- /dev/null +++ b/src/Tools/Move.tscn @@ -0,0 +1,23 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://src/Tools/BaseTool.tscn" type="PackedScene" id=1] +[ext_resource path="res://src/Tools/Move.gd" type="Script" id=2] + + +[node name="ToolOptions" instance=ExtResource( 1 )] +script = ExtResource( 2 ) + +[node name="PixelPerfect" parent="." index="1"] +visible = false +margin_top = 126.0 +margin_bottom = 150.0 + +[node name="EmptySpacer" parent="." index="2"] +visible = false +margin_top = 126.0 +margin_bottom = 138.0 + +[node name="Mirror" parent="." index="3"] +visible = false +margin_top = 126.0 +margin_bottom = 143.0 diff --git a/src/Tools/Pencil.gd b/src/Tools/Pencil.gd index c139a06cc..967f45bae 100644 --- a/src/Tools/Pencil.gd +++ b/src/Tools/Pencil.gd @@ -46,6 +46,7 @@ func update_config() -> void: func draw_start(position : Vector2) -> void: + Global.canvas.selection.move_content_confirm() update_mask() _changed = false _drawer.color_op.changed = false diff --git a/src/Tools/RectSelect.gd b/src/Tools/RectSelect.gd index e4f10c8a5..18c5bbda9 100644 --- a/src/Tools/RectSelect.gd +++ b/src/Tools/RectSelect.gd @@ -1,56 +1,63 @@ extends BaseTool +var _rect := Rect2(0, 0, 0, 0) var _start := Rect2(0, 0, 0, 0) var _offset := Vector2.ZERO -var _drag := false var _move := false +var undo_data : Dictionary func draw_start(position : Vector2) -> void: - if Global.selection_rectangle.has_point(position): + Global.canvas.selection.move_content_confirm() + undo_data = Global.canvas.selection._get_undo_data(false) + var selection_position : Vector2 = Global.canvas.selection.big_bounding_rectangle.position + var offsetted_pos := position + if selection_position.x < 0: + offsetted_pos.x -= selection_position.x + if selection_position.y < 0: + offsetted_pos.y -= selection_position.y + + if offsetted_pos.x >= 0 and offsetted_pos.y >= 0 and Global.current_project.selection_bitmap.get_bit(offsetted_pos) and !Tools.control and !Tools.shift: + # Move current selection _move = true _offset = position - Global.selection_rectangle.move_start(Tools.shift) - _set_cursor_text(Global.selection_rectangle.get_rect()) + Global.canvas.selection.move_borders_start() + else: - _drag = true _start = Rect2(position, Vector2.ZERO) - Global.selection_rectangle.set_rect(_start) func draw_move(position : Vector2) -> void: if _move: - Global.selection_rectangle.move_rect(position - _offset) + Global.canvas.selection.move_borders(position - _offset) _offset = position - _set_cursor_text(Global.selection_rectangle.get_rect()) + _set_cursor_text(Global.canvas.selection.big_bounding_rectangle) else: - var rect := _start.expand(position).abs() - rect = rect.grow_individual(0, 0, 1, 1) - Global.selection_rectangle.set_rect(rect) - _set_cursor_text(rect) + _rect = _start.expand(position).abs() + _rect = _rect.grow_individual(0, 0, 1, 1) + _set_cursor_text(_rect) + Global.canvas.selection.drawn_rect = _rect + Global.canvas.selection.update() func draw_end(_position : Vector2) -> void: if _move: - Global.selection_rectangle.move_end() + Global.canvas.selection.move_borders_end() else: - Global.selection_rectangle.select_rect() - _drag = false + if !Tools.shift and !Tools.control: + Global.canvas.selection.clear_selection() + if _rect.size == Vector2.ZERO and Global.current_project.has_selection: + Global.canvas.selection.commit_undo("Rectangle Select", undo_data) + if _rect.size != Vector2.ZERO: + Global.canvas.selection.select_rect(_rect, !Tools.control) + Global.canvas.selection.commit_undo("Rectangle Select", undo_data) + _move = false cursor_text = "" - - -func cursor_move(position : Vector2) -> void: - if _drag: - _cursor = Vector2.INF - elif Global.selection_rectangle.has_point(position): - _cursor = Vector2.INF - Global.main_viewport.mouse_default_cursor_shape = Input.CURSOR_MOVE - Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) - else: - _cursor = position - Global.main_viewport.mouse_default_cursor_shape = Input.CURSOR_CROSS + _rect = Rect2(0, 0, 0, 0) + Global.canvas.selection.drawn_rect = _rect + Global.canvas.selection.update() func _set_cursor_text(rect : Rect2) -> void: diff --git a/src/UI/Canvas/CameraMovement.gd b/src/UI/Canvas/CameraMovement.gd index 689149654..090a8dfc8 100644 --- a/src/UI/Canvas/CameraMovement.gd +++ b/src/UI/Canvas/CameraMovement.gd @@ -178,6 +178,9 @@ func zoom_changed() -> void: update_rulers() for guide in Global.current_project.guides: guide.width = zoom.x * 2 + + Global.canvas.selection.update_on_zoom(zoom.x) + elif name == "CameraPreview": Global.preview_zoom_slider.value = -zoom.x diff --git a/src/UI/Canvas/Canvas.gd b/src/UI/Canvas/Canvas.gd index 92e4d3579..74e759149 100644 --- a/src/UI/Canvas/Canvas.gd +++ b/src/UI/Canvas/Canvas.gd @@ -7,12 +7,14 @@ var current_pixel := Vector2.ZERO var can_undo := true var cursor_image_has_changed := false var sprite_changed_this_frame := false # for optimization purposes +var move_preview_location := Vector2.ZERO onready var currently_visible_frame : Viewport = $CurrentlyVisibleFrame onready var current_frame_drawer = $CurrentlyVisibleFrame/CurrentFrameDrawer +onready var tile_mode = $TileMode onready var pixel_grid = $PixelGrid onready var grid = $Grid -onready var tile_mode = $TileMode +onready var selection = $Selection onready var indicators = $Indicators onready var previews = $Previews @@ -30,7 +32,7 @@ func _draw() -> void: Global.small_preview_viewport.get_child(0).get_node("CanvasPreview").update() var current_cels : Array = Global.current_project.frames[Global.current_project.current_frame].cels - + var current_layer : int = Global.current_project.current_layer var _position := position var _scale := scale if Global.mirror_view: @@ -41,7 +43,10 @@ func _draw() -> void: for i in range(Global.current_project.layers.size()): var modulate_color := Color(1, 1, 1, current_cels[i].opacity) if Global.current_project.layers[i].visible: # if it's visible - draw_texture(current_cels[i].image_texture, Vector2.ZERO, modulate_color) + if i == current_layer: + draw_texture(current_cels[i].image_texture, move_preview_location, modulate_color) + else: + draw_texture(current_cels[i].image_texture, Vector2.ZERO, modulate_color) if Global.onion_skinning: onion_skinning() diff --git a/src/UI/Canvas/Canvas.tscn b/src/UI/Canvas/Canvas.tscn index 75e266ffa..c23c9d8c2 100644 --- a/src/UI/Canvas/Canvas.tscn +++ b/src/UI/Canvas/Canvas.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=9 format=2] +[gd_scene load_steps=12 format=2] [ext_resource path="res://src/UI/Canvas/Canvas.gd" type="Script" id=1] [ext_resource path="res://src/UI/Canvas/Grid.gd" type="Script" id=2] @@ -7,10 +7,21 @@ [ext_resource path="res://src/UI/Canvas/CurrentFrameDrawer.gd" type="Script" id=5] [ext_resource path="res://src/UI/Canvas/PixelGrid.gd" type="Script" id=6] [ext_resource path="res://src/UI/Canvas/Previews.gd" type="Script" id=7] +[ext_resource path="res://src/UI/Canvas/Selection.gd" type="Script" id=8] +[ext_resource path="res://src/Shaders/MarchingAntsOutline.shader" type="Shader" id=9] [sub_resource type="CanvasItemMaterial" id=1] blend_mode = 4 +[sub_resource type="ShaderMaterial" id=2] +shader = ExtResource( 9 ) +shader_param/first_color = Color( 1, 1, 1, 1 ) +shader_param/second_color = Color( 0, 0, 0, 1 ) +shader_param/animated = true +shader_param/width = 0.05 +shader_param/frequency = 200.0 +shader_param/stripe_direction = 0.5 + [node name="Canvas" type="Node2D"] script = ExtResource( 1 ) @@ -36,6 +47,13 @@ script = ExtResource( 6 ) [node name="Grid" type="Node2D" parent="."] script = ExtResource( 2 ) +[node name="Selection" type="Node2D" parent="."] +script = ExtResource( 8 ) + +[node name="MarchingAntsOutline" type="Sprite" parent="Selection"] +material = SubResource( 2 ) +centered = false + [node name="Indicators" type="Node2D" parent="."] script = ExtResource( 3 ) diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd new file mode 100644 index 000000000..469f6eeb4 --- /dev/null +++ b/src/UI/Canvas/Selection.gd @@ -0,0 +1,498 @@ +extends Node2D + + +class Clipboard: + var image := Image.new() + var selection_bitmap := BitMap.new() + var big_bounding_rectangle := Rect2() + var selection_offset := Vector2.ZERO + + +class Gizmo: + enum Type {SCALE, ROTATE} + + var rect : Rect2 + var direction := Vector2.ZERO + var type : int + + func _init(_type : int = Type.SCALE, _direction := Vector2.ZERO) -> void: + type = _type + direction = _direction + + + func get_cursor() -> int: + var cursor := Input.CURSOR_MOVE + if direction == Vector2.ZERO: + return Input.CURSOR_POINTING_HAND + elif direction == Vector2(-1, -1) or direction == Vector2(1, 1): # Top left or bottom right + cursor = Input.CURSOR_FDIAGSIZE + elif direction == Vector2(1, -1) or direction == Vector2(-1, 1): # Top right or bottom left + cursor = Input.CURSOR_BDIAGSIZE + elif direction == Vector2(0, -1) or direction == Vector2(0, 1): # Center top or center bottom + cursor = Input.CURSOR_VSIZE + elif direction == Vector2(-1, 0) or direction == Vector2(1, 0): # Center left or center right + cursor = Input.CURSOR_HSIZE + return cursor + + +var clipboard := Clipboard.new() +var is_moving_content := false +var big_bounding_rectangle := Rect2() setget _big_bounding_rectangle_changed +var temp_rect := Rect2() +var original_big_bounding_rectangle := Rect2() +var original_preview_image := Image.new() +var original_bitmap := BitMap.new() +var original_offset := Vector2.ZERO +var preview_image := Image.new() +var preview_image_texture := ImageTexture.new() +var undo_data : Dictionary +var drawn_rect := Rect2(0, 0, 0, 0) +var gizmos := [] # Array of Gizmos +var dragged_gizmo : Gizmo = null +var prev_angle := 0 +var mouse_pos_on_gizmo_drag := Vector2.ZERO + +onready var marching_ants_outline : Sprite = $MarchingAntsOutline + + +func _ready() -> void: + gizmos.append(Gizmo.new(Gizmo.Type.SCALE, Vector2(-1, -1))) # Top left + gizmos.append(Gizmo.new(Gizmo.Type.SCALE, Vector2(0, -1))) # Center top + gizmos.append(Gizmo.new(Gizmo.Type.SCALE, Vector2(1, -1))) # Top right + gizmos.append(Gizmo.new(Gizmo.Type.SCALE, Vector2(1, 0))) # Center right + gizmos.append(Gizmo.new(Gizmo.Type.SCALE, Vector2(1, 1))) # Bottom right + gizmos.append(Gizmo.new(Gizmo.Type.SCALE, Vector2(0, 1))) # Center bottom + gizmos.append(Gizmo.new(Gizmo.Type.SCALE, Vector2(-1, 1))) # Bottom left + gizmos.append(Gizmo.new(Gizmo.Type.SCALE, Vector2(-1, 0))) # Center left + +# gizmos.append(Gizmo.new(Gizmo.Type.ROTATE)) # Rotation gizmo (temp) + + +func _input(event : InputEvent) -> void: + if event is InputEventKey: + if is_moving_content: # Temporary code + if event.scancode == 16777221: # Enter + move_content_confirm() + elif event.scancode == 16777217: # Escape + move_content_cancel() + elif event is InputEventMouse: + var gizmo : Gizmo + for g in gizmos: + if g.rect.has_point(Global.canvas.current_pixel): + gizmo = g + break + if gizmo: + Global.main_viewport.mouse_default_cursor_shape = gizmo.get_cursor() + elif !dragged_gizmo: + Global.main_viewport.mouse_default_cursor_shape = Input.CURSOR_CROSS + + if event is InputEventMouseButton and event.button_index == BUTTON_LEFT: + if event.pressed: + if gizmo: + Global.has_focus = false + mouse_pos_on_gizmo_drag = Global.canvas.current_pixel + dragged_gizmo = gizmo + temp_rect = big_bounding_rectangle + move_content_start() + Global.current_project.selection_offset = Vector2.ZERO + if gizmo.type == Gizmo.Type.ROTATE: + var img_size := max(original_preview_image.get_width(), original_preview_image.get_height()) + original_preview_image.crop(img_size, img_size) + + elif dragged_gizmo: + Global.has_focus = true + dragged_gizmo = null + + if dragged_gizmo: + if dragged_gizmo.type == Gizmo.Type.SCALE: + gizmo_resize() + else: + gizmo_rotate() + + +func _draw() -> void: + var _position := position + var _scale := scale + if Global.mirror_view: + _position.x = _position.x + Global.current_project.size.x + _scale.x = -1 + draw_set_transform(_position, rotation, _scale) + draw_rect(drawn_rect, Color.black, false) + if big_bounding_rectangle.size != Vector2.ZERO: + for gizmo in gizmos: # Draw gizmos + draw_rect(gizmo.rect, Color.black) + var filled_rect : Rect2 = gizmo.rect + var filled_size : Vector2 = gizmo.rect.size * Vector2(0.2, 0.2) + filled_rect.position += filled_size + filled_rect.size -= filled_size * 2 + draw_rect(filled_rect, Color.white) # Filled white square + + if is_moving_content and !preview_image.is_empty(): + draw_texture(preview_image_texture, big_bounding_rectangle.position, Color(1, 1, 1, 0.5)) + draw_set_transform(position, rotation, scale) + + +func _big_bounding_rectangle_changed(value : Rect2) -> void: + big_bounding_rectangle = value + update_gizmos() + + +func update_gizmos() -> void: + var rect_pos : Vector2 = big_bounding_rectangle.position + var rect_end : Vector2 = big_bounding_rectangle.end + var size := Vector2.ONE * Global.camera.zoom * 10 + # Clockwise, starting from top-left corner + gizmos[0].rect = Rect2(rect_pos - size, size) + gizmos[1].rect = Rect2(Vector2((rect_end.x + rect_pos.x - size.x) / 2, rect_pos.y - size.y), size) + gizmos[2].rect = Rect2(Vector2(rect_end.x, rect_pos.y - size.y), size) + gizmos[3].rect = Rect2(Vector2(rect_end.x, (rect_end.y + rect_pos.y - size.y) / 2), size) + gizmos[4].rect = Rect2(rect_end, size) + gizmos[5].rect = Rect2(Vector2((rect_end.x + rect_pos.x - size.x) / 2, rect_end.y), size) + gizmos[6].rect = Rect2(Vector2(rect_pos.x - size.x, rect_end.y), size) + gizmos[7].rect = Rect2(Vector2(rect_pos.x - size.x, (rect_end.y + rect_pos.y - size.y) / 2), size) + + # Rotation gizmo (temp) +# gizmos[8].rect = Rect2(Vector2((rect_end.x + rect_pos.x - size.x) / 2, rect_pos.y - size.y - (size.y * 2)), size) + update() + + +func update_on_zoom(zoom : float) -> void: + var size := max(Global.current_project.selection_bitmap.get_size().x, Global.current_project.selection_bitmap.get_size().y) + marching_ants_outline.material.set_shader_param("width", zoom) + marching_ants_outline.material.set_shader_param("frequency", (1.0 / zoom) * 10 * size / 64) + for gizmo in gizmos: + if gizmo.rect.size == Vector2.ZERO: + return + update_gizmos() + + +func gizmo_resize() -> void: + var diff : Vector2 = (Global.canvas.current_pixel - mouse_pos_on_gizmo_drag) * dragged_gizmo.direction + var dir := dragged_gizmo.direction + if diff != Vector2.ZERO: + mouse_pos_on_gizmo_drag = Global.canvas.current_pixel + var left := 0.0 if dir.x >= 0 else diff.x + var top := 0.0 if dir.y >= 0 else diff.y + var right := diff.x if dir.x >= 0 else 0.0 + var bottom := diff.y if dir.y >= 0 else 0.0 + temp_rect = temp_rect.grow_individual(left, top, right, bottom) + big_bounding_rectangle = temp_rect.abs() + big_bounding_rectangle.position = big_bounding_rectangle.position.ceil() + self.big_bounding_rectangle.size = big_bounding_rectangle.size.ceil() + var size = big_bounding_rectangle.size.abs() + preview_image.copy_from(original_preview_image) + preview_image.resize(size.x, size.y, Image.INTERPOLATE_NEAREST) + if temp_rect.size.x < 0: + preview_image.flip_x() + if temp_rect.size.y < 0: + preview_image.flip_y() + preview_image_texture.create_from_image(preview_image, 0) + Global.current_project.selection_bitmap = Global.current_project.resize_bitmap_values(original_bitmap, size, temp_rect.size.x < 0, temp_rect.size.y < 0) + Global.current_project.selection_bitmap_changed() + update() + + +func gizmo_rotate() -> void: # Does not work properly yet + var angle := Global.canvas.current_pixel.angle_to_point(mouse_pos_on_gizmo_drag) + angle = deg2rad(floor(rad2deg(angle))) + if angle == prev_angle: + return + prev_angle = angle +# var img_size := max(original_preview_image.get_width(), original_preview_image.get_height()) +# warning-ignore:integer_division +# warning-ignore:integer_division +# var pivot = Vector2(original_preview_image.get_width() / 2, original_preview_image.get_height() / 2) + var pivot = Vector2(big_bounding_rectangle.size.x / 2, big_bounding_rectangle.size.y / 2) + preview_image.copy_from(original_preview_image) + if original_big_bounding_rectangle.position != big_bounding_rectangle.position: + preview_image.fill(Color(0, 0, 0, 0)) + var pos_diff := (original_big_bounding_rectangle.position - big_bounding_rectangle.position).abs() +# pos_diff.y = 0 + preview_image.blit_rect(original_preview_image, Rect2(Vector2.ZERO, preview_image.get_size()), pos_diff) + DrawingAlgos.nn_rotate(preview_image, angle, pivot) + preview_image_texture.create_from_image(preview_image, 0) + + var bitmap_image = Global.current_project.bitmap_to_image(original_bitmap) + var bitmap_pivot = original_big_bounding_rectangle.position + ((original_big_bounding_rectangle.end - original_big_bounding_rectangle.position) / 2) + DrawingAlgos.nn_rotate(bitmap_image, angle, bitmap_pivot) + Global.current_project.selection_bitmap.create_from_image_alpha(bitmap_image) + Global.current_project.selection_bitmap_changed() + self.big_bounding_rectangle = bitmap_image.get_used_rect() + update() + + +func select_rect(rect : Rect2, select := true) -> void: + var project : Project = Global.current_project + var selection_bitmap_copy : BitMap = project.selection_bitmap.duplicate() + var offset_position := Vector2.ZERO # Used only if the selection is outside of the canvas boundaries, on the left and/or above (negative coords) + if big_bounding_rectangle.position.x < 0: + rect.position.x -= big_bounding_rectangle.position.x + offset_position.x = big_bounding_rectangle.position.x + if big_bounding_rectangle.position.y < 0: + rect.position.y -= big_bounding_rectangle.position.y + offset_position.y = big_bounding_rectangle.position.y + + if offset_position != Vector2.ZERO: + big_bounding_rectangle.position -= offset_position + project.move_bitmap_values(selection_bitmap_copy) + + selection_bitmap_copy.set_bit_rect(rect, select) + big_bounding_rectangle = project.get_selection_rectangle(selection_bitmap_copy) + + if offset_position != Vector2.ZERO: + big_bounding_rectangle.position += offset_position + project.move_bitmap_values(selection_bitmap_copy) + + project.selection_bitmap = selection_bitmap_copy + self.big_bounding_rectangle = big_bounding_rectangle # call getter method + + + +func move_borders_start() -> void: + undo_data = _get_undo_data(false) + + +func move_borders(move : Vector2) -> void: + marching_ants_outline.offset += move + self.big_bounding_rectangle.position += move + update() + + +func move_borders_end() -> void: + var selected_bitmap_copy = Global.current_project.selection_bitmap.duplicate() + Global.current_project.move_bitmap_values(selected_bitmap_copy) + + Global.current_project.selection_bitmap = selected_bitmap_copy + commit_undo("Rectangle Select", undo_data) + update() + + +func move_content_start() -> void: + if !is_moving_content: + is_moving_content = true + undo_data = _get_undo_data(true) + get_preview_image() + original_bitmap = Global.current_project.selection_bitmap.duplicate() + original_big_bounding_rectangle = big_bounding_rectangle + original_offset = Global.current_project.selection_offset + update() + + +func move_content(move : Vector2) -> void: + move_borders(move) + + +func move_content_confirm() -> void: + if !is_moving_content: + return + var project : Project = Global.current_project + var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image + cel_image.blit_rect_mask(preview_image, preview_image, Rect2(Vector2.ZERO, Global.current_project.selection_bitmap.get_size()), big_bounding_rectangle.position) + var selected_bitmap_copy = Global.current_project.selection_bitmap.duplicate() + Global.current_project.move_bitmap_values(selected_bitmap_copy) + Global.current_project.selection_bitmap = selected_bitmap_copy + + original_preview_image = Image.new() + preview_image = Image.new() + original_bitmap = BitMap.new() + is_moving_content = false + commit_undo("Move Selection", undo_data) + update() + + +func move_content_cancel() -> void: + if preview_image.is_empty(): + return + var project : Project = Global.current_project + project.selection_offset = original_offset + + is_moving_content = false + self.big_bounding_rectangle = original_big_bounding_rectangle + project.selection_bitmap = original_bitmap + project.selection_bitmap_changed() + preview_image = original_preview_image + var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image + cel_image.blit_rect_mask(preview_image, preview_image, Rect2(Vector2.ZERO, Global.current_project.selection_bitmap.get_size()), big_bounding_rectangle.position) + Global.canvas.update_texture(project.current_layer) + original_preview_image = Image.new() + preview_image = Image.new() + original_bitmap = BitMap.new() + update() + + +func commit_undo(action : String, _undo_data : Dictionary) -> void: + var redo_data = _get_undo_data("image_data" in _undo_data) + var project := Global.current_project + + project.undos += 1 + project.undo_redo.create_action(action) + project.undo_redo.add_do_property(project, "selection_bitmap", redo_data["selection_bitmap"]) + project.undo_redo.add_do_property(self, "big_bounding_rectangle", redo_data["big_bounding_rectangle"]) + project.undo_redo.add_do_property(project, "selection_offset", redo_data["outline_offset"]) + + project.undo_redo.add_undo_property(project, "selection_bitmap", _undo_data["selection_bitmap"]) + project.undo_redo.add_undo_property(self, "big_bounding_rectangle", _undo_data["big_bounding_rectangle"]) + project.undo_redo.add_undo_property(project, "selection_offset", _undo_data["outline_offset"]) + + + if "image_data" in _undo_data: + var image : Image = project.frames[project.current_frame].cels[project.current_layer].image + project.undo_redo.add_do_property(image, "data", redo_data["image_data"]) + project.undo_redo.add_undo_property(image, "data", _undo_data["image_data"]) + project.undo_redo.add_do_method(Global, "redo", project.current_frame, project.current_layer) + project.undo_redo.add_do_method(project, "selection_bitmap_changed") + project.undo_redo.add_undo_method(Global, "undo", project.current_frame, project.current_layer) + project.undo_redo.add_undo_method(project, "selection_bitmap_changed") + project.undo_redo.commit_action() + + undo_data.clear() + + +func _get_undo_data(undo_image : bool) -> Dictionary: + var data := {} + var project := Global.current_project + data["selection_bitmap"] = project.selection_bitmap + data["big_bounding_rectangle"] = big_bounding_rectangle + data["outline_offset"] = Global.current_project.selection_offset + + if undo_image: + var image : Image = project.frames[project.current_frame].cels[project.current_layer].image + image.unlock() + data["image_data"] = image.data + image.lock() + + return data + + +func cut() -> void: + copy() + delete() + + +func copy() -> void: + var project := Global.current_project + if !project.has_selection: + return + var image : Image = project.frames[project.current_frame].cels[project.current_layer].image + var to_copy := Image.new() + to_copy = image.get_rect(big_bounding_rectangle) + to_copy.lock() + # Only remove unincluded pixels if the selection is not a single rectangle + for x in to_copy.get_size().x: + for y in to_copy.get_size().y: + var pos := Vector2(x, y) + var offset_pos = big_bounding_rectangle.position + if offset_pos.x < 0: + offset_pos.x = 0 + if offset_pos.y < 0: + offset_pos.y = 0 + if not project.selection_bitmap.get_bit(pos + offset_pos): + to_copy.set_pixelv(pos, Color(0)) + to_copy.unlock() + clipboard.image = to_copy + clipboard.selection_bitmap = project.selection_bitmap.duplicate() + clipboard.big_bounding_rectangle = big_bounding_rectangle + clipboard.selection_offset = project.selection_offset + + +func paste() -> void: + if !clipboard.image: + return + var _undo_data = _get_undo_data(true) + var project := Global.current_project + var image : Image = project.frames[project.current_frame].cels[project.current_layer].image + clear_selection() + project.selection_bitmap = clipboard.selection_bitmap.duplicate() + self.big_bounding_rectangle = clipboard.big_bounding_rectangle + project.selection_offset = clipboard.selection_offset + image.blend_rect(clipboard.image, Rect2(Vector2.ZERO, project.size), big_bounding_rectangle.position) + commit_undo("Draw", _undo_data) + + +func delete() -> void: + var project := Global.current_project + if !project.has_selection: + return + var _undo_data = _get_undo_data(true) + var image : Image = project.frames[project.current_frame].cels[project.current_layer].image + for x in big_bounding_rectangle.size.x: + for y in big_bounding_rectangle.size.y: + var pos := Vector2(x, y) + big_bounding_rectangle.position + if project.can_pixel_get_drawn(pos): + image.set_pixelv(pos, Color(0)) + commit_undo("Draw", _undo_data) + + +func select_all() -> void: + var project := Global.current_project + var _undo_data = _get_undo_data(false) + clear_selection() + var full_rect = Rect2(Vector2.ZERO, project.size) + select_rect(full_rect) + commit_undo("Rectangle Select", _undo_data) + + +func invert() -> void: + move_content_confirm() + var project := Global.current_project + var _undo_data = _get_undo_data(false) + var selection_bitmap_copy : BitMap = project.selection_bitmap.duplicate() + selection_bitmap_copy = project.resize_bitmap(selection_bitmap_copy, project.size) + project.invert_bitmap(selection_bitmap_copy) + project.selection_bitmap = selection_bitmap_copy + Global.current_project.selection_bitmap_changed() + self.big_bounding_rectangle = project.get_selection_rectangle(selection_bitmap_copy) + project.selection_offset = Vector2.ZERO + commit_undo("Rectangle Select", _undo_data) + + +func clear_selection(use_undo := false) -> void: + var project := Global.current_project + if !project.has_selection: + return + move_content_confirm() + var _undo_data = _get_undo_data(false) + var selection_bitmap_copy : BitMap = project.selection_bitmap.duplicate() + selection_bitmap_copy = project.resize_bitmap(selection_bitmap_copy, project.size) + var full_rect = Rect2(Vector2.ZERO, selection_bitmap_copy.get_size()) + selection_bitmap_copy.set_bit_rect(full_rect, false) + project.selection_bitmap = selection_bitmap_copy + + self.big_bounding_rectangle = Rect2() + project.selection_offset = Vector2.ZERO + update() + if use_undo: + commit_undo("Clear Selection", _undo_data) + + +func get_preview_image() -> void: + var project : Project = Global.current_project + var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image + if original_preview_image.is_empty(): +# original_preview_image.copy_from(cel_image) + original_preview_image = cel_image.get_rect(big_bounding_rectangle) + original_preview_image.lock() + # For non-rectangular selections + for x in range(0, big_bounding_rectangle.size.x): + for y in range(0, big_bounding_rectangle.size.y): + var pos := Vector2(x, y) + if !project.can_pixel_get_drawn(pos + big_bounding_rectangle.position): + original_preview_image.set_pixelv(pos, Color(0, 0, 0, 0)) + + original_preview_image.unlock() + preview_image.copy_from(original_preview_image) + preview_image_texture.create_from_image(preview_image, 0) + + var clear_image := Image.new() + clear_image.create(original_preview_image.get_width(), original_preview_image.get_height(), false, Image.FORMAT_RGBA8) + cel_image.blit_rect_mask(clear_image, original_preview_image, Rect2(Vector2.ZERO, Global.current_project.selection_bitmap.get_size()), big_bounding_rectangle.position) + Global.canvas.update_texture(project.current_layer) + + +func get_big_bounding_rectangle() -> Rect2: + # Returns a rectangle that contains the entire selection, with multiple polygons + var project : Project = Global.current_project + var rect := Rect2() + var image : Image = project.bitmap_to_image(project.selection_bitmap) + rect = image.get_used_rect() + return rect diff --git a/src/UI/Dialogs/ExportDialog.gd b/src/UI/Dialogs/ExportDialog.gd index c54b21158..0186a2bce 100644 --- a/src/UI/Dialogs/ExportDialog.gd +++ b/src/UI/Dialogs/ExportDialog.gd @@ -222,6 +222,7 @@ func set_export_progress_bar(value: float) -> void: func _on_ExportDialog_about_to_show() -> void: + Global.canvas.selection.move_content_confirm() # If we're on HTML5, don't let the user change the directory path if OS.get_name() == "HTML5": path_container.visible = false diff --git a/src/UI/Dialogs/ImageEffects/DesaturateDialog.gd b/src/UI/Dialogs/ImageEffects/DesaturateDialog.gd index 3fc702001..ad73205f6 100644 --- a/src/UI/Dialogs/ImageEffects/DesaturateDialog.gd +++ b/src/UI/Dialogs/ImageEffects/DesaturateDialog.gd @@ -13,8 +13,8 @@ func set_nodes() -> void: affect_option_button = $VBoxContainer/OptionsContainer/AffectOptionButton -func commit_action(_cel : Image, _pixels : Array, _project : Project = Global.current_project) -> void: - DrawingAlgos.desaturate_image(_cel, _pixels, red, green, blue, alpha) +func commit_action(_cel : Image, _project : Project = Global.current_project) -> void: + DrawingAlgos.desaturate_image(_cel, selection_checkbox.pressed, _project, red, green, blue, alpha) func _on_RButton_toggled(button_pressed : bool) -> void: diff --git a/src/UI/Dialogs/ImageEffects/FlipImageDialog.gd b/src/UI/Dialogs/ImageEffects/FlipImageDialog.gd index a49260acd..d55df164a 100644 --- a/src/UI/Dialogs/ImageEffects/FlipImageDialog.gd +++ b/src/UI/Dialogs/ImageEffects/FlipImageDialog.gd @@ -11,8 +11,8 @@ func set_nodes() -> void: affect_option_button = $VBoxContainer/OptionsContainer/AffectOptionButton -func commit_action(_cel : Image, _pixels : Array, project : Project = Global.current_project) -> void: - flip_image(_cel, _pixels, project) +func commit_action(_cel : Image, project : Project = Global.current_project) -> void: + flip_image(_cel, selection_checkbox.pressed, project) func _on_FlipHorizontal_toggled(_button_pressed : bool) -> void: @@ -23,14 +23,8 @@ func _on_FlipVertical_toggled(_button_pressed : bool) -> void: update_preview() -func _on_SelectionCheckBox_toggled(button_pressed : bool) -> void: - ._on_SelectionCheckBox_toggled(button_pressed) - update_preview() - - -func flip_image(image : Image, _pixels : Array, project : Project = Global.current_project) -> void: - var entire_image_selected : bool = _pixels.size() == project.size.x * project.size.y - if entire_image_selected: +func flip_image(image : Image, affect_selection : bool, project : Project = Global.current_project) -> void: + if !(affect_selection and project.has_selection): if flip_h.pressed: image.flip_x() if flip_v.pressed: @@ -41,10 +35,13 @@ func flip_image(image : Image, _pixels : Array, project : Project = Global.curre selected_image.create(image.get_width(), image.get_height(), false, Image.FORMAT_RGBA8) selected_image.lock() image.lock() - for i in _pixels: - var color : Color = image.get_pixelv(i) - selected_image.set_pixelv(i, color) - image.set_pixelv(i, Color(0, 0, 0, 0)) + for x in image.get_width(): + for y in image.get_width(): + var pos := Vector2(x, y) + if project.can_pixel_get_drawn(pos): + var color : Color = image.get_pixelv(pos) + selected_image.set_pixelv(pos, color) + image.set_pixelv(pos, Color(0, 0, 0, 0)) if flip_h.pressed: selected_image.flip_x() diff --git a/src/UI/Dialogs/ImageEffects/GradientDialog.gd b/src/UI/Dialogs/ImageEffects/GradientDialog.gd index 0e0c87a40..83091075d 100644 --- a/src/UI/Dialogs/ImageEffects/GradientDialog.gd +++ b/src/UI/Dialogs/ImageEffects/GradientDialog.gd @@ -18,8 +18,8 @@ func set_nodes() -> void: affect_option_button = $VBoxContainer/OptionsContainer/AffectOptionButton -func commit_action(_cel : Image, _pixels : Array, _project : Project = Global.current_project) -> void: - DrawingAlgos.generate_gradient(_cel, [color1.color, color2.color], steps.value, direction.selected, _pixels) +func commit_action(_cel : Image, _project : Project = Global.current_project) -> void: + DrawingAlgos.generate_gradient(_cel, [color1.color, color2.color], steps.value, direction.selected, selection_checkbox.pressed, _project) func _on_ColorPickerButton_color_changed(_color : Color) -> void: diff --git a/src/UI/Dialogs/ImageEffects/HSVDialog.gd b/src/UI/Dialogs/ImageEffects/HSVDialog.gd index c77a0d694..2ab3b4a03 100644 --- a/src/UI/Dialogs/ImageEffects/HSVDialog.gd +++ b/src/UI/Dialogs/ImageEffects/HSVDialog.gd @@ -21,8 +21,8 @@ func _confirmed() -> void: reset() -func commit_action(_cel : Image, _pixels : Array, _project : Project = Global.current_project) -> void: - DrawingAlgos.adjust_hsv(_cel, hue_slider.value, sat_slider.value, val_slider.value, _pixels) +func commit_action(_cel : Image, _project : Project = Global.current_project) -> void: + DrawingAlgos.adjust_hsv(_cel, hue_slider.value, sat_slider.value, val_slider.value, selection_checkbox.pressed, _project) func reset() -> void: diff --git a/src/UI/Dialogs/ImageEffects/InvertColorsDialog.gd b/src/UI/Dialogs/ImageEffects/InvertColorsDialog.gd index 08d05c662..a1cd387e3 100644 --- a/src/UI/Dialogs/ImageEffects/InvertColorsDialog.gd +++ b/src/UI/Dialogs/ImageEffects/InvertColorsDialog.gd @@ -13,8 +13,8 @@ func set_nodes() -> void: affect_option_button = $VBoxContainer/OptionsContainer/AffectOptionButton -func commit_action(_cel : Image, _pixels : Array, _project : Project = Global.current_project) -> void: - DrawingAlgos.invert_image_colors(_cel, _pixels, red, green, blue, alpha) +func commit_action(_cel : Image, _project : Project = Global.current_project) -> void: + DrawingAlgos.invert_image_colors(_cel, selection_checkbox.pressed, _project, red, green, blue, alpha) func _on_RButton_toggled(button_pressed : bool) -> void: diff --git a/src/UI/Dialogs/ImageEffects/OutlineDialog.gd b/src/UI/Dialogs/ImageEffects/OutlineDialog.gd index f6c1f5297..8df287320 100644 --- a/src/UI/Dialogs/ImageEffects/OutlineDialog.gd +++ b/src/UI/Dialogs/ImageEffects/OutlineDialog.gd @@ -20,8 +20,8 @@ func set_nodes() -> void: affect_option_button = $VBoxContainer/OptionsContainer/AffectOptionButton -func commit_action(_cel : Image, _pixels : Array, _project : Project = Global.current_project) -> void: - DrawingAlgos.generate_outline(_cel, _pixels, color, thickness, diagonal, inside_image) +func commit_action(_cel : Image, _project : Project = Global.current_project) -> void: + DrawingAlgos.generate_outline(_cel, selection_checkbox.pressed, _project, color, thickness, diagonal, inside_image) func _on_ThickValue_value_changed(value : int) -> void: diff --git a/src/UI/Dialogs/ImageEffects/ResizeCanvas.gd b/src/UI/Dialogs/ImageEffects/ResizeCanvas.gd index ee26c695a..198cabf09 100644 --- a/src/UI/Dialogs/ImageEffects/ResizeCanvas.gd +++ b/src/UI/Dialogs/ImageEffects/ResizeCanvas.gd @@ -16,6 +16,7 @@ onready var preview_rect : TextureRect = $VBoxContainer/Preview func _on_ResizeCanvas_about_to_show() -> void: + Global.canvas.selection.move_content_confirm() image = Image.new() image.create(Global.current_project.size.x, Global.current_project.size.y, false, Image.FORMAT_RGBA8) image.lock() diff --git a/src/UI/Dialogs/ImageEffects/RotateImage.gd b/src/UI/Dialogs/ImageEffects/RotateImage.gd index 8aa5dcd95..465ef6322 100644 --- a/src/UI/Dialogs/ImageEffects/RotateImage.gd +++ b/src/UI/Dialogs/ImageEffects/RotateImage.gd @@ -23,15 +23,38 @@ func _about_to_show() -> void: angle_hslider.value = 0 -func commit_action(_cel : Image, _pixels : Array, _project : Project = Global.current_project) -> void: +func commit_action(_cel : Image, _project : Project = Global.current_project) -> void: var angle : float = deg2rad(angle_hslider.value) +# warning-ignore:integer_division +# warning-ignore:integer_division + var pivot = Vector2(_cel.get_width() / 2, _cel.get_height() / 2) + var image := Image.new() + image.copy_from(_cel) + if _project.has_selection and selection_checkbox.pressed: + var selection_rectangle : Rect2 = _project.get_selection_rectangle() + pivot = selection_rectangle.position + ((selection_rectangle.end - selection_rectangle.position) / 2) + image.lock() + _cel.lock() + for x in _project.size.x: + for y in _project.size.y: + var pos := Vector2(x, y) + if !_project.can_pixel_get_drawn(pos): + image.set_pixelv(pos, Color(0, 0, 0, 0)) + else: + _cel.set_pixelv(pos, Color(0, 0, 0, 0)) + image.unlock() + _cel.unlock() match type_option_button.text: "Rotxel": - DrawingAlgos.rotxel(_cel, angle, _pixels) + DrawingAlgos.rotxel(image, angle, pivot) "Nearest neighbour": - DrawingAlgos.nn_rotate(_cel, angle, _pixels) + DrawingAlgos.nn_rotate(image, angle, pivot) "Upscale, Rotate and Downscale": - DrawingAlgos.fake_rotsprite(_cel, angle, _pixels) + DrawingAlgos.fake_rotsprite(image, angle, pivot) + if _project.has_selection and selection_checkbox.pressed: + _cel.blend_rect(image, Rect2(Vector2.ZERO, image.get_size()), Vector2.ZERO) + else: + _cel.blit_rect(image, Rect2(Vector2.ZERO, image.get_size()), Vector2.ZERO) func _confirmed() -> void: diff --git a/src/UI/Dialogs/ImageEffects/ScaleImage.gd b/src/UI/Dialogs/ImageEffects/ScaleImage.gd index 89b5df288..1d4ac430b 100644 --- a/src/UI/Dialogs/ImageEffects/ScaleImage.gd +++ b/src/UI/Dialogs/ImageEffects/ScaleImage.gd @@ -12,6 +12,7 @@ onready var ratio_box : BaseButton = find_node("AspectRatioButton") func _on_ScaleImage_about_to_show() -> void: + Global.canvas.selection.move_content_confirm() width_value.value = Global.current_project.size.x height_value.value = Global.current_project.size.y width_value_perc.value = 100 diff --git a/src/UI/ToolButtons.gd b/src/UI/ToolButtons.gd index fc7643873..178514296 100644 --- a/src/UI/ToolButtons.gd +++ b/src/UI/ToolButtons.gd @@ -4,6 +4,7 @@ extends VBoxContainer # Node, shortcut onready var tools := [ [$RectSelect, "rectangle_select"], + [$Move, "move"], [$Zoom, "zoom"], [$Pan, "pan"], [$ColorPicker, "colorpicker"], diff --git a/src/UI/TopMenuContainer.gd b/src/UI/TopMenuContainer.gd index 9607478f2..69b76353d 100644 --- a/src/UI/TopMenuContainer.gd +++ b/src/UI/TopMenuContainer.gd @@ -2,21 +2,37 @@ extends Panel enum FileMenuId {NEW, OPEN, OPEN_LAST_PROJECT, SAVE, SAVE_AS, EXPORT, EXPORT_AS, QUIT} -enum EditMenuId {UNDO, REDO, COPY, CUT, PASTE, DELETE, CLEAR_SELECTION, PREFERENCES} +enum EditMenuId {UNDO, REDO, COPY, CUT, PASTE, DELETE, PREFERENCES} enum ViewMenuId {TILE_MODE, WINDOW_TRANSPARENCY, PANEL_LAYOUT, MIRROR_VIEW, SHOW_GRID, SHOW_PIXEL_GRID, SHOW_RULERS, SHOW_GUIDES, SHOW_ANIMATION_TIMELINE, ZEN_MODE, FULLSCREEN_MODE} -enum ImageMenuId {SCALE_IMAGE,CENTRALIZE_IMAGE, CROP_IMAGE, RESIZE_CANVAS, FLIP, ROTATE, INVERT_COLORS, DESATURATION, OUTLINE, HSV, GRADIENT, SHADER} +enum ImageMenuId {SCALE_IMAGE, CENTRALIZE_IMAGE, CROP_IMAGE, RESIZE_CANVAS, FLIP, ROTATE, INVERT_COLORS, DESATURATION, OUTLINE, HSV, GRADIENT, SHADER} +enum SelectMenuId {SELECT_ALL, CLEAR_SELECTION, INVERT} enum HelpMenuId {VIEW_SPLASH_SCREEN, ONLINE_DOCS, ISSUE_TRACKER, CHANGELOG, ABOUT_PIXELORAMA} +var file_menu_button : MenuButton +var edit_menu_button : MenuButton +var view_menu_button : MenuButton +var image_menu_button : MenuButton +var select_menu_button : MenuButton +var help_menu_button : MenuButton + var file_menu : PopupMenu var view_menu : PopupMenu var zen_mode := false func _ready() -> void: + file_menu_button = find_node("FileMenu") + edit_menu_button = find_node("EditMenu") + view_menu_button = find_node("ViewMenu") + image_menu_button = find_node("ImageMenu") + select_menu_button = find_node("SelectMenu") + help_menu_button = find_node("HelpMenu") + setup_file_menu() setup_edit_menu() setup_view_menu() setup_image_menu() + setup_select_menu() setup_help_menu() @@ -32,7 +48,7 @@ func setup_file_menu() -> void: "Export as..." : InputMap.get_action_list("export_file_as")[0].get_scancode_with_modifiers(), "Quit" : InputMap.get_action_list("quit")[0].get_scancode_with_modifiers(), } - file_menu = Global.file_menu.get_popup() + file_menu = file_menu_button.get_popup() var i := 0 for item in file_menu_items.keys(): @@ -65,10 +81,9 @@ func setup_edit_menu() -> void: "Cut" : InputMap.get_action_list("cut")[0].get_scancode_with_modifiers(), "Paste" : InputMap.get_action_list("paste")[0].get_scancode_with_modifiers(), "Delete" : InputMap.get_action_list("delete")[0].get_scancode_with_modifiers(), - "Clear Selection" : InputMap.get_action_list("clear_selection")[0].get_scancode_with_modifiers(), "Preferences" : 0 } - var edit_menu : PopupMenu = Global.edit_menu.get_popup() + var edit_menu : PopupMenu = edit_menu_button.get_popup() var i := 0 for item in edit_menu_items.keys(): @@ -92,7 +107,7 @@ func setup_view_menu() -> void: "Zen Mode" : InputMap.get_action_list("zen_mode")[0].get_scancode_with_modifiers(), "Fullscreen Mode" : InputMap.get_action_list("toggle_fullscreen")[0].get_scancode_with_modifiers(), } - view_menu = Global.view_menu.get_popup() + view_menu = view_menu_button.get_popup() var i := 0 for item in view_menu_items.keys(): @@ -147,7 +162,7 @@ func setup_image_menu() -> void: "Gradient" : 0, # "Shader" : 0 } - var image_menu : PopupMenu = Global.image_menu.get_popup() + var image_menu : PopupMenu = image_menu_button.get_popup() var i := 0 for item in image_menu_items.keys(): @@ -159,6 +174,22 @@ func setup_image_menu() -> void: image_menu.connect("id_pressed", self, "image_menu_id_pressed") +func setup_select_menu() -> void: + var select_menu_items := { # order as in EditMenuId enum + "Select All" : InputMap.get_action_list("select_all")[0].get_scancode_with_modifiers(), + "Clear Selection" : InputMap.get_action_list("clear_selection")[0].get_scancode_with_modifiers(), + "Invert" : InputMap.get_action_list("invert_selection")[0].get_scancode_with_modifiers(), + } + var select_menu : PopupMenu = select_menu_button.get_popup() + var i := 0 + + for item in select_menu_items.keys(): + select_menu.add_item(item, i, select_menu_items[item]) + i += 1 + + select_menu.connect("id_pressed", self, "select_menu_id_pressed") + + func setup_help_menu() -> void: var help_menu_items := { # order as in HelpMenuId enum "View Splash Screen" : 0, @@ -167,7 +198,7 @@ func setup_help_menu() -> void: "Changelog" : 0, "About Pixelorama" : 0 } - var help_menu : PopupMenu = Global.help_menu.get_popup() + var help_menu : PopupMenu = help_menu_button.get_popup() var i := 0 for item in help_menu_items.keys(): @@ -259,22 +290,17 @@ func on_recent_projects_submenu_id_pressed(id : int) -> void: func edit_menu_id_pressed(id : int) -> void: match id: EditMenuId.UNDO: - Global.current_project.undo_redo.undo() + Global.current_project.commit_undo() EditMenuId.REDO: - Global.control.redone = true - Global.current_project.undo_redo.redo() - Global.control.redone = false + Global.current_project.commit_redo() EditMenuId.COPY: - Global.selection_rectangle.copy() + Global.canvas.selection.copy() EditMenuId.CUT: - Global.selection_rectangle.cut() + Global.canvas.selection.cut() EditMenuId.PASTE: - Global.selection_rectangle.paste() + Global.canvas.selection.paste() EditMenuId.DELETE: - Global.selection_rectangle.delete() - EditMenuId.CLEAR_SELECTION: - Global.selection_rectangle.set_rect(Rect2(0, 0, 0, 0)) - Global.selection_rectangle.select_rect() + Global.canvas.selection.delete() EditMenuId.PREFERENCES: Global.preferences_dialog.popup_centered(Vector2(400, 280)) Global.dialog_open(true) @@ -343,6 +369,12 @@ func window_transparency(value :float) -> void: func toggle_mirror_view() -> void: Global.mirror_view = !Global.mirror_view + Global.canvas.selection.marching_ants_outline.scale.x = -Global.canvas.selection.marching_ants_outline.scale.x + if Global.mirror_view: + Global.canvas.selection.marching_ants_outline.position.x = Global.canvas.selection.marching_ants_outline.position.x + Global.current_project.size.x + else: + Global.canvas.selection.marching_ants_outline.position.x = 0 + Global.canvas.selection.update() view_menu.set_item_checked(ViewMenuId.MIRROR_VIEW, Global.mirror_view) @@ -476,6 +508,16 @@ func show_hsv_configuration_popup() -> void: Global.dialog_open(true) +func select_menu_id_pressed(id : int) -> void: + match id: + SelectMenuId.SELECT_ALL: + Global.canvas.selection.select_all() + SelectMenuId.CLEAR_SELECTION: + Global.canvas.selection.clear_selection(true) + SelectMenuId.INVERT: + Global.canvas.selection.invert() + + func help_menu_id_pressed(id : int) -> void: match id: HelpMenuId.VIEW_SPLASH_SCREEN: diff --git a/src/UI/TopMenuContainer.tscn b/src/UI/TopMenuContainer.tscn index ad7ecb034..1e9b3bd1d 100644 --- a/src/UI/TopMenuContainer.tscn +++ b/src/UI/TopMenuContainer.tscn @@ -21,7 +21,7 @@ __meta__ = { [node name="FileMenu" type="MenuButton" parent="MenuItems"] margin_right = 35.0 -margin_bottom = 23.0 +margin_bottom = 20.0 mouse_default_cursor_shape = 2 text = "File" switch_on_hover = true @@ -29,7 +29,7 @@ switch_on_hover = true [node name="EditMenu" type="MenuButton" parent="MenuItems"] margin_left = 39.0 margin_right = 75.0 -margin_bottom = 23.0 +margin_bottom = 20.0 mouse_default_cursor_shape = 2 text = "Edit" switch_on_hover = true @@ -37,7 +37,7 @@ switch_on_hover = true [node name="ViewMenu" type="MenuButton" parent="MenuItems"] margin_left = 79.0 margin_right = 121.0 -margin_bottom = 23.0 +margin_bottom = 20.0 mouse_default_cursor_shape = 2 text = "View" switch_on_hover = true @@ -45,15 +45,23 @@ switch_on_hover = true [node name="ImageMenu" type="MenuButton" parent="MenuItems"] margin_left = 125.0 margin_right = 177.0 -margin_bottom = 23.0 +margin_bottom = 20.0 mouse_default_cursor_shape = 2 text = "Image" switch_on_hover = true -[node name="HelpMenu" type="MenuButton" parent="MenuItems"] +[node name="SelectMenu" type="MenuButton" parent="MenuItems"] margin_left = 181.0 -margin_right = 223.0 -margin_bottom = 23.0 +margin_right = 232.0 +margin_bottom = 20.0 +mouse_default_cursor_shape = 2 +text = "Select" +switch_on_hover = true + +[node name="HelpMenu" type="MenuButton" parent="MenuItems"] +margin_left = 236.0 +margin_right = 278.0 +margin_bottom = 20.0 mouse_default_cursor_shape = 2 text = "Help" switch_on_hover = true diff --git a/src/UI/UI.tscn b/src/UI/UI.tscn index 1084e4a05..5b177614c 100644 --- a/src/UI/UI.tscn +++ b/src/UI/UI.tscn @@ -7,7 +7,6 @@ [ext_resource path="res://src/UI/TransparentChecker.tscn" type="PackedScene" id=5] [ext_resource path="res://src/UI/Canvas/Rulers/HorizontalRuler.gd" type="Script" id=6] [ext_resource path="res://src/UI/Canvas/CameraMovement.gd" type="Script" id=7] -[ext_resource path="res://src/SelectionRectangle.gd" type="Script" id=8] [ext_resource path="res://src/Shaders/TransparentChecker.shader" type="Shader" id=9] [ext_resource path="res://assets/graphics/dark_themes/tools/bucket.png" type="Texture" id=10] [ext_resource path="res://assets/graphics/dark_themes/tools/colorpicker.png" type="Texture" id=11] @@ -25,6 +24,7 @@ [ext_resource path="res://src/UI/ViewportContainer.gd" type="Script" id=23] [ext_resource path="res://assets/graphics/dark_themes/tools/rectangletool.png" type="Texture" id=24] [ext_resource path="res://assets/graphics/dark_themes/tools/ellipsetool.png" type="Texture" id=25] +[ext_resource path="res://assets/graphics/dark_themes/tools/move.png" type="Texture" id=26] [sub_resource type="ShaderMaterial" id=1] shader = ExtResource( 9 ) @@ -88,7 +88,7 @@ __meta__ = { margin_left = 7.0 margin_top = 7.0 margin_right = 39.0 -margin_bottom = 363.0 +margin_bottom = 399.0 size_flags_horizontal = 4 size_flags_vertical = 0 script = ExtResource( 1 ) @@ -113,7 +113,7 @@ __meta__ = { "_edit_use_anchors_": false } -[node name="Zoom" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ +[node name="Move" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] margin_top = 36.0 @@ -123,6 +123,24 @@ rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 +[node name="TextureRect" type="TextureRect" parent="ToolPanel/PanelContainer/ToolButtons/Move"] +margin_right = 32.0 +margin_bottom = 32.0 +texture = ExtResource( 26 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Zoom" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ +"UIButtons", +]] +margin_top = 72.0 +margin_right = 32.0 +margin_bottom = 104.0 +rect_min_size = Vector2( 32, 32 ) +mouse_default_cursor_shape = 2 +button_mask = 3 + [node name="TextureRect" type="TextureRect" parent="ToolPanel/PanelContainer/ToolButtons/Zoom"] margin_right = 32.0 margin_bottom = 32.0 @@ -134,9 +152,9 @@ __meta__ = { [node name="Pan" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 72.0 +margin_top = 108.0 margin_right = 32.0 -margin_bottom = 104.0 +margin_bottom = 140.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 @@ -152,9 +170,9 @@ __meta__ = { [node name="ColorPicker" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 108.0 +margin_top = 144.0 margin_right = 32.0 -margin_bottom = 140.0 +margin_bottom = 176.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 @@ -170,9 +188,9 @@ __meta__ = { [node name="Pencil" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 144.0 +margin_top = 180.0 margin_right = 32.0 -margin_bottom = 176.0 +margin_bottom = 212.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 @@ -188,9 +206,9 @@ __meta__ = { [node name="Eraser" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 180.0 +margin_top = 216.0 margin_right = 32.0 -margin_bottom = 212.0 +margin_bottom = 248.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 @@ -206,9 +224,9 @@ __meta__ = { [node name="Bucket" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 216.0 +margin_top = 252.0 margin_right = 32.0 -margin_bottom = 248.0 +margin_bottom = 284.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 @@ -224,9 +242,9 @@ __meta__ = { [node name="LightenDarken" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 252.0 +margin_top = 288.0 margin_right = 32.0 -margin_bottom = 284.0 +margin_bottom = 320.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 @@ -242,9 +260,9 @@ __meta__ = { [node name="RectangleTool" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 288.0 +margin_top = 324.0 margin_right = 32.0 -margin_bottom = 320.0 +margin_bottom = 356.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 @@ -260,9 +278,9 @@ __meta__ = { [node name="EllipseTool" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 324.0 +margin_top = 360.0 margin_right = 32.0 -margin_bottom = 356.0 +margin_bottom = 392.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 @@ -367,15 +385,6 @@ current = true zoom = Vector2( 0.15, 0.15 ) script = ExtResource( 7 ) -[node name="SelectionRectangle" type="Polygon2D" parent="CanvasAndTimeline/ViewportAndRulers/HSplitContainer/ViewportandVerticalRuler/ViewportContainer/Viewport"] -visible = false -z_index = 1 -color = Color( 0.0823529, 0.694118, 0.623529, 0.592157 ) -invert_enable = true -invert_border = 0.5 -polygon = PoolVector2Array( 0, 0, 0, 0, 0, 0, 0, 0 ) -script = ExtResource( 8 ) - [node name="ViewportContainer2" type="ViewportContainer" parent="CanvasAndTimeline/ViewportAndRulers/HSplitContainer"] margin_left = 902.0 margin_right = 902.0