1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-02-13 09:13:07 +00:00
Pixelorama/src/Autoload/DrawingAlgos.gd

776 lines
25 KiB
GDScript

extends Node
enum RotationAlgorithm { ROTXEL_SMEAR, CLEANEDGE, OMNISCALE, NNS, NN, ROTXEL, URD }
enum GradientDirection { TOP, BOTTOM, LEFT, RIGHT }
## Continuation from Image.Interpolation
enum Interpolation { SCALE3X = 5, CLEANEDGE = 6, OMNISCALE = 7 }
var blend_layers_shader := preload("res://src/Shaders/BlendLayers.gdshader")
var clean_edge_shader: Shader:
get:
if clean_edge_shader == null:
clean_edge_shader = load("res://src/Shaders/Effects/Rotation/cleanEdge.gdshader")
return clean_edge_shader
var omniscale_shader: Shader:
get:
if omniscale_shader == null:
omniscale_shader = load("res://src/Shaders/Effects/Rotation/OmniScale.gdshader")
return omniscale_shader
var rotxel_shader := preload("res://src/Shaders/Effects/Rotation/SmearRotxel.gdshader")
var nn_shader := preload("res://src/Shaders/Effects/Rotation/NearestNeighbour.gdshader")
## Blends canvas layers into passed image starting from the origin position
func blend_layers(
image: Image,
frame: Frame,
origin := Vector2i.ZERO,
project := Global.current_project,
only_selected_cels := false,
only_selected_layers := false,
) -> void:
var frame_index := project.frames.find(frame)
var previous_ordered_layers: Array[int] = project.ordered_layers
project.order_layers(frame_index)
var textures: Array[Image] = []
# Nx4 texture, where N is the number of layers and the first row are the blend modes,
# the second are the opacities, the third are the origins and the fourth are the
# clipping mask booleans.
var metadata_image := Image.create(project.layers.size(), 4, false, Image.FORMAT_R8)
for i in project.layers.size():
var ordered_index := project.ordered_layers[i]
var layer := project.layers[ordered_index]
var include := true if layer.is_visible_in_hierarchy() else false
if only_selected_cels and include:
var test_array := [frame_index, i]
if not test_array in project.selected_cels:
include = false
if only_selected_layers and include:
var layer_is_selected := false
for selected_cel in project.selected_cels:
if i == selected_cel[1]:
layer_is_selected = true
break
if not layer_is_selected:
include = false
var cel := frame.cels[ordered_index]
if DisplayServer.get_name() == "headless":
blend_layers_headless(image, project, layer, cel, origin)
else:
if layer.is_blender():
var cel_image := (layer as GroupLayer).blend_children(frame)
textures.append(cel_image)
else:
var cel_image := layer.display_effects(cel)
textures.append(cel_image)
if (
layer.is_blended_by_ancestor()
and not only_selected_cels
and not only_selected_layers
):
include = false
set_layer_metadata_image(layer, cel, metadata_image, ordered_index, include)
if DisplayServer.get_name() != "headless":
var texture_array := Texture2DArray.new()
texture_array.create_from_images(textures)
var params := {
"layers": texture_array,
"metadata": ImageTexture.create_from_image(metadata_image),
}
var blended := Image.create(project.size.x, project.size.y, false, image.get_format())
var gen := ShaderImageEffect.new()
gen.generate_image(blended, blend_layers_shader, params, project.size)
image.blend_rect(blended, Rect2i(Vector2i.ZERO, project.size), origin)
# Re-order the layers again to ensure correct canvas drawing
project.ordered_layers = previous_ordered_layers
func set_layer_metadata_image(
layer: BaseLayer, cel: BaseCel, image: Image, index: int, include := true
) -> void:
# Store the blend mode
image.set_pixel(index, 0, Color(layer.blend_mode / 255.0, 0.0, 0.0, 0.0))
# Store the opacity
if layer.is_visible_in_hierarchy() and include:
var opacity := cel.get_final_opacity(layer)
image.set_pixel(index, 1, Color(opacity, 0.0, 0.0, 0.0))
else:
image.set_pixel(index, 1, Color())
# Store the clipping mask boolean
if layer.clipping_mask:
image.set_pixel(index, 3, Color.RED)
else:
image.set_pixel(index, 3, Color.BLACK)
if not include:
# Store a small red value as a way to indicate that this layer should be skipped
# Used for layers such as child layers of a group, so that the group layer itself can
# successfully be used as a clipping mask with the layer below it.
image.set_pixel(index, 3, Color(0.2, 0.0, 0.0, 0.0))
func blend_layers_headless(
image: Image, project: Project, layer: BaseLayer, cel: BaseCel, origin: Vector2i
) -> void:
var opacity := cel.get_final_opacity(layer)
var cel_image := Image.new()
cel_image.copy_from(cel.get_image())
if opacity < 1.0: # If we have cel or layer transparency
for xx in cel_image.get_size().x:
for yy in cel_image.get_size().y:
var pixel_color := cel_image.get_pixel(xx, yy)
pixel_color.a *= opacity
cel_image.set_pixel(xx, yy, pixel_color)
image.blend_rect(cel_image, Rect2i(Vector2i.ZERO, project.size), origin)
## Algorithm based on http://members.chello.at/easyfilter/bresenham.html
func get_ellipse_points(pos: Vector2i, size: Vector2i) -> Array[Vector2i]:
var array: Array[Vector2i] = []
var x0 := pos.x
var x1 := pos.x + (size.x - 1)
var y0 := pos.y
var y1 := pos.y + (size.y - 1)
var a := absi(x1 - x0)
var b := absi(y1 - y0)
var b1 := b & 1
var dx := 4 * (1 - a) * b * b
var dy := 4 * (b1 + 1) * a * a
var err := dx + dy + b1 * a * a
var e2 := 0
if x0 > x1:
x0 = x1
x1 += a
if y0 > y1:
y0 = y1
y0 += (b + 1) / 2
y1 = y0 - b1
a *= 8 * a
b1 = 8 * b * b
while x0 <= x1:
var v1 := Vector2i(x1, y0)
var v2 := Vector2i(x0, y0)
var v3 := Vector2i(x0, y1)
var v4 := Vector2i(x1, y1)
array.append(v1)
array.append(v2)
array.append(v3)
array.append(v4)
e2 = 2 * err
if e2 <= dy:
y0 += 1
y1 -= 1
dy += a
err += dy
if e2 >= dx || 2 * err > dy:
x0 += 1
x1 -= 1
dx += b1
err += dx
while y0 - y1 < b:
var v1 := Vector2i(x0 - 1, y0)
var v2 := Vector2i(x1 + 1, y0)
var v3 := Vector2i(x0 - 1, y1)
var v4 := Vector2i(x1 + 1, y1)
array.append(v1)
array.append(v2)
array.append(v3)
array.append(v4)
y0 += 1
y1 -= 1
return array
func get_ellipse_points_filled(pos: Vector2i, size: Vector2i, thickness := 1) -> Array[Vector2i]:
var offsetted_size := size + Vector2i.ONE * (thickness - 1)
var border := get_ellipse_points(pos, offsetted_size)
var filling: Array[Vector2i] = []
for x in range(1, ceili(offsetted_size.x / 2.0)):
var fill := false
var prev_is_true := false
for y in range(0, ceili(offsetted_size.y / 2.0)):
var top_l_p := Vector2i(x, y)
var bit := border.has(pos + top_l_p)
if bit and not fill:
prev_is_true = true
continue
if not bit and (fill or prev_is_true):
filling.append(pos + top_l_p)
filling.append(pos + Vector2i(x, offsetted_size.y - y - 1))
filling.append(pos + Vector2i(offsetted_size.x - x - 1, y))
filling.append(pos + Vector2i(offsetted_size.x - x - 1, offsetted_size.y - y - 1))
if prev_is_true:
fill = true
prev_is_true = false
elif bit and fill:
break
return border + filling
func scale_3x(sprite: Image, tol := 0.196078) -> Image:
var scaled := Image.create(
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
for x in range(0, sprite.get_width()):
for y in range(0, sprite.get_height()):
var xs := 3 * x
var ys := 3 * y
var a := sprite.get_pixel(maxi(x - 1, 0), maxi(y - 1, 0))
var b := sprite.get_pixel(mini(x, width_minus_one), maxi(y - 1, 0))
var c := sprite.get_pixel(mini(x + 1, width_minus_one), maxi(y - 1, 0))
var d := sprite.get_pixel(maxi(x - 1, 0), mini(y, height_minus_one))
var e := sprite.get_pixel(mini(x, width_minus_one), mini(y, height_minus_one))
var f := sprite.get_pixel(mini(x + 1, width_minus_one), mini(y, height_minus_one))
var g := sprite.get_pixel(maxi(x - 1, 0), mini(y + 1, height_minus_one))
var h := sprite.get_pixel(mini(x, width_minus_one), mini(y + 1, height_minus_one))
var i := sprite.get_pixel(mini(x + 1, width_minus_one), mini(y + 1, height_minus_one))
var db: bool = similar_colors(d, b, tol)
var dh: bool = similar_colors(d, h, tol)
var bf: bool = similar_colors(f, b, tol)
var ec: bool = similar_colors(e, c, tol)
var ea: bool = similar_colors(e, a, tol)
var fh: bool = similar_colors(f, h, tol)
var eg: bool = similar_colors(e, g, tol)
var ei: bool = similar_colors(e, i, tol)
scaled.set_pixel(maxi(xs - 1, 0), maxi(ys - 1, 0), d if (db and !dh and !bf) else e)
scaled.set_pixel(
xs,
maxi(ys - 1, 0),
b if (db and !dh and !bf and !ec) or (bf and !db and !fh and !ea) else e
)
scaled.set_pixel(xs + 1, maxi(ys - 1, 0), f if (bf and !db and !fh) else e)
scaled.set_pixel(
maxi(xs - 1, 0),
ys,
d if (dh and !fh and !db and !ea) or (db and !dh and !bf and !eg) else e
)
scaled.set_pixel(xs, ys, e)
scaled.set_pixel(
xs + 1, ys, f if (bf and !db and !fh and !ei) or (fh and !bf and !dh and !ec) else e
)
scaled.set_pixel(maxi(xs - 1, 0), ys + 1, d if (dh and !fh and !db) else e)
scaled.set_pixel(
xs, ys + 1, h if (fh and !bf and !dh and !eg) or (dh and !fh and !db and !ei) else e
)
scaled.set_pixel(xs + 1, ys + 1, f if (fh and !bf and !dh) else e)
return scaled
func transform(
image: Image, params: Dictionary, algorithm: RotationAlgorithm, expand := false
) -> void:
var transformation_matrix: Transform2D = params.get("transformation_matrix", Transform2D())
var pivot: Vector2 = params.get("pivot", image.get_size() / 2)
if expand:
var image_rect := Rect2(Vector2.ZERO, image.get_size())
var new_image_rect := image_rect * transformation_matrix as Rect2i
var new_image_size := new_image_rect.size
if image.get_size() != new_image_size:
pivot = new_image_size / 2 - (Vector2i(pivot) - image.get_size() / 2)
var tmp_image := Image.create_empty(
new_image_size.x, new_image_size.y, image.has_mipmaps(), image.get_format()
)
tmp_image.blit_rect(image, image_rect, (new_image_size - image.get_size()) / 2)
image.copy_from(tmp_image)
if type_is_shader(algorithm):
params["pivot"] = pivot / Vector2(image.get_size())
var shader := rotxel_shader
match algorithm:
RotationAlgorithm.CLEANEDGE:
shader = clean_edge_shader
RotationAlgorithm.OMNISCALE:
shader = omniscale_shader
RotationAlgorithm.NNS:
shader = nn_shader
var gen := ShaderImageEffect.new()
gen.generate_image(image, shader, params, image.get_size())
else:
var angle := transformation_matrix.get_rotation()
match algorithm:
RotationAlgorithm.ROTXEL:
rotxel(image, angle, pivot)
RotationAlgorithm.NN:
nn_rotate(image, angle, pivot)
RotationAlgorithm.URD:
fake_rotsprite(image, angle, pivot)
func type_is_shader(algorithm: RotationAlgorithm) -> bool:
return algorithm <= RotationAlgorithm.NNS
func transform_rectangle(rect: Rect2, matrix: Transform2D, pivot := rect.size / 2) -> Rect2:
var offset_rect := rect
var offset_pos := -pivot
offset_rect.position = offset_pos
offset_rect = offset_rect * matrix
offset_rect.position = rect.position + offset_rect.position - offset_pos
return offset_rect
func rotxel(sprite: Image, angle: float, pivot: Vector2) -> void:
if is_zero_approx(angle) or is_equal_approx(angle, TAU):
return
if is_equal_approx(angle, PI / 2.0) or is_equal_approx(angle, 3.0 * PI / 2.0):
nn_rotate(sprite, angle, pivot)
return
if is_equal_approx(angle, PI):
sprite.rotate_180()
return
var aux := Image.new()
aux.copy_from(sprite)
var ox: int
var oy: int
for x in sprite.get_size().x:
for y in sprite.get_size().y:
var dx := 3 * (x - pivot.x)
var dy := 3 * (y - pivot.y)
var found_pixel := false
for k in range(9):
var modk := -1 + k % 3
var divk := -1 + int(k / 3)
var dir := atan2(dy + divk, dx + modk)
var mag := sqrt(pow(dx + modk, 2) + pow(dy + divk, 2))
dir -= angle
ox = roundi(pivot.x * 3 + 1 + mag * cos(dir))
oy = roundi(pivot.y * 3 + 1 + mag * sin(dir))
if sprite.get_width() % 2 != 0:
ox += 1
oy += 1
if (
ox >= 0
&& ox < sprite.get_width() * 3
&& oy >= 0
&& oy < sprite.get_height() * 3
):
found_pixel = true
break
if !found_pixel:
sprite.set_pixel(x, y, Color(0, 0, 0, 0))
continue
var fil: int = oy % 3
var col: int = ox % 3
var index: int = col + 3 * fil
ox = roundi((ox - 1) / 3.0)
oy = roundi((oy - 1) / 3.0)
var p: Color
if ox == 0 || ox == sprite.get_width() - 1 || oy == 0 || oy == sprite.get_height() - 1:
p = aux.get_pixel(ox, oy)
else:
var a := aux.get_pixel(ox - 1, oy - 1)
var b := aux.get_pixel(ox, oy - 1)
var c := aux.get_pixel(ox + 1, oy - 1)
var d := aux.get_pixel(ox - 1, oy)
var e := aux.get_pixel(ox, oy)
var f := aux.get_pixel(ox + 1, oy)
var g := aux.get_pixel(ox - 1, oy + 1)
var h := aux.get_pixel(ox, oy + 1)
var i := aux.get_pixel(ox + 1, oy + 1)
match index:
0:
p = (
d
if (
similar_colors(d, b)
&& !similar_colors(d, h)
&& !similar_colors(b, f)
)
else e
)
1:
p = (
b
if (
(
similar_colors(d, b)
&& !similar_colors(d, h)
&& !similar_colors(b, f)
&& !similar_colors(e, c)
)
|| (
similar_colors(b, f)
&& !similar_colors(d, b)
&& !similar_colors(f, h)
&& !similar_colors(e, a)
)
)
else e
)
2:
p = (
f
if (
similar_colors(b, f)
&& !similar_colors(d, b)
&& !similar_colors(f, h)
)
else e
)
3:
p = (
d
if (
(
similar_colors(d, h)
&& !similar_colors(f, h)
&& !similar_colors(d, b)
&& !similar_colors(e, a)
)
|| (
similar_colors(d, b)
&& !similar_colors(d, h)
&& !similar_colors(b, f)
&& !similar_colors(e, g)
)
)
else e
)
4:
p = e
5:
p = (
f
if (
(
similar_colors(b, f)
&& !similar_colors(d, b)
&& !similar_colors(f, h)
&& !similar_colors(e, i)
)
|| (
similar_colors(f, h)
&& !similar_colors(b, f)
&& !similar_colors(d, h)
&& !similar_colors(e, c)
)
)
else e
)
6:
p = (
d
if (
similar_colors(d, h)
&& !similar_colors(f, h)
&& !similar_colors(d, b)
)
else e
)
7:
p = (
h
if (
(
similar_colors(f, h)
&& !similar_colors(f, b)
&& !similar_colors(d, h)
&& !similar_colors(e, g)
)
|| (
similar_colors(d, h)
&& !similar_colors(f, h)
&& !similar_colors(d, b)
&& !similar_colors(e, i)
)
)
else e
)
8:
p = (
f
if (
similar_colors(f, h)
&& !similar_colors(f, b)
&& !similar_colors(d, h)
)
else e
)
sprite.set_pixel(x, y, p)
func fake_rotsprite(sprite: Image, angle: float, pivot: Vector2) -> void:
if is_zero_approx(angle) or is_equal_approx(angle, TAU):
return
if is_equal_approx(angle, PI / 2.0) or is_equal_approx(angle, 3.0 * PI / 2.0):
nn_rotate(sprite, angle, pivot)
return
if is_equal_approx(angle, PI):
sprite.rotate_180()
return
var selected_sprite := scale_3x(sprite)
nn_rotate(selected_sprite, angle, pivot * 3)
selected_sprite.resize(
selected_sprite.get_width() / 3, selected_sprite.get_height() / 3, Image.INTERPOLATE_NEAREST
)
sprite.blit_rect(selected_sprite, Rect2(Vector2.ZERO, selected_sprite.get_size()), Vector2.ZERO)
func nn_rotate(sprite: Image, angle: float, pivot: Vector2) -> void:
if is_zero_approx(angle) or is_equal_approx(angle, TAU):
return
if is_equal_approx(angle, PI):
sprite.rotate_180()
return
var aux := Image.new()
aux.copy_from(sprite)
var angle_sin := sin(angle)
var angle_cos := cos(angle)
for x in range(sprite.get_width()):
for y in range(sprite.get_height()):
var ox := (x - pivot.x) * angle_cos + (y - pivot.y) * angle_sin + pivot.x
var oy := -(x - pivot.x) * angle_sin + (y - pivot.y) * angle_cos + pivot.y
if ox >= 0 && ox < sprite.get_width() && oy >= 0 && oy < sprite.get_height():
sprite.set_pixel(x, y, aux.get_pixel(ox, oy))
else:
sprite.set_pixel(x, y, Color(0, 0, 0, 0))
## Compares two colors, and returns [code]true[/code] if the difference of these colors is
## less or equal to the tolerance [param tol]. [param tol] is in the range of 0-1.
func similar_colors(c1: Color, c2: Color, tol := 0.392157) -> bool:
return (
absf(c1.r - c2.r) <= tol
&& absf(c1.g - c2.g) <= tol
&& absf(c1.b - c2.b) <= tol
&& absf(c1.a - c2.a) <= tol
)
# Image effects
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:
# Find used rect of the current frame (across all of the layers)
var used_rect := Rect2i()
for cel in project.frames[frame].cels:
if not cel is PixelCel:
continue
var cel_rect := cel.get_image().get_used_rect()
if cel_rect.has_area():
used_rect = used_rect.merge(cel_rect) if used_rect.has_area() else cel_rect
if not used_rect.has_area():
continue
# Now apply centering
var offset: Vector2i = (0.5 * (project.size - used_rect.size)).floor()
for cel in project.frames[frame].cels:
if not cel is PixelCel:
continue
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)
if cel is CelTileMap:
(cel as CelTileMap).serialize_undo_data_source_image(centered, redo_data, undo_data)
centered.add_data_to_dictionary(redo_data, cel_image)
cel_image.add_data_to_dictionary(undo_data)
project.deserialize_cel_undo_data(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_project(width: int, height: int, interpolation: int) -> void:
var redo_data := {}
var undo_data := {}
for cel in Global.current_project.get_all_pixel_cels():
if not cel is PixelCel:
continue
var cel_image := (cel as PixelCel).get_image()
var sprite := _resize_image(cel_image, width, height, interpolation) as ImageExtended
if cel is CelTileMap:
(cel as CelTileMap).serialize_undo_data_source_image(sprite, redo_data, undo_data)
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:
return
Global.canvas.selection.transform_content_confirm()
var redo_data := {}
var undo_data := {}
var rect: Rect2i = Global.canvas.selection.big_bounding_rectangle
# Loop through all the cels to crop them
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)
if cel is CelTileMap:
(cel as CelTileMap).serialize_undo_data_source_image(cropped, redo_data, undo_data)
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)
## Automatically makes the project smaller by looping through all of the cels and
## trimming out the pixels that are transparent in all cels.
func crop_to_content() -> void:
Global.canvas.selection.transform_content_confirm()
var used_rect := Rect2i()
for cel in Global.current_project.get_all_pixel_cels():
if not cel is PixelCel:
continue
var cel_used_rect := cel.get_image().get_used_rect()
if cel_used_rect == Rect2i(0, 0, 0, 0): # If the cel has no content
continue
if used_rect == Rect2i(0, 0, 0, 0): # If we still haven't found the first cel with content
used_rect = cel_used_rect
else:
used_rect = used_rect.merge(cel_used_rect)
# If no layer has any content, just return
if used_rect == Rect2i(0, 0, 0, 0):
return
var width := used_rect.size.x
var height := used_rect.size.y
var redo_data := {}
var undo_data := {}
# Loop through all the cels to trim them
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)
if cel is CelTileMap:
(cel as CelTileMap).serialize_undo_data_source_image(cropped, redo_data, undo_data)
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)
func resize_canvas(width: int, height: int, offset_x: int, offset_y: int) -> void:
var redo_data := {}
var undo_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()
if cel is CelTileMap:
(cel as CelTileMap).serialize_undo_data_source_image(resized, redo_data, undo_data)
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)
func general_do_and_undo_scale(
width: int, height: int, redo_data: Dictionary, undo_data: Dictionary
) -> void:
var project := Global.current_project
var size := Vector2i(width, height)
var x_ratio := float(project.size.x) / width
var y_ratio := float(project.size.y) / height
var selection_map_copy := SelectionMap.new()
selection_map_copy.copy_from(project.selection_map)
selection_map_copy.crop(size.x, size.y)
redo_data[project.selection_map] = selection_map_copy.data
undo_data[project.selection_map] = project.selection_map.data
var new_x_symmetry_point := project.x_symmetry_point / x_ratio
var new_y_symmetry_point := project.y_symmetry_point / y_ratio
var new_x_symmetry_axis_points := project.x_symmetry_axis.points
var new_y_symmetry_axis_points := project.y_symmetry_axis.points
new_x_symmetry_axis_points[0].y /= y_ratio
new_x_symmetry_axis_points[1].y /= y_ratio
new_y_symmetry_axis_points[0].x /= x_ratio
new_y_symmetry_axis_points[1].x /= x_ratio
project.undos += 1
project.undo_redo.create_action("Scale")
project.undo_redo.add_do_property(project, "size", size)
project.undo_redo.add_do_property(project, "x_symmetry_point", new_x_symmetry_point)
project.undo_redo.add_do_property(project, "y_symmetry_point", new_y_symmetry_point)
project.undo_redo.add_do_property(project.x_symmetry_axis, "points", new_x_symmetry_axis_points)
project.undo_redo.add_do_property(project.y_symmetry_axis, "points", new_y_symmetry_axis_points)
project.deserialize_cel_undo_data(redo_data, undo_data)
project.undo_redo.add_undo_property(project, "size", project.size)
project.undo_redo.add_undo_property(project, "x_symmetry_point", project.x_symmetry_point)
project.undo_redo.add_undo_property(project, "y_symmetry_point", project.y_symmetry_point)
project.undo_redo.add_undo_property(
project.x_symmetry_axis, "points", project.x_symmetry_axis.points
)
project.undo_redo.add_undo_property(
project.y_symmetry_axis, "points", project.y_symmetry_axis.points
)
project.undo_redo.add_undo_method(Global.undo_or_redo.bind(true))
project.undo_redo.add_do_method(Global.undo_or_redo.bind(false))
project.undo_redo.commit_action()