Compare commits
34 commits
a984004b94
...
18be79a6e1
Author | SHA1 | Date | |
---|---|---|---|
|
18be79a6e1 | ||
|
c72a1f4b90 | ||
|
be8b7728e4 | ||
|
31981a1def | ||
|
7f4c7a6bf1 | ||
|
41ea287df4 | ||
|
a3e372c5d8 | ||
|
6224d06428 | ||
|
6459151549 | ||
|
fe6efb0f1d | ||
|
8b1367494d | ||
|
01b55aca07 | ||
|
5f53a3eb7b | ||
|
658477ed4b | ||
|
3fb8484ac5 | ||
|
0484b1012f | ||
|
b87a8e2ab8 | ||
|
e6c4a72158 | ||
|
1dcb696c35 | ||
|
d580523c6e | ||
|
11da07b9ac | ||
|
7cf87ac142 | ||
|
bd7d3b19cc | ||
|
996a234d0d | ||
|
77f6bcf07b | ||
|
fede2d8e6f | ||
|
d0ecf3b03d | ||
|
3d65e48c92 | ||
|
aa1731b701 | ||
|
558140b309 | ||
|
849b815562 | ||
|
3615ce087c | ||
|
2d28136449 | ||
|
74d95c2424 |
22
CHANGELOG.md
|
@ -4,6 +4,28 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). All the dates are in YYYY-MM-DD format.
|
||||
<br><br>
|
||||
|
||||
## [v1.1] - Unreleased
|
||||
This update has been brought to you by the contributions of:
|
||||
Fayez Akhtar ([@Variable-ind](https://github.com/Variable-ind))
|
||||
|
||||
Built using Godot 4.3
|
||||
|
||||
### Added
|
||||
- Indexed mode has finally been implemented! [#1136](https://github.com/Orama-Interactive/Pixelorama/pull/1136)
|
||||
- Added a new text tool. Destructive only for now, meaning that once the text is confirmed, it cannot be changed later. [#1134](https://github.com/Orama-Interactive/Pixelorama/pull/1134)
|
||||
- Implemented support for multiple grids. [#1122](https://github.com/Orama-Interactive/Pixelorama/pull/1122)
|
||||
|
||||
### Changed
|
||||
- System font names are now sorted by alphabetical order.
|
||||
|
||||
### Fixed
|
||||
- Fixed crash when Pixelorama starts without a palette.
|
||||
- Undo/redo now works again when the cursor is hovering over the timeline.
|
||||
- Palette swatches now get deleted when the user removes all palettes
|
||||
- Fixed the Palettize effect and palette exporting to images storing slightly wrong color values. [77f6bcf](https://github.com/Orama-Interactive/Pixelorama/commit/77f6bcf07bd80bc042e478bb883d05900cebe436)
|
||||
- Fixed some issues with the Palettize effect where the output would be different if the palette size changed and empty swatches were added, even if the colors themselves stayed the same. Initially fixed by [bd7d3b1](https://github.com/Orama-Interactive/Pixelorama/commit/bd7d3b19cc98804e9b99754153c4d553d2048ee3), but [1dcb696](https://github.com/Orama-Interactive/Pixelorama/commit/1dcb696c35121f8208bde699f87bb75deff99d13) is the proper fix.
|
||||
- Fixed recorder label not updating when project is changed. [#1139](https://github.com/Orama-Interactive/Pixelorama/pull/1139)
|
||||
|
||||
## [v1.0.5] - 2024-11-18
|
||||
This update has been brought to you by the contributions of:
|
||||
Fayez Akhtar ([@Variable-ind](https://github.com/Variable-ind))
|
||||
|
|
|
@ -3,16 +3,23 @@ Name=Pixelorama
|
|||
GenericName=2D sprite editor
|
||||
GenericName[el]=Επεξεργαστής δισδιάστατων εικόνων
|
||||
GenericName[fr]=Éditeur de sprites 2D
|
||||
GenericName[ru]=2Д редактор спрайтов
|
||||
GenericName[pt_BR]=Editor de sprites 2D
|
||||
GenericName[uk]=2Д редактор спрайтів
|
||||
GenericName[zh_CN]=2D 精灵编辑器
|
||||
Comment=Create and edit static or animated 2D sprites
|
||||
Comment[el]=Δημιουργήστε και επεξεργαστείτε στατικές ή κινούμενες δισδιάστατες εικόνες
|
||||
Comment[fr]=Créez et modifiez des sprites 2D statiques ou animées
|
||||
Comment[ru]=Создавайте и редактируйте статичные и анимированные 2Д спрайты
|
||||
Comment[pt_BR]=Crie e edite sprites 2D estáticos ou animados
|
||||
Comment[uk]=Створюйте та редагуйте статичні та анімовані 2Д спрайти
|
||||
Comment[zh_CN]=创建并编辑 2D 精灵图片或动画
|
||||
Exec=pixelorama
|
||||
Icon=pixelorama
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Graphics;2DGraphics;RasterGraphics;
|
||||
Keywords=pixel;retro;animation;art;image;2d;sprite;graphics;drawing;editor;
|
||||
Keywords[ru]=pixel;retro;animation;art;image;2d;sprite;graphics;drawing;editor;пиксель;ретро;анимация;арт;изображение;2д;спрайт;графика;рисование;редактор;
|
||||
Keywords[uk]=pixel;retro;animation;art;image;2d;sprite;graphics;drawing;editor;піксель;ретро;анімація;арт;зображення;2д;спрайт;графіка;малювання;редактор;
|
||||
MimeType=image/pxo;image/png;image/bmp;image/vnd.radiance;image/jpeg;image/svg+xml;image/x-tga;image/webp;
|
||||
|
|
|
@ -156,6 +156,18 @@ msgstr ""
|
|||
msgid "Percentage"
|
||||
msgstr ""
|
||||
|
||||
#. Found in the create new image dialog. Allows users to change the color mode of the new project, such as RGBA or indexed mode.
|
||||
msgid "Color mode:"
|
||||
msgstr ""
|
||||
|
||||
#. Found in the image menu. A submenu that allows users to change the color mode of the project, such as RGBA or indexed mode.
|
||||
msgid "Color Mode"
|
||||
msgstr ""
|
||||
|
||||
#. Found in the image menu, under the "Color Mode" submenu. Refers to the indexed color mode. See this wikipedia page for more information: https://en.wikipedia.org/wiki/Indexed_color
|
||||
msgid "Indexed"
|
||||
msgstr ""
|
||||
|
||||
#. Found in the image menu. Sets the size of the project to be the same as the size of the active selection.
|
||||
msgid "Crop to Selection"
|
||||
msgstr ""
|
||||
|
@ -1494,7 +1506,7 @@ msgstr ""
|
|||
|
||||
msgid "Text\n\n"
|
||||
"%s for left mouse button\n"
|
||||
"%s for right mouse button\n\n"
|
||||
"%s for right mouse button"
|
||||
msgstr ""
|
||||
|
||||
msgid "Rectangle"
|
||||
|
|
BIN
assets/graphics/misc/x_minus_y_mirror_off.png
Normal file
After Width: | Height: | Size: 218 B |
34
assets/graphics/misc/x_minus_y_mirror_off.png.import
Normal file
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://1kj5gcswa3t2"
|
||||
path="res://.godot/imported/x_minus_y_mirror_off.png-da237e3b5b7ad1dfef1c935385f53dc5.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/graphics/misc/x_minus_y_mirror_off.png"
|
||||
dest_files=["res://.godot/imported/x_minus_y_mirror_off.png-da237e3b5b7ad1dfef1c935385f53dc5.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
BIN
assets/graphics/misc/x_minus_y_mirror_on.png
Normal file
After Width: | Height: | Size: 187 B |
34
assets/graphics/misc/x_minus_y_mirror_on.png.import
Normal file
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dn14bkxwdqsfk"
|
||||
path="res://.godot/imported/x_minus_y_mirror_on.png-0e9186904d8241facc4a0c1190f32c53.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/graphics/misc/x_minus_y_mirror_on.png"
|
||||
dest_files=["res://.godot/imported/x_minus_y_mirror_on.png-0e9186904d8241facc4a0c1190f32c53.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
BIN
assets/graphics/misc/xy_mirror_off.png
Normal file
After Width: | Height: | Size: 183 B |
34
assets/graphics/misc/xy_mirror_off.png.import
Normal file
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dlxhm0ronna25"
|
||||
path="res://.godot/imported/xy_mirror_off.png-8d2fd9ebdf350f0cd384fdf39fed4ec1.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/graphics/misc/xy_mirror_off.png"
|
||||
dest_files=["res://.godot/imported/xy_mirror_off.png-8d2fd9ebdf350f0cd384fdf39fed4ec1.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
BIN
assets/graphics/misc/xy_mirror_on.png
Normal file
After Width: | Height: | Size: 185 B |
34
assets/graphics/misc/xy_mirror_on.png.import
Normal file
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cu2uqp5oupt80"
|
||||
path="res://.godot/imported/xy_mirror_on.png-95d443df3b6d17add41283bdd720ea7e.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/graphics/misc/xy_mirror_on.png"
|
||||
dest_files=["res://.godot/imported/xy_mirror_on.png-95d443df3b6d17add41283bdd720ea7e.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
Before Width: | Height: | Size: 218 B After Width: | Height: | Size: 374 B |
Before Width: | Height: | Size: 162 B After Width: | Height: | Size: 136 B |
|
@ -921,6 +921,10 @@ right_text_tool={
|
|||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":true,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
show_pixel_indices={
|
||||
"deadzone": 0.5,
|
||||
"events": []
|
||||
}
|
||||
|
||||
[input_devices]
|
||||
|
||||
|
|
|
@ -217,7 +217,7 @@ func get_ellipse_points_filled(pos: Vector2i, size: Vector2i, thickness := 1) ->
|
|||
|
||||
func scale_3x(sprite: Image, tol := 0.196078) -> Image:
|
||||
var scaled := Image.create(
|
||||
sprite.get_width() * 3, sprite.get_height() * 3, false, Image.FORMAT_RGBA8
|
||||
sprite.get_width() * 3, sprite.get_height() * 3, sprite.has_mipmaps(), sprite.get_format()
|
||||
)
|
||||
var width_minus_one := sprite.get_width() - 1
|
||||
var height_minus_one := sprite.get_height() - 1
|
||||
|
@ -509,6 +509,8 @@ func similar_colors(c1: Color, c2: Color, tol := 0.392157) -> bool:
|
|||
func center(indices: Array) -> void:
|
||||
var project := Global.current_project
|
||||
Global.canvas.selection.transform_content_confirm()
|
||||
var redo_data := {}
|
||||
var undo_data := {}
|
||||
project.undos += 1
|
||||
project.undo_redo.create_action("Center Frames")
|
||||
for frame in indices:
|
||||
|
@ -528,15 +530,20 @@ func center(indices: Array) -> void:
|
|||
for cel in project.frames[frame].cels:
|
||||
if not cel is PixelCel:
|
||||
continue
|
||||
var sprite := Image.create(project.size.x, project.size.y, false, Image.FORMAT_RGBA8)
|
||||
sprite.blend_rect(cel.image, used_rect, offset)
|
||||
Global.undo_redo_compress_images({cel.image: sprite.data}, {cel.image: cel.image.data})
|
||||
var cel_image := (cel as PixelCel).get_image()
|
||||
var tmp_centered := project.new_empty_image()
|
||||
tmp_centered.blend_rect(cel.image, used_rect, offset)
|
||||
var centered := ImageExtended.new()
|
||||
centered.copy_from_custom(tmp_centered, cel_image.is_indexed)
|
||||
centered.add_data_to_dictionary(redo_data, cel_image)
|
||||
cel_image.add_data_to_dictionary(undo_data)
|
||||
Global.undo_redo_compress_images(redo_data, undo_data)
|
||||
project.undo_redo.add_undo_method(Global.undo_or_redo.bind(true))
|
||||
project.undo_redo.add_do_method(Global.undo_or_redo.bind(false))
|
||||
project.undo_redo.commit_action()
|
||||
|
||||
|
||||
func scale_image(width: int, height: int, interpolation: int) -> void:
|
||||
func scale_project(width: int, height: int, interpolation: int) -> void:
|
||||
var redo_data := {}
|
||||
var undo_data := {}
|
||||
for f in Global.current_project.frames:
|
||||
|
@ -544,30 +551,47 @@ func scale_image(width: int, height: int, interpolation: int) -> void:
|
|||
var cel := f.cels[i]
|
||||
if not cel is PixelCel:
|
||||
continue
|
||||
var sprite := Image.new()
|
||||
sprite.copy_from(cel.get_image())
|
||||
if interpolation == Interpolation.SCALE3X:
|
||||
var times := Vector2i(
|
||||
ceili(width / (3.0 * sprite.get_width())),
|
||||
ceili(height / (3.0 * sprite.get_height()))
|
||||
)
|
||||
for _j in range(maxi(times.x, times.y)):
|
||||
sprite.copy_from(scale_3x(sprite))
|
||||
sprite.resize(width, height, Image.INTERPOLATE_NEAREST)
|
||||
elif interpolation == Interpolation.CLEANEDGE:
|
||||
var gen := ShaderImageEffect.new()
|
||||
gen.generate_image(sprite, clean_edge_shader, {}, Vector2i(width, height))
|
||||
elif interpolation == Interpolation.OMNISCALE and omniscale_shader:
|
||||
var gen := ShaderImageEffect.new()
|
||||
gen.generate_image(sprite, omniscale_shader, {}, Vector2i(width, height))
|
||||
else:
|
||||
sprite.resize(width, height, interpolation)
|
||||
redo_data[cel.image] = sprite.data
|
||||
undo_data[cel.image] = cel.image.data
|
||||
var cel_image := (cel as PixelCel).get_image()
|
||||
var sprite := _resize_image(cel_image, width, height, interpolation) as ImageExtended
|
||||
sprite.add_data_to_dictionary(redo_data, cel_image)
|
||||
cel_image.add_data_to_dictionary(undo_data)
|
||||
|
||||
general_do_and_undo_scale(width, height, redo_data, undo_data)
|
||||
|
||||
|
||||
func _resize_image(
|
||||
image: Image, width: int, height: int, interpolation: Image.Interpolation
|
||||
) -> Image:
|
||||
var new_image: Image
|
||||
if image is ImageExtended:
|
||||
new_image = ImageExtended.new()
|
||||
new_image.is_indexed = image.is_indexed
|
||||
new_image.copy_from(image)
|
||||
new_image.select_palette("", false)
|
||||
else:
|
||||
new_image = Image.new()
|
||||
new_image.copy_from(image)
|
||||
if interpolation == Interpolation.SCALE3X:
|
||||
var times := Vector2i(
|
||||
ceili(width / (3.0 * new_image.get_width())),
|
||||
ceili(height / (3.0 * new_image.get_height()))
|
||||
)
|
||||
for _j in range(maxi(times.x, times.y)):
|
||||
new_image.copy_from(scale_3x(new_image))
|
||||
new_image.resize(width, height, Image.INTERPOLATE_NEAREST)
|
||||
elif interpolation == Interpolation.CLEANEDGE:
|
||||
var gen := ShaderImageEffect.new()
|
||||
gen.generate_image(new_image, clean_edge_shader, {}, Vector2i(width, height), false)
|
||||
elif interpolation == Interpolation.OMNISCALE and omniscale_shader:
|
||||
var gen := ShaderImageEffect.new()
|
||||
gen.generate_image(new_image, omniscale_shader, {}, Vector2i(width, height), false)
|
||||
else:
|
||||
new_image.resize(width, height, interpolation)
|
||||
if new_image is ImageExtended:
|
||||
new_image.on_size_changed()
|
||||
return new_image
|
||||
|
||||
|
||||
## Sets the size of the project to be the same as the size of the active selection.
|
||||
func crop_to_selection() -> void:
|
||||
if not Global.current_project.has_selection:
|
||||
|
@ -577,13 +601,13 @@ func crop_to_selection() -> void:
|
|||
Global.canvas.selection.transform_content_confirm()
|
||||
var rect: Rect2i = Global.canvas.selection.big_bounding_rectangle
|
||||
# Loop through all the cels to crop them
|
||||
for f in Global.current_project.frames:
|
||||
for cel in f.cels:
|
||||
if not cel is PixelCel:
|
||||
continue
|
||||
var sprite := cel.get_image().get_region(rect)
|
||||
redo_data[cel.image] = sprite.data
|
||||
undo_data[cel.image] = cel.image.data
|
||||
for cel in Global.current_project.get_all_pixel_cels():
|
||||
var cel_image := cel.get_image()
|
||||
var tmp_cropped := cel_image.get_region(rect)
|
||||
var cropped := ImageExtended.new()
|
||||
cropped.copy_from_custom(tmp_cropped, cel_image.is_indexed)
|
||||
cropped.add_data_to_dictionary(redo_data, cel_image)
|
||||
cel_image.add_data_to_dictionary(undo_data)
|
||||
|
||||
general_do_and_undo_scale(rect.size.x, rect.size.y, redo_data, undo_data)
|
||||
|
||||
|
@ -615,13 +639,13 @@ func crop_to_content() -> void:
|
|||
var redo_data := {}
|
||||
var undo_data := {}
|
||||
# Loop through all the cels to trim them
|
||||
for f in Global.current_project.frames:
|
||||
for cel in f.cels:
|
||||
if not cel is PixelCel:
|
||||
continue
|
||||
var sprite := cel.get_image().get_region(used_rect)
|
||||
redo_data[cel.image] = sprite.data
|
||||
undo_data[cel.image] = cel.image.data
|
||||
for cel in Global.current_project.get_all_pixel_cels():
|
||||
var cel_image := cel.get_image()
|
||||
var tmp_cropped := cel_image.get_region(used_rect)
|
||||
var cropped := ImageExtended.new()
|
||||
cropped.copy_from_custom(tmp_cropped, cel_image.is_indexed)
|
||||
cropped.add_data_to_dictionary(redo_data, cel_image)
|
||||
cel_image.add_data_to_dictionary(undo_data)
|
||||
|
||||
general_do_and_undo_scale(width, height, redo_data, undo_data)
|
||||
|
||||
|
@ -629,18 +653,17 @@ func crop_to_content() -> void:
|
|||
func resize_canvas(width: int, height: int, offset_x: int, offset_y: int) -> void:
|
||||
var redo_data := {}
|
||||
var undo_data := {}
|
||||
for f in Global.current_project.frames:
|
||||
for cel in f.cels:
|
||||
if not cel is PixelCel:
|
||||
continue
|
||||
var sprite := Image.create(width, height, false, Image.FORMAT_RGBA8)
|
||||
sprite.blend_rect(
|
||||
cel.get_image(),
|
||||
Rect2i(Vector2i.ZERO, Global.current_project.size),
|
||||
Vector2i(offset_x, offset_y)
|
||||
)
|
||||
redo_data[cel.image] = sprite.data
|
||||
undo_data[cel.image] = cel.image.data
|
||||
for cel in Global.current_project.get_all_pixel_cels():
|
||||
var cel_image := cel.get_image()
|
||||
var resized := ImageExtended.create_custom(
|
||||
width, height, cel_image.has_mipmaps(), cel_image.get_format(), cel_image.is_indexed
|
||||
)
|
||||
resized.blend_rect(
|
||||
cel_image, Rect2i(Vector2i.ZERO, cel_image.get_size()), Vector2i(offset_x, offset_y)
|
||||
)
|
||||
resized.convert_rgb_to_indexed()
|
||||
resized.add_data_to_dictionary(redo_data, cel_image)
|
||||
cel_image.add_data_to_dictionary(undo_data)
|
||||
|
||||
general_do_and_undo_scale(width, height, redo_data, undo_data)
|
||||
|
||||
|
|
|
@ -161,7 +161,7 @@ func cache_blended_frames(project := Global.current_project) -> void:
|
|||
blended_frames.clear()
|
||||
var frames := _calculate_frames(project)
|
||||
for frame in frames:
|
||||
var image := Image.create(project.size.x, project.size.y, false, Image.FORMAT_RGBA8)
|
||||
var image := project.new_empty_image()
|
||||
_blend_layers(image, frame)
|
||||
blended_frames[frame] = image
|
||||
|
||||
|
@ -208,7 +208,7 @@ func process_spritesheet(project := Global.current_project) -> void:
|
|||
spritesheet_columns = temp
|
||||
var width := project.size.x * spritesheet_columns
|
||||
var height := project.size.y * spritesheet_rows
|
||||
var whole_image := Image.create(width, height, false, Image.FORMAT_RGBA8)
|
||||
var whole_image := Image.create(width, height, false, project.get_image_format())
|
||||
var origin := Vector2i.ZERO
|
||||
var hh := 0
|
||||
var vv := 0
|
||||
|
@ -287,10 +287,10 @@ func process_animation(project := Global.current_project) -> void:
|
|||
ProcessedImage.new(image, project.frames.find(frame), duration)
|
||||
)
|
||||
else:
|
||||
var image := Image.create(project.size.x, project.size.y, false, Image.FORMAT_RGBA8)
|
||||
var image := project.new_empty_image()
|
||||
image.copy_from(blended_frames[frame])
|
||||
if erase_unselected_area and project.has_selection:
|
||||
var crop := Image.create(project.size.x, project.size.y, false, Image.FORMAT_RGBA8)
|
||||
var crop := project.new_empty_image()
|
||||
var selection_image = project.selection_map.return_cropped_copy(project.size)
|
||||
crop.blit_rect_mask(
|
||||
image, selection_image, Rect2i(Vector2i.ZERO, image.get_size()), Vector2i.ZERO
|
||||
|
|
|
@ -35,6 +35,7 @@ enum ViewMenu {
|
|||
MIRROR_VIEW,
|
||||
SHOW_GRID,
|
||||
SHOW_PIXEL_GRID,
|
||||
SHOW_PIXEL_INDICES,
|
||||
SHOW_RULERS,
|
||||
SHOW_GUIDES,
|
||||
SHOW_MOUSE_GUIDES,
|
||||
|
@ -46,6 +47,7 @@ enum WindowMenu { WINDOW_OPACITY, PANELS, LAYOUTS, MOVABLE_PANELS, ZEN_MODE, FUL
|
|||
## Enumeration of items present in the Image Menu.
|
||||
enum ImageMenu {
|
||||
PROJECT_PROPERTIES,
|
||||
COLOR_MODE,
|
||||
RESIZE_CANVAS,
|
||||
SCALE_IMAGE,
|
||||
CROP_TO_SELECTION,
|
||||
|
@ -178,10 +180,14 @@ var can_draw := true
|
|||
var move_guides_on_canvas := true
|
||||
|
||||
var play_only_tags := true ## If [code]true[/code], animation plays only on frames of the same tag.
|
||||
## (Intended to be used as getter only) Tells if the x-symmetry guide ( -- ) is visible.
|
||||
## If true, the x symmetry guide ( -- ) is visible.
|
||||
var show_x_symmetry_axis := false
|
||||
## (Intended to be used as getter only) Tells if the y-symmetry guide ( | ) is visible.
|
||||
## If true, the y symmetry guide ( | ) is visible.
|
||||
var show_y_symmetry_axis := false
|
||||
## If true, the x=y symmetry guide ( / ) is visible.
|
||||
var show_xy_symmetry_axis := false
|
||||
## If true, the x==y symmetry guide ( \ ) is visible.
|
||||
var show_x_minus_y_symmetry_axis := false
|
||||
|
||||
# Preferences
|
||||
## Found in Preferences. If [code]true[/code], the last saved project will open on startup.
|
||||
|
@ -332,55 +338,8 @@ var default_height := 64 ## Found in Preferences. The default height of startup
|
|||
var default_fill_color := Color(0, 0, 0, 0)
|
||||
## Found in Preferences. The distance to the guide or grig below which cursor snapping activates.
|
||||
var snapping_distance := 32.0
|
||||
## Found in Preferences. The grid type defined by [enum GridTypes] enum.
|
||||
var grid_type := GridTypes.CARTESIAN:
|
||||
set(value):
|
||||
if value == grid_type:
|
||||
return
|
||||
grid_type = value
|
||||
if is_instance_valid(canvas.grid):
|
||||
canvas.grid.queue_redraw()
|
||||
## Found in Preferences. The size of rectangular grid.
|
||||
var grid_size := Vector2i(2, 2):
|
||||
set(value):
|
||||
if value == grid_size:
|
||||
return
|
||||
grid_size = value
|
||||
if is_instance_valid(canvas.grid):
|
||||
canvas.grid.queue_redraw()
|
||||
## Found in Preferences. The size of isometric grid.
|
||||
var isometric_grid_size := Vector2i(16, 8):
|
||||
set(value):
|
||||
if value == isometric_grid_size:
|
||||
return
|
||||
isometric_grid_size = value
|
||||
if is_instance_valid(canvas.grid):
|
||||
canvas.grid.queue_redraw()
|
||||
## Found in Preferences. The grid offset from top-left corner of the canvas.
|
||||
var grid_offset := Vector2i.ZERO:
|
||||
set(value):
|
||||
if value == grid_offset:
|
||||
return
|
||||
grid_offset = value
|
||||
if is_instance_valid(canvas.grid):
|
||||
canvas.grid.queue_redraw()
|
||||
## Found in Preferences. If [code]true[/code], The grid draws over the area extended by
|
||||
## tile-mode as well.
|
||||
var grid_draw_over_tile_mode := false:
|
||||
set(value):
|
||||
if value == grid_draw_over_tile_mode:
|
||||
return
|
||||
grid_draw_over_tile_mode = value
|
||||
if is_instance_valid(canvas.grid):
|
||||
canvas.grid.queue_redraw()
|
||||
## Found in Preferences. The color of grid.
|
||||
var grid_color := Color.BLACK:
|
||||
set(value):
|
||||
if value == grid_color:
|
||||
return
|
||||
grid_color = value
|
||||
if is_instance_valid(canvas.grid):
|
||||
canvas.grid.queue_redraw()
|
||||
## Contains dictionaries of individual grids.
|
||||
var grids: Array[Grid] = []
|
||||
## Found in Preferences. The minimum zoom after which pixel grid gets drawn if enabled.
|
||||
var pixel_grid_show_at_zoom := 1500.0: # percentage
|
||||
set(value):
|
||||
|
@ -597,6 +556,12 @@ var show_rulers := true:
|
|||
var show_guides := true
|
||||
## If [code]true[/code], the mouse guides are visible.
|
||||
var show_mouse_guides := false
|
||||
## If [code]true[/code], the indices of color are shown.
|
||||
var show_pixel_indices := false:
|
||||
set(value):
|
||||
show_pixel_indices = value
|
||||
if is_instance_valid(canvas.color_index):
|
||||
canvas.color_index.enabled = value
|
||||
var display_layer_effects := true:
|
||||
set(value):
|
||||
if value == display_layer_effects:
|
||||
|
@ -672,6 +637,62 @@ var cel_button_scene: PackedScene = load("res://src/UI/Timeline/CelButton.tscn")
|
|||
@onready var error_dialog: AcceptDialog = control.find_child("ErrorDialog")
|
||||
|
||||
|
||||
class Grid:
|
||||
var grid_type := GridTypes.CARTESIAN:
|
||||
set(value):
|
||||
if value == grid_type:
|
||||
return
|
||||
grid_type = value
|
||||
if is_instance_valid(Global.canvas.grid):
|
||||
Global.canvas.grid.queue_redraw()
|
||||
## Found in Preferences. The size of rectangular grid.
|
||||
var grid_size := Vector2i(2, 2):
|
||||
set(value):
|
||||
if value == grid_size:
|
||||
return
|
||||
grid_size = value
|
||||
if is_instance_valid(Global.canvas.grid):
|
||||
Global.canvas.grid.queue_redraw()
|
||||
## Found in Preferences. The size of isometric grid.
|
||||
var isometric_grid_size := Vector2i(16, 8):
|
||||
set(value):
|
||||
if value == isometric_grid_size:
|
||||
return
|
||||
isometric_grid_size = value
|
||||
if is_instance_valid(Global.canvas.grid):
|
||||
Global.canvas.grid.queue_redraw()
|
||||
## Found in Preferences. The grid offset from top-left corner of the canvas.
|
||||
var grid_offset := Vector2i.ZERO:
|
||||
set(value):
|
||||
if value == grid_offset:
|
||||
return
|
||||
grid_offset = value
|
||||
if is_instance_valid(Global.canvas.grid):
|
||||
Global.canvas.grid.queue_redraw()
|
||||
## Found in Preferences. If [code]true[/code], The grid draws over the area extended by
|
||||
## tile-mode as well.
|
||||
var grid_draw_over_tile_mode := false:
|
||||
set(value):
|
||||
if value == grid_draw_over_tile_mode:
|
||||
return
|
||||
grid_draw_over_tile_mode = value
|
||||
if is_instance_valid(Global.canvas.grid):
|
||||
Global.canvas.grid.queue_redraw()
|
||||
## Found in Preferences. The color of grid.
|
||||
var grid_color := Color.BLACK:
|
||||
set(value):
|
||||
if value == grid_color:
|
||||
return
|
||||
grid_color = value
|
||||
if is_instance_valid(Global.canvas.grid):
|
||||
Global.canvas.grid.queue_redraw()
|
||||
|
||||
func _init(properties := {}) -> void:
|
||||
Global.grids.append(self)
|
||||
for prop in properties.keys():
|
||||
set(prop, properties[prop])
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
# Load settings from the config file
|
||||
config_cache.load(CONFIG_PATH)
|
||||
|
@ -708,6 +729,8 @@ func _init() -> void:
|
|||
|
||||
|
||||
func _ready() -> void:
|
||||
# Initialize Grid
|
||||
Grid.new() # gets auto added to grids array
|
||||
_initialize_keychain()
|
||||
default_width = config_cache.get_value("preferences", "default_width", default_width)
|
||||
default_height = config_cache.get_value("preferences", "default_height", default_height)
|
||||
|
@ -724,11 +747,26 @@ func _ready() -> void:
|
|||
if get(pref) == null:
|
||||
continue
|
||||
var value = config_cache.get_value("preferences", pref)
|
||||
set(pref, value)
|
||||
if pref == "grids":
|
||||
if value:
|
||||
update_grids(value)
|
||||
else:
|
||||
set(pref, value)
|
||||
if OS.is_sandboxed():
|
||||
Global.use_native_file_dialogs = true
|
||||
await get_tree().process_frame
|
||||
project_switched.emit()
|
||||
canvas.color_index.enabled = show_pixel_indices # Initialize color index preview
|
||||
|
||||
|
||||
func update_grids(grids_data: Dictionary):
|
||||
# Remove old grids
|
||||
grids.clear()
|
||||
if is_instance_valid(Global.canvas.grid):
|
||||
Global.canvas.grid.queue_redraw()
|
||||
# ADD new ones
|
||||
for grid_idx in grids_data.size():
|
||||
Grid.new(grids_data[grid_idx]) # gets auto added to grids array
|
||||
|
||||
|
||||
func _initialize_keychain() -> void:
|
||||
|
@ -1061,7 +1099,9 @@ func get_available_font_names() -> PackedStringArray:
|
|||
if font_name in font_names:
|
||||
continue
|
||||
font_names.append(font_name)
|
||||
for system_font_name in OS.get_system_fonts():
|
||||
var system_fonts := OS.get_system_fonts()
|
||||
system_fonts.sort()
|
||||
for system_font_name in system_fonts:
|
||||
if system_font_name in font_names:
|
||||
continue
|
||||
font_names.append(system_font_name)
|
||||
|
@ -1113,8 +1153,17 @@ func undo_redo_compress_images(
|
|||
func undo_redo_draw_op(
|
||||
image: Image, new_size: Vector2i, compressed_image_data: PackedByteArray, buffer_size: int
|
||||
) -> void:
|
||||
var decompressed := compressed_image_data.decompress(buffer_size)
|
||||
image.set_data(new_size.x, new_size.y, image.has_mipmaps(), image.get_format(), decompressed)
|
||||
if image is ImageExtended and image.is_indexed:
|
||||
# If using indexed mode,
|
||||
# just convert the indices to RGB instead of setting the image data directly.
|
||||
if image.get_size() != new_size:
|
||||
image.crop(new_size.x, new_size.y)
|
||||
image.convert_indexed_to_rgb()
|
||||
else:
|
||||
var decompressed := compressed_image_data.decompress(buffer_size)
|
||||
image.set_data(
|
||||
new_size.x, new_size.y, image.has_mipmaps(), image.get_format(), decompressed
|
||||
)
|
||||
|
||||
|
||||
## This method is used to write project setting overrides to the override.cfg file, located
|
||||
|
|
|
@ -90,9 +90,9 @@ func get_brush_files_from_directory(directory: String): # -> Array
|
|||
func add_randomised_brush(fpaths: Array, tooltip_name: String) -> void:
|
||||
# Attempt to load the images from the file paths.
|
||||
var loaded_images: Array = []
|
||||
for filen in fpaths:
|
||||
for file in fpaths:
|
||||
var image := Image.new()
|
||||
var err := image.load(filen)
|
||||
var err := image.load(file)
|
||||
if err == OK:
|
||||
image.convert(Image.FORMAT_RGBA8)
|
||||
loaded_images.append(image)
|
||||
|
|
|
@ -150,7 +150,7 @@ func handle_loading_aimg(path: String, frames: Array) -> void:
|
|||
if not frames_agree:
|
||||
frame.duration = aimg_frame.duration * project.fps
|
||||
var content := aimg_frame.content
|
||||
content.convert(Image.FORMAT_RGBA8)
|
||||
content.convert(project.get_image_format())
|
||||
frame.cels.append(PixelCel.new(content, 1))
|
||||
project.frames.append(frame)
|
||||
|
||||
|
@ -389,18 +389,23 @@ func save_pxo_file(
|
|||
var frame_index := 1
|
||||
for frame in project.frames:
|
||||
if not autosave and include_blended:
|
||||
var blended := Image.create(project.size.x, project.size.y, false, Image.FORMAT_RGBA8)
|
||||
var blended := project.new_empty_image()
|
||||
DrawingAlgos.blend_layers(blended, frame, Vector2i.ZERO, project)
|
||||
zip_packer.start_file("image_data/final_images/%s" % frame_index)
|
||||
zip_packer.write_file(blended.get_data())
|
||||
zip_packer.close_file()
|
||||
var cel_index := 1
|
||||
for cel in frame.cels:
|
||||
var cel_image := cel.get_image()
|
||||
var cel_image := cel.get_image() as ImageExtended
|
||||
if is_instance_valid(cel_image) and cel is PixelCel:
|
||||
zip_packer.start_file("image_data/frames/%s/layer_%s" % [frame_index, cel_index])
|
||||
zip_packer.write_file(cel_image.get_data())
|
||||
zip_packer.close_file()
|
||||
zip_packer.start_file(
|
||||
"image_data/frames/%s/indices_layer_%s" % [frame_index, cel_index]
|
||||
)
|
||||
zip_packer.write_file(cel_image.indices_image.get_data())
|
||||
zip_packer.close_file()
|
||||
cel_index += 1
|
||||
frame_index += 1
|
||||
var brush_index := 0
|
||||
|
@ -457,12 +462,13 @@ func save_pxo_file(
|
|||
|
||||
func open_image_as_new_tab(path: String, image: Image) -> void:
|
||||
var project := Project.new([], path.get_file(), image.get_size())
|
||||
project.layers.append(PixelLayer.new(project))
|
||||
var layer := PixelLayer.new(project)
|
||||
project.layers.append(layer)
|
||||
Global.projects.append(project)
|
||||
|
||||
var frame := Frame.new()
|
||||
image.convert(Image.FORMAT_RGBA8)
|
||||
frame.cels.append(PixelCel.new(image, 1))
|
||||
image.convert(project.get_image_format())
|
||||
frame.cels.append(layer.new_cel_from_image(image))
|
||||
|
||||
project.frames.append(frame)
|
||||
set_new_imported_tab(project, path)
|
||||
|
@ -475,15 +481,18 @@ func open_image_as_spritesheet_tab_smart(
|
|||
frame_size = image.get_size()
|
||||
sliced_rects.append(Rect2i(Vector2i.ZERO, frame_size))
|
||||
var project := Project.new([], path.get_file(), frame_size)
|
||||
project.layers.append(PixelLayer.new(project))
|
||||
var layer := PixelLayer.new(project)
|
||||
project.layers.append(layer)
|
||||
Global.projects.append(project)
|
||||
for rect in sliced_rects:
|
||||
var offset: Vector2 = (0.5 * (frame_size - rect.size)).floor()
|
||||
var frame := Frame.new()
|
||||
var cropped_image := Image.create(frame_size.x, frame_size.y, false, Image.FORMAT_RGBA8)
|
||||
image.convert(Image.FORMAT_RGBA8)
|
||||
var cropped_image := Image.create(
|
||||
frame_size.x, frame_size.y, false, project.get_image_format()
|
||||
)
|
||||
image.convert(project.get_image_format())
|
||||
cropped_image.blit_rect(image, rect, offset)
|
||||
frame.cels.append(PixelCel.new(cropped_image, 1))
|
||||
frame.cels.append(layer.new_cel_from_image(cropped_image))
|
||||
project.frames.append(frame)
|
||||
set_new_imported_tab(project, path)
|
||||
|
||||
|
@ -494,7 +503,8 @@ func open_image_as_spritesheet_tab(path: String, image: Image, horiz: int, vert:
|
|||
var frame_width := image.get_size().x / horiz
|
||||
var frame_height := image.get_size().y / vert
|
||||
var project := Project.new([], path.get_file(), Vector2(frame_width, frame_height))
|
||||
project.layers.append(PixelLayer.new(project))
|
||||
var layer := PixelLayer.new(project)
|
||||
project.layers.append(layer)
|
||||
Global.projects.append(project)
|
||||
for yy in range(vert):
|
||||
for xx in range(horiz):
|
||||
|
@ -503,8 +513,8 @@ func open_image_as_spritesheet_tab(path: String, image: Image, horiz: int, vert:
|
|||
Rect2i(frame_width * xx, frame_height * yy, frame_width, frame_height)
|
||||
)
|
||||
project.size = cropped_image.get_size()
|
||||
cropped_image.convert(Image.FORMAT_RGBA8)
|
||||
frame.cels.append(PixelCel.new(cropped_image, 1))
|
||||
cropped_image.convert(project.get_image_format())
|
||||
frame.cels.append(layer.new_cel_from_image(cropped_image))
|
||||
project.frames.append(frame)
|
||||
set_new_imported_tab(project, path)
|
||||
|
||||
|
@ -562,12 +572,12 @@ func open_image_as_spritesheet_layer_smart(
|
|||
if f >= start_frame and f < (start_frame + sliced_rects.size()):
|
||||
# Slice spritesheet
|
||||
var offset: Vector2 = (0.5 * (frame_size - sliced_rects[f - start_frame].size)).floor()
|
||||
image.convert(Image.FORMAT_RGBA8)
|
||||
image.convert(project.get_image_format())
|
||||
var cropped_image := Image.create(
|
||||
project_width, project_height, false, Image.FORMAT_RGBA8
|
||||
project_width, project_height, false, project.get_image_format()
|
||||
)
|
||||
cropped_image.blit_rect(image, sliced_rects[f - start_frame], offset)
|
||||
cels.append(PixelCel.new(cropped_image))
|
||||
cels.append(layer.new_cel_from_image(cropped_image))
|
||||
else:
|
||||
cels.append(layer.new_empty_cel())
|
||||
|
||||
|
@ -644,16 +654,16 @@ func open_image_as_spritesheet_layer(
|
|||
# Slice spritesheet
|
||||
var xx := (f - start_frame) % horizontal
|
||||
var yy := (f - start_frame) / horizontal
|
||||
image.convert(Image.FORMAT_RGBA8)
|
||||
image.convert(project.get_image_format())
|
||||
var cropped_image := Image.create(
|
||||
project_width, project_height, false, Image.FORMAT_RGBA8
|
||||
project_width, project_height, false, project.get_image_format()
|
||||
)
|
||||
cropped_image.blit_rect(
|
||||
image,
|
||||
Rect2i(frame_width * xx, frame_height * yy, frame_width, frame_height),
|
||||
Vector2i.ZERO
|
||||
)
|
||||
cels.append(PixelCel.new(cropped_image))
|
||||
cels.append(layer.new_cel_from_image(cropped_image))
|
||||
else:
|
||||
cels.append(layer.new_empty_cel())
|
||||
|
||||
|
@ -687,12 +697,18 @@ func open_image_at_cel(image: Image, layer_index := 0, frame_index := 0) -> void
|
|||
var cel := project.frames[frame_index].cels[layer_index]
|
||||
if not cel is PixelCel:
|
||||
return
|
||||
image.convert(Image.FORMAT_RGBA8)
|
||||
var cel_image := Image.create(project_width, project_height, false, Image.FORMAT_RGBA8)
|
||||
cel_image.blit_rect(image, Rect2i(Vector2i.ZERO, image.get_size()), Vector2i.ZERO)
|
||||
Global.undo_redo_compress_images(
|
||||
{cel.image: cel_image.data}, {cel.image: cel.image.data}, project
|
||||
image.convert(project.get_image_format())
|
||||
var cel_image := (cel as PixelCel).get_image()
|
||||
var new_cel_image := ImageExtended.create_custom(
|
||||
project_width, project_height, false, project.get_image_format(), cel_image.is_indexed
|
||||
)
|
||||
new_cel_image.blit_rect(image, Rect2i(Vector2i.ZERO, image.get_size()), Vector2i.ZERO)
|
||||
new_cel_image.convert_rgb_to_indexed()
|
||||
var redo_data := {}
|
||||
new_cel_image.add_data_to_dictionary(redo_data, cel_image)
|
||||
var undo_data := {}
|
||||
cel_image.add_data_to_dictionary(undo_data)
|
||||
Global.undo_redo_compress_images(redo_data, undo_data, project)
|
||||
|
||||
project.undo_redo.add_do_property(project, "selected_cels", [])
|
||||
project.undo_redo.add_do_method(project.change_cel.bind(frame_index, layer_index))
|
||||
|
@ -716,11 +732,14 @@ func open_image_as_new_frame(
|
|||
|
||||
var frame := Frame.new()
|
||||
for i in project.layers.size():
|
||||
if i == layer_index:
|
||||
image.convert(Image.FORMAT_RGBA8)
|
||||
var cel_image := Image.create(project_width, project_height, false, Image.FORMAT_RGBA8)
|
||||
var layer := project.layers[i]
|
||||
if i == layer_index and layer is PixelLayer:
|
||||
image.convert(project.get_image_format())
|
||||
var cel_image := Image.create(
|
||||
project_width, project_height, false, project.get_image_format()
|
||||
)
|
||||
cel_image.blit_rect(image, Rect2i(Vector2i.ZERO, image.get_size()), Vector2i.ZERO)
|
||||
frame.cels.append(PixelCel.new(cel_image, 1))
|
||||
frame.cels.append(layer.new_cel_from_image(cel_image))
|
||||
else:
|
||||
frame.cels.append(project.layers[i].new_empty_cel())
|
||||
if not undo:
|
||||
|
@ -753,10 +772,12 @@ func open_image_as_new_layer(image: Image, file_name: String, frame_index := 0)
|
|||
Global.current_project.undo_redo.create_action("Add Layer")
|
||||
for i in project.frames.size():
|
||||
if i == frame_index:
|
||||
image.convert(Image.FORMAT_RGBA8)
|
||||
var cel_image := Image.create(project_width, project_height, false, Image.FORMAT_RGBA8)
|
||||
image.convert(project.get_image_format())
|
||||
var cel_image := Image.create(
|
||||
project_width, project_height, false, project.get_image_format()
|
||||
)
|
||||
cel_image.blit_rect(image, Rect2i(Vector2i.ZERO, image.get_size()), Vector2i.ZERO)
|
||||
cels.append(PixelCel.new(cel_image, 1))
|
||||
cels.append(layer.new_cel_from_image(cel_image))
|
||||
else:
|
||||
cels.append(layer.new_empty_cel())
|
||||
|
||||
|
|
|
@ -36,9 +36,10 @@ func does_palette_exist(palette_name: String) -> bool:
|
|||
|
||||
|
||||
func select_palette(palette_name: String) -> void:
|
||||
current_palette = palettes.get(palette_name)
|
||||
current_palette = palettes.get(palette_name, null)
|
||||
_clear_selected_colors()
|
||||
Global.config_cache.set_value("data", "last_palette", current_palette.name)
|
||||
if is_instance_valid(current_palette):
|
||||
Global.config_cache.set_value("data", "last_palette", current_palette.name)
|
||||
palette_selected.emit(palette_name)
|
||||
|
||||
|
||||
|
@ -224,6 +225,7 @@ func current_palete_delete(permanent := true) -> void:
|
|||
select_palette(palettes.keys()[0])
|
||||
else:
|
||||
current_palette = null
|
||||
select_palette("")
|
||||
|
||||
|
||||
func current_palette_add_color(mouse_button: int, start_index := 0) -> void:
|
||||
|
@ -294,14 +296,14 @@ func current_palette_select_color(mouse_button: int, index: int) -> void:
|
|||
if color == null:
|
||||
return
|
||||
|
||||
_select_color(mouse_button, index)
|
||||
|
||||
match mouse_button:
|
||||
MOUSE_BUTTON_LEFT:
|
||||
Tools.assign_color(color, mouse_button)
|
||||
MOUSE_BUTTON_RIGHT:
|
||||
Tools.assign_color(color, mouse_button)
|
||||
|
||||
_select_color(mouse_button, index)
|
||||
|
||||
|
||||
func _select_color(mouse_button: int, index: int) -> void:
|
||||
match mouse_button:
|
||||
|
|
|
@ -9,9 +9,15 @@ signal options_reset
|
|||
|
||||
enum Dynamics { NONE, PRESSURE, VELOCITY }
|
||||
|
||||
const XY_LINE := Vector2(-0.707107, 0.707107)
|
||||
const X_MINUS_Y_LINE := Vector2(0.707107, 0.707107)
|
||||
|
||||
var active_button := -1
|
||||
var picking_color_for := MOUSE_BUTTON_LEFT
|
||||
var horizontal_mirror := false
|
||||
var vertical_mirror := false
|
||||
var diagonal_xy_mirror := false
|
||||
var diagonal_x_minus_y_mirror := false
|
||||
var pixel_perfect := false
|
||||
var alpha_locked := false
|
||||
|
||||
|
@ -233,7 +239,6 @@ var _right_tools_per_layer_type := {
|
|||
Global.LayerTypes.THREE_D: "Pan",
|
||||
}
|
||||
var _tool_buttons: Node
|
||||
var _active_button := -1
|
||||
var _last_position := Vector2i(Vector2.INF)
|
||||
|
||||
|
||||
|
@ -524,20 +529,51 @@ func get_mirrored_positions(
|
|||
) -> Array[Vector2i]:
|
||||
var positions: Array[Vector2i] = []
|
||||
if horizontal_mirror:
|
||||
var mirror_x := pos
|
||||
mirror_x.x = project.x_symmetry_point - pos.x + offset
|
||||
var mirror_x := calculate_mirror_horizontal(pos, project, offset)
|
||||
positions.append(mirror_x)
|
||||
if vertical_mirror:
|
||||
var mirror_xy := mirror_x
|
||||
mirror_xy.y = project.y_symmetry_point - pos.y + offset
|
||||
positions.append(mirror_xy)
|
||||
positions.append(calculate_mirror_vertical(mirror_x, project, offset))
|
||||
else:
|
||||
if diagonal_xy_mirror:
|
||||
positions.append(calculate_mirror_xy(mirror_x, project))
|
||||
if diagonal_x_minus_y_mirror:
|
||||
positions.append(calculate_mirror_x_minus_y(mirror_x, project))
|
||||
if vertical_mirror:
|
||||
var mirror_y := pos
|
||||
mirror_y.y = project.y_symmetry_point - pos.y + offset
|
||||
var mirror_y := calculate_mirror_vertical(pos, project, offset)
|
||||
positions.append(mirror_y)
|
||||
if diagonal_xy_mirror:
|
||||
positions.append(calculate_mirror_xy(mirror_y, project))
|
||||
if diagonal_x_minus_y_mirror:
|
||||
positions.append(calculate_mirror_x_minus_y(mirror_y, project))
|
||||
if diagonal_xy_mirror:
|
||||
var mirror_diagonal := calculate_mirror_xy(pos, project)
|
||||
positions.append(mirror_diagonal)
|
||||
if not horizontal_mirror and not vertical_mirror and diagonal_x_minus_y_mirror:
|
||||
positions.append(calculate_mirror_x_minus_y(mirror_diagonal, project))
|
||||
if diagonal_x_minus_y_mirror:
|
||||
positions.append(calculate_mirror_x_minus_y(pos, project))
|
||||
return positions
|
||||
|
||||
|
||||
func calculate_mirror_horizontal(pos: Vector2i, project: Project, offset := 0) -> Vector2i:
|
||||
return Vector2i(project.x_symmetry_point - pos.x + offset, pos.y)
|
||||
|
||||
|
||||
func calculate_mirror_vertical(pos: Vector2i, project: Project, offset := 0) -> Vector2i:
|
||||
return Vector2i(pos.x, project.y_symmetry_point - pos.y + offset)
|
||||
|
||||
|
||||
func calculate_mirror_xy(pos: Vector2i, project: Project) -> Vector2i:
|
||||
return Vector2i(Vector2(pos).reflect(XY_LINE).round()) + Vector2i(project.xy_symmetry_point)
|
||||
|
||||
|
||||
func calculate_mirror_x_minus_y(pos: Vector2i, project: Project) -> Vector2i:
|
||||
return (
|
||||
Vector2i(Vector2(pos).reflect(X_MINUS_Y_LINE).round())
|
||||
+ Vector2i(project.x_minus_y_symmetry_point)
|
||||
)
|
||||
|
||||
|
||||
func set_button_size(button_size: int) -> void:
|
||||
var size := Vector2(24, 24) if button_size == Global.ButtonSize.SMALL else Vector2(32, 32)
|
||||
if not is_instance_valid(_tool_buttons):
|
||||
|
@ -591,32 +627,28 @@ func handle_draw(position: Vector2i, event: InputEvent) -> void:
|
|||
change_layer_automatically(draw_pos)
|
||||
return
|
||||
|
||||
if event.is_action_pressed(&"activate_left_tool") and _active_button == -1 and not pen_inverted:
|
||||
_active_button = MOUSE_BUTTON_LEFT
|
||||
_slots[_active_button].tool_node.draw_start(draw_pos)
|
||||
elif event.is_action_released(&"activate_left_tool") and _active_button == MOUSE_BUTTON_LEFT:
|
||||
_slots[_active_button].tool_node.draw_end(draw_pos)
|
||||
_active_button = -1
|
||||
if event.is_action_pressed(&"activate_left_tool") and active_button == -1 and not pen_inverted:
|
||||
active_button = MOUSE_BUTTON_LEFT
|
||||
_slots[active_button].tool_node.draw_start(draw_pos)
|
||||
elif event.is_action_released(&"activate_left_tool") and active_button == MOUSE_BUTTON_LEFT:
|
||||
_slots[active_button].tool_node.draw_end(draw_pos)
|
||||
active_button = -1
|
||||
elif (
|
||||
(
|
||||
event.is_action_pressed(&"activate_right_tool")
|
||||
and _active_button == -1
|
||||
and active_button == -1
|
||||
and not pen_inverted
|
||||
)
|
||||
or (
|
||||
event.is_action_pressed(&"activate_left_tool") and _active_button == -1 and pen_inverted
|
||||
)
|
||||
or event.is_action_pressed(&"activate_left_tool") and active_button == -1 and pen_inverted
|
||||
):
|
||||
_active_button = MOUSE_BUTTON_RIGHT
|
||||
_slots[_active_button].tool_node.draw_start(draw_pos)
|
||||
active_button = MOUSE_BUTTON_RIGHT
|
||||
_slots[active_button].tool_node.draw_start(draw_pos)
|
||||
elif (
|
||||
(event.is_action_released(&"activate_right_tool") and _active_button == MOUSE_BUTTON_RIGHT)
|
||||
or (
|
||||
event.is_action_released(&"activate_left_tool") and _active_button == MOUSE_BUTTON_RIGHT
|
||||
)
|
||||
(event.is_action_released(&"activate_right_tool") and active_button == MOUSE_BUTTON_RIGHT)
|
||||
or event.is_action_released(&"activate_left_tool") and active_button == MOUSE_BUTTON_RIGHT
|
||||
):
|
||||
_slots[_active_button].tool_node.draw_end(draw_pos)
|
||||
_active_button = -1
|
||||
_slots[active_button].tool_node.draw_end(draw_pos)
|
||||
active_button = -1
|
||||
|
||||
if event is InputEventMouseMotion:
|
||||
pen_pressure = event.pressure
|
||||
|
@ -647,8 +679,8 @@ func handle_draw(position: Vector2i, event: InputEvent) -> void:
|
|||
_last_position = position
|
||||
_slots[MOUSE_BUTTON_LEFT].tool_node.cursor_move(position)
|
||||
_slots[MOUSE_BUTTON_RIGHT].tool_node.cursor_move(position)
|
||||
if _active_button != -1:
|
||||
_slots[_active_button].tool_node.draw_move(draw_pos)
|
||||
if active_button != -1:
|
||||
_slots[active_button].tool_node.draw_move(draw_pos)
|
||||
|
||||
var project := Global.current_project
|
||||
var text := "[%s×%s]" % [project.size.x, project.size.y]
|
||||
|
|
|
@ -10,9 +10,7 @@ func _init(_opacity := 1.0) -> void:
|
|||
|
||||
|
||||
func get_image() -> Image:
|
||||
var image := Image.create(
|
||||
Global.current_project.size.x, Global.current_project.size.y, false, Image.FORMAT_RGBA8
|
||||
)
|
||||
var image := Global.current_project.new_empty_image()
|
||||
return image
|
||||
|
||||
|
||||
|
|
|
@ -4,23 +4,23 @@ extends BaseCel
|
|||
## The term "cel" comes from "celluloid" (https://en.wikipedia.org/wiki/Cel).
|
||||
|
||||
## This variable is where the image data of the cel are.
|
||||
var image: Image:
|
||||
var image: ImageExtended:
|
||||
set = image_changed
|
||||
|
||||
|
||||
func _init(_image := Image.new(), _opacity := 1.0) -> void:
|
||||
func _init(_image := ImageExtended.new(), _opacity := 1.0) -> void:
|
||||
image_texture = ImageTexture.new()
|
||||
image = _image # Set image and call setter
|
||||
opacity = _opacity
|
||||
|
||||
|
||||
func image_changed(value: Image) -> void:
|
||||
func image_changed(value: ImageExtended) -> void:
|
||||
image = value
|
||||
if not image.is_empty() and is_instance_valid(image_texture):
|
||||
image_texture.set_image(image)
|
||||
|
||||
|
||||
func get_content():
|
||||
func get_content() -> ImageExtended:
|
||||
return image
|
||||
|
||||
|
||||
|
@ -34,21 +34,23 @@ func set_content(content, texture: ImageTexture = null) -> void:
|
|||
image_texture.update(image)
|
||||
|
||||
|
||||
func create_empty_content():
|
||||
var empty_image := Image.create(
|
||||
image.get_size().x, image.get_size().y, false, Image.FORMAT_RGBA8
|
||||
)
|
||||
return empty_image
|
||||
func create_empty_content() -> ImageExtended:
|
||||
var empty := Image.create(image.get_width(), image.get_height(), false, image.get_format())
|
||||
var new_image := ImageExtended.new()
|
||||
new_image.copy_from_custom(empty, image.is_indexed)
|
||||
return new_image
|
||||
|
||||
|
||||
func copy_content():
|
||||
var copy_image := Image.create_from_data(
|
||||
image.get_width(), image.get_height(), false, Image.FORMAT_RGBA8, image.get_data()
|
||||
func copy_content() -> ImageExtended:
|
||||
var tmp_image := Image.create_from_data(
|
||||
image.get_width(), image.get_height(), false, image.get_format(), image.get_data()
|
||||
)
|
||||
var copy_image := ImageExtended.new()
|
||||
copy_image.copy_from_custom(tmp_image, image.is_indexed)
|
||||
return copy_image
|
||||
|
||||
|
||||
func get_image() -> Image:
|
||||
func get_image() -> ImageExtended:
|
||||
return image
|
||||
|
||||
|
||||
|
|
|
@ -1,22 +1,19 @@
|
|||
class_name Drawer
|
||||
|
||||
const NUMBER_OF_DRAWERS := 8
|
||||
|
||||
var pixel_perfect := false:
|
||||
set(value):
|
||||
pixel_perfect = value
|
||||
if pixel_perfect:
|
||||
drawers = pixel_perfect_drawers.duplicate()
|
||||
else:
|
||||
drawers = [simple_drawer, simple_drawer, simple_drawer, simple_drawer]
|
||||
_create_simple_drawers()
|
||||
var color_op := ColorOp.new()
|
||||
|
||||
var simple_drawer := SimpleDrawer.new()
|
||||
var pixel_perfect_drawers: Array[PixelPerfectDrawer] = [
|
||||
PixelPerfectDrawer.new(),
|
||||
PixelPerfectDrawer.new(),
|
||||
PixelPerfectDrawer.new(),
|
||||
PixelPerfectDrawer.new()
|
||||
]
|
||||
var drawers := [simple_drawer, simple_drawer, simple_drawer, simple_drawer]
|
||||
var pixel_perfect_drawers: Array[PixelPerfectDrawer] = []
|
||||
var drawers := []
|
||||
|
||||
|
||||
class ColorOp:
|
||||
|
@ -27,12 +24,12 @@ class ColorOp:
|
|||
|
||||
|
||||
class SimpleDrawer:
|
||||
func set_pixel(image: Image, position: Vector2i, color: Color, op: ColorOp) -> void:
|
||||
func set_pixel(image: ImageExtended, position: Vector2i, color: Color, op: ColorOp) -> void:
|
||||
var color_old := image.get_pixelv(position)
|
||||
var color_str := color.to_html()
|
||||
var color_new := op.process(Color(color_str), color_old)
|
||||
if not color_new.is_equal_approx(color_old):
|
||||
image.set_pixelv(position, color_new)
|
||||
image.set_pixelv_custom(position, color_new)
|
||||
|
||||
|
||||
class PixelPerfectDrawer:
|
||||
|
@ -43,11 +40,11 @@ class PixelPerfectDrawer:
|
|||
func reset() -> void:
|
||||
last_pixels = [null, null]
|
||||
|
||||
func set_pixel(image: Image, position: Vector2i, color: Color, op: ColorOp) -> void:
|
||||
func set_pixel(image: ImageExtended, position: Vector2i, color: Color, op: ColorOp) -> void:
|
||||
var color_old := image.get_pixelv(position)
|
||||
var color_str := color.to_html()
|
||||
last_pixels.push_back([position, color_old])
|
||||
image.set_pixelv(position, op.process(Color(color_str), color_old))
|
||||
image.set_pixelv_custom(position, op.process(Color(color_str), color_old))
|
||||
|
||||
var corner = last_pixels.pop_front()
|
||||
var neighbour = last_pixels[0]
|
||||
|
@ -56,10 +53,25 @@ class PixelPerfectDrawer:
|
|||
return
|
||||
|
||||
if position - corner[0] in CORNERS and position - neighbour[0] in NEIGHBOURS:
|
||||
image.set_pixel(neighbour[0].x, neighbour[0].y, neighbour[1])
|
||||
image.set_pixel_custom(neighbour[0].x, neighbour[0].y, neighbour[1])
|
||||
last_pixels[0] = corner
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
drawers.resize(NUMBER_OF_DRAWERS)
|
||||
pixel_perfect_drawers.resize(NUMBER_OF_DRAWERS)
|
||||
for i in NUMBER_OF_DRAWERS:
|
||||
drawers[i] = simple_drawer
|
||||
pixel_perfect_drawers[i] = PixelPerfectDrawer.new()
|
||||
|
||||
|
||||
func _create_simple_drawers() -> void:
|
||||
drawers = []
|
||||
drawers.resize(NUMBER_OF_DRAWERS)
|
||||
for i in NUMBER_OF_DRAWERS:
|
||||
drawers[i] = simple_drawer
|
||||
|
||||
|
||||
func reset() -> void:
|
||||
for drawer in pixel_perfect_drawers:
|
||||
drawer.reset()
|
||||
|
@ -72,7 +84,12 @@ func set_pixel(image: Image, position: Vector2i, color: Color, ignore_mirroring
|
|||
SteamManager.set_achievement("ACH_FIRST_PIXEL")
|
||||
if ignore_mirroring:
|
||||
return
|
||||
if not Tools.horizontal_mirror and not Tools.vertical_mirror:
|
||||
if (
|
||||
not Tools.horizontal_mirror
|
||||
and not Tools.vertical_mirror
|
||||
and not Tools.diagonal_xy_mirror
|
||||
and not Tools.diagonal_x_minus_y_mirror
|
||||
):
|
||||
return
|
||||
# Handle mirroring
|
||||
var mirrored_positions := Tools.get_mirrored_positions(position, project)
|
||||
|
|
|
@ -170,12 +170,12 @@ func _get_undo_data(project: Project) -> Dictionary:
|
|||
var data := {}
|
||||
var images := _get_selected_draw_images(project)
|
||||
for image in images:
|
||||
data[image] = image.data
|
||||
image.add_data_to_dictionary(data)
|
||||
return data
|
||||
|
||||
|
||||
func _get_selected_draw_images(project: Project) -> Array[Image]:
|
||||
var images: Array[Image] = []
|
||||
func _get_selected_draw_images(project: Project) -> Array[ImageExtended]:
|
||||
var images: Array[ImageExtended] = []
|
||||
if affect == SELECTED_CELS:
|
||||
for cel_index in project.selected_cels:
|
||||
var cel: BaseCel = project.frames[cel_index[0]].cels[cel_index[1]]
|
||||
|
|
191
src/Classes/ImageExtended.gd
Normal file
|
@ -0,0 +1,191 @@
|
|||
class_name ImageExtended
|
||||
extends Image
|
||||
|
||||
## A custom [Image] class that implements support for indexed mode.
|
||||
## Before implementing indexed mode, we just used the [Image] class.
|
||||
## In indexed mode, each pixel is assigned to a number that references a palette color.
|
||||
## This essentially means that the colors of the image are restricted to a specific palette,
|
||||
## and they will automatically get updated when you make changes to that palette, or when
|
||||
## you switch to a different one.
|
||||
|
||||
const TRANSPARENT := Color(0)
|
||||
const SET_INDICES := preload("res://src/Shaders/SetIndices.gdshader")
|
||||
const INDEXED_TO_RGB := preload("res://src/Shaders/IndexedToRGB.gdshader")
|
||||
|
||||
## If [code]true[/code], the image uses indexed mode.
|
||||
var is_indexed := false
|
||||
## The [Palette] the image is currently using for indexed mode.
|
||||
var current_palette := Palettes.current_palette
|
||||
## An [Image] that contains the index of each pixel of the main image for indexed mode.
|
||||
## The indices are stored in the red channel of this image, by diving each index by 255.
|
||||
## This means that there can be a maximum index size of 255. 0 means that the pixel is transparent.
|
||||
var indices_image := Image.create_empty(1, 1, false, Image.FORMAT_R8)
|
||||
## A [PackedColorArray] containing all of the colors of the [member current_palette].
|
||||
var palette := PackedColorArray()
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
indices_image.fill(TRANSPARENT)
|
||||
Palettes.palette_selected.connect(select_palette)
|
||||
|
||||
|
||||
## Equivalent of [method Image.create_empty], but returns [ImageExtended] instead.
|
||||
## If [param _is_indexed] is [code]true[/code], the image that is being returned uses indexed mode.
|
||||
static func create_custom(
|
||||
width: int, height: int, mipmaps: bool, format: Image.Format, _is_indexed := false
|
||||
) -> ImageExtended:
|
||||
var new_image := ImageExtended.new()
|
||||
new_image.crop(width, height)
|
||||
if mipmaps:
|
||||
new_image.generate_mipmaps()
|
||||
new_image.convert(format)
|
||||
new_image.fill(TRANSPARENT)
|
||||
new_image.is_indexed = _is_indexed
|
||||
if new_image.is_indexed:
|
||||
new_image.resize_indices()
|
||||
new_image.select_palette("", false)
|
||||
return new_image
|
||||
|
||||
|
||||
## Equivalent of [method Image.copy_from], but also handles the logic necessary for indexed mode.
|
||||
## If [param _is_indexed] is [code]true[/code], the image is set to be using indexed mode.
|
||||
func copy_from_custom(image: Image, indexed := is_indexed) -> void:
|
||||
is_indexed = indexed
|
||||
copy_from(image)
|
||||
if is_indexed:
|
||||
resize_indices()
|
||||
select_palette("", false)
|
||||
convert_rgb_to_indexed()
|
||||
|
||||
|
||||
## Selects a new palette to use in indexed mode.
|
||||
func select_palette(_name: String, convert_to_rgb := true) -> void:
|
||||
current_palette = Palettes.current_palette
|
||||
if not is_instance_valid(current_palette) or not is_indexed:
|
||||
return
|
||||
update_palette()
|
||||
if not current_palette.data_changed.is_connected(update_palette):
|
||||
current_palette.data_changed.connect(update_palette)
|
||||
if not current_palette.data_changed.is_connected(convert_indexed_to_rgb):
|
||||
current_palette.data_changed.connect(convert_indexed_to_rgb)
|
||||
if convert_to_rgb:
|
||||
convert_indexed_to_rgb()
|
||||
|
||||
|
||||
## Updates [member palette] to contain the colors of [member current_palette].
|
||||
func update_palette() -> void:
|
||||
if not is_instance_valid(current_palette):
|
||||
return
|
||||
if palette.size() != current_palette.colors_max:
|
||||
palette.resize(current_palette.colors_max)
|
||||
palette.fill(TRANSPARENT)
|
||||
for i in current_palette.colors:
|
||||
# Due to the decimal nature of the color values, some values get rounded off
|
||||
# unintentionally.
|
||||
# Even though the decimal values change, the HTML code remains the same after the change.
|
||||
# So we're using this trick to convert the values back to how they are shown in
|
||||
# the palette.
|
||||
palette[i] = Color(current_palette.colors[i].color.to_html())
|
||||
|
||||
|
||||
## Displays the actual RGBA values of each pixel in the image from indexed mode.
|
||||
func convert_indexed_to_rgb() -> void:
|
||||
if not is_indexed or not is_instance_valid(current_palette):
|
||||
return
|
||||
var palette_image := current_palette.convert_to_image(false)
|
||||
var palette_texture := ImageTexture.create_from_image(palette_image)
|
||||
var shader_image_effect := ShaderImageEffect.new()
|
||||
var indices_texture := ImageTexture.create_from_image(indices_image)
|
||||
var params := {"palette_texture": palette_texture, "indices_texture": indices_texture}
|
||||
shader_image_effect.generate_image(self, INDEXED_TO_RGB, params, get_size(), false)
|
||||
Global.canvas.queue_redraw()
|
||||
|
||||
|
||||
## Automatically maps each color of the image's pixel to the closest color of the palette,
|
||||
## by finding the palette color's index and storing it in [member indices_image].
|
||||
func convert_rgb_to_indexed() -> void:
|
||||
if not is_indexed or not is_instance_valid(current_palette):
|
||||
return
|
||||
var palette_image := current_palette.convert_to_image(false)
|
||||
var palette_texture := ImageTexture.create_from_image(palette_image)
|
||||
var params := {
|
||||
"palette_texture": palette_texture, "rgb_texture": ImageTexture.create_from_image(self)
|
||||
}
|
||||
var shader_image_effect := ShaderImageEffect.new()
|
||||
shader_image_effect.generate_image(
|
||||
indices_image, SET_INDICES, params, indices_image.get_size(), false
|
||||
)
|
||||
convert_indexed_to_rgb()
|
||||
|
||||
|
||||
## Resizes indices and calls [method convert_rgb_to_indexed] when the image's size changes
|
||||
## and indexed mode is enabled.
|
||||
func on_size_changed() -> void:
|
||||
if is_indexed:
|
||||
resize_indices()
|
||||
convert_rgb_to_indexed()
|
||||
|
||||
|
||||
## Resizes [indices_image] to the image's size.
|
||||
func resize_indices() -> void:
|
||||
indices_image.crop(get_width(), get_height())
|
||||
|
||||
|
||||
## Equivalent of [method Image.set_pixel],
|
||||
## but also handles the logic necessary for indexed mode.
|
||||
func set_pixel_custom(x: int, y: int, color: Color) -> void:
|
||||
set_pixelv_custom(Vector2i(x, y), color)
|
||||
|
||||
|
||||
## Equivalent of [method Image.set_pixelv],
|
||||
## but also handles the logic necessary for indexed mode.
|
||||
func set_pixelv_custom(point: Vector2i, color: Color) -> void:
|
||||
var new_color := color
|
||||
if is_indexed:
|
||||
var color_to_fill := TRANSPARENT
|
||||
var color_index := 0
|
||||
if not color.is_equal_approx(TRANSPARENT):
|
||||
if palette.has(color):
|
||||
color_index = palette.find(color)
|
||||
# If the color selected in the palette is the same then it should take prioity.
|
||||
var selected_index = Palettes.current_palette_get_selected_color_index(
|
||||
Tools.active_button
|
||||
)
|
||||
if selected_index != -1:
|
||||
if palette[selected_index].is_equal_approx(color):
|
||||
color_index = selected_index
|
||||
else: # Find the most similar color
|
||||
var smaller_distance := color_distance(color, palette[0])
|
||||
for i in palette.size():
|
||||
var swatch := palette[i]
|
||||
if is_zero_approx(swatch.a): # Skip transparent colors
|
||||
continue
|
||||
var dist := color_distance(color, swatch)
|
||||
if dist < smaller_distance:
|
||||
smaller_distance = dist
|
||||
color_index = i
|
||||
indices_image.set_pixelv(point, Color((color_index + 1) / 255.0, 0, 0, 0))
|
||||
color_to_fill = palette[color_index]
|
||||
new_color = color_to_fill
|
||||
else:
|
||||
indices_image.set_pixelv(point, TRANSPARENT)
|
||||
new_color = TRANSPARENT
|
||||
set_pixelv(point, new_color)
|
||||
|
||||
|
||||
## Finds the distance between colors [param c1] and [param c2].
|
||||
func color_distance(c1: Color, c2: Color) -> float:
|
||||
var v1 := Vector4(c1.r, c1.g, c1.b, c1.a)
|
||||
var v2 := Vector4(c2.r, c2.g, c2.b, c2.a)
|
||||
return v2.distance_to(v1)
|
||||
|
||||
|
||||
## Adds image data to a [param dict] [Dictionary]. Used for undo/redo.
|
||||
func add_data_to_dictionary(dict: Dictionary, other_image: ImageExtended = null) -> void:
|
||||
# The order matters! Setting self's data first would make undo/redo appear to work incorrectly.
|
||||
if is_instance_valid(other_image):
|
||||
dict[other_image.indices_image] = indices_image.data
|
||||
dict[other_image] = data
|
||||
else:
|
||||
dict[indices_image] = indices_image.data
|
||||
dict[self] = data
|
|
@ -218,11 +218,16 @@ func link_cel(cel: BaseCel, link_set = null) -> void:
|
|||
## This method is not destructive as it does NOT change the data of the image,
|
||||
## it just returns a copy.
|
||||
func display_effects(cel: BaseCel, image_override: Image = null) -> Image:
|
||||
var image := Image.new()
|
||||
var image := ImageExtended.new()
|
||||
if is_instance_valid(image_override):
|
||||
image.copy_from(image_override)
|
||||
if image_override is ImageExtended:
|
||||
image.is_indexed = image_override.is_indexed
|
||||
image.copy_from_custom(image_override)
|
||||
else:
|
||||
image.copy_from(cel.get_image())
|
||||
var cel_image := cel.get_image()
|
||||
if cel_image is ImageExtended:
|
||||
image.is_indexed = cel_image.is_indexed
|
||||
image.copy_from_custom(cel_image)
|
||||
if not effects_enabled:
|
||||
return image
|
||||
var image_size := image.get_size()
|
||||
|
|
|
@ -13,7 +13,9 @@ func _init(_project: Project, _name := "") -> void:
|
|||
|
||||
## Blends all of the images of children layer of the group layer into a single image.
|
||||
func blend_children(frame: Frame, origin := Vector2i.ZERO, apply_effects := true) -> Image:
|
||||
var image := Image.create(project.size.x, project.size.y, false, Image.FORMAT_RGBA8)
|
||||
var image := ImageExtended.create_custom(
|
||||
project.size.x, project.size.y, false, project.get_image_format(), project.is_indexed()
|
||||
)
|
||||
var children := get_children(false)
|
||||
if children.size() <= 0:
|
||||
return image
|
||||
|
@ -66,7 +68,7 @@ func blend_children(frame: Frame, origin := Vector2i.ZERO, apply_effects := true
|
|||
|
||||
|
||||
func _include_child_in_blending(
|
||||
image: Image,
|
||||
image: ImageExtended,
|
||||
layer: BaseLayer,
|
||||
frame: Frame,
|
||||
textures: Array[Image],
|
||||
|
@ -100,7 +102,7 @@ func _include_child_in_blending(
|
|||
## Gets called recursively if the child group has children groups of its own,
|
||||
## and they are also set to pass through mode.
|
||||
func _blend_child_group(
|
||||
image: Image,
|
||||
image: ImageExtended,
|
||||
layer: BaseLayer,
|
||||
frame: Frame,
|
||||
textures: Array[Image],
|
||||
|
|
|
@ -28,9 +28,19 @@ func get_layer_type() -> int:
|
|||
|
||||
|
||||
func new_empty_cel() -> BaseCel:
|
||||
var image := Image.create(project.size.x, project.size.y, false, Image.FORMAT_RGBA8)
|
||||
var format := project.get_image_format()
|
||||
var is_indexed := project.is_indexed()
|
||||
var image := ImageExtended.create_custom(
|
||||
project.size.x, project.size.y, false, format, is_indexed
|
||||
)
|
||||
return PixelCel.new(image)
|
||||
|
||||
|
||||
func new_cel_from_image(image: Image) -> PixelCel:
|
||||
var pixelorama_image := ImageExtended.new()
|
||||
pixelorama_image.copy_from_custom(image, project.is_indexed())
|
||||
return PixelCel.new(pixelorama_image)
|
||||
|
||||
|
||||
func can_layer_get_drawn() -> bool:
|
||||
return is_visible_in_hierarchy() && !is_locked_in_hierarchy()
|
||||
|
|
|
@ -9,6 +9,8 @@ signal about_to_deserialize(dict: Dictionary)
|
|||
signal resized
|
||||
signal timeline_updated
|
||||
|
||||
const INDEXED_MODE := Image.FORMAT_MAX + 1
|
||||
|
||||
var name := "":
|
||||
set(value):
|
||||
name = value
|
||||
|
@ -21,6 +23,18 @@ var undo_redo := UndoRedo.new()
|
|||
var tiles: Tiles
|
||||
var undos := 0 ## The number of times we added undo properties
|
||||
var can_undo := true
|
||||
var color_mode: int = Image.FORMAT_RGBA8:
|
||||
set(value):
|
||||
if color_mode != value:
|
||||
color_mode = value
|
||||
for cel in get_all_pixel_cels():
|
||||
var image := cel.get_image()
|
||||
image.is_indexed = is_indexed()
|
||||
if image.is_indexed:
|
||||
image.resize_indices()
|
||||
image.select_palette("", false)
|
||||
image.convert_rgb_to_indexed()
|
||||
Global.canvas.color_index.queue_redraw()
|
||||
var fill_color := Color(0)
|
||||
var has_changed := false:
|
||||
set(value):
|
||||
|
@ -56,8 +70,12 @@ var user_data := "" ## User defined data, set in the project properties.
|
|||
|
||||
var x_symmetry_point: float
|
||||
var y_symmetry_point: float
|
||||
var xy_symmetry_point: Vector2
|
||||
var x_minus_y_symmetry_point: Vector2
|
||||
var x_symmetry_axis := SymmetryGuide.new()
|
||||
var y_symmetry_axis := SymmetryGuide.new()
|
||||
var diagonal_xy_symmetry_axis := SymmetryGuide.new()
|
||||
var diagonal_x_minus_y_symmetry_axis := SymmetryGuide.new()
|
||||
|
||||
var selection_map := SelectionMap.new()
|
||||
## This is useful for when the selection is outside of the canvas boundaries,
|
||||
|
@ -98,17 +116,32 @@ func _init(_frames: Array[Frame] = [], _name := tr("untitled"), _size := Vector2
|
|||
|
||||
x_symmetry_point = size.x - 1
|
||||
y_symmetry_point = size.y - 1
|
||||
x_symmetry_axis.type = x_symmetry_axis.Types.HORIZONTAL
|
||||
xy_symmetry_point = Vector2i(size.y, size.x) - Vector2i.ONE
|
||||
x_minus_y_symmetry_point = Vector2(maxi(size.x - size.y, 0), maxi(size.y - size.x, 0))
|
||||
x_symmetry_axis.type = Guide.Types.HORIZONTAL
|
||||
x_symmetry_axis.project = self
|
||||
x_symmetry_axis.add_point(Vector2(-19999, y_symmetry_point / 2 + 0.5))
|
||||
x_symmetry_axis.add_point(Vector2(19999, y_symmetry_point / 2 + 0.5))
|
||||
Global.canvas.add_child(x_symmetry_axis)
|
||||
y_symmetry_axis.type = y_symmetry_axis.Types.VERTICAL
|
||||
|
||||
y_symmetry_axis.type = Guide.Types.VERTICAL
|
||||
y_symmetry_axis.project = self
|
||||
y_symmetry_axis.add_point(Vector2(x_symmetry_point / 2 + 0.5, -19999))
|
||||
y_symmetry_axis.add_point(Vector2(x_symmetry_point / 2 + 0.5, 19999))
|
||||
Global.canvas.add_child(y_symmetry_axis)
|
||||
|
||||
diagonal_xy_symmetry_axis.type = Guide.Types.XY
|
||||
diagonal_xy_symmetry_axis.project = self
|
||||
diagonal_xy_symmetry_axis.add_point(Vector2(19999, -19999))
|
||||
diagonal_xy_symmetry_axis.add_point(Vector2(-19999, 19999) + xy_symmetry_point + Vector2.ONE)
|
||||
Global.canvas.add_child(diagonal_xy_symmetry_axis)
|
||||
|
||||
diagonal_x_minus_y_symmetry_axis.type = Guide.Types.X_MINUS_Y
|
||||
diagonal_x_minus_y_symmetry_axis.project = self
|
||||
diagonal_x_minus_y_symmetry_axis.add_point(Vector2(-19999, -19999))
|
||||
diagonal_x_minus_y_symmetry_axis.add_point(Vector2(19999, 19999) + x_minus_y_symmetry_point)
|
||||
Global.canvas.add_child(diagonal_x_minus_y_symmetry_axis)
|
||||
|
||||
if OS.get_name() == "Web":
|
||||
export_directory_path = "user://"
|
||||
else:
|
||||
|
@ -176,11 +209,26 @@ func new_empty_frame() -> Frame:
|
|||
return frame
|
||||
|
||||
|
||||
## Returns a new [Image] of size [member size] and format [method get_image_format].
|
||||
func new_empty_image() -> Image:
|
||||
return Image.create(size.x, size.y, false, get_image_format())
|
||||
|
||||
|
||||
## Returns the currently selected [BaseCel].
|
||||
func get_current_cel() -> BaseCel:
|
||||
return frames[current_frame].cels[current_layer]
|
||||
|
||||
|
||||
func get_image_format() -> Image.Format:
|
||||
if color_mode == INDEXED_MODE:
|
||||
return Image.FORMAT_RGBA8
|
||||
return color_mode
|
||||
|
||||
|
||||
func is_indexed() -> bool:
|
||||
return color_mode == INDEXED_MODE
|
||||
|
||||
|
||||
func selection_map_changed() -> void:
|
||||
var image_texture: ImageTexture
|
||||
has_selection = !selection_map.is_invisible()
|
||||
|
@ -255,6 +303,7 @@ func serialize() -> Dictionary:
|
|||
"pxo_version": ProjectSettings.get_setting("application/config/Pxo_Version"),
|
||||
"size_x": size.x,
|
||||
"size_y": size.y,
|
||||
"color_mode": color_mode,
|
||||
"tile_mode_x_basis_x": tiles.x_basis.x,
|
||||
"tile_mode_x_basis_y": tiles.x_basis.y,
|
||||
"tile_mode_y_basis_x": tiles.y_basis.x,
|
||||
|
@ -288,6 +337,7 @@ func deserialize(dict: Dictionary, zip_reader: ZIPReader = null, file: FileAcces
|
|||
size.y = dict.size_y
|
||||
tiles.tile_size = size
|
||||
selection_map.crop(size.x, size.y)
|
||||
color_mode = dict.get("color_mode", color_mode)
|
||||
if dict.has("tile_mode_x_basis_x") and dict.has("tile_mode_x_basis_y"):
|
||||
tiles.x_basis.x = dict.tile_mode_x_basis_x
|
||||
tiles.x_basis.y = dict.tile_mode_x_basis_y
|
||||
|
@ -311,20 +361,33 @@ func deserialize(dict: Dictionary, zip_reader: ZIPReader = null, file: FileAcces
|
|||
for cel in frame.cels:
|
||||
match int(dict.layers[cel_i].get("type", Global.LayerTypes.PIXEL)):
|
||||
Global.LayerTypes.PIXEL:
|
||||
var image := Image.new()
|
||||
var image: Image
|
||||
var indices_data := PackedByteArray()
|
||||
if is_instance_valid(zip_reader): # For pxo files saved in 1.0+
|
||||
var image_data := zip_reader.read_file(
|
||||
"image_data/frames/%s/layer_%s" % [frame_i + 1, cel_i + 1]
|
||||
)
|
||||
var path := "image_data/frames/%s/layer_%s" % [frame_i + 1, cel_i + 1]
|
||||
var image_data := zip_reader.read_file(path)
|
||||
image = Image.create_from_data(
|
||||
size.x, size.y, false, Image.FORMAT_RGBA8, image_data
|
||||
size.x, size.y, false, get_image_format(), image_data
|
||||
)
|
||||
var indices_path := (
|
||||
"image_data/frames/%s/indices_layer_%s" % [frame_i + 1, cel_i + 1]
|
||||
)
|
||||
if zip_reader.file_exists(indices_path):
|
||||
indices_data = zip_reader.read_file(indices_path)
|
||||
elif is_instance_valid(file): # For pxo files saved in 0.x
|
||||
var buffer := file.get_buffer(size.x * size.y * 4)
|
||||
image = Image.create_from_data(
|
||||
size.x, size.y, false, Image.FORMAT_RGBA8, buffer
|
||||
size.x, size.y, false, get_image_format(), buffer
|
||||
)
|
||||
cels.append(PixelCel.new(image))
|
||||
var pixelorama_image := ImageExtended.new()
|
||||
pixelorama_image.is_indexed = is_indexed()
|
||||
if not indices_data.is_empty() and is_indexed():
|
||||
pixelorama_image.indices_image = Image.create_from_data(
|
||||
size.x, size.y, false, Image.FORMAT_R8, indices_data
|
||||
)
|
||||
pixelorama_image.copy_from(image)
|
||||
pixelorama_image.select_palette("", true)
|
||||
cels.append(PixelCel.new(pixelorama_image))
|
||||
Global.LayerTypes.GROUP:
|
||||
cels.append(GroupCel.new())
|
||||
Global.LayerTypes.THREE_D:
|
||||
|
@ -559,6 +622,16 @@ func find_first_drawable_cel(frame := frames[current_frame]) -> BaseCel:
|
|||
return result
|
||||
|
||||
|
||||
## Returns an [Array] of type [PixelCel] containing all of the pixel cels of the project.
|
||||
func get_all_pixel_cels() -> Array[PixelCel]:
|
||||
var cels: Array[PixelCel]
|
||||
for frame in frames:
|
||||
for cel in frame.cels:
|
||||
if cel is PixelCel:
|
||||
cels.append(cel)
|
||||
return cels
|
||||
|
||||
|
||||
## Re-order layers to take each cel's z-index into account. If all z-indexes are 0,
|
||||
## then the order of drawing is the same as the order of the layers itself.
|
||||
func order_layers(frame_index := current_frame) -> void:
|
||||
|
|
|
@ -5,7 +5,9 @@ extends RefCounted
|
|||
signal done
|
||||
|
||||
|
||||
func generate_image(img: Image, shader: Shader, params: Dictionary, size: Vector2i) -> void:
|
||||
func generate_image(
|
||||
img: Image, shader: Shader, params: Dictionary, size: Vector2i, respect_indexed := true
|
||||
) -> void:
|
||||
# duplicate shader before modifying code to avoid affecting original resource
|
||||
var resized_width := false
|
||||
var resized_height := false
|
||||
|
@ -60,4 +62,6 @@ func generate_image(img: Image, shader: Shader, params: Dictionary, size: Vector
|
|||
img.crop(img.get_width() - 1, img.get_height())
|
||||
if resized_height:
|
||||
img.crop(img.get_width(), img.get_height() - 1)
|
||||
if img is ImageExtended and respect_indexed:
|
||||
img.convert_rgb_to_indexed()
|
||||
done.emit()
|
||||
|
|
|
@ -601,6 +601,7 @@ func _exit_tree() -> void:
|
|||
Global.config_cache.set_value("window", "size", get_window().size)
|
||||
Global.config_cache.set_value("view_menu", "draw_grid", Global.draw_grid)
|
||||
Global.config_cache.set_value("view_menu", "draw_pixel_grid", Global.draw_pixel_grid)
|
||||
Global.config_cache.set_value("view_menu", "show_pixel_indices", Global.show_pixel_indices)
|
||||
Global.config_cache.set_value("view_menu", "show_rulers", Global.show_rulers)
|
||||
Global.config_cache.set_value("view_menu", "show_guides", Global.show_guides)
|
||||
Global.config_cache.set_value("view_menu", "show_mouse_guides", Global.show_mouse_guides)
|
||||
|
|
|
@ -25,7 +25,7 @@ var colors_max := 0
|
|||
|
||||
|
||||
class PaletteColor:
|
||||
var color := Color.TRANSPARENT
|
||||
var color := Color(0, 0, 0, 0)
|
||||
var index := -1
|
||||
|
||||
func _init(init_color := Color.BLACK, init_index := -1) -> void:
|
||||
|
@ -358,9 +358,11 @@ static func strip_unvalid_characters(string_to_strip: String) -> String:
|
|||
return regex.sub(string_to_strip, "", true)
|
||||
|
||||
|
||||
func convert_to_image() -> Image:
|
||||
func convert_to_image(crop_image := true) -> Image:
|
||||
var image := Image.create(colors_max, 1, false, Image.FORMAT_RGBA8)
|
||||
for i in colors_max:
|
||||
if colors.has(i):
|
||||
image.set_pixel(i, 0, colors[i].color)
|
||||
image.set_pixel(i, 0, Color(colors[i].color.to_html()))
|
||||
if crop_image:
|
||||
image.copy_from(image.get_region(image.get_used_rect()))
|
||||
return image
|
||||
|
|
|
@ -23,10 +23,6 @@ func _ready() -> void:
|
|||
|
||||
|
||||
func set_palette(new_palette: Palette) -> void:
|
||||
# Only display valid palette objects
|
||||
if not new_palette:
|
||||
return
|
||||
|
||||
current_palette = new_palette
|
||||
grid_window_origin = Vector2.ZERO
|
||||
|
||||
|
@ -86,15 +82,20 @@ func scroll_palette(origin: Vector2i) -> void:
|
|||
|
||||
## Called when the color changes, either the left or the right, determined by [param mouse_button].
|
||||
## If current palette has [param target_color] as a [Color], then select it.
|
||||
## This is helpful when we select color indirectly (e.g through colorpicker)
|
||||
func find_and_select_color(target_color: Color, mouse_button: int) -> void:
|
||||
var old_index := Palettes.current_palette_get_selected_color_index(mouse_button)
|
||||
if not is_instance_valid(current_palette):
|
||||
return
|
||||
var selected_index := Palettes.current_palette_get_selected_color_index(mouse_button)
|
||||
if get_swatch_color(selected_index) == target_color: # Color already selected
|
||||
return
|
||||
for color_ind in swatches.size():
|
||||
if (
|
||||
target_color.is_equal_approx(swatches[color_ind].color)
|
||||
or target_color.to_html() == swatches[color_ind].color.to_html()
|
||||
):
|
||||
var index := convert_grid_index_to_palette_index(color_ind)
|
||||
select_swatch(mouse_button, index, old_index)
|
||||
select_swatch(mouse_button, index, selected_index)
|
||||
match mouse_button:
|
||||
MOUSE_BUTTON_LEFT:
|
||||
Palettes.left_selected_color = index
|
||||
|
@ -115,6 +116,8 @@ func find_and_select_color(target_color: Color, mouse_button: int) -> void:
|
|||
|
||||
## Displays a left/right highlight over a swatch
|
||||
func select_swatch(mouse_button: int, palette_index: int, old_palette_index: int) -> void:
|
||||
if not is_instance_valid(current_palette):
|
||||
return
|
||||
var index := convert_palette_index_to_grid_index(palette_index)
|
||||
var old_index := convert_palette_index_to_grid_index(old_palette_index)
|
||||
if index >= 0 and index < swatches.size():
|
||||
|
@ -159,16 +162,17 @@ func convert_palette_index_to_grid_index(palette_index: int) -> int:
|
|||
|
||||
|
||||
func resize_grid(new_rect_size: Vector2) -> void:
|
||||
if not is_instance_valid(current_palette):
|
||||
return
|
||||
var grid_x: int = (
|
||||
new_rect_size.x / (swatch_size.x + get("theme_override_constants/h_separation"))
|
||||
)
|
||||
var grid_y: int = (
|
||||
new_rect_size.y / (swatch_size.y + get("theme_override_constants/v_separation"))
|
||||
)
|
||||
grid_size.x = mini(grid_x, current_palette.width)
|
||||
grid_size.y = mini(grid_y, current_palette.height)
|
||||
if is_instance_valid(current_palette):
|
||||
grid_size.x = mini(grid_x, current_palette.width)
|
||||
grid_size.y = mini(grid_y, current_palette.height)
|
||||
else:
|
||||
grid_size = Vector2i.ZERO
|
||||
setup_swatches()
|
||||
draw_palette()
|
||||
|
||||
|
|
|
@ -89,16 +89,16 @@ func select_palette(palette_name: String) -> void:
|
|||
var palette_id = palettes_path_id.get(palette_name)
|
||||
if palette_id != null:
|
||||
palette_select.selected = palette_id
|
||||
palette_grid.set_palette(Palettes.current_palette)
|
||||
palette_scroll.resize_grid()
|
||||
palette_scroll.set_sliders(Palettes.current_palette, palette_grid.grid_window_origin)
|
||||
palette_grid.set_palette(Palettes.current_palette)
|
||||
palette_scroll.resize_grid()
|
||||
palette_scroll.set_sliders(Palettes.current_palette, palette_grid.grid_window_origin)
|
||||
|
||||
var left_selected := Palettes.current_palette_get_selected_color_index(MOUSE_BUTTON_LEFT)
|
||||
var right_selected := Palettes.current_palette_get_selected_color_index(MOUSE_BUTTON_RIGHT)
|
||||
palette_grid.select_swatch(MOUSE_BUTTON_LEFT, left_selected, left_selected)
|
||||
palette_grid.select_swatch(MOUSE_BUTTON_RIGHT, right_selected, right_selected)
|
||||
var left_selected := Palettes.current_palette_get_selected_color_index(MOUSE_BUTTON_LEFT)
|
||||
var right_selected := Palettes.current_palette_get_selected_color_index(MOUSE_BUTTON_RIGHT)
|
||||
palette_grid.select_swatch(MOUSE_BUTTON_LEFT, left_selected, left_selected)
|
||||
palette_grid.select_swatch(MOUSE_BUTTON_RIGHT, right_selected, right_selected)
|
||||
|
||||
toggle_add_delete_buttons()
|
||||
toggle_add_delete_buttons()
|
||||
|
||||
|
||||
## Select and display current palette
|
||||
|
@ -115,6 +115,8 @@ func redraw_current_palette() -> void:
|
|||
|
||||
|
||||
func toggle_add_delete_buttons() -> void:
|
||||
if not is_instance_valid(Palettes.current_palette):
|
||||
return
|
||||
add_color_button.disabled = Palettes.current_palette.is_full()
|
||||
if add_color_button.disabled:
|
||||
add_color_button.mouse_default_cursor_shape = CURSOR_FORBIDDEN
|
||||
|
@ -252,6 +254,7 @@ func _on_ColorPicker_color_changed(color: Color) -> void:
|
|||
== Palettes.current_palette_get_selected_color_index(MOUSE_BUTTON_RIGHT)
|
||||
):
|
||||
Tools.assign_color(color, MOUSE_BUTTON_RIGHT)
|
||||
Palettes.current_palette_set_color(edited_swatch_index, edited_swatch_color)
|
||||
|
||||
|
||||
## Saves edited swatch to palette file when color selection dialog is closed
|
||||
|
|
|
@ -4,9 +4,9 @@ var scroll := Vector2i.ZERO
|
|||
var drag_started := false
|
||||
var drag_start_position := Vector2i.ZERO
|
||||
|
||||
@onready var h_slider := %HScrollBar
|
||||
@onready var v_slider := %VScrollBar
|
||||
@onready var palette_grid := %PaletteGrid
|
||||
@onready var h_slider := %HScrollBar as HScrollBar
|
||||
@onready var v_slider := %VScrollBar as VScrollBar
|
||||
@onready var palette_grid := %PaletteGrid as PaletteGrid
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
|
@ -17,16 +17,21 @@ func _input(event: InputEvent) -> void:
|
|||
|
||||
|
||||
func set_sliders(palette: Palette, origin: Vector2i) -> void:
|
||||
if not is_instance_valid(palette):
|
||||
return
|
||||
h_slider.value = origin.x
|
||||
h_slider.max_value = palette.width
|
||||
h_slider.page = palette_grid.grid_size.x
|
||||
if is_instance_valid(palette):
|
||||
h_slider.value = origin.x
|
||||
h_slider.max_value = palette.width
|
||||
h_slider.page = palette_grid.grid_size.x
|
||||
v_slider.value = origin.y
|
||||
v_slider.max_value = palette.height
|
||||
v_slider.page = palette_grid.grid_size.y
|
||||
else:
|
||||
h_slider.value = 0
|
||||
h_slider.max_value = 0
|
||||
h_slider.page = 0
|
||||
v_slider.value = 0
|
||||
v_slider.max_value = 0
|
||||
v_slider.page = 0
|
||||
h_slider.visible = false if h_slider.max_value <= palette_grid.grid_size.x else true
|
||||
|
||||
v_slider.value = origin.y
|
||||
v_slider.max_value = palette.height
|
||||
v_slider.page = palette_grid.grid_size.y
|
||||
v_slider.visible = false if v_slider.max_value <= palette_grid.grid_size.y else true
|
||||
|
||||
|
||||
|
@ -58,7 +63,7 @@ func _on_PaletteGrid_gui_input(event: InputEvent) -> void:
|
|||
drag_started = true
|
||||
# Keeps position where the dragging started
|
||||
drag_start_position = (
|
||||
event.position + Vector2i(h_slider.value, v_slider.value) * palette_grid.swatch_size
|
||||
event.position + Vector2(h_slider.value, v_slider.value) * palette_grid.swatch_size
|
||||
)
|
||||
|
||||
if event is InputEventMouseMotion and drag_started:
|
||||
|
|
200
src/Preferences/GridPreferences.gd
Normal file
|
@ -0,0 +1,200 @@
|
|||
extends GridContainer
|
||||
|
||||
# We should use pre defined initial grid colors instead of random colors
|
||||
const INITIAL_GRID_COLORS := [
|
||||
Color.BLACK,
|
||||
Color.WHITE,
|
||||
Color.YELLOW,
|
||||
Color.GREEN,
|
||||
Color.BLUE,
|
||||
Color.GRAY,
|
||||
Color.ORANGE,
|
||||
Color.PINK,
|
||||
Color.SIENNA,
|
||||
Color.CORAL,
|
||||
]
|
||||
|
||||
var grid_preferences: Array[GridPreference] = [
|
||||
GridPreference.new("grid_type", "GridType", "selected", Global.GridTypes.CARTESIAN),
|
||||
GridPreference.new("grid_size", "GridSizeValue", "value", Vector2i(2, 2)),
|
||||
GridPreference.new("isometric_grid_size", "IsometricGridSizeValue", "value", Vector2i(16, 8)),
|
||||
GridPreference.new("grid_offset", "GridOffsetValue", "value", Vector2i.ZERO),
|
||||
GridPreference.new("grid_draw_over_tile_mode", "GridDrawOverTileMode", "button_pressed", false),
|
||||
GridPreference.new("grid_color", "GridColor", "color", Color.BLACK),
|
||||
]
|
||||
|
||||
var grid_selected: int = 0:
|
||||
set(key):
|
||||
grid_selected = key
|
||||
for child: BaseButton in grids_select_container.get_children():
|
||||
if child.get_index() == grid_selected:
|
||||
child.self_modulate = Color.WHITE
|
||||
else:
|
||||
child.self_modulate = Color.DIM_GRAY
|
||||
var grids: Dictionary = Global.config_cache.get_value(
|
||||
"preferences", "grids", {0: create_default_properties()}
|
||||
)
|
||||
if grids.has(key):
|
||||
update_pref_ui(grids[key])
|
||||
|
||||
@onready var grids_select_container: HFlowContainer = $GridsSelectContainer
|
||||
|
||||
|
||||
class GridPreference:
|
||||
var prop_name: String
|
||||
var node_path: String
|
||||
var value_type: String
|
||||
var default_value
|
||||
|
||||
func _init(
|
||||
_prop_name: String,
|
||||
_node_path: String,
|
||||
_value_type: String,
|
||||
_default_value = null,
|
||||
_require_restart := false
|
||||
) -> void:
|
||||
prop_name = _prop_name
|
||||
node_path = _node_path
|
||||
value_type = _value_type
|
||||
if _default_value != null:
|
||||
default_value = _default_value
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var grids = Global.config_cache.get_value(
|
||||
"preferences", "grids", {0: create_default_properties()}
|
||||
)
|
||||
Global.config_cache.set_value("preferences", "grids", grids)
|
||||
$GridsCount.value = grids.size()
|
||||
if grids.size() == 1:
|
||||
add_remove_select_button(0)
|
||||
for pref in grid_preferences:
|
||||
if not has_node(pref.node_path):
|
||||
continue
|
||||
var node := get_node(pref.node_path)
|
||||
var restore_default_button := RestoreDefaultButton.new()
|
||||
restore_default_button.pressed.connect(
|
||||
_on_grid_pref_value_changed.bind(pref.default_value, pref, restore_default_button)
|
||||
)
|
||||
restore_default_button.setting_name = pref.prop_name
|
||||
restore_default_button.value_type = pref.value_type
|
||||
restore_default_button.default_value = pref.default_value
|
||||
restore_default_button.node = node
|
||||
|
||||
var node_position := node.get_index()
|
||||
node.get_parent().add_child(restore_default_button)
|
||||
node.get_parent().move_child(restore_default_button, node_position)
|
||||
|
||||
match pref.value_type:
|
||||
"button_pressed":
|
||||
node.toggled.connect(_on_grid_pref_value_changed.bind(pref, restore_default_button))
|
||||
"value":
|
||||
node.value_changed.connect(
|
||||
_on_grid_pref_value_changed.bind(pref, restore_default_button)
|
||||
)
|
||||
"color":
|
||||
node.get_picker().presets_visible = false
|
||||
node.color_changed.connect(
|
||||
_on_grid_pref_value_changed.bind(pref, restore_default_button)
|
||||
)
|
||||
"selected":
|
||||
node.item_selected.connect(
|
||||
_on_grid_pref_value_changed.bind(pref, restore_default_button)
|
||||
)
|
||||
grid_selected = 0
|
||||
|
||||
|
||||
func _on_grid_pref_value_changed(value, pref: GridPreference, button: RestoreDefaultButton) -> void:
|
||||
var grids: Dictionary = Global.config_cache.get_value(
|
||||
"preferences", "grids", {0: create_default_properties()}
|
||||
)
|
||||
if grids.has(grid_selected): # Failsafe (Always true)
|
||||
var grid_info: Dictionary = grids[grid_selected]
|
||||
var prop := pref.prop_name
|
||||
grid_info[prop] = value
|
||||
grids[grid_selected] = grid_info
|
||||
Global.update_grids(grids)
|
||||
var default_value = pref.default_value
|
||||
var disable: bool = Global.grids[grid_selected].get(prop) == default_value
|
||||
if typeof(value) == TYPE_COLOR:
|
||||
disable = value.is_equal_approx(default_value)
|
||||
disable_restore_default_button(button, disable)
|
||||
Global.config_cache.set_value("preferences", "grids", grids)
|
||||
|
||||
|
||||
func _on_grids_count_value_changed(value: float) -> void:
|
||||
var new_grids: Dictionary = Global.config_cache.get_value(
|
||||
"preferences", "grids", {0: create_default_properties()}
|
||||
)
|
||||
var last_grid_idx = int(value - 1)
|
||||
if last_grid_idx >= grids_select_container.get_child_count():
|
||||
# Add missing grids
|
||||
for key in range(grids_select_container.get_child_count(), value):
|
||||
if not new_grids.has(key):
|
||||
var new_grid := create_default_properties()
|
||||
if new_grids.has(key - 1): # Failsafe
|
||||
var last_grid = new_grids[key - 1]
|
||||
# This small bit of code is there to make ui look a little neater
|
||||
# Reasons:
|
||||
# - Usually user intends to make the next grid twice the size.
|
||||
# - Having all grids being same size initially may cause confusion for some
|
||||
# users when they try to change color of a middle grid not seeing it's changing
|
||||
# (due to being covered by grids above it).
|
||||
if (
|
||||
new_grid.has("grid_size")
|
||||
and new_grid.has("isometric_grid_size")
|
||||
and new_grid.has("grid_color")
|
||||
):
|
||||
new_grid["grid_size"] = last_grid["grid_size"] * 2
|
||||
new_grid["isometric_grid_size"] = last_grid["isometric_grid_size"] * 2
|
||||
if key < INITIAL_GRID_COLORS.size():
|
||||
new_grid["grid_color"] = INITIAL_GRID_COLORS[key]
|
||||
new_grids[key] = new_grid
|
||||
add_remove_select_button(key)
|
||||
else:
|
||||
# Remove extra grids
|
||||
for key: int in range(value, new_grids.size()):
|
||||
new_grids.erase(key)
|
||||
add_remove_select_button(key, true)
|
||||
grid_selected = min(grid_selected, last_grid_idx)
|
||||
Global.update_grids(new_grids)
|
||||
Global.config_cache.set_value("preferences", "grids", new_grids)
|
||||
|
||||
|
||||
func create_default_properties() -> Dictionary:
|
||||
var grid_info = {}
|
||||
for pref in grid_preferences:
|
||||
grid_info[pref.prop_name] = pref.default_value
|
||||
return grid_info
|
||||
|
||||
|
||||
func disable_restore_default_button(button: RestoreDefaultButton, disable: bool) -> void:
|
||||
button.disabled = disable
|
||||
if disable:
|
||||
button.mouse_default_cursor_shape = Control.CURSOR_ARROW
|
||||
button.tooltip_text = ""
|
||||
else:
|
||||
button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||||
button.tooltip_text = "Restore default value"
|
||||
|
||||
|
||||
func add_remove_select_button(grid_idx: int, remove := false):
|
||||
if not remove:
|
||||
var select_button = Button.new()
|
||||
select_button.text = str(grid_idx)
|
||||
grids_select_container.add_child(select_button)
|
||||
select_button.pressed.connect(func(): grid_selected = grid_idx)
|
||||
else:
|
||||
if grid_idx < grids_select_container.get_child_count():
|
||||
grids_select_container.get_child(grid_idx).queue_free()
|
||||
|
||||
|
||||
func update_pref_ui(grid_data: Dictionary):
|
||||
for pref in grid_preferences:
|
||||
var key = pref.prop_name
|
||||
if grid_data.has(key):
|
||||
var node := get_node(pref.node_path)
|
||||
node.set(pref.value_type, grid_data[key])
|
||||
if pref.value_type == "color":
|
||||
# the signal doesn't seem to be emitted automatically
|
||||
node.color_changed.emit(grid_data[key])
|
|
@ -94,21 +94,6 @@ var preferences: Array[Preference] = [
|
|||
Preference.new("smooth_zoom", "Canvas/ZoomOptions/SmoothZoom", "button_pressed", true),
|
||||
Preference.new("integer_zoom", "Canvas/ZoomOptions/IntegerZoom", "button_pressed", false),
|
||||
Preference.new("snapping_distance", "Canvas/SnappingOptions/DistanceValue", "value", 32.0),
|
||||
Preference.new(
|
||||
"grid_type", "Canvas/GridOptions/GridType", "selected", Global.GridTypes.CARTESIAN
|
||||
),
|
||||
Preference.new("grid_size", "Canvas/GridOptions/GridSizeValue", "value", Vector2i(2, 2)),
|
||||
Preference.new(
|
||||
"isometric_grid_size", "Canvas/GridOptions/IsometricGridSizeValue", "value", Vector2i(16, 8)
|
||||
),
|
||||
Preference.new("grid_offset", "Canvas/GridOptions/GridOffsetValue", "value", Vector2i.ZERO),
|
||||
Preference.new(
|
||||
"grid_draw_over_tile_mode",
|
||||
"Canvas/GridOptions/GridDrawOverTileMode",
|
||||
"button_pressed",
|
||||
false
|
||||
),
|
||||
Preference.new("grid_color", "Canvas/GridOptions/GridColor", "color", Color.BLACK),
|
||||
Preference.new(
|
||||
"pixel_grid_show_at_zoom", "Canvas/PixelGridOptions/ShowAtZoom", "value", 1500.0
|
||||
),
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
[gd_scene load_steps=9 format=3 uid="uid://b3hkjj3s6pe4x"]
|
||||
[gd_scene load_steps=11 format=3 uid="uid://b3hkjj3s6pe4x"]
|
||||
|
||||
[ext_resource type="Script" path="res://src/Preferences/PreferencesDialog.gd" id="1"]
|
||||
[ext_resource type="PackedScene" uid="uid://bq7ibhm0txl5p" path="res://addons/keychain/ShortcutEdit.tscn" id="3"]
|
||||
[ext_resource type="Script" path="res://src/Preferences/ThemesPreferences.gd" id="3_nvl8k"]
|
||||
[ext_resource type="Script" path="res://src/Preferences/GridPreferences.gd" id="4_76iff"]
|
||||
[ext_resource type="PackedScene" uid="uid://yjhp0ssng2mp" path="res://src/UI/Nodes/ValueSlider.tscn" id="5_rlmsh"]
|
||||
[ext_resource type="PackedScene" path="res://src/UI/Nodes/ValueSliderV2.tscn" id="7"]
|
||||
[ext_resource type="Script" path="res://src/Preferences/ExtensionsPreferences.gd" id="7_8ume5"]
|
||||
[ext_resource type="Script" path="res://src/UI/Nodes/ValueSlider.gd" id="8"]
|
||||
|
@ -482,6 +484,30 @@ layout_mode = 2
|
|||
theme_override_constants/h_separation = 4
|
||||
theme_override_constants/v_separation = 4
|
||||
columns = 3
|
||||
script = ExtResource("4_76iff")
|
||||
|
||||
[node name="GridsCountLabel" type="Label" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Canvas/GridOptions"]
|
||||
layout_mode = 2
|
||||
text = "Grids Visible:"
|
||||
|
||||
[node name="Spacer" type="Control" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Canvas/GridOptions"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="GridsCount" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Canvas/GridOptions" instance=ExtResource("5_rlmsh")]
|
||||
layout_mode = 2
|
||||
min_value = 1.0
|
||||
max_value = 10.0
|
||||
value = 1.0
|
||||
|
||||
[node name="GridsSelectLabel" type="Label" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Canvas/GridOptions"]
|
||||
layout_mode = 2
|
||||
text = "Editing Grid:"
|
||||
|
||||
[node name="Spacer2" type="Control" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Canvas/GridOptions"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="GridsSelectContainer" type="HFlowContainer" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Canvas/GridOptions"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="GridTypeLabel" type="Label" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Canvas/GridOptions"]
|
||||
layout_mode = 2
|
||||
|
@ -1478,6 +1504,7 @@ dialog_text = "Are you sure you want to reset the selected options? There will b
|
|||
[connection signal="pressed" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Language/System Language" to="." method="_on_language_pressed" binds= [1]]
|
||||
[connection signal="pressed" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Interface/InterfaceOptions/ShrinkContainer/ShrinkApplyButton" to="." method="_on_shrink_apply_button_pressed"]
|
||||
[connection signal="pressed" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Interface/InterfaceOptions/FontSizeContainer/FontSizeApplyButton" to="." method="_on_font_size_apply_button_pressed"]
|
||||
[connection signal="value_changed" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Canvas/GridOptions/GridsCount" to="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Canvas/GridOptions" method="_on_grids_count_value_changed"]
|
||||
[connection signal="pressed" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Extensions/ExtensionsHeader/Explore" to="Store" method="_on_explore_pressed"]
|
||||
[connection signal="empty_clicked" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Extensions/InstalledExtensions" to="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Extensions" method="_on_InstalledExtensions_empty_clicked"]
|
||||
[connection signal="item_selected" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Extensions/InstalledExtensions" to="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Extensions" method="_on_InstalledExtensions_item_selected"]
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
shader_type canvas_item;
|
||||
render_mode unshaded;
|
||||
|
||||
#include "res://src/Shaders/FindPaletteColorIndex.gdshaderinc"
|
||||
uniform sampler2D palette_texture : filter_nearest;
|
||||
uniform sampler2D selection : filter_nearest;
|
||||
|
||||
|
@ -10,19 +11,10 @@ vec4 swap_color(vec4 color) {
|
|||
if (color.a <= 0.01) {
|
||||
return color;
|
||||
}
|
||||
int color_index = 0;
|
||||
|
||||
int n_of_colors = textureSize(palette_texture, 0).x;
|
||||
float smaller_distance = distance(color, texture(palette_texture, vec2(0.0)));
|
||||
for (int i = 0; i <= n_of_colors; i++) {
|
||||
vec2 uv = vec2(float(i) / float(n_of_colors), 0.0);
|
||||
vec4 palette_color = texture(palette_texture, uv);
|
||||
float dist = distance(color, palette_color);
|
||||
if (dist < smaller_distance) {
|
||||
smaller_distance = dist;
|
||||
color_index = i;
|
||||
}
|
||||
}
|
||||
return texture(palette_texture, vec2(float(color_index) / float(n_of_colors), 0.0));
|
||||
int color_index = find_index(color, n_of_colors, palette_texture);
|
||||
return texelFetch(palette_texture, ivec2(color_index, 0), 0);
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
|
|
13
src/Shaders/FindPaletteColorIndex.gdshaderinc
Normal file
|
@ -0,0 +1,13 @@
|
|||
int find_index(vec4 color, int n_of_colors, sampler2D palette_texture) {
|
||||
int color_index = 0;
|
||||
float smaller_distance = distance(color, texture(palette_texture, vec2(0.0)));
|
||||
for (int i = 0; i <= n_of_colors; i++) {
|
||||
vec4 palette_color = texelFetch(palette_texture, ivec2(i, 0), 0);
|
||||
float dist = distance(color, palette_color);
|
||||
if (dist < smaller_distance) {
|
||||
smaller_distance = dist;
|
||||
color_index = i;
|
||||
}
|
||||
}
|
||||
return color_index;
|
||||
}
|
28
src/Shaders/IndexedToRGB.gdshader
Normal file
|
@ -0,0 +1,28 @@
|
|||
shader_type canvas_item;
|
||||
render_mode unshaded;
|
||||
|
||||
const float EPSILON = 0.0001;
|
||||
|
||||
uniform sampler2D palette_texture : filter_nearest;
|
||||
uniform sampler2D indices_texture : filter_nearest;
|
||||
|
||||
void fragment() {
|
||||
float index = texture(indices_texture, UV).r * 255.0;
|
||||
if (index <= EPSILON) { // If index is zero, make it transparent
|
||||
COLOR = vec4(0.0);
|
||||
}
|
||||
else {
|
||||
float n_of_colors = float(textureSize(palette_texture, 0).x);
|
||||
index -= 1.0;
|
||||
float index_normalized = index / n_of_colors;
|
||||
if (index < n_of_colors) {
|
||||
COLOR = texelFetch(palette_texture, ivec2(int(index), 0), 0);
|
||||
}
|
||||
else {
|
||||
// If index is bigger than the size of the palette, make it transparent.
|
||||
// This happens when switching to a palette, where the previous palette was bigger
|
||||
// than the newer one, and the current index is out of bounds of the new one.
|
||||
COLOR = vec4(0.0);
|
||||
}
|
||||
}
|
||||
}
|
18
src/Shaders/SetIndices.gdshader
Normal file
|
@ -0,0 +1,18 @@
|
|||
shader_type canvas_item;
|
||||
render_mode unshaded;
|
||||
|
||||
#include "res://src/Shaders/FindPaletteColorIndex.gdshaderinc"
|
||||
uniform sampler2D rgb_texture : filter_nearest;
|
||||
uniform sampler2D palette_texture : filter_nearest;
|
||||
|
||||
|
||||
void fragment() {
|
||||
vec4 color = texture(rgb_texture, UV);
|
||||
if (color.a <= 0.0001) {
|
||||
COLOR.r = 0.0;
|
||||
}
|
||||
else {
|
||||
int color_index = find_index(color, textureSize(palette_texture, 0).x, palette_texture);
|
||||
COLOR.r = float(color_index + 1) / 255.0;
|
||||
}
|
||||
}
|
|
@ -35,7 +35,7 @@ var _line_polylines := []
|
|||
|
||||
# Memorize some stuff when doing brush strokes
|
||||
var _stroke_project: Project
|
||||
var _stroke_images: Array[Image] = []
|
||||
var _stroke_images: Array[ImageExtended] = []
|
||||
var _is_mask_size_zero := true
|
||||
var _circle_tool_shortcut: Array[Vector2i]
|
||||
|
||||
|
@ -730,8 +730,8 @@ func _get_undo_data() -> Dictionary:
|
|||
for cel in cels:
|
||||
if not cel is PixelCel:
|
||||
continue
|
||||
var image := cel.get_image()
|
||||
data[image] = image.data
|
||||
var image := (cel as PixelCel).get_image()
|
||||
image.add_data_to_dictionary(data)
|
||||
return data
|
||||
|
||||
|
||||
|
|
|
@ -159,16 +159,17 @@ func draw_move(pos: Vector2i) -> void:
|
|||
else:
|
||||
pos.x = _start_pos.x
|
||||
if Input.is_action_pressed("transform_snap_grid"):
|
||||
_offset = _offset.snapped(Global.grid_size)
|
||||
_offset = _offset.snapped(Global.grids[0].grid_size)
|
||||
var prev_pos: Vector2i = selection_node.big_bounding_rectangle.position
|
||||
selection_node.big_bounding_rectangle.position = prev_pos.snapped(Global.grid_size)
|
||||
selection_node.big_bounding_rectangle.position = prev_pos.snapped(Global.grids[0].grid_size)
|
||||
selection_node.marching_ants_outline.offset += Vector2(
|
||||
selection_node.big_bounding_rectangle.position - prev_pos
|
||||
)
|
||||
pos = pos.snapped(Global.grid_size)
|
||||
var grid_offset := Global.grid_offset
|
||||
pos = pos.snapped(Global.grids[0].grid_size)
|
||||
var grid_offset := Global.grids[0].grid_offset
|
||||
grid_offset = Vector2i(
|
||||
fmod(grid_offset.x, Global.grid_size.x), fmod(grid_offset.y, Global.grid_size.y)
|
||||
fmod(grid_offset.x, Global.grids[0].grid_size.x),
|
||||
fmod(grid_offset.y, Global.grids[0].grid_size.y)
|
||||
)
|
||||
pos += grid_offset
|
||||
|
||||
|
|
|
@ -168,18 +168,9 @@ func draw_preview() -> void:
|
|||
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(points[i]):
|
||||
image.set_pixelv(points[i], Color.WHITE)
|
||||
# Handle mirroring
|
||||
if Tools.horizontal_mirror:
|
||||
for point in mirror_array(points, true, false):
|
||||
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(point):
|
||||
image.set_pixelv(point, Color.WHITE)
|
||||
if Tools.vertical_mirror:
|
||||
for point in mirror_array(points, true, true):
|
||||
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(point):
|
||||
image.set_pixelv(point, Color.WHITE)
|
||||
if Tools.vertical_mirror:
|
||||
for point in mirror_array(points, false, true):
|
||||
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(point):
|
||||
image.set_pixelv(point, Color.WHITE)
|
||||
for point in mirror_array(points):
|
||||
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(point):
|
||||
image.set_pixelv(point, Color.WHITE)
|
||||
var texture := ImageTexture.create_from_image(image)
|
||||
canvas.texture = texture
|
||||
else:
|
||||
|
|
|
@ -129,19 +129,20 @@ func draw_preview() -> void:
|
|||
func snap_position(pos: Vector2) -> Vector2:
|
||||
var snapping_distance := Global.snapping_distance / Global.camera.zoom.x
|
||||
if Global.snap_to_rectangular_grid_boundary:
|
||||
var grid_pos := pos.snapped(Global.grid_size)
|
||||
grid_pos += Vector2(Global.grid_offset)
|
||||
var grid_pos := pos.snapped(Global.grids[0].grid_size)
|
||||
grid_pos += Vector2(Global.grids[0].grid_offset)
|
||||
# keeping grid_pos as is would have been fine but this adds extra accuracy as to
|
||||
# which snap point (from the list below) is closest to mouse and occupy THAT point
|
||||
var t_l := grid_pos + Vector2(-Global.grid_size.x, -Global.grid_size.y)
|
||||
var t_c := grid_pos + Vector2(0, -Global.grid_size.y) # t_c is for "top centre" and so on
|
||||
var t_r := grid_pos + Vector2(Global.grid_size.x, -Global.grid_size.y)
|
||||
var m_l := grid_pos + Vector2(-Global.grid_size.x, 0)
|
||||
# t_l is for "top left" and so on
|
||||
var t_l := grid_pos + Vector2(-Global.grids[0].grid_size.x, -Global.grids[0].grid_size.y)
|
||||
var t_c := grid_pos + Vector2(0, -Global.grids[0].grid_size.y)
|
||||
var t_r := grid_pos + Vector2(Global.grids[0].grid_size.x, -Global.grids[0].grid_size.y)
|
||||
var m_l := grid_pos + Vector2(-Global.grids[0].grid_size.x, 0)
|
||||
var m_c := grid_pos
|
||||
var m_r := grid_pos + Vector2(Global.grid_size.x, 0)
|
||||
var b_l := grid_pos + Vector2(-Global.grid_size.x, Global.grid_size.y)
|
||||
var b_c := grid_pos + Vector2(0, Global.grid_size.y)
|
||||
var b_r := grid_pos + Vector2(Global.grid_size)
|
||||
var m_r := grid_pos + Vector2(Global.grids[0].grid_size.x, 0)
|
||||
var b_l := grid_pos + Vector2(-Global.grids[0].grid_size.x, Global.grids[0].grid_size.y)
|
||||
var b_c := grid_pos + Vector2(0, Global.grids[0].grid_size.y)
|
||||
var b_r := grid_pos + Vector2(Global.grids[0].grid_size)
|
||||
var vec_arr: PackedVector2Array = [t_l, t_c, t_r, m_l, m_c, m_r, b_l, b_c, b_r]
|
||||
for vec in vec_arr:
|
||||
if vec.distance_to(pos) < grid_pos.distance_to(pos):
|
||||
|
@ -152,19 +153,22 @@ func snap_position(pos: Vector2) -> Vector2:
|
|||
pos = grid_point.floor()
|
||||
|
||||
if Global.snap_to_rectangular_grid_center:
|
||||
var grid_center := pos.snapped(Global.grid_size) + Vector2(Global.grid_size / 2)
|
||||
grid_center += Vector2(Global.grid_offset)
|
||||
var grid_center := (
|
||||
pos.snapped(Global.grids[0].grid_size) + Vector2(Global.grids[0].grid_size / 2)
|
||||
)
|
||||
grid_center += Vector2(Global.grids[0].grid_offset)
|
||||
# keeping grid_center as is would have been fine but this adds extra accuracy as to
|
||||
# which snap point (from the list below) is closest to mouse and occupy THAT point
|
||||
var t_l := grid_center + Vector2(-Global.grid_size.x, -Global.grid_size.y)
|
||||
var t_c := grid_center + Vector2(0, -Global.grid_size.y) # t_c is for "top centre" and so on
|
||||
var t_r := grid_center + Vector2(Global.grid_size.x, -Global.grid_size.y)
|
||||
var m_l := grid_center + Vector2(-Global.grid_size.x, 0)
|
||||
# t_l is for "top left" and so on
|
||||
var t_l := grid_center + Vector2(-Global.grids[0].grid_size.x, -Global.grids[0].grid_size.y)
|
||||
var t_c := grid_center + Vector2(0, -Global.grids[0].grid_size.y)
|
||||
var t_r := grid_center + Vector2(Global.grids[0].grid_size.x, -Global.grids[0].grid_size.y)
|
||||
var m_l := grid_center + Vector2(-Global.grids[0].grid_size.x, 0)
|
||||
var m_c := grid_center
|
||||
var m_r := grid_center + Vector2(Global.grid_size.x, 0)
|
||||
var b_l := grid_center + Vector2(-Global.grid_size.x, Global.grid_size.y)
|
||||
var b_c := grid_center + Vector2(0, Global.grid_size.y)
|
||||
var b_r := grid_center + Vector2(Global.grid_size)
|
||||
var m_r := grid_center + Vector2(Global.grids[0].grid_size.x, 0)
|
||||
var b_l := grid_center + Vector2(-Global.grids[0].grid_size.x, Global.grids[0].grid_size.y)
|
||||
var b_c := grid_center + Vector2(0, Global.grids[0].grid_size.y)
|
||||
var b_r := grid_center + Vector2(Global.grids[0].grid_size)
|
||||
var vec_arr := [t_l, t_c, t_r, m_l, m_c, m_r, b_l, b_c, b_r]
|
||||
for vec in vec_arr:
|
||||
if vec.distance_to(pos) < grid_center.distance_to(pos):
|
||||
|
@ -205,18 +209,33 @@ func snap_position(pos: Vector2) -> Vector2:
|
|||
return pos
|
||||
|
||||
|
||||
func mirror_array(array: Array[Vector2i], h: bool, v: bool) -> Array[Vector2i]:
|
||||
## Returns an array that mirrors each point of the [param array].
|
||||
## An optional [param callable] can be passed, which gets called for each type of symmetry.
|
||||
func mirror_array(array: Array[Vector2i], callable := func(_array): pass) -> Array[Vector2i]:
|
||||
var new_array: Array[Vector2i] = []
|
||||
var project := Global.current_project
|
||||
for point in array:
|
||||
if h and v:
|
||||
new_array.append(
|
||||
Vector2i(project.x_symmetry_point - point.x, project.y_symmetry_point - point.y)
|
||||
)
|
||||
elif h:
|
||||
new_array.append(Vector2i(project.x_symmetry_point - point.x, point.y))
|
||||
elif v:
|
||||
new_array.append(Vector2i(point.x, project.y_symmetry_point - point.y))
|
||||
if Tools.horizontal_mirror and Tools.vertical_mirror:
|
||||
var hv_array: Array[Vector2i] = []
|
||||
for point in array:
|
||||
var mirror_x := Tools.calculate_mirror_horizontal(point, project)
|
||||
hv_array.append(Tools.calculate_mirror_vertical(mirror_x, project))
|
||||
if callable.is_valid():
|
||||
callable.call(hv_array)
|
||||
new_array += hv_array
|
||||
if Tools.horizontal_mirror:
|
||||
var h_array: Array[Vector2i] = []
|
||||
for point in array:
|
||||
h_array.append(Tools.calculate_mirror_horizontal(point, project))
|
||||
if callable.is_valid():
|
||||
callable.call(h_array)
|
||||
new_array += h_array
|
||||
if Tools.vertical_mirror:
|
||||
var v_array: Array[Vector2i] = []
|
||||
for point in array:
|
||||
v_array.append(Tools.calculate_mirror_vertical(point, project))
|
||||
if callable.is_valid():
|
||||
callable.call(v_array)
|
||||
new_array += v_array
|
||||
|
||||
return new_array
|
||||
|
||||
|
@ -299,12 +318,12 @@ func _get_draw_rect() -> Rect2i:
|
|||
return Rect2i(Vector2i.ZERO, Global.current_project.size)
|
||||
|
||||
|
||||
func _get_draw_image() -> Image:
|
||||
func _get_draw_image() -> ImageExtended:
|
||||
return Global.current_project.get_current_cel().get_image()
|
||||
|
||||
|
||||
func _get_selected_draw_images() -> Array[Image]:
|
||||
var images: Array[Image] = []
|
||||
func _get_selected_draw_images() -> Array[ImageExtended]:
|
||||
var images: Array[ImageExtended] = []
|
||||
var project := Global.current_project
|
||||
for cel_index in project.selected_cels:
|
||||
var cel: BaseCel = project.frames[cel_index[0]].cels[cel_index[1]]
|
||||
|
|
|
@ -220,7 +220,7 @@ func fill_in_color(pos: Vector2i) -> void:
|
|||
if project.has_selection:
|
||||
selection = project.selection_map.return_cropped_copy(project.size)
|
||||
else:
|
||||
selection = Image.create(project.size.x, project.size.y, false, Image.FORMAT_RGBA8)
|
||||
selection = project.new_empty_image()
|
||||
selection.fill(Color(1, 1, 1, 1))
|
||||
|
||||
selection_tex = ImageTexture.create_from_image(selection)
|
||||
|
@ -263,15 +263,17 @@ func fill_in_selection() -> void:
|
|||
var images := _get_selected_draw_images()
|
||||
if _fill_with == FillWith.COLOR or _pattern == null:
|
||||
if project.has_selection:
|
||||
var filler := Image.create(project.size.x, project.size.y, false, Image.FORMAT_RGBA8)
|
||||
var filler := project.new_empty_image()
|
||||
filler.fill(tool_slot.color)
|
||||
var rect: Rect2i = Global.canvas.selection.big_bounding_rectangle
|
||||
var selection_map_copy := project.selection_map.return_cropped_copy(project.size)
|
||||
for image in images:
|
||||
image.blit_rect_mask(filler, selection_map_copy, rect, rect.position)
|
||||
image.convert_rgb_to_indexed()
|
||||
else:
|
||||
for image in images:
|
||||
image.fill(tool_slot.color)
|
||||
image.convert_rgb_to_indexed()
|
||||
else:
|
||||
# End early if we are filling with an empty pattern
|
||||
var pattern_image: Image = _pattern.image
|
||||
|
@ -284,7 +286,7 @@ func fill_in_selection() -> void:
|
|||
if project.has_selection:
|
||||
selection = project.selection_map.return_cropped_copy(project.size)
|
||||
else:
|
||||
selection = Image.create(project.size.x, project.size.y, false, Image.FORMAT_RGBA8)
|
||||
selection = project.new_empty_image()
|
||||
selection.fill(Color(1, 1, 1, 1))
|
||||
|
||||
selection_tex = ImageTexture.create_from_image(selection)
|
||||
|
@ -461,7 +463,7 @@ func _compute_segments_for_image(
|
|||
done = false
|
||||
|
||||
|
||||
func _color_segments(image: Image) -> void:
|
||||
func _color_segments(image: ImageExtended) -> void:
|
||||
if _fill_with == FillWith.COLOR or _pattern == null:
|
||||
# This is needed to ensure that the color used to fill is not wrong, due to float
|
||||
# rounding issues.
|
||||
|
@ -472,7 +474,7 @@ func _color_segments(image: Image) -> void:
|
|||
var p := _allegro_image_segments[c]
|
||||
for px in range(p.left_position, p.right_position + 1):
|
||||
# We don't have to check again whether the point being processed is within the bounds
|
||||
image.set_pixel(px, p.y, color)
|
||||
image.set_pixel_custom(px, p.y, color)
|
||||
else:
|
||||
# shortcircuit tests for patternfills
|
||||
var pattern_size := _pattern.image.get_size()
|
||||
|
@ -484,11 +486,11 @@ func _color_segments(image: Image) -> void:
|
|||
_set_pixel_pattern(image, px, p.y, pattern_size)
|
||||
|
||||
|
||||
func _set_pixel_pattern(image: Image, x: int, y: int, pattern_size: Vector2i) -> void:
|
||||
func _set_pixel_pattern(image: ImageExtended, x: int, y: int, pattern_size: Vector2i) -> void:
|
||||
var px := (x + _offset_x) % pattern_size.x
|
||||
var py := (y + _offset_y) % pattern_size.y
|
||||
var pc := _pattern.image.get_pixel(px, py)
|
||||
image.set_pixel(x, y, pc)
|
||||
image.set_pixel_custom(x, y, pc)
|
||||
|
||||
|
||||
func commit_undo() -> void:
|
||||
|
@ -514,12 +516,12 @@ func _get_undo_data() -> Dictionary:
|
|||
if Global.animation_timeline.animation_timer.is_stopped():
|
||||
var images := _get_selected_draw_images()
|
||||
for image in images:
|
||||
data[image] = image.data
|
||||
image.add_data_to_dictionary(data)
|
||||
else:
|
||||
for frame in Global.current_project.frames:
|
||||
var cel := frame.cels[Global.current_project.current_layer]
|
||||
if not cel is PixelCel:
|
||||
continue
|
||||
var image := cel.get_image()
|
||||
data[image] = image.data
|
||||
var image := (cel as PixelCel).get_image()
|
||||
image.add_data_to_dictionary(data)
|
||||
return data
|
||||
|
|
|
@ -141,18 +141,9 @@ func draw_preview() -> void:
|
|||
image.set_pixelv(points[i], Color.WHITE)
|
||||
|
||||
# Handle mirroring
|
||||
if Tools.horizontal_mirror:
|
||||
for point in mirror_array(points, true, false):
|
||||
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(point):
|
||||
image.set_pixelv(point, Color.WHITE)
|
||||
if Tools.vertical_mirror:
|
||||
for point in mirror_array(points, true, true):
|
||||
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(point):
|
||||
image.set_pixelv(point, Color.WHITE)
|
||||
if Tools.vertical_mirror:
|
||||
for point in mirror_array(points, false, true):
|
||||
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(point):
|
||||
image.set_pixelv(point, Color.WHITE)
|
||||
for point in mirror_array(points):
|
||||
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(point):
|
||||
image.set_pixelv(point, Color.WHITE)
|
||||
var texture := ImageTexture.create_from_image(image)
|
||||
previews.texture = texture
|
||||
|
||||
|
@ -203,7 +194,7 @@ func _draw_shape() -> void:
|
|||
commit_undo()
|
||||
|
||||
|
||||
func _draw_pixel(point: Vector2i, images: Array[Image]) -> void:
|
||||
func _draw_pixel(point: Vector2i, images: Array[ImageExtended]) -> void:
|
||||
if Global.current_project.can_pixel_get_drawn(point):
|
||||
for image in images:
|
||||
_drawer.set_pixel(image, point, tool_slot.color)
|
||||
|
|
|
@ -122,6 +122,7 @@ func _draw_brush_image(image: Image, src_rect: Rect2i, dst: Vector2i) -> void:
|
|||
var images := _get_selected_draw_images()
|
||||
for draw_image in images:
|
||||
draw_image.blit_rect_mask(_clear_image, image, src_rect, dst)
|
||||
draw_image.convert_rgb_to_indexed()
|
||||
else:
|
||||
for xx in image.get_size().x:
|
||||
for yy in image.get_size().y:
|
||||
|
|
|
@ -157,18 +157,9 @@ func draw_preview() -> void:
|
|||
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(point):
|
||||
image.set_pixelv(point, Color.WHITE)
|
||||
# Handle mirroring
|
||||
if Tools.horizontal_mirror:
|
||||
for point in mirror_array(points, true, false):
|
||||
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(point):
|
||||
image.set_pixelv(point, Color.WHITE)
|
||||
if Tools.vertical_mirror:
|
||||
for point in mirror_array(points, true, true):
|
||||
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(point):
|
||||
image.set_pixelv(point, Color.WHITE)
|
||||
if Tools.vertical_mirror:
|
||||
for point in mirror_array(points, false, true):
|
||||
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(point):
|
||||
image.set_pixelv(point, Color.WHITE)
|
||||
for point in mirror_array(points):
|
||||
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(point):
|
||||
image.set_pixelv(point, Color.WHITE)
|
||||
var texture := ImageTexture.create_from_image(image)
|
||||
canvas.texture = texture
|
||||
else:
|
||||
|
|
|
@ -211,6 +211,7 @@ func _draw_brush_image(brush_image: Image, src_rect: Rect2i, dst: Vector2i) -> v
|
|||
draw_image.blit_rect_mask(brush_image, mask, src_rect, dst)
|
||||
else:
|
||||
draw_image.blit_rect(brush_image, src_rect, dst)
|
||||
draw_image.convert_rgb_to_indexed()
|
||||
else:
|
||||
for draw_image in images:
|
||||
if Tools.alpha_locked:
|
||||
|
@ -218,3 +219,4 @@ func _draw_brush_image(brush_image: Image, src_rect: Rect2i, dst: Vector2i) -> v
|
|||
draw_image.blend_rect_mask(brush_image, mask, src_rect, dst)
|
||||
else:
|
||||
draw_image.blend_rect(brush_image, src_rect, dst)
|
||||
draw_image.convert_rgb_to_indexed()
|
||||
|
|
|
@ -63,18 +63,9 @@ func draw_preview() -> void:
|
|||
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(points[i]):
|
||||
image.set_pixelv(points[i], Color.WHITE)
|
||||
# Handle mirroring
|
||||
if Tools.horizontal_mirror:
|
||||
for point in mirror_array(points, true, false):
|
||||
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(point):
|
||||
image.set_pixelv(point, Color.WHITE)
|
||||
if Tools.vertical_mirror:
|
||||
for point in mirror_array(points, true, true):
|
||||
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(point):
|
||||
image.set_pixelv(point, Color.WHITE)
|
||||
if Tools.vertical_mirror:
|
||||
for point in mirror_array(points, false, true):
|
||||
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(point):
|
||||
image.set_pixelv(point, Color.WHITE)
|
||||
for point in mirror_array(points):
|
||||
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(point):
|
||||
image.set_pixelv(point, Color.WHITE)
|
||||
var texture := ImageTexture.create_from_image(image)
|
||||
canvas.texture = texture
|
||||
else:
|
||||
|
|
|
@ -46,27 +46,12 @@ func draw_preview() -> void:
|
|||
image.set_pixelv(draw_point, Color.WHITE)
|
||||
|
||||
# Handle mirroring
|
||||
if Tools.horizontal_mirror:
|
||||
for point in mirror_array(_draw_points, true, false):
|
||||
var draw_point := point
|
||||
if Global.mirror_view: # This fixes previewing in mirror mode
|
||||
draw_point.x = image.get_width() - draw_point.x - 1
|
||||
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(draw_point):
|
||||
image.set_pixelv(draw_point, Color.WHITE)
|
||||
if Tools.vertical_mirror:
|
||||
for point in mirror_array(_draw_points, true, true):
|
||||
var draw_point := point
|
||||
if Global.mirror_view: # This fixes previewing in mirror mode
|
||||
draw_point.x = image.get_width() - draw_point.x - 1
|
||||
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(draw_point):
|
||||
image.set_pixelv(draw_point, Color.WHITE)
|
||||
if Tools.vertical_mirror:
|
||||
for point in mirror_array(_draw_points, false, true):
|
||||
var draw_point := point
|
||||
if Global.mirror_view: # This fixes previewing in mirror mode
|
||||
draw_point.x = image.get_width() - draw_point.x - 1
|
||||
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(draw_point):
|
||||
image.set_pixelv(draw_point, Color.WHITE)
|
||||
for point in mirror_array(_draw_points):
|
||||
var draw_point := point
|
||||
if Global.mirror_view: # This fixes previewing in mirror mode
|
||||
draw_point.x = image.get_width() - draw_point.x - 1
|
||||
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(draw_point):
|
||||
image.set_pixelv(draw_point, Color.WHITE)
|
||||
var texture := ImageTexture.create_from_image(image)
|
||||
canvas.texture = texture
|
||||
else:
|
||||
|
@ -85,19 +70,10 @@ func apply_selection(_position) -> void:
|
|||
if _draw_points.size() > 3:
|
||||
if _intersect:
|
||||
project.selection_map.clear()
|
||||
lasso_selection(project.selection_map, previous_selection_map, _draw_points)
|
||||
|
||||
lasso_selection(_draw_points, project.selection_map, previous_selection_map)
|
||||
# Handle mirroring
|
||||
if Tools.horizontal_mirror:
|
||||
var mirror_x := mirror_array(_draw_points, true, false)
|
||||
lasso_selection(project.selection_map, previous_selection_map, mirror_x)
|
||||
if Tools.vertical_mirror:
|
||||
var mirror_xy := mirror_array(_draw_points, true, true)
|
||||
lasso_selection(project.selection_map, previous_selection_map, mirror_xy)
|
||||
if Tools.vertical_mirror:
|
||||
var mirror_y := mirror_array(_draw_points, false, true)
|
||||
lasso_selection(project.selection_map, previous_selection_map, mirror_y)
|
||||
|
||||
var callable := lasso_selection.bind(project.selection_map, previous_selection_map)
|
||||
mirror_array(_draw_points, callable)
|
||||
Global.canvas.selection.big_bounding_rectangle = project.selection_map.get_used_rect()
|
||||
else:
|
||||
if !cleared:
|
||||
|
@ -109,7 +85,7 @@ func apply_selection(_position) -> void:
|
|||
|
||||
|
||||
func lasso_selection(
|
||||
selection_map: SelectionMap, previous_selection_map: SelectionMap, points: Array[Vector2i]
|
||||
points: Array[Vector2i], selection_map: SelectionMap, previous_selection_map: SelectionMap
|
||||
) -> void:
|
||||
var selection_size := selection_map.get_size()
|
||||
var bounding_rect := Rect2i(points[0], Vector2i.ZERO)
|
||||
|
|
|
@ -74,27 +74,12 @@ func draw_preview() -> void:
|
|||
image.set_pixelv(draw_point, Color.WHITE)
|
||||
|
||||
# Handle mirroring
|
||||
if Tools.horizontal_mirror:
|
||||
for point in mirror_array(_draw_points, true, false):
|
||||
var draw_point := point
|
||||
if Global.mirror_view: # This fixes previewing in mirror mode
|
||||
draw_point.x = image.get_width() - draw_point.x - 1
|
||||
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(draw_point):
|
||||
image.set_pixelv(draw_point, Color.WHITE)
|
||||
if Tools.vertical_mirror:
|
||||
for point in mirror_array(_draw_points, true, true):
|
||||
var draw_point := point
|
||||
if Global.mirror_view: # This fixes previewing in mirror mode
|
||||
draw_point.x = image.get_width() - draw_point.x - 1
|
||||
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(draw_point):
|
||||
image.set_pixelv(draw_point, Color.WHITE)
|
||||
if Tools.vertical_mirror:
|
||||
for point in mirror_array(_draw_points, false, true):
|
||||
var draw_point := point
|
||||
if Global.mirror_view: # This fixes previewing in mirror mode
|
||||
draw_point.x = image.get_width() - draw_point.x - 1
|
||||
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(draw_point):
|
||||
image.set_pixelv(draw_point, Color.WHITE)
|
||||
for point in mirror_array(_draw_points):
|
||||
var draw_point := point
|
||||
if Global.mirror_view: # This fixes previewing in mirror mode
|
||||
draw_point.x = image.get_width() - draw_point.x - 1
|
||||
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(draw_point):
|
||||
image.set_pixelv(draw_point, Color.WHITE)
|
||||
var texture := ImageTexture.create_from_image(image)
|
||||
canvas.texture = texture
|
||||
else:
|
||||
|
@ -115,18 +100,9 @@ func apply_selection(pos: Vector2i) -> void:
|
|||
if _intersect:
|
||||
project.selection_map.clear()
|
||||
paint_selection(project.selection_map, previous_selection_map, _draw_points)
|
||||
|
||||
# Handle mirroring
|
||||
if Tools.horizontal_mirror:
|
||||
var mirror_x := mirror_array(_draw_points, true, false)
|
||||
paint_selection(project.selection_map, previous_selection_map, mirror_x)
|
||||
if Tools.vertical_mirror:
|
||||
var mirror_xy := mirror_array(_draw_points, true, true)
|
||||
paint_selection(project.selection_map, previous_selection_map, mirror_xy)
|
||||
if Tools.vertical_mirror:
|
||||
var mirror_y := mirror_array(_draw_points, false, true)
|
||||
paint_selection(project.selection_map, previous_selection_map, mirror_y)
|
||||
|
||||
var mirror := mirror_array(_draw_points)
|
||||
paint_selection(project.selection_map, previous_selection_map, mirror)
|
||||
Global.canvas.selection.big_bounding_rectangle = project.selection_map.get_used_rect()
|
||||
else:
|
||||
if !cleared:
|
||||
|
|
|
@ -81,27 +81,12 @@ func draw_preview() -> void:
|
|||
)
|
||||
|
||||
# Handle mirroring
|
||||
if Tools.horizontal_mirror:
|
||||
for point in mirror_array(preview_draw_points, true, false):
|
||||
var draw_point := point
|
||||
if Global.mirror_view: # This fixes previewing in mirror mode
|
||||
draw_point.x = image.get_width() - draw_point.x - 1
|
||||
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(draw_point):
|
||||
image.set_pixelv(draw_point, Color.WHITE)
|
||||
if Tools.vertical_mirror:
|
||||
for point in mirror_array(preview_draw_points, true, true):
|
||||
var draw_point := point
|
||||
if Global.mirror_view: # This fixes previewing in mirror mode
|
||||
draw_point.x = image.get_width() - draw_point.x - 1
|
||||
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(draw_point):
|
||||
image.set_pixelv(draw_point, Color.WHITE)
|
||||
if Tools.vertical_mirror:
|
||||
for point in mirror_array(preview_draw_points, false, true):
|
||||
var draw_point := point
|
||||
if Global.mirror_view: # This fixes previewing in mirror mode
|
||||
draw_point.x = image.get_width() - draw_point.x - 1
|
||||
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(draw_point):
|
||||
image.set_pixelv(draw_point, Color.WHITE)
|
||||
for point in mirror_array(preview_draw_points):
|
||||
var draw_point := point
|
||||
if Global.mirror_view: # This fixes previewing in mirror mode
|
||||
draw_point.x = image.get_width() - draw_point.x - 1
|
||||
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(draw_point):
|
||||
image.set_pixelv(draw_point, Color.WHITE)
|
||||
var texture := ImageTexture.create_from_image(image)
|
||||
previews.texture = texture
|
||||
else:
|
||||
|
@ -122,19 +107,10 @@ func apply_selection(pos: Vector2i) -> void:
|
|||
if _draw_points.size() > 3:
|
||||
if _intersect:
|
||||
project.selection_map.clear()
|
||||
lasso_selection(project.selection_map, previous_selection_map, _draw_points)
|
||||
|
||||
lasso_selection(_draw_points, project.selection_map, previous_selection_map)
|
||||
# Handle mirroring
|
||||
if Tools.horizontal_mirror:
|
||||
var mirror_x := mirror_array(_draw_points, true, false)
|
||||
lasso_selection(project.selection_map, previous_selection_map, mirror_x)
|
||||
if Tools.vertical_mirror:
|
||||
var mirror_xy := mirror_array(_draw_points, true, true)
|
||||
lasso_selection(project.selection_map, previous_selection_map, mirror_xy)
|
||||
if Tools.vertical_mirror:
|
||||
var mirror_y := mirror_array(_draw_points, false, true)
|
||||
lasso_selection(project.selection_map, previous_selection_map, mirror_y)
|
||||
|
||||
var callable := lasso_selection.bind(project.selection_map, previous_selection_map)
|
||||
mirror_array(_draw_points, callable)
|
||||
Global.canvas.selection.big_bounding_rectangle = project.selection_map.get_used_rect()
|
||||
else:
|
||||
if !cleared:
|
||||
|
@ -152,7 +128,7 @@ func _clear() -> void:
|
|||
|
||||
|
||||
func lasso_selection(
|
||||
selection_map: SelectionMap, previous_selection_map: SelectionMap, points: Array[Vector2i]
|
||||
points: Array[Vector2i], selection_map: SelectionMap, previous_selection_map: SelectionMap
|
||||
) -> void:
|
||||
var selection_size := selection_map.get_size()
|
||||
var bounding_rect := Rect2i(points[0], Vector2i.ZERO)
|
||||
|
|
|
@ -8,7 +8,7 @@ var _content_transformation_check := false
|
|||
var _snap_to_grid := false ## Mouse Click + Ctrl
|
||||
var _undo_data := {}
|
||||
|
||||
@onready var selection_node: Node2D = Global.canvas.selection
|
||||
@onready var selection_node := Global.canvas.selection
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
|
@ -16,17 +16,17 @@ func _input(event: InputEvent) -> void:
|
|||
return
|
||||
if event.is_action_pressed("transform_snap_grid"):
|
||||
_snap_to_grid = true
|
||||
_offset = _offset.snapped(Global.grid_size)
|
||||
_offset = _offset.snapped(Global.grids[0].grid_size)
|
||||
if Global.current_project.has_selection and selection_node.is_moving_content:
|
||||
var prev_pos: Vector2i = selection_node.big_bounding_rectangle.position
|
||||
selection_node.big_bounding_rectangle.position = Vector2i(
|
||||
prev_pos.snapped(Global.grid_size)
|
||||
prev_pos.snapped(Global.grids[0].grid_size)
|
||||
)
|
||||
# The first time transform_snap_grid is enabled then _snap_position() is not called
|
||||
# and the selection had wrong offset, so do selection offsetting here
|
||||
var grid_offset := Vector2i(
|
||||
fmod(Global.grid_offset.x, Global.grid_size.x),
|
||||
fmod(Global.grid_offset.y, Global.grid_size.y)
|
||||
fmod(Global.grids[0].grid_offset.x, Global.grids[0].grid_size.x),
|
||||
fmod(Global.grids[0].grid_offset.y, Global.grids[0].grid_size.y)
|
||||
)
|
||||
selection_node.big_bounding_rectangle.position += grid_offset
|
||||
selection_node.marching_ants_outline.offset += Vector2(
|
||||
|
@ -78,19 +78,15 @@ func draw_end(pos: Vector2i) -> void:
|
|||
and _content_transformation_check == selection_node.is_moving_content
|
||||
):
|
||||
pos = _snap_position(pos)
|
||||
var project := Global.current_project
|
||||
|
||||
if project.has_selection:
|
||||
if Global.current_project.has_selection:
|
||||
selection_node.move_borders_end()
|
||||
else:
|
||||
var pixel_diff := pos - _start_pos
|
||||
Global.canvas.move_preview_location = Vector2i.ZERO
|
||||
var images := _get_selected_draw_images()
|
||||
for image in images:
|
||||
var image_copy := Image.new()
|
||||
image_copy.copy_from(image)
|
||||
image.fill(Color(0, 0, 0, 0))
|
||||
image.blit_rect(image_copy, Rect2i(Vector2i.ZERO, project.size), pixel_diff)
|
||||
_move_image(image, pixel_diff)
|
||||
_move_image(image.indices_image, pixel_diff)
|
||||
_commit_undo("Draw")
|
||||
|
||||
_start_pos = Vector2.INF
|
||||
|
@ -99,6 +95,13 @@ func draw_end(pos: Vector2i) -> void:
|
|||
Global.canvas.measurements.update_measurement(Global.MeasurementMode.NONE)
|
||||
|
||||
|
||||
func _move_image(image: Image, pixel_diff: Vector2i) -> void:
|
||||
var image_copy := Image.new()
|
||||
image_copy.copy_from(image)
|
||||
image.fill(Color(0, 0, 0, 0))
|
||||
image.blit_rect(image_copy, Rect2i(Vector2i.ZERO, image.get_size()), pixel_diff)
|
||||
|
||||
|
||||
func _snap_position(pos: Vector2) -> Vector2:
|
||||
if Input.is_action_pressed("transform_snap_axis"):
|
||||
var angle := pos.angle_to_point(_start_pos)
|
||||
|
@ -107,16 +110,18 @@ func _snap_position(pos: Vector2) -> Vector2:
|
|||
else:
|
||||
pos.x = _start_pos.x
|
||||
if _snap_to_grid: # Snap to grid
|
||||
pos = pos.snapped(Global.grid_size)
|
||||
pos = pos.snapped(Global.grids[0].grid_size)
|
||||
# The part below only corrects the offset for situations when there is no selection
|
||||
# Offsets when there is selection is controlled in _input() function
|
||||
if !Global.current_project.has_selection:
|
||||
var move_offset := Vector2.ZERO
|
||||
move_offset.x = (
|
||||
_start_pos.x - (_start_pos.x / Global.grid_size.x) * Global.grid_size.x
|
||||
_start_pos.x
|
||||
- (_start_pos.x / Global.grids[0].grid_size.x) * Global.grids[0].grid_size.x
|
||||
)
|
||||
move_offset.y = (
|
||||
_start_pos.y - (_start_pos.y / Global.grid_size.y) * Global.grid_size.y
|
||||
_start_pos.y
|
||||
- (_start_pos.y / Global.grids[0].grid_size.y) * Global.grids[0].grid_size.y
|
||||
)
|
||||
pos += move_offset
|
||||
|
||||
|
@ -155,6 +160,6 @@ func _get_undo_data() -> Dictionary:
|
|||
for cel in cels:
|
||||
if not cel is PixelCel:
|
||||
continue
|
||||
var image: Image = cel.image
|
||||
data[image] = image.data
|
||||
var image := (cel as PixelCel).get_image()
|
||||
image.add_data_to_dictionary(data)
|
||||
return data
|
||||
|
|
|
@ -149,12 +149,14 @@ func text_to_pixels() -> void:
|
|||
RenderingServer.free_rid(canvas)
|
||||
RenderingServer.free_rid(ci_rid)
|
||||
RenderingServer.free_rid(texture)
|
||||
viewport_texture.convert(Image.FORMAT_RGBA8)
|
||||
viewport_texture.convert(image.get_format())
|
||||
|
||||
text_edit.queue_free()
|
||||
text_edit = null
|
||||
if not viewport_texture.is_empty():
|
||||
image.copy_from(viewport_texture)
|
||||
if image is ImageExtended:
|
||||
image.convert_rgb_to_indexed()
|
||||
commit_undo("Draw", undo_data)
|
||||
|
||||
|
||||
|
@ -179,7 +181,7 @@ func _get_undo_data() -> Dictionary:
|
|||
var data := {}
|
||||
var images := _get_selected_draw_images()
|
||||
for image in images:
|
||||
data[image] = image.data
|
||||
image.add_data_to_dictionary(data)
|
||||
return data
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
[gd_scene load_steps=6 format=3 uid="uid://ct4o5i1jeul3k"]
|
||||
[gd_scene load_steps=6 format=3 uid="uid://bdregpkflev7u"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://ctfgfelg0sho8" path="res://src/Tools/BaseTool.tscn" id="1_1q6ub"]
|
||||
[ext_resource type="Script" path="res://src/Tools/UtilityTools/Text.gd" id="2_ql5g6"]
|
||||
|
@ -63,6 +63,8 @@ stretch_margin_bottom = 3
|
|||
script = ExtResource("3_tidsq")
|
||||
prefix = "Size:"
|
||||
suffix = "px"
|
||||
global_increment_action = "brush_size_increment"
|
||||
global_decrement_action = "brush_size_decrement"
|
||||
|
||||
[node name="GridContainer" type="GridContainer" parent="." index="4"]
|
||||
layout_mode = 2
|
||||
|
|
|
@ -15,6 +15,7 @@ var layer_metadata_texture := ImageTexture.new()
|
|||
@onready var currently_visible_frame := $CurrentlyVisibleFrame as SubViewport
|
||||
@onready var current_frame_drawer := $CurrentlyVisibleFrame/CurrentFrameDrawer as Node2D
|
||||
@onready var tile_mode := $TileMode as Node2D
|
||||
@onready var color_index := $ColorIndex as Node2D
|
||||
@onready var pixel_grid := $PixelGrid as Node2D
|
||||
@onready var grid := $Grid as Node2D
|
||||
@onready var selection := $Selection as SelectionNode
|
||||
|
@ -67,6 +68,7 @@ func _draw() -> void:
|
|||
current_frame_drawer.queue_redraw()
|
||||
tile_mode.queue_redraw()
|
||||
draw_set_transform(position, rotation, scale)
|
||||
color_index.queue_redraw()
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
[gd_scene load_steps=22 format=3 uid="uid://ba24iuv55m4l3"]
|
||||
[gd_scene load_steps=24 format=3 uid="uid://ba24iuv55m4l3"]
|
||||
|
||||
[ext_resource type="Script" path="res://src/UI/Canvas/Canvas.gd" id="1"]
|
||||
[ext_resource type="Shader" path="res://src/Shaders/BlendLayers.gdshader" id="1_253dh"]
|
||||
|
@ -17,6 +17,7 @@
|
|||
[ext_resource type="Script" path="res://src/UI/Canvas/Measurements.gd" id="16_nxilb"]
|
||||
[ext_resource type="Shader" path="res://src/Shaders/AutoInvertColors.gdshader" id="17_lowhf"]
|
||||
[ext_resource type="Script" path="res://src/UI/Canvas/ReferenceImages.gd" id="17_qfjb4"]
|
||||
[ext_resource type="Script" path="res://src/UI/Canvas/color_index.gd" id="18_o3xx2"]
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_6b0ox"]
|
||||
shader = ExtResource("1_253dh")
|
||||
|
@ -26,6 +27,11 @@ shader_parameter/origin_y_positive = true
|
|||
[sub_resource type="CanvasItemMaterial" id="1"]
|
||||
blend_mode = 4
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_ascg6"]
|
||||
shader = ExtResource("17_lowhf")
|
||||
shader_parameter/width = 0.05
|
||||
shader_parameter/hollow_shapes = false
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="2"]
|
||||
shader = ExtResource("9")
|
||||
shader_parameter/width = 0.05
|
||||
|
@ -59,6 +65,10 @@ show_behind_parent = true
|
|||
material = SubResource("1")
|
||||
script = ExtResource("4")
|
||||
|
||||
[node name="ColorIndex" type="Node2D" parent="."]
|
||||
material = SubResource("ShaderMaterial_ascg6")
|
||||
script = ExtResource("18_o3xx2")
|
||||
|
||||
[node name="PixelGrid" type="Node2D" parent="."]
|
||||
script = ExtResource("6")
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
extends Node2D
|
||||
|
||||
var unique_rect_lines := PackedVector2Array()
|
||||
var unique_iso_lines := PackedVector2Array()
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
Global.project_switched.connect(queue_redraw)
|
||||
|
@ -10,54 +13,60 @@ func _draw() -> void:
|
|||
return
|
||||
|
||||
var target_rect: Rect2i
|
||||
if Global.grid_draw_over_tile_mode:
|
||||
target_rect = Global.current_project.tiles.get_bounding_rect()
|
||||
else:
|
||||
target_rect = Rect2i(Vector2i.ZERO, Global.current_project.size)
|
||||
if not target_rect.has_area():
|
||||
return
|
||||
unique_rect_lines.clear()
|
||||
unique_iso_lines.clear()
|
||||
for grid_idx in range(Global.grids.size() - 1, -1, -1):
|
||||
if Global.grids[grid_idx].grid_draw_over_tile_mode:
|
||||
target_rect = Global.current_project.tiles.get_bounding_rect()
|
||||
else:
|
||||
target_rect = Rect2i(Vector2i.ZERO, Global.current_project.size)
|
||||
if not target_rect.has_area():
|
||||
return
|
||||
|
||||
var grid_type := Global.grid_type
|
||||
if grid_type == Global.GridTypes.CARTESIAN || grid_type == Global.GridTypes.ALL:
|
||||
_draw_cartesian_grid(target_rect)
|
||||
var grid_type := Global.grids[grid_idx].grid_type
|
||||
if grid_type == Global.GridTypes.CARTESIAN || grid_type == Global.GridTypes.ALL:
|
||||
_draw_cartesian_grid(grid_idx, target_rect)
|
||||
|
||||
if grid_type == Global.GridTypes.ISOMETRIC || grid_type == Global.GridTypes.ALL:
|
||||
_draw_isometric_grid(target_rect)
|
||||
if grid_type == Global.GridTypes.ISOMETRIC || grid_type == Global.GridTypes.ALL:
|
||||
_draw_isometric_grid(grid_idx, target_rect)
|
||||
|
||||
|
||||
func _draw_cartesian_grid(target_rect: Rect2i) -> void:
|
||||
func _draw_cartesian_grid(grid_index: int, target_rect: Rect2i) -> void:
|
||||
var grid = Global.grids[grid_index]
|
||||
var grid_multiline_points := PackedVector2Array()
|
||||
|
||||
var x: float = (
|
||||
target_rect.position.x
|
||||
+ fposmod(Global.grid_offset.x - target_rect.position.x, Global.grid_size.x)
|
||||
+ fposmod(grid.grid_offset.x - target_rect.position.x, grid.grid_size.x)
|
||||
)
|
||||
while x <= target_rect.end.x:
|
||||
grid_multiline_points.push_back(Vector2(x, target_rect.position.y))
|
||||
grid_multiline_points.push_back(Vector2(x, target_rect.end.y))
|
||||
x += Global.grid_size.x
|
||||
if not Vector2(x, target_rect.position.y) in unique_rect_lines:
|
||||
grid_multiline_points.push_back(Vector2(x, target_rect.position.y))
|
||||
grid_multiline_points.push_back(Vector2(x, target_rect.end.y))
|
||||
x += grid.grid_size.x
|
||||
|
||||
var y: float = (
|
||||
target_rect.position.y
|
||||
+ fposmod(Global.grid_offset.y - target_rect.position.y, Global.grid_size.y)
|
||||
+ fposmod(grid.grid_offset.y - target_rect.position.y, grid.grid_size.y)
|
||||
)
|
||||
while y <= target_rect.end.y:
|
||||
grid_multiline_points.push_back(Vector2(target_rect.position.x, y))
|
||||
grid_multiline_points.push_back(Vector2(target_rect.end.x, y))
|
||||
y += Global.grid_size.y
|
||||
if not Vector2(target_rect.position.x, y) in unique_rect_lines:
|
||||
grid_multiline_points.push_back(Vector2(target_rect.position.x, y))
|
||||
grid_multiline_points.push_back(Vector2(target_rect.end.x, y))
|
||||
y += grid.grid_size.y
|
||||
|
||||
unique_rect_lines.append_array(grid_multiline_points)
|
||||
if not grid_multiline_points.is_empty():
|
||||
draw_multiline(grid_multiline_points, Global.grid_color)
|
||||
draw_multiline(grid_multiline_points, grid.grid_color)
|
||||
|
||||
|
||||
func _draw_isometric_grid(target_rect: Rect2i) -> void:
|
||||
func _draw_isometric_grid(grid_index: int, target_rect: Rect2i) -> void:
|
||||
var grid = Global.grids[grid_index]
|
||||
var grid_multiline_points := PackedVector2Array()
|
||||
|
||||
var cell_size: Vector2 = Global.isometric_grid_size
|
||||
var cell_size: Vector2 = grid.isometric_grid_size
|
||||
var max_cell_count: Vector2 = Vector2(target_rect.size) / cell_size
|
||||
var origin_offset: Vector2 = Vector2(Global.grid_offset - target_rect.position).posmodv(
|
||||
cell_size
|
||||
)
|
||||
var origin_offset: Vector2 = Vector2(grid.grid_offset - target_rect.position).posmodv(cell_size)
|
||||
|
||||
# lines ↗↗↗ (from bottom-left to top-right)
|
||||
var per_cell_offset: Vector2 = cell_size * Vector2(1, -1)
|
||||
|
@ -70,8 +79,9 @@ func _draw_isometric_grid(target_rect: Rect2i) -> void:
|
|||
var start: Vector2 = Vector2(target_rect.position) + Vector2(0, y)
|
||||
var cells_to_rect_bounds: float = minf(max_cell_count.x, y / cell_size.y)
|
||||
var end := start + cells_to_rect_bounds * per_cell_offset
|
||||
grid_multiline_points.push_back(start)
|
||||
grid_multiline_points.push_back(end)
|
||||
if not start in unique_iso_lines:
|
||||
grid_multiline_points.push_back(start)
|
||||
grid_multiline_points.push_back(end)
|
||||
y += cell_size.y
|
||||
|
||||
# lines ↗↗↗ starting from the rect's bottom side (left to right):
|
||||
|
@ -80,8 +90,9 @@ func _draw_isometric_grid(target_rect: Rect2i) -> void:
|
|||
var start: Vector2 = Vector2(target_rect.position) + Vector2(x, target_rect.size.y)
|
||||
var cells_to_rect_bounds: float = minf(max_cell_count.y, max_cell_count.x - x / cell_size.x)
|
||||
var end: Vector2 = start + cells_to_rect_bounds * per_cell_offset
|
||||
grid_multiline_points.push_back(start)
|
||||
grid_multiline_points.push_back(end)
|
||||
if not start in unique_iso_lines:
|
||||
grid_multiline_points.push_back(start)
|
||||
grid_multiline_points.push_back(end)
|
||||
x += cell_size.x
|
||||
|
||||
# lines ↘↘↘ (from top-left to bottom-right)
|
||||
|
@ -93,8 +104,9 @@ func _draw_isometric_grid(target_rect: Rect2i) -> void:
|
|||
var start: Vector2 = Vector2(target_rect.position) + Vector2(0, y)
|
||||
var cells_to_rect_bounds: float = minf(max_cell_count.x, max_cell_count.y - y / cell_size.y)
|
||||
var end: Vector2 = start + cells_to_rect_bounds * per_cell_offset
|
||||
grid_multiline_points.push_back(start)
|
||||
grid_multiline_points.push_back(end)
|
||||
if not start in unique_iso_lines:
|
||||
grid_multiline_points.push_back(start)
|
||||
grid_multiline_points.push_back(end)
|
||||
y += cell_size.y
|
||||
|
||||
# lines ↘↘↘ starting from the rect's top side (left to right):
|
||||
|
@ -103,9 +115,11 @@ func _draw_isometric_grid(target_rect: Rect2i) -> void:
|
|||
var start: Vector2 = Vector2(target_rect.position) + Vector2(x, 0)
|
||||
var cells_to_rect_bounds: float = minf(max_cell_count.y, max_cell_count.x - x / cell_size.x)
|
||||
var end: Vector2 = start + cells_to_rect_bounds * per_cell_offset
|
||||
grid_multiline_points.push_back(start)
|
||||
grid_multiline_points.push_back(end)
|
||||
if not start in unique_iso_lines:
|
||||
grid_multiline_points.push_back(start)
|
||||
grid_multiline_points.push_back(end)
|
||||
x += cell_size.x
|
||||
grid_multiline_points.append_array(grid_multiline_points)
|
||||
|
||||
if not grid_multiline_points.is_empty():
|
||||
draw_multiline(grid_multiline_points, Global.grid_color)
|
||||
draw_multiline(grid_multiline_points, grid.grid_color)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
class_name Guide
|
||||
extends Line2D
|
||||
|
||||
enum Types { HORIZONTAL, VERTICAL }
|
||||
enum Types { HORIZONTAL, VERTICAL, XY, X_MINUS_Y }
|
||||
|
||||
const INPUT_WIDTH := 4
|
||||
|
||||
|
@ -31,12 +31,13 @@ func _input(_event: InputEvent) -> void:
|
|||
if type == Types.HORIZONTAL:
|
||||
point0.y -= width * INPUT_WIDTH
|
||||
point1.y += width * INPUT_WIDTH
|
||||
else:
|
||||
elif type == Types.VERTICAL:
|
||||
point0.x -= width * INPUT_WIDTH
|
||||
point1.x += width * INPUT_WIDTH
|
||||
var rect := Rect2()
|
||||
rect.position = point0
|
||||
rect.end = point1
|
||||
rect = rect.abs()
|
||||
if (
|
||||
Input.is_action_just_pressed(&"left_mouse")
|
||||
and Global.can_draw
|
||||
|
@ -55,7 +56,7 @@ func _input(_event: InputEvent) -> void:
|
|||
var yy := snappedf(mouse_pos.y, 0.5)
|
||||
points[0].y = yy
|
||||
points[1].y = yy
|
||||
else:
|
||||
elif type == Types.VERTICAL:
|
||||
var xx := snappedf(mouse_pos.x, 0.5)
|
||||
points[0].x = xx
|
||||
points[1].x = xx
|
||||
|
@ -221,14 +222,22 @@ func set_color(color: Color) -> void:
|
|||
default_color = color
|
||||
|
||||
|
||||
func get_direction() -> Vector2:
|
||||
return points[0].direction_to(points[1])
|
||||
|
||||
|
||||
func _project_switched() -> void:
|
||||
if self in Global.current_project.guides:
|
||||
visible = Global.show_guides
|
||||
if self is SymmetryGuide:
|
||||
if type == Types.HORIZONTAL:
|
||||
visible = Global.show_x_symmetry_axis and Global.show_guides
|
||||
else:
|
||||
elif type == Types.VERTICAL:
|
||||
visible = Global.show_y_symmetry_axis and Global.show_guides
|
||||
elif type == Types.XY:
|
||||
visible = Global.show_xy_symmetry_axis and Global.show_guides
|
||||
elif type == Types.X_MINUS_Y:
|
||||
visible = Global.show_x_minus_y_symmetry_axis and Global.show_guides
|
||||
else:
|
||||
visible = false
|
||||
|
||||
|
|
|
@ -214,7 +214,7 @@ func _move_with_arrow_keys(event: InputEvent) -> void:
|
|||
if _is_action_direction(event) and arrow_key_move:
|
||||
var step := Vector2.ONE
|
||||
if Input.is_key_pressed(KEY_CTRL):
|
||||
step = Global.grid_size
|
||||
step = Global.grids[0].grid_size
|
||||
var input := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
|
||||
var move := input.rotated(snappedf(Global.camera.rotation, PI / 2))
|
||||
# These checks are needed to fix a bug where the selection got stuck
|
||||
|
@ -516,6 +516,7 @@ func transform_content_confirm() -> void:
|
|||
Rect2i(Vector2i.ZERO, project.selection_map.get_size()),
|
||||
big_bounding_rectangle.position
|
||||
)
|
||||
cel_image.convert_rgb_to_indexed()
|
||||
project.selection_map.move_bitmap_values(project)
|
||||
commit_undo("Move Selection", undo_data)
|
||||
|
||||
|
@ -605,13 +606,13 @@ func get_undo_data(undo_image: bool) -> Dictionary:
|
|||
if undo_image:
|
||||
var images := _get_selected_draw_images()
|
||||
for image in images:
|
||||
data[image] = image.data
|
||||
image.add_data_to_dictionary(data)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
func _get_selected_draw_cels() -> Array[BaseCel]:
|
||||
var cels: Array[BaseCel] = []
|
||||
func _get_selected_draw_cels() -> Array[PixelCel]:
|
||||
var cels: Array[PixelCel] = []
|
||||
var project := Global.current_project
|
||||
for cel_index in project.selected_cels:
|
||||
var cel: BaseCel = project.frames[cel_index[0]].cels[cel_index[1]]
|
||||
|
@ -622,8 +623,8 @@ func _get_selected_draw_cels() -> Array[BaseCel]:
|
|||
return cels
|
||||
|
||||
|
||||
func _get_selected_draw_images() -> Array[Image]:
|
||||
var images: Array[Image] = []
|
||||
func _get_selected_draw_images() -> Array[ImageExtended]:
|
||||
var images: Array[ImageExtended] = []
|
||||
var project := Global.current_project
|
||||
for cel_index in project.selected_cels:
|
||||
var cel: BaseCel = project.frames[cel_index[0]].cels[cel_index[1]]
|
||||
|
@ -794,22 +795,24 @@ func delete(selected_cels := true) -> void:
|
|||
return
|
||||
|
||||
var undo_data_tmp := get_undo_data(true)
|
||||
var images: Array[Image]
|
||||
var images: Array[ImageExtended]
|
||||
if selected_cels:
|
||||
images = _get_selected_draw_images()
|
||||
else:
|
||||
images = [project.get_current_cel().get_image()]
|
||||
|
||||
if project.has_selection:
|
||||
var blank := Image.create(project.size.x, project.size.y, false, Image.FORMAT_RGBA8)
|
||||
var blank := project.new_empty_image()
|
||||
var selection_map_copy := project.selection_map.return_cropped_copy(project.size)
|
||||
for image in images:
|
||||
image.blit_rect_mask(
|
||||
blank, selection_map_copy, big_bounding_rectangle, big_bounding_rectangle.position
|
||||
)
|
||||
image.convert_rgb_to_indexed()
|
||||
else:
|
||||
for image in images:
|
||||
image.fill(0)
|
||||
image.convert_rgb_to_indexed()
|
||||
commit_undo("Draw", undo_data_tmp)
|
||||
|
||||
|
||||
|
@ -870,13 +873,16 @@ func _project_switched() -> void:
|
|||
|
||||
func _get_preview_image() -> void:
|
||||
var project := Global.current_project
|
||||
var blended_image := Image.create(project.size.x, project.size.y, false, Image.FORMAT_RGBA8)
|
||||
var blended_image := project.new_empty_image()
|
||||
DrawingAlgos.blend_layers(
|
||||
blended_image, project.frames[project.current_frame], Vector2i.ZERO, project, true
|
||||
)
|
||||
if original_preview_image.is_empty():
|
||||
original_preview_image = Image.create(
|
||||
big_bounding_rectangle.size.x, big_bounding_rectangle.size.y, false, Image.FORMAT_RGBA8
|
||||
big_bounding_rectangle.size.x,
|
||||
big_bounding_rectangle.size.y,
|
||||
false,
|
||||
project.get_image_format()
|
||||
)
|
||||
var selection_map_copy := project.selection_map.return_cropped_copy(project.size)
|
||||
original_preview_image.blit_rect_mask(
|
||||
|
@ -892,11 +898,11 @@ func _get_preview_image() -> void:
|
|||
var clear_image := Image.create(
|
||||
original_preview_image.get_width(),
|
||||
original_preview_image.get_height(),
|
||||
false,
|
||||
Image.FORMAT_RGBA8
|
||||
original_preview_image.has_mipmaps(),
|
||||
original_preview_image.get_format()
|
||||
)
|
||||
for cel in _get_selected_draw_cels():
|
||||
var cel_image: Image = cel.get_image()
|
||||
var cel_image := cel.get_image()
|
||||
cel.transformed_content = _get_selected_image(cel_image)
|
||||
cel_image.blit_rect_mask(
|
||||
clear_image,
|
||||
|
@ -911,7 +917,10 @@ func _get_preview_image() -> void:
|
|||
func _get_selected_image(cel_image: Image) -> Image:
|
||||
var project := Global.current_project
|
||||
var image := Image.create(
|
||||
big_bounding_rectangle.size.x, big_bounding_rectangle.size.y, false, Image.FORMAT_RGBA8
|
||||
big_bounding_rectangle.size.x,
|
||||
big_bounding_rectangle.size.y,
|
||||
false,
|
||||
project.get_image_format()
|
||||
)
|
||||
var selection_map_copy := project.selection_map.return_cropped_copy(project.size)
|
||||
image.blit_rect_mask(cel_image, selection_map_copy, big_bounding_rectangle, Vector2i.ZERO)
|
||||
|
|
54
src/UI/Canvas/color_index.gd
Normal file
|
@ -0,0 +1,54 @@
|
|||
extends Node2D
|
||||
|
||||
const FONT_SIZE = 16
|
||||
|
||||
var users := 1
|
||||
var enabled: bool = false:
|
||||
set(value):
|
||||
enabled = value
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
Global.camera.zoom_changed.connect(queue_redraw)
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
if not enabled:
|
||||
return
|
||||
# when we zoom out there is a visual issue that inverts the text
|
||||
# (kind of how you look through a magnifying glass)
|
||||
# so we should restrict the rendering distance of this preview.
|
||||
var zoom_percentage := 100.0 * Global.camera.zoom.x
|
||||
if zoom_percentage < Global.pixel_grid_show_at_zoom:
|
||||
return
|
||||
var project = ExtensionsApi.project.current_project
|
||||
var cel: BaseCel = project.frames[project.current_frame].cels[project.current_layer]
|
||||
if not cel is PixelCel:
|
||||
return
|
||||
var index_image: Image = cel.image.indices_image
|
||||
if index_image.get_size() != project.size or not cel.image.is_indexed:
|
||||
return
|
||||
|
||||
var used_rect: Rect2i = cel.image.get_used_rect()
|
||||
if used_rect.size != Vector2i.ZERO:
|
||||
# use smaller image for optimization
|
||||
index_image = index_image.get_region(used_rect)
|
||||
|
||||
var font: Font = ExtensionsApi.theme.get_theme().default_font
|
||||
var offset = position + Vector2(used_rect.position)
|
||||
draw_set_transform(offset, rotation, Vector2(0.05, 0.05))
|
||||
for x in range(index_image.get_size().x):
|
||||
for y in range(index_image.get_size().y):
|
||||
var index := index_image.get_pixel(x, y).r8
|
||||
if index == 0:
|
||||
continue
|
||||
draw_string(
|
||||
font,
|
||||
Vector2(x, y) * 20 + Vector2.DOWN * 16,
|
||||
str(index),
|
||||
HORIZONTAL_ALIGNMENT_LEFT,
|
||||
-1,
|
||||
FONT_SIZE if (index < 100) else int(FONT_SIZE / 1.5)
|
||||
)
|
||||
draw_set_transform(position, rotation, scale)
|
|
@ -52,7 +52,9 @@ var templates: Array[Template] = [
|
|||
@onready var height_value := %HeightValue as SpinBox
|
||||
@onready var portrait_button := %PortraitButton as Button
|
||||
@onready var landscape_button := %LandscapeButton as Button
|
||||
@onready var name_input := $VBoxContainer/FillColorContainer/NameInput as LineEdit
|
||||
@onready var fill_color_node := %FillColor as ColorPickerButton
|
||||
@onready var color_mode := $VBoxContainer/FillColorContainer/ColorMode as OptionButton
|
||||
@onready var recent_templates_list := %RecentTemplates as ItemList
|
||||
|
||||
|
||||
|
@ -123,13 +125,14 @@ func _on_CreateNewImage_confirmed() -> void:
|
|||
if recent_sizes.size() > 10:
|
||||
recent_sizes.resize(10)
|
||||
Global.config_cache.set_value("templates", "recent_sizes", recent_sizes)
|
||||
var fill_color: Color = fill_color_node.color
|
||||
|
||||
var proj_name: String = $VBoxContainer/ProjectName/NameInput.text
|
||||
var fill_color := fill_color_node.color
|
||||
var proj_name := name_input.text
|
||||
if !proj_name.is_valid_filename():
|
||||
proj_name = tr("untitled")
|
||||
|
||||
var new_project := Project.new([], proj_name, image_size)
|
||||
if color_mode.selected == 1:
|
||||
new_project.color_mode = Project.INDEXED_MODE
|
||||
new_project.layers.append(PixelLayer.new(new_project))
|
||||
new_project.fill_color = fill_color
|
||||
new_project.frames.append(new_project.new_empty_frame())
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
|
||||
[node name="CreateNewImage" type="ConfirmationDialog"]
|
||||
title = "New..."
|
||||
size = Vector2i(384, 330)
|
||||
position = Vector2i(0, 36)
|
||||
size = Vector2i(434, 330)
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
|
@ -22,19 +23,6 @@ offset_right = -8.0
|
|||
offset_bottom = -49.0
|
||||
size_flags_horizontal = 0
|
||||
|
||||
[node name="ProjectName" type="HBoxContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="NameLabel" type="Label" parent="VBoxContainer/ProjectName"]
|
||||
custom_minimum_size = Vector2(100, 0)
|
||||
layout_mode = 2
|
||||
text = "Project Name:"
|
||||
|
||||
[node name="NameInput" type="LineEdit" parent="VBoxContainer/ProjectName"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
placeholder_text = "Enter name... (Default \"untitled\")"
|
||||
|
||||
[node name="ImageSize" type="Label" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Image Size"
|
||||
|
@ -42,49 +30,48 @@ text = "Image Size"
|
|||
[node name="HSeparator" type="HSeparator" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="VBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
|
||||
[node name="SizeContainer" type="HBoxContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Templates" type="VBoxContainer" parent="VBoxContainer/VBoxContainer"]
|
||||
[node name="SizeOptions" type="VBoxContainer" parent="VBoxContainer/SizeContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="TemplatesContainer" type="HBoxContainer" parent="VBoxContainer/VBoxContainer/Templates"]
|
||||
[node name="TemplatesContainer" type="HBoxContainer" parent="VBoxContainer/SizeContainer/SizeOptions"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="TemplatesLabel" type="Label" parent="VBoxContainer/VBoxContainer/Templates/TemplatesContainer"]
|
||||
[node name="TemplatesLabel" type="Label" parent="VBoxContainer/SizeContainer/SizeOptions/TemplatesContainer"]
|
||||
custom_minimum_size = Vector2(100, 0)
|
||||
layout_mode = 2
|
||||
text = "Templates:"
|
||||
|
||||
[node name="TemplatesOptions" type="OptionButton" parent="VBoxContainer/VBoxContainer/Templates/TemplatesContainer"]
|
||||
[node name="TemplatesOptions" type="OptionButton" parent="VBoxContainer/SizeContainer/SizeOptions/TemplatesContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
mouse_default_cursor_shape = 2
|
||||
toggle_mode = false
|
||||
item_count = 1
|
||||
selected = 0
|
||||
item_count = 1
|
||||
popup/item_0/text = "Default"
|
||||
popup/item_0/id = 0
|
||||
|
||||
[node name="SizeContainer" type="HBoxContainer" parent="VBoxContainer/VBoxContainer/Templates"]
|
||||
[node name="WidthHeightContainer" type="HBoxContainer" parent="VBoxContainer/SizeContainer/SizeOptions"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/VBoxContainer/Templates/SizeContainer"]
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/SizeContainer/SizeOptions/WidthHeightContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="WidthContainer" type="HBoxContainer" parent="VBoxContainer/VBoxContainer/Templates/SizeContainer/VBoxContainer"]
|
||||
[node name="WidthContainer" type="HBoxContainer" parent="VBoxContainer/SizeContainer/SizeOptions/WidthHeightContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="WidthLabel" type="Label" parent="VBoxContainer/VBoxContainer/Templates/SizeContainer/VBoxContainer/WidthContainer"]
|
||||
[node name="WidthLabel" type="Label" parent="VBoxContainer/SizeContainer/SizeOptions/WidthHeightContainer/VBoxContainer/WidthContainer"]
|
||||
custom_minimum_size = Vector2(100, 0)
|
||||
layout_mode = 2
|
||||
text = "Width:"
|
||||
|
||||
[node name="WidthValue" type="SpinBox" parent="VBoxContainer/VBoxContainer/Templates/SizeContainer/VBoxContainer/WidthContainer"]
|
||||
[node name="WidthValue" type="SpinBox" parent="VBoxContainer/SizeContainer/SizeOptions/WidthHeightContainer/VBoxContainer/WidthContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
@ -94,15 +81,15 @@ max_value = 16384.0
|
|||
value = 64.0
|
||||
suffix = "px"
|
||||
|
||||
[node name="HeightContainer" type="HBoxContainer" parent="VBoxContainer/VBoxContainer/Templates/SizeContainer/VBoxContainer"]
|
||||
[node name="HeightContainer" type="HBoxContainer" parent="VBoxContainer/SizeContainer/SizeOptions/WidthHeightContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HeightLabel" type="Label" parent="VBoxContainer/VBoxContainer/Templates/SizeContainer/VBoxContainer/HeightContainer"]
|
||||
[node name="HeightLabel" type="Label" parent="VBoxContainer/SizeContainer/SizeOptions/WidthHeightContainer/VBoxContainer/HeightContainer"]
|
||||
custom_minimum_size = Vector2(100, 0)
|
||||
layout_mode = 2
|
||||
text = "Height:"
|
||||
|
||||
[node name="HeightValue" type="SpinBox" parent="VBoxContainer/VBoxContainer/Templates/SizeContainer/VBoxContainer/HeightContainer"]
|
||||
[node name="HeightValue" type="SpinBox" parent="VBoxContainer/SizeContainer/SizeOptions/WidthHeightContainer/VBoxContainer/HeightContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
@ -112,11 +99,11 @@ max_value = 16384.0
|
|||
value = 64.0
|
||||
suffix = "px"
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="VBoxContainer/VBoxContainer/Templates/SizeContainer" groups=["UIButtons"]]
|
||||
[node name="TextureRect" type="TextureRect" parent="VBoxContainer/SizeContainer/SizeOptions/WidthHeightContainer" groups=["UIButtons"]]
|
||||
layout_mode = 2
|
||||
texture = ExtResource("6")
|
||||
|
||||
[node name="AspectRatioButton" type="TextureButton" parent="VBoxContainer/VBoxContainer/Templates/SizeContainer/TextureRect" groups=["UIButtons"]]
|
||||
[node name="AspectRatioButton" type="TextureButton" parent="VBoxContainer/SizeContainer/SizeOptions/WidthHeightContainer/TextureRect" groups=["UIButtons"]]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 0
|
||||
anchor_left = 0.5
|
||||
|
@ -133,31 +120,10 @@ toggle_mode = true
|
|||
texture_normal = ExtResource("4")
|
||||
texture_pressed = ExtResource("5")
|
||||
|
||||
[node name="VSeparator" type="VSeparator" parent="VBoxContainer/VBoxContainer"]
|
||||
[node name="SizeButtonsContainer" type="HBoxContainer" parent="VBoxContainer/SizeContainer/SizeOptions"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/VBoxContainer"]
|
||||
clip_contents = true
|
||||
custom_minimum_size = Vector2(150, 0)
|
||||
layout_mode = 2
|
||||
focus_mode = 2
|
||||
mouse_filter = 0
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer/VBoxContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Recent:"
|
||||
|
||||
[node name="RecentTemplates" type="ItemList" parent="VBoxContainer/VBoxContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
allow_reselect = true
|
||||
|
||||
[node name="SizeButtonsContainer" type="HBoxContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="PortraitButton" type="Button" parent="VBoxContainer/SizeButtonsContainer" groups=["UIButtons"]]
|
||||
[node name="PortraitButton" type="Button" parent="VBoxContainer/SizeContainer/SizeOptions/SizeButtonsContainer" groups=["UIButtons"]]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(24, 24)
|
||||
layout_mode = 2
|
||||
|
@ -166,7 +132,7 @@ focus_mode = 0
|
|||
mouse_default_cursor_shape = 2
|
||||
toggle_mode = true
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="VBoxContainer/SizeButtonsContainer/PortraitButton"]
|
||||
[node name="TextureRect" type="TextureRect" parent="VBoxContainer/SizeContainer/SizeOptions/SizeButtonsContainer/PortraitButton"]
|
||||
layout_mode = 0
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
|
@ -178,7 +144,7 @@ offset_right = 8.0
|
|||
offset_bottom = 8.0
|
||||
texture = ExtResource("2")
|
||||
|
||||
[node name="LandscapeButton" type="Button" parent="VBoxContainer/SizeButtonsContainer" groups=["UIButtons"]]
|
||||
[node name="LandscapeButton" type="Button" parent="VBoxContainer/SizeContainer/SizeOptions/SizeButtonsContainer" groups=["UIButtons"]]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(24, 24)
|
||||
layout_mode = 2
|
||||
|
@ -187,7 +153,7 @@ focus_mode = 0
|
|||
mouse_default_cursor_shape = 2
|
||||
toggle_mode = true
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="VBoxContainer/SizeButtonsContainer/LandscapeButton"]
|
||||
[node name="TextureRect" type="TextureRect" parent="VBoxContainer/SizeContainer/SizeOptions/SizeButtonsContainer/LandscapeButton"]
|
||||
layout_mode = 0
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
|
@ -199,12 +165,49 @@ offset_right = 8.0
|
|||
offset_bottom = 8.0
|
||||
texture = ExtResource("3")
|
||||
|
||||
[node name="FillColorContainer" type="HBoxContainer" parent="VBoxContainer"]
|
||||
[node name="VSeparator" type="VSeparator" parent="VBoxContainer/SizeContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/SizeContainer"]
|
||||
clip_contents = true
|
||||
custom_minimum_size = Vector2(150, 0)
|
||||
layout_mode = 2
|
||||
focus_mode = 2
|
||||
mouse_filter = 0
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer/SizeContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Recent:"
|
||||
|
||||
[node name="RecentTemplates" type="ItemList" parent="VBoxContainer/SizeContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
allow_reselect = true
|
||||
|
||||
[node name="HSeparator2" type="HSeparator" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="FillColorContainer" type="GridContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
columns = 2
|
||||
|
||||
[node name="NameLabel" type="Label" parent="VBoxContainer/FillColorContainer"]
|
||||
custom_minimum_size = Vector2(100, 0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Project Name:"
|
||||
|
||||
[node name="NameInput" type="LineEdit" parent="VBoxContainer/FillColorContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
placeholder_text = "Enter name... (Default \"untitled\")"
|
||||
|
||||
[node name="FillColorLabel" type="Label" parent="VBoxContainer/FillColorContainer"]
|
||||
custom_minimum_size = Vector2(100, 0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Fill with color:"
|
||||
|
||||
[node name="FillColor" type="ColorPickerButton" parent="VBoxContainer/FillColorContainer"]
|
||||
|
@ -215,13 +218,28 @@ size_flags_horizontal = 3
|
|||
mouse_default_cursor_shape = 2
|
||||
color = Color(0, 0, 0, 0)
|
||||
|
||||
[node name="ColorModeLabel" type="Label" parent="VBoxContainer/FillColorContainer"]
|
||||
custom_minimum_size = Vector2(100, 0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Color mode:"
|
||||
|
||||
[node name="ColorMode" type="OptionButton" parent="VBoxContainer/FillColorContainer"]
|
||||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
selected = 0
|
||||
item_count = 2
|
||||
popup/item_0/text = "RGBA"
|
||||
popup/item_1/text = "Indexed"
|
||||
popup/item_1/id = 1
|
||||
|
||||
[connection signal="about_to_popup" from="." to="." method="_on_CreateNewImage_about_to_show"]
|
||||
[connection signal="confirmed" from="." to="." method="_on_CreateNewImage_confirmed"]
|
||||
[connection signal="visibility_changed" from="." to="." method="_on_visibility_changed"]
|
||||
[connection signal="item_selected" from="VBoxContainer/VBoxContainer/Templates/TemplatesContainer/TemplatesOptions" to="." method="_on_TemplatesOptions_item_selected"]
|
||||
[connection signal="value_changed" from="VBoxContainer/VBoxContainer/Templates/SizeContainer/VBoxContainer/WidthContainer/WidthValue" to="." method="_on_SizeValue_value_changed"]
|
||||
[connection signal="value_changed" from="VBoxContainer/VBoxContainer/Templates/SizeContainer/VBoxContainer/HeightContainer/HeightValue" to="." method="_on_SizeValue_value_changed"]
|
||||
[connection signal="toggled" from="VBoxContainer/VBoxContainer/Templates/SizeContainer/TextureRect/AspectRatioButton" to="." method="_on_AspectRatioButton_toggled"]
|
||||
[connection signal="item_selected" from="VBoxContainer/VBoxContainer/VBoxContainer/RecentTemplates" to="." method="_on_RecentTemplates_item_selected"]
|
||||
[connection signal="toggled" from="VBoxContainer/SizeButtonsContainer/PortraitButton" to="." method="_on_PortraitButton_toggled"]
|
||||
[connection signal="toggled" from="VBoxContainer/SizeButtonsContainer/LandscapeButton" to="." method="_on_LandscapeButton_toggled"]
|
||||
[connection signal="item_selected" from="VBoxContainer/SizeContainer/SizeOptions/TemplatesContainer/TemplatesOptions" to="." method="_on_TemplatesOptions_item_selected"]
|
||||
[connection signal="value_changed" from="VBoxContainer/SizeContainer/SizeOptions/WidthHeightContainer/VBoxContainer/WidthContainer/WidthValue" to="." method="_on_SizeValue_value_changed"]
|
||||
[connection signal="value_changed" from="VBoxContainer/SizeContainer/SizeOptions/WidthHeightContainer/VBoxContainer/HeightContainer/HeightValue" to="." method="_on_SizeValue_value_changed"]
|
||||
[connection signal="toggled" from="VBoxContainer/SizeContainer/SizeOptions/WidthHeightContainer/TextureRect/AspectRatioButton" to="." method="_on_AspectRatioButton_toggled"]
|
||||
[connection signal="toggled" from="VBoxContainer/SizeContainer/SizeOptions/SizeButtonsContainer/PortraitButton" to="." method="_on_PortraitButton_toggled"]
|
||||
[connection signal="toggled" from="VBoxContainer/SizeContainer/SizeOptions/SizeButtonsContainer/LandscapeButton" to="." method="_on_LandscapeButton_toggled"]
|
||||
[connection signal="item_selected" from="VBoxContainer/SizeContainer/VBoxContainer/RecentTemplates" to="." method="_on_RecentTemplates_item_selected"]
|
||||
|
|
|
@ -44,7 +44,6 @@ size_flags_vertical = 3
|
|||
[node name="TransparentChecker" parent="VBoxContainer/VSplitContainer/PreviewPanel" instance=ExtResource("2")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 0
|
||||
anchors_preset = 0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
|
||||
|
@ -335,19 +334,23 @@ text = "Clip image content to selection"
|
|||
mode = 2
|
||||
title = "Open a Directory"
|
||||
size = Vector2i(675, 500)
|
||||
always_on_top = true
|
||||
ok_button_text = "Select Current Folder"
|
||||
file_mode = 2
|
||||
access = 2
|
||||
|
||||
[node name="PathValidationAlert" type="AcceptDialog" parent="."]
|
||||
always_on_top = true
|
||||
dialog_text = "DirAccess path and file name are not valid!"
|
||||
|
||||
[node name="FileExistsAlert" type="AcceptDialog" parent="."]
|
||||
always_on_top = true
|
||||
dialog_text = "File %s already exists. Overwrite?"
|
||||
|
||||
[node name="ExportProgressBar" type="Window" parent="."]
|
||||
visible = false
|
||||
exclusive = true
|
||||
always_on_top = true
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="ExportProgressBar"]
|
||||
anchors_preset = 15
|
||||
|
|
|
@ -89,7 +89,7 @@ func commit_action(cel: Image, project := Global.current_project) -> void:
|
|||
selection_tex = ImageTexture.create_from_image(selection)
|
||||
|
||||
if !_type_is_shader():
|
||||
var blank := Image.create(project.size.x, project.size.y, false, Image.FORMAT_RGBA8)
|
||||
var blank := project.new_empty_image()
|
||||
cel.blit_rect_mask(
|
||||
blank, selection, Rect2i(Vector2i.ZERO, cel.get_size()), Vector2i.ZERO
|
||||
)
|
||||
|
@ -136,6 +136,8 @@ func commit_action(cel: Image, project := Global.current_project) -> void:
|
|||
cel.blend_rect(image, Rect2i(Vector2i.ZERO, image.get_size()), Vector2i.ZERO)
|
||||
else:
|
||||
cel.blit_rect(image, Rect2i(Vector2i.ZERO, image.get_size()), Vector2i.ZERO)
|
||||
if cel is ImageExtended:
|
||||
cel.convert_rgb_to_indexed()
|
||||
|
||||
|
||||
func _type_is_shader() -> bool:
|
||||
|
|
|
@ -34,7 +34,7 @@ func _on_ScaleImage_confirmed() -> void:
|
|||
var width: int = width_value.value
|
||||
var height: int = height_value.value
|
||||
var interpolation: int = interpolation_type.selected
|
||||
DrawingAlgos.scale_image(width, height, interpolation)
|
||||
DrawingAlgos.scale_project(width, height, interpolation)
|
||||
|
||||
|
||||
func _on_visibility_changed() -> void:
|
||||
|
|
|
@ -22,7 +22,7 @@ func refresh_list() -> void:
|
|||
animation_tags_list.clear()
|
||||
get_ok_button().disabled = true
|
||||
for tag: AnimationTag in from_project.animation_tags:
|
||||
var img = Image.create(from_project.size.x, from_project.size.y, true, Image.FORMAT_RGBA8)
|
||||
var img := from_project.new_empty_image()
|
||||
DrawingAlgos.blend_layers(
|
||||
img, from_project.frames[tag.from - 1], Vector2i.ZERO, from_project
|
||||
)
|
||||
|
@ -186,9 +186,7 @@ func add_animation(indices: Array, destination: int, from_tag: AnimationTag = nu
|
|||
# add more types here if they have a copy_content() method
|
||||
if src_cel is PixelCel:
|
||||
var src_img = src_cel.copy_content()
|
||||
var copy := Image.create(
|
||||
project.size.x, project.size.y, false, Image.FORMAT_RGBA8
|
||||
)
|
||||
var copy := project.new_empty_image()
|
||||
copy.blit_rect(
|
||||
src_img, Rect2(Vector2.ZERO, src_img.get_size()), Vector2.ZERO
|
||||
)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
extends AcceptDialog
|
||||
|
||||
@onready var size_value_label := $GridContainer/SizeValueLabel as Label
|
||||
@onready var color_mode_value_label := $GridContainer/ColorModeValueLabel as Label
|
||||
@onready var frames_value_label := $GridContainer/FramesValueLabel as Label
|
||||
@onready var layers_value_label := $GridContainer/LayersValueLabel as Label
|
||||
@onready var name_line_edit := $GridContainer/NameLineEdit as LineEdit
|
||||
|
@ -10,6 +11,12 @@ extends AcceptDialog
|
|||
func _on_visibility_changed() -> void:
|
||||
Global.dialog_open(visible)
|
||||
size_value_label.text = str(Global.current_project.size)
|
||||
if Global.current_project.get_image_format() == Image.FORMAT_RGBA8:
|
||||
color_mode_value_label.text = "RGBA8"
|
||||
else:
|
||||
color_mode_value_label.text = str(Global.current_project.get_image_format())
|
||||
if Global.current_project.is_indexed():
|
||||
color_mode_value_label.text += " (%s)" % tr("Indexed")
|
||||
frames_value_label.text = str(Global.current_project.frames.size())
|
||||
layers_value_label.text = str(Global.current_project.layers.size())
|
||||
name_line_edit.text = Global.current_project.name
|
||||
|
|
|
@ -24,6 +24,16 @@ layout_mode = 2
|
|||
size_flags_horizontal = 3
|
||||
text = "64x64"
|
||||
|
||||
[node name="ColorModeLabel" type="Label" parent="GridContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Color mode:"
|
||||
|
||||
[node name="ColorModeValueLabel" type="Label" parent="GridContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "RGBA8"
|
||||
|
||||
[node name="FramesLabel" type="Label" parent="GridContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
@ -32,7 +42,7 @@ text = "Frames:"
|
|||
[node name="FramesValueLabel" type="Label" parent="GridContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "64x64"
|
||||
text = "1"
|
||||
|
||||
[node name="LayersLabel" type="Label" parent="GridContainer"]
|
||||
layout_mode = 2
|
||||
|
@ -42,7 +52,7 @@ text = "Layers:"
|
|||
[node name="LayersValueLabel" type="Label" parent="GridContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "64x64"
|
||||
text = "1"
|
||||
|
||||
[node name="NameLabel" type="Label" parent="GridContainer"]
|
||||
layout_mode = 2
|
||||
|
|
|
@ -3,6 +3,8 @@ extends PanelContainer
|
|||
@onready var grid_container: GridContainer = find_child("GridContainer")
|
||||
@onready var horizontal_mirror: BaseButton = grid_container.get_node("Horizontal")
|
||||
@onready var vertical_mirror: BaseButton = grid_container.get_node("Vertical")
|
||||
@onready var diagonal_xy_mirror: BaseButton = grid_container.get_node("DiagonalXY")
|
||||
@onready var diagonal_x_minus_y_mirror: BaseButton = grid_container.get_node("DiagonalXMinusY")
|
||||
@onready var pixel_perfect: BaseButton = grid_container.get_node("PixelPerfect")
|
||||
@onready var alpha_lock: BaseButton = grid_container.get_node("AlphaLock")
|
||||
@onready var dynamics: Button = $"%Dynamics"
|
||||
|
@ -39,25 +41,25 @@ func _on_resized() -> void:
|
|||
grid_container.columns = column_n
|
||||
|
||||
|
||||
func _on_Horizontal_toggled(button_pressed: bool) -> void:
|
||||
Tools.horizontal_mirror = button_pressed
|
||||
Global.config_cache.set_value("tools", "horizontal_mirror", button_pressed)
|
||||
Global.show_y_symmetry_axis = button_pressed
|
||||
func _on_Horizontal_toggled(toggled_on: bool) -> void:
|
||||
Tools.horizontal_mirror = toggled_on
|
||||
Global.config_cache.set_value("tools", "horizontal_mirror", toggled_on)
|
||||
Global.show_y_symmetry_axis = toggled_on
|
||||
Global.current_project.y_symmetry_axis.visible = (
|
||||
Global.show_y_symmetry_axis and Global.show_guides
|
||||
)
|
||||
|
||||
var texture_button: TextureRect = horizontal_mirror.get_node("TextureRect")
|
||||
var file_name := "horizontal_mirror_on.png"
|
||||
if !button_pressed:
|
||||
if not toggled_on:
|
||||
file_name = "horizontal_mirror_off.png"
|
||||
Global.change_button_texturerect(texture_button, file_name)
|
||||
|
||||
|
||||
func _on_Vertical_toggled(button_pressed: bool) -> void:
|
||||
Tools.vertical_mirror = button_pressed
|
||||
Global.config_cache.set_value("tools", "vertical_mirror", button_pressed)
|
||||
Global.show_x_symmetry_axis = button_pressed
|
||||
func _on_Vertical_toggled(toggled_on: bool) -> void:
|
||||
Tools.vertical_mirror = toggled_on
|
||||
Global.config_cache.set_value("tools", "vertical_mirror", toggled_on)
|
||||
Global.show_x_symmetry_axis = toggled_on
|
||||
# If the button is not pressed but another button is, keep the symmetry guide visible
|
||||
Global.current_project.x_symmetry_axis.visible = (
|
||||
Global.show_x_symmetry_axis and Global.show_guides
|
||||
|
@ -65,11 +67,43 @@ func _on_Vertical_toggled(button_pressed: bool) -> void:
|
|||
|
||||
var texture_button: TextureRect = vertical_mirror.get_node("TextureRect")
|
||||
var file_name := "vertical_mirror_on.png"
|
||||
if !button_pressed:
|
||||
if not toggled_on:
|
||||
file_name = "vertical_mirror_off.png"
|
||||
Global.change_button_texturerect(texture_button, file_name)
|
||||
|
||||
|
||||
func _on_diagonal_xy_toggled(toggled_on: bool) -> void:
|
||||
Tools.diagonal_xy_mirror = toggled_on
|
||||
Global.config_cache.set_value("tools", "diagonal_xy_mirror", toggled_on)
|
||||
Global.show_xy_symmetry_axis = toggled_on
|
||||
# If the button is not pressed but another button is, keep the symmetry guide visible
|
||||
Global.current_project.diagonal_xy_symmetry_axis.visible = (
|
||||
Global.show_xy_symmetry_axis and Global.show_guides
|
||||
)
|
||||
|
||||
var texture_button: TextureRect = diagonal_xy_mirror.get_node("TextureRect")
|
||||
var file_name := "xy_mirror_on.png"
|
||||
if not toggled_on:
|
||||
file_name = "xy_mirror_off.png"
|
||||
Global.change_button_texturerect(texture_button, file_name)
|
||||
|
||||
|
||||
func _on_diagonal_x_minus_y_toggled(toggled_on: bool) -> void:
|
||||
Tools.diagonal_x_minus_y_mirror = toggled_on
|
||||
Global.config_cache.set_value("tools", "diagonal_x_minus_y_mirror", toggled_on)
|
||||
Global.show_x_minus_y_symmetry_axis = toggled_on
|
||||
# If the button is not pressed but another button is, keep the symmetry guide visible
|
||||
Global.current_project.diagonal_x_minus_y_symmetry_axis.visible = (
|
||||
Global.show_x_minus_y_symmetry_axis and Global.show_guides
|
||||
)
|
||||
|
||||
var texture_button: TextureRect = diagonal_x_minus_y_mirror.get_node("TextureRect")
|
||||
var file_name := "x_minus_y_mirror_on.png"
|
||||
if not toggled_on:
|
||||
file_name = "x_minus_y_mirror_off.png"
|
||||
Global.change_button_texturerect(texture_button, file_name)
|
||||
|
||||
|
||||
func _on_PixelPerfect_toggled(button_pressed: bool) -> void:
|
||||
Tools.pixel_perfect = button_pressed
|
||||
Global.config_cache.set_value("tools", "pixel_perfect", button_pressed)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
[gd_scene load_steps=25 format=3 uid="uid://wo0hqxkst808"]
|
||||
[gd_scene load_steps=27 format=3 uid="uid://wo0hqxkst808"]
|
||||
|
||||
[ext_resource type="Texture2D" uid="uid://cjrokejjsp5dm" path="res://assets/graphics/misc/horizontal_mirror_off.png" id="1"]
|
||||
[ext_resource type="Texture2D" uid="uid://hiduvaa73fr6" path="res://assets/graphics/misc/vertical_mirror_off.png" id="2"]
|
||||
|
@ -6,8 +6,10 @@
|
|||
[ext_resource type="Texture2D" uid="uid://ct8wn8m6x4m54" path="res://assets/graphics/misc/value_arrow.svg" id="3_faalk"]
|
||||
[ext_resource type="Texture2D" uid="uid://22h12g8p3jtd" path="res://assets/graphics/misc/pixel_perfect_off.png" id="4"]
|
||||
[ext_resource type="Script" path="res://src/UI/Nodes/ValueSlider.gd" id="5"]
|
||||
[ext_resource type="Texture2D" uid="uid://dlxhm0ronna25" path="res://assets/graphics/misc/xy_mirror_off.png" id="5_hcmgx"]
|
||||
[ext_resource type="Texture2D" uid="uid://j8eywwy082a4" path="res://assets/graphics/misc/alpha_lock_off.png" id="5_jv20x"]
|
||||
[ext_resource type="Texture2D" uid="uid://dg3dumyfj1682" path="res://assets/graphics/misc/dynamics.png" id="6"]
|
||||
[ext_resource type="Texture2D" uid="uid://1kj5gcswa3t2" path="res://assets/graphics/misc/x_minus_y_mirror_off.png" id="6_sw8fy"]
|
||||
[ext_resource type="Texture2D" uid="uid://di8au2u87jgv5" path="res://assets/graphics/misc/uncheck.png" id="7"]
|
||||
[ext_resource type="Script" path="res://src/UI/GlobalToolOptions/DynamicsPanel.gd" id="7_iqcw1"]
|
||||
[ext_resource type="PackedScene" uid="uid://bmsc0s03pwji4" path="res://src/UI/Nodes/MaxMinEdit.tscn" id="8"]
|
||||
|
@ -179,6 +181,108 @@ grow_vertical = 2
|
|||
texture = ExtResource("3_faalk")
|
||||
stretch_mode = 3
|
||||
|
||||
[node name="DiagonalXY" type="Button" parent="ScrollContainer/CenterContainer/GridContainer" groups=["UIButtons"]]
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(46, 32)
|
||||
layout_mode = 2
|
||||
tooltip_text = "Enable vertical mirrored drawing"
|
||||
mouse_default_cursor_shape = 2
|
||||
toggle_mode = true
|
||||
shortcut = SubResource("Shortcut_ai7qc")
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="ScrollContainer/CenterContainer/GridContainer/DiagonalXY"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 4
|
||||
anchor_top = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = 5.0
|
||||
offset_top = -10.0
|
||||
offset_right = 25.0
|
||||
offset_bottom = 10.0
|
||||
grow_vertical = 2
|
||||
texture = ExtResource("5_hcmgx")
|
||||
|
||||
[node name="MirrorOptions" type="MenuButton" parent="ScrollContainer/CenterContainer/GridContainer/DiagonalXY"]
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(20, 0)
|
||||
layout_mode = 1
|
||||
anchors_preset = 6
|
||||
anchor_left = 1.0
|
||||
anchor_top = 0.5
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -20.0
|
||||
offset_top = -10.0
|
||||
offset_bottom = 10.0
|
||||
grow_horizontal = 0
|
||||
grow_vertical = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
item_count = 2
|
||||
popup/item_0/text = "Move to canvas center"
|
||||
popup/item_1/text = "Move to view center"
|
||||
popup/item_1/id = 1
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="ScrollContainer/CenterContainer/GridContainer/DiagonalXY/MirrorOptions"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
texture = ExtResource("3_faalk")
|
||||
stretch_mode = 3
|
||||
|
||||
[node name="DiagonalXMinusY" type="Button" parent="ScrollContainer/CenterContainer/GridContainer" groups=["UIButtons"]]
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(46, 32)
|
||||
layout_mode = 2
|
||||
tooltip_text = "Enable vertical mirrored drawing"
|
||||
mouse_default_cursor_shape = 2
|
||||
toggle_mode = true
|
||||
shortcut = SubResource("Shortcut_ai7qc")
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="ScrollContainer/CenterContainer/GridContainer/DiagonalXMinusY"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 4
|
||||
anchor_top = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = 5.0
|
||||
offset_top = -10.0
|
||||
offset_right = 25.0
|
||||
offset_bottom = 10.0
|
||||
grow_vertical = 2
|
||||
texture = ExtResource("6_sw8fy")
|
||||
|
||||
[node name="MirrorOptions" type="MenuButton" parent="ScrollContainer/CenterContainer/GridContainer/DiagonalXMinusY"]
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(20, 0)
|
||||
layout_mode = 1
|
||||
anchors_preset = 6
|
||||
anchor_left = 1.0
|
||||
anchor_top = 0.5
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -20.0
|
||||
offset_top = -10.0
|
||||
offset_bottom = 10.0
|
||||
grow_horizontal = 0
|
||||
grow_vertical = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
item_count = 2
|
||||
popup/item_0/text = "Move to canvas center"
|
||||
popup/item_1/text = "Move to view center"
|
||||
popup/item_1/id = 1
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="ScrollContainer/CenterContainer/GridContainer/DiagonalXMinusY/MirrorOptions"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
texture = ExtResource("3_faalk")
|
||||
stretch_mode = 3
|
||||
|
||||
[node name="PixelPerfect" type="Button" parent="ScrollContainer/CenterContainer/GridContainer" groups=["UIButtons"]]
|
||||
custom_minimum_size = Vector2(32, 32)
|
||||
layout_mode = 2
|
||||
|
@ -546,6 +650,8 @@ offset_bottom = 23.0
|
|||
[connection signal="resized" from="." to="." method="_on_resized"]
|
||||
[connection signal="toggled" from="ScrollContainer/CenterContainer/GridContainer/Horizontal" to="." method="_on_Horizontal_toggled"]
|
||||
[connection signal="toggled" from="ScrollContainer/CenterContainer/GridContainer/Vertical" to="." method="_on_Vertical_toggled"]
|
||||
[connection signal="toggled" from="ScrollContainer/CenterContainer/GridContainer/DiagonalXY" to="." method="_on_diagonal_xy_toggled"]
|
||||
[connection signal="toggled" from="ScrollContainer/CenterContainer/GridContainer/DiagonalXMinusY" to="." method="_on_diagonal_x_minus_y_toggled"]
|
||||
[connection signal="toggled" from="ScrollContainer/CenterContainer/GridContainer/PixelPerfect" to="." method="_on_PixelPerfect_toggled"]
|
||||
[connection signal="toggled" from="ScrollContainer/CenterContainer/GridContainer/AlphaLock" to="." method="_on_alpha_lock_toggled"]
|
||||
[connection signal="pressed" from="ScrollContainer/CenterContainer/GridContainer/Dynamics" to="." method="_on_Dynamics_pressed"]
|
||||
|
|
|
@ -55,6 +55,7 @@ class Recorder:
|
|||
dir.make_dir_recursive(save_directory)
|
||||
project.removed.connect(recorder_panel.finalize_recording.bind(project))
|
||||
project.undo_redo.version_changed.connect(capture_frame)
|
||||
recorder_panel.captured_label.text = ""
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
if what == NOTIFICATION_PREDELETE:
|
||||
|
@ -70,7 +71,7 @@ class Recorder:
|
|||
image = recorder_panel.get_window().get_texture().get_image()
|
||||
else:
|
||||
var frame := project.frames[project.current_frame]
|
||||
image = Image.create(project.size.x, project.size.y, false, Image.FORMAT_RGBA8)
|
||||
image = project.new_empty_image()
|
||||
DrawingAlgos.blend_layers(image, frame, Vector2i.ZERO, project)
|
||||
|
||||
if recorder_panel.resize_percent != 100:
|
||||
|
@ -100,6 +101,9 @@ func _on_project_switched() -> void:
|
|||
initialize_recording()
|
||||
start_button.set_pressed_no_signal(true)
|
||||
Global.change_button_texturerect(start_button.get_child(0), "stop.png")
|
||||
captured_label.text = str(
|
||||
"Saved: ", recorded_projects[Global.current_project].frames_captured
|
||||
)
|
||||
else:
|
||||
finalize_recording()
|
||||
start_button.set_pressed_no_signal(false)
|
||||
|
|
|
@ -40,15 +40,18 @@ mouse_default_cursor_shape = 2
|
|||
toggle_mode = true
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="ScrollContainer/CenterContainer/GridContainer/Start"]
|
||||
layout_mode = 0
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -10.0
|
||||
offset_top = -10.5
|
||||
offset_right = 10.0
|
||||
offset_bottom = 10.5
|
||||
offset_left = -11.0
|
||||
offset_top = -11.0
|
||||
offset_right = 11.0
|
||||
offset_bottom = 11.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
texture = ExtResource("1")
|
||||
expand_mode = 1
|
||||
stretch_mode = 5
|
||||
|
@ -74,7 +77,7 @@ offset_bottom = 10.5
|
|||
texture = ExtResource("3")
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="OpenFolder" type="Button" parent="ScrollContainer/CenterContainer/GridContainer"]
|
||||
[node name="OpenFolder" type="Button" parent="ScrollContainer/CenterContainer/GridContainer" groups=["UIButtons"]]
|
||||
custom_minimum_size = Vector2(32, 32)
|
||||
layout_mode = 2
|
||||
tooltip_text = "Open Folder"
|
||||
|
|
|
@ -140,8 +140,10 @@ func _input(event: InputEvent) -> void:
|
|||
var timeline_rect := Rect2(global_position, size)
|
||||
if timeline_rect.has_point(mouse_pos):
|
||||
if Input.is_key_pressed(KEY_CTRL):
|
||||
cel_size += (2 * int(event.is_action("zoom_in")) - 2 * int(event.is_action("zoom_out")))
|
||||
get_viewport().set_input_as_handled()
|
||||
var zoom := 2 * int(event.is_action("zoom_in")) - 2 * int(event.is_action("zoom_out"))
|
||||
cel_size += zoom
|
||||
if zoom != 0:
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
func reset_settings() -> void:
|
||||
|
@ -1045,9 +1047,10 @@ func _on_MergeDownLayer_pressed() -> void:
|
|||
top_cels.append(top_cel) # Store for undo purposes
|
||||
|
||||
var top_image := top_layer.display_effects(top_cel)
|
||||
var bottom_cel := frame.cels[bottom_layer.index]
|
||||
var bottom_cel := frame.cels[bottom_layer.index] as PixelCel
|
||||
var bottom_image := bottom_cel.get_image()
|
||||
var textures: Array[Image] = []
|
||||
textures.append(bottom_cel.get_image())
|
||||
textures.append(bottom_image)
|
||||
textures.append(top_image)
|
||||
var metadata_image := Image.create(2, 4, false, Image.FORMAT_R8)
|
||||
DrawingAlgos.set_layer_metadata_image(bottom_layer, bottom_cel, metadata_image, 0)
|
||||
|
@ -1058,12 +1061,17 @@ func _on_MergeDownLayer_pressed() -> void:
|
|||
var params := {
|
||||
"layers": texture_array, "metadata": ImageTexture.create_from_image(metadata_image)
|
||||
}
|
||||
var bottom_image := Image.create(
|
||||
top_image.get_width(), top_image.get_height(), false, top_image.get_format()
|
||||
var new_bottom_image := ImageExtended.create_custom(
|
||||
top_image.get_width(),
|
||||
top_image.get_height(),
|
||||
top_image.has_mipmaps(),
|
||||
top_image.get_format(),
|
||||
project.is_indexed()
|
||||
)
|
||||
# Merge the image itself.
|
||||
var gen := ShaderImageEffect.new()
|
||||
gen.generate_image(bottom_image, DrawingAlgos.blend_layers_shader, params, project.size)
|
||||
|
||||
gen.generate_image(new_bottom_image, DrawingAlgos.blend_layers_shader, params, project.size)
|
||||
new_bottom_image.convert_rgb_to_indexed()
|
||||
if (
|
||||
bottom_cel.link_set != null
|
||||
and bottom_cel.link_set.size() > 1
|
||||
|
@ -1074,14 +1082,14 @@ func _on_MergeDownLayer_pressed() -> void:
|
|||
project.undo_redo.add_undo_method(
|
||||
bottom_layer.link_cel.bind(bottom_cel, bottom_cel.link_set)
|
||||
)
|
||||
project.undo_redo.add_do_property(bottom_cel, "image", bottom_image)
|
||||
project.undo_redo.add_do_property(bottom_cel, "image", new_bottom_image)
|
||||
project.undo_redo.add_undo_property(bottom_cel, "image", bottom_cel.image)
|
||||
else:
|
||||
Global.undo_redo_compress_images(
|
||||
{bottom_cel.image: bottom_image.data},
|
||||
{bottom_cel.image: bottom_cel.image.data},
|
||||
project
|
||||
)
|
||||
var redo_data := {}
|
||||
var undo_data := {}
|
||||
new_bottom_image.add_data_to_dictionary(redo_data, bottom_image)
|
||||
bottom_image.add_data_to_dictionary(undo_data)
|
||||
Global.undo_redo_compress_images(redo_data, undo_data, project)
|
||||
|
||||
project.undo_redo.add_do_method(project.remove_layers.bind([top_layer.index]))
|
||||
project.undo_redo.add_undo_method(
|
||||
|
|
|
@ -154,13 +154,19 @@ func _apply_effect(layer: BaseLayer, effect: LayerEffect) -> void:
|
|||
var undo_data := {}
|
||||
for frame in Global.current_project.frames:
|
||||
var cel := frame.cels[layer.index]
|
||||
var new_image := Image.new()
|
||||
new_image.copy_from(cel.get_image())
|
||||
var new_image := ImageExtended.new()
|
||||
var cel_image := cel.get_image()
|
||||
if cel_image is ImageExtended:
|
||||
new_image.is_indexed = cel_image.is_indexed
|
||||
new_image.copy_from_custom(cel_image)
|
||||
var image_size := new_image.get_size()
|
||||
var shader_image_effect := ShaderImageEffect.new()
|
||||
shader_image_effect.generate_image(new_image, effect.shader, effect.params, image_size)
|
||||
redo_data[cel.image] = new_image.data
|
||||
undo_data[cel.image] = cel.image.data
|
||||
if cel_image is ImageExtended:
|
||||
redo_data[cel_image.indices_image] = new_image.indices_image.data
|
||||
undo_data[cel_image.indices_image] = cel_image.indices_image.data
|
||||
redo_data[cel_image] = new_image.data
|
||||
undo_data[cel_image] = cel_image.data
|
||||
Global.current_project.undos += 1
|
||||
Global.current_project.undo_redo.create_action("Apply layer effect")
|
||||
Global.undo_redo_compress_images(redo_data, undo_data)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
extends Panel
|
||||
|
||||
enum ColorModes { RGBA, INDEXED }
|
||||
|
||||
const DOCS_URL := "https://www.oramainteractive.com/Pixelorama-Docs/"
|
||||
const ISSUES_URL := "https://github.com/Orama-Interactive/Pixelorama/issues"
|
||||
const SUPPORT_URL := "https://www.patreon.com/OramaInteractive"
|
||||
|
@ -56,6 +58,7 @@ var about_dialog := Dialog.new("res://src/UI/Dialogs/AboutDialog.tscn")
|
|||
@onready var greyscale_vision: ColorRect = main_ui.find_child("GreyscaleVision")
|
||||
@onready var tile_mode_submenu := PopupMenu.new()
|
||||
@onready var selection_modify_submenu := PopupMenu.new()
|
||||
@onready var color_mode_submenu := PopupMenu.new()
|
||||
@onready var snap_to_submenu := PopupMenu.new()
|
||||
@onready var panels_submenu := PopupMenu.new()
|
||||
@onready var layouts_submenu := PopupMenu.new()
|
||||
|
@ -124,6 +127,7 @@ func _project_switched() -> void:
|
|||
_update_file_menu_buttons(project)
|
||||
for j in Tiles.MODE.values():
|
||||
tile_mode_submenu.set_item_checked(j, j == project.tiles.mode)
|
||||
_check_color_mode_submenu_item(project)
|
||||
|
||||
_update_current_frame_mark()
|
||||
|
||||
|
@ -228,6 +232,7 @@ func _setup_view_menu() -> void:
|
|||
"Mirror View": "mirror_view",
|
||||
"Show Grid": "show_grid",
|
||||
"Show Pixel Grid": "show_pixel_grid",
|
||||
"Show Pixel Indices": "show_pixel_indices",
|
||||
"Show Rulers": "show_rulers",
|
||||
"Show Guides": "show_guides",
|
||||
"Show Mouse Guides": "",
|
||||
|
@ -257,6 +262,9 @@ func _setup_view_menu() -> void:
|
|||
var draw_pixel_grid: bool = Global.config_cache.get_value(
|
||||
"view_menu", "draw_pixel_grid", Global.draw_pixel_grid
|
||||
)
|
||||
var show_pixel_indices: bool = Global.config_cache.get_value(
|
||||
"view_menu", "show_pixel_indices", Global.show_pixel_indices
|
||||
)
|
||||
var show_rulers: bool = Global.config_cache.get_value(
|
||||
"view_menu", "show_rulers", Global.show_rulers
|
||||
)
|
||||
|
@ -291,6 +299,8 @@ func _setup_view_menu() -> void:
|
|||
_toggle_show_guides()
|
||||
if show_mouse_guides != Global.show_mouse_guides:
|
||||
_toggle_show_mouse_guides()
|
||||
if show_pixel_indices != Global.show_pixel_indices:
|
||||
_toggle_show_pixel_indices()
|
||||
if display_layer_effects != Global.display_layer_effects:
|
||||
Global.display_layer_effects = display_layer_effects
|
||||
if snap_to_rectangular_grid_boundary != Global.snap_to_rectangular_grid_boundary:
|
||||
|
@ -396,19 +406,33 @@ func _setup_image_menu() -> void:
|
|||
# Order as in Global.ImageMenu enum
|
||||
var image_menu_items := {
|
||||
"Project Properties": "project_properties",
|
||||
"Color Mode": "",
|
||||
"Resize Canvas": "resize_canvas",
|
||||
"Scale Image": "scale_image",
|
||||
"Crop to Selection": "crop_to_selection",
|
||||
"Crop to Content": "crop_to_content",
|
||||
}
|
||||
var i := 0
|
||||
for item in image_menu_items:
|
||||
_set_menu_shortcut(image_menu_items[item], image_menu, i, item)
|
||||
i += 1
|
||||
for i in image_menu_items.size():
|
||||
var item: String = image_menu_items.keys()[i]
|
||||
if item == "Color Mode":
|
||||
_setup_color_mode_submenu(item)
|
||||
else:
|
||||
_set_menu_shortcut(image_menu_items[item], image_menu, i, item)
|
||||
image_menu.set_item_disabled(Global.ImageMenu.CROP_TO_SELECTION, true)
|
||||
image_menu.id_pressed.connect(image_menu_id_pressed)
|
||||
|
||||
|
||||
func _setup_color_mode_submenu(item: String) -> void:
|
||||
color_mode_submenu.set_name("color_mode_submenu")
|
||||
color_mode_submenu.add_radio_check_item("RGBA", ColorModes.RGBA)
|
||||
color_mode_submenu.set_item_checked(ColorModes.RGBA, true)
|
||||
color_mode_submenu.add_radio_check_item("Indexed", ColorModes.INDEXED)
|
||||
|
||||
color_mode_submenu.id_pressed.connect(_color_mode_submenu_id_pressed)
|
||||
image_menu.add_child(color_mode_submenu)
|
||||
image_menu.add_submenu_item(item, color_mode_submenu.get_name())
|
||||
|
||||
|
||||
func _setup_effects_menu() -> void:
|
||||
# Order as in Global.EffectMenu enum
|
||||
var menu_items := {
|
||||
|
@ -648,6 +672,8 @@ func view_menu_id_pressed(id: int) -> void:
|
|||
_toggle_show_guides()
|
||||
Global.ViewMenu.SHOW_MOUSE_GUIDES:
|
||||
_toggle_show_mouse_guides()
|
||||
Global.ViewMenu.SHOW_PIXEL_INDICES:
|
||||
_toggle_show_pixel_indices()
|
||||
Global.ViewMenu.DISPLAY_LAYER_EFFECTS:
|
||||
Global.display_layer_effects = not Global.display_layer_effects
|
||||
_:
|
||||
|
@ -687,6 +713,38 @@ func _selection_modify_submenu_id_pressed(id: int) -> void:
|
|||
modify_selection.node.type = id
|
||||
|
||||
|
||||
func _color_mode_submenu_id_pressed(id: ColorModes) -> void:
|
||||
var project := Global.current_project
|
||||
var old_color_mode := project.color_mode
|
||||
var redo_data := {}
|
||||
var undo_data := {}
|
||||
for cel in project.get_all_pixel_cels():
|
||||
cel.get_image().add_data_to_dictionary(undo_data)
|
||||
# Change the color mode directly before undo/redo in order to affect the images,
|
||||
# so we can store them as redo data.
|
||||
if id == ColorModes.RGBA:
|
||||
project.color_mode = Image.FORMAT_RGBA8
|
||||
else:
|
||||
project.color_mode = Project.INDEXED_MODE
|
||||
for cel in project.get_all_pixel_cels():
|
||||
cel.get_image().add_data_to_dictionary(redo_data)
|
||||
project.undo_redo.create_action("Change color mode")
|
||||
project.undos += 1
|
||||
project.undo_redo.add_do_property(project, "color_mode", project.color_mode)
|
||||
project.undo_redo.add_undo_property(project, "color_mode", old_color_mode)
|
||||
Global.undo_redo_compress_images(redo_data, undo_data, project)
|
||||
project.undo_redo.add_do_method(_check_color_mode_submenu_item.bind(project))
|
||||
project.undo_redo.add_undo_method(_check_color_mode_submenu_item.bind(project))
|
||||
project.undo_redo.add_do_method(Global.undo_or_redo.bind(false))
|
||||
project.undo_redo.add_undo_method(Global.undo_or_redo.bind(true))
|
||||
project.undo_redo.commit_action()
|
||||
|
||||
|
||||
func _check_color_mode_submenu_item(project: Project) -> void:
|
||||
color_mode_submenu.set_item_checked(ColorModes.RGBA, project.color_mode == Image.FORMAT_RGBA8)
|
||||
color_mode_submenu.set_item_checked(ColorModes.INDEXED, project.is_indexed())
|
||||
|
||||
|
||||
func _snap_to_submenu_id_pressed(id: int) -> void:
|
||||
if id == 0:
|
||||
Global.snap_to_rectangular_grid_boundary = !Global.snap_to_rectangular_grid_boundary
|
||||
|
@ -770,6 +828,11 @@ func _toggle_show_pixel_grid() -> void:
|
|||
view_menu.set_item_checked(Global.ViewMenu.SHOW_PIXEL_GRID, Global.draw_pixel_grid)
|
||||
|
||||
|
||||
func _toggle_show_pixel_indices() -> void:
|
||||
Global.show_pixel_indices = !Global.show_pixel_indices
|
||||
view_menu.set_item_checked(Global.ViewMenu.SHOW_PIXEL_INDICES, Global.show_pixel_indices)
|
||||
|
||||
|
||||
func _toggle_show_rulers() -> void:
|
||||
Global.show_rulers = !Global.show_rulers
|
||||
view_menu.set_item_checked(Global.ViewMenu.SHOW_RULERS, Global.show_rulers)
|
||||
|
@ -784,8 +847,12 @@ func _toggle_show_guides() -> void:
|
|||
if guide is SymmetryGuide:
|
||||
if guide.type == Guide.Types.HORIZONTAL:
|
||||
guide.visible = Global.show_x_symmetry_axis and Global.show_guides
|
||||
else:
|
||||
elif guide.type == Guide.Types.VERTICAL:
|
||||
guide.visible = Global.show_y_symmetry_axis and Global.show_guides
|
||||
elif guide.type == Guide.Types.XY:
|
||||
guide.visible = Global.show_xy_symmetry_axis and Global.show_guides
|
||||
elif guide.type == Guide.Types.X_MINUS_Y:
|
||||
guide.visible = Global.show_x_minus_y_symmetry_axis and Global.show_guides
|
||||
|
||||
|
||||
func _toggle_show_mouse_guides() -> void:
|
||||
|
|