1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-02-20 12:33:14 +00:00

New selection system (#474)

* Basic move tool

* Added marching ants effect on the selection borders

* Rename SelectionRectangle to SelectionShape, make it have non-rectangular shape and multiple SelectionShapes can exist

- Create multiple selection rectangles
- Merge them together if they intersect
- Move the selections (without contents as of right now)
- Gizmos are being drawn but they are not functional yet

Code is very ugly.

* Sort vectors counter-clockwise to be used as polygon borders

I did this, no idea if it works properly, probably won't be used but I thought I'd keep it saved somewhere

* More experiments I may or may not need

Trying to generate a polygon from the individual selected pixels

* Change default rectangle select behavior and ability to clip polygons using Control

* Fix rectangle selection clipping

* Split polygon into two with selection subtracting

* Move selection with contents with the move tool

Code is still a mess, don't bother looking.

* Move some methods from SelectionShape.gd to Selection.gd

The purpose of this is to generalize some selection code, so that it applies to all polygons, the entire selection. More will follow.

* UndoRedo for border moving

Nothing else in the selections system works properly in UndoRedo right now. Needs:

- UR support for creating selections
- UR support for modifying selections (merging and cutting selections together)
- UR support for removing selection
- UR support for moving content

& for all the rest of the remaining features

* Moving all of the selection shape logic to Selection.gd

Handle all of the polygons there instead of having them as individual nodes. Should be easier to handle undo/redo this way. This commit probably breaks move tool + selection tool and undo/redo. Code is still a mess. For your sanity, I hope you are not reading this. I promise I will clean up.

* Move tool works again

Buggy and messy, of course.

* Remove unneeded code and restore selection move undoredo logic

* Made Selection.gd have one big preview_image for when moving content, instead of each polygon having its own image

Could be further optimized for some specific cases. We could also remove selected_pixels from SelectionPolygon.

* UndoRedo support for creating, deleting, merging and clipping selections

UndoRedo support for moving content not added in this commit. Should work but needs more testing. This PR also removes selected_pixels from the SelectionPolygon class.

* Confirm & cancel selection movement, should support undoredo properly too

Press Enter or do any editing to confirm movement, Escape to cancel. I will most likely add UI buttons for confirm and cancel too.

* Mirror View affects selection

* Restore Cut, Copy, Paste and Clear Selection

Pasting now no longer requires a pre-existing selection and instead copies the selections themselves too.

* Created a new Select menu, which has Select All and Clear Selection as options

Clear Selection now also confirms content moving. TopMenuContainer code has changed to no longer rely on Global for the menu buttons.

* Draw gizmos as rectangles

No functionality yet. They may need to be turned to nodes, so that they can easily resize based on zoom level and check for mouse enter/exit events.

* Made gizmos get drawn in the sides and corners of the big bounding rectangle instead of individual selection parts

Still no functionality yet.

* Restore label text

* Minor optimization when clipping selections

This will execute the for loop less times

* Made a Gizmo class, cursor change on hover, has_focus = false on mouse click

Now I should actually make them resize when dragged, aye?

* Very basic gizmo resizing, still a WIP, does not work properly yet

* Start replacing the array of selected pixels with a BitMap

This should optimize the selection making a lot, and it also allows for easy border drawing without having to deal with polygons, thanks to the MarchingAntsOutline.shader

Still commit is still a WIP, image effects and brushes may not work properly yet.

Because the BitMap has a fixed size, the size of the project, moving the selection outside of canvas boundaries has proven to be a bit tricky. I did implement a hacky way of handling it, but it may be buggy and problematic. I'm still unsure whether this is the best way to handle the situation.

* Selection works with mirror view

* Draw a black rectangle when the user is making a rectangular selection

After they release the mouse, the black rectangle becomes the selection

* Make Selection.gd update when undoing/redoing

* Fix brushes not working properly with non-rectangular selections

* Added invert selection

* Cache has_selection as a variable for a speedup

* Fix conflict issues with the shape tools

* Made the bitmap image squared so the marching ants effect will be the same on both dimensions

There may be a better way to fix the issue, perhaps inside the shader itself.

* Some optimizations to call selection_bitmap_changed() less times

* Restored almost all of the image effects

Left to do:
- Change gradient's behavior. Unsure of how it will work with non-rectangular selections yet, but it should not generate pixels outside of the selection.
- Restore rotation
- Resize bitmap on image resize
- Remove the `pixels` array from the ImageEffect

* Fix Selection.gd not updating when changing project

* Resize the selection bitmap along with image resize

* Restored rotation's old behavior and finally got rid of the selected_pixels array

The rotation does not yet work properly with selections, but at least it now "works".

* Resize selection too when using gizmos

Left to do for gizmos:
- Proper cancel transformation
- Begin transformation
(currently named move_content_start but it should be renamed to something more general) when resizing gizmos
- Keep the original image and selection in memory and resize them. Meaning, gizmos should not resize the already resized data, but only resize the original. This is less destructive as there is no danger of data loss.
- Always resize on InputEventMouseMotion. This is going to be worse for performance, but it will look better for the user.

* Image and bitmap resizing now uses the original data and begin transformation on gizmo click

No matter how many times the user resizes on the current transformation, the original data will not be lost until they either confirm or cancel, so there is no data loss before confirmation/cancel.

* Cancel transformation now works properly when the selection has been resized

* Made gizmos resize on mouse motion, fix issues with negative bounding rectangle and when combined with the move tool

* Resizing can now get out of positive bounds, clearing and inverting now gets limited to the canvas bounds

Resizing currently does not work properly with negative (left & up) canvas boundaries

* Flip image when resizing and the bounding rectangle gets flipped

* Call move_content_confirm() when inverting selection

* Attempt to implement selection resizing that goes outside of the canvas boundaries (not working properly yet)

* Flip selection when resizing to negative bounding rectangle sizes

And fix preview_image vertical flipping

* Fix rotation so that it works (almost) properly with selections

Rotation algorithms now accept and only work with a given image, and the pivot has been added as a parameter

* Experimental gizmo rotation - does not work properly yet

Transforming the selection outside of the canvas is still broken.

* Fix some issues with moving selection out of canvas bounds

* Fix more issues with selection getting resized outside of canvas bounds

* Update marching ants effect properly when switching between projects

And make sure the frequency of the marching ants effect always looks roughly the same on all project sizes

* Made the rotation gizmo part of the gizmos array and resize them based on camera zoom

* Remove unneeded parameter from move_bitmap_values()

* Remove more unneeded parameters

* Move the selection only if the cursor is above it and neither shift nor control are currently pressed

* Gradient generation now works on non-rectangular selections

Although this behavior might not be the intended one

* Copy/paste marching ants effect offset

Useful for when the selection is in negative coords

* Fix issue with clear selection & UndoRedo

* Restore the ability to move selection when it's in negative coords

* Made the marching ants offset a Project variable

This fixes the issue of project switching and keeping the previous project's offset. Again, this is only relevant for when the selection is in negative coords.

* Made the "from current selection" palette preset work with the new selection system

* Fix out of bounds error when using the rectangular select tool on negative coords

* Some code cleanup

* Comment out the rotation gizmo for now, since it does not work properly

* Update marching ants shader params and gizmo sizes when the bitmap changes

* Move some methods around in Selection.gd
This commit is contained in:
Manolis Papadeas 2021-04-17 11:30:12 -07:00 committed by GitHub
parent 0c54470209
commit 27cb0d2d2f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 1468 additions and 784 deletions

View file

@ -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]

View file

@ -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])

View file

@ -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):

View file

@ -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:

View file

@ -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)

View file

@ -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)

View file

@ -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",

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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;
}
}

View file

@ -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:

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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

40
src/Tools/Move.gd Normal file
View file

@ -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

23
src/Tools/Move.tscn Normal file
View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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()

View file

@ -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 )

498
src/UI/Canvas/Selection.gd Normal file
View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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()

View file

@ -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:

View file

@ -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:

View file

@ -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:

View file

@ -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:

View file

@ -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()

View file

@ -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:

View file

@ -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

View file

@ -4,6 +4,7 @@ extends VBoxContainer
# Node, shortcut
onready var tools := [
[$RectSelect, "rectangle_select"],
[$Move, "move"],
[$Zoom, "zoom"],
[$Pan, "pan"],
[$ColorPicker, "colorpicker"],

View file

@ -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:

View file

@ -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

View file

@ -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