From e0bd25c3745d83e75997d8695451a739c5596a5a Mon Sep 17 00:00:00 2001 From: Emmanouil Papadeas Date: Thu, 16 Nov 2023 20:59:48 +0200 Subject: [PATCH] [Undo/Redo] Compress most of the images stored in memory Combines e62548517f4d976bdcf8e6ade3c7fa684ae1d865, 4f5f37a52286cb68899e29d6505ebe065d4880f5, e22794e61180a0ad7ffc018758b0308f87713365, 9279a8e0ab8eb06c392e61badb064b9984b4a4f3 and 2c5ece53ddcbc091f0421981d401633b55098635 from the master branch. --- src/Autoload/DrawingAlgos.gd | 25 ++++------ src/Autoload/Global.gd | 46 +++++++++++++++++-- src/Autoload/OpenSave.gd | 5 +- src/Classes/ImageEffect.gd | 5 +- src/Main.tscn | 2 +- src/Tools/Bucket.gd | 6 +-- src/Tools/Draw.gd | 16 +------ src/Tools/Move.gd | 46 ++----------------- .../Dialogs/ImageEffects/FlipImageDialog.gd | 37 +++++++-------- src/UI/Timeline/AnimationTimeline.gd | 7 ++- 10 files changed, 86 insertions(+), 109 deletions(-) diff --git a/src/Autoload/DrawingAlgos.gd b/src/Autoload/DrawingAlgos.gd index 7ed930d5f..96faace65 100644 --- a/src/Autoload/DrawingAlgos.gd +++ b/src/Autoload/DrawingAlgos.gd @@ -432,10 +432,11 @@ func scale_image(width: int, height: int, interpolation: int) -> void: for f in Global.current_project.frames: for i in range(f.cels.size() - 1, -1, -1): - if not f.cels[i] is PixelCel: + var cel: BaseCel = f.cels[i] + if not cel is PixelCel: continue var sprite := Image.new() - sprite.copy_from(f.cels[i].image) + sprite.copy_from(cel.image) if interpolation == Interpolation.SCALE3X: var times: Vector2 = Vector2( ceil(width / (3.0 * sprite.get_width())), @@ -454,10 +455,7 @@ func scale_image(width: int, height: int, interpolation: int) -> void: gen.generate_image(sprite, omniscale_shader, params, Vector2(width, height)) else: sprite.resize(width, height, interpolation) - Global.current_project.undo_redo.add_do_property(f.cels[i].image, "data", sprite.data) - Global.current_project.undo_redo.add_undo_property( - f.cels[i].image, "data", f.cels[i].image.data - ) + Global.undo_redo_compress_images({cel.image: sprite.data}, {cel.image: cel.image.data}) general_undo_scale() @@ -487,8 +485,7 @@ func center(indices: Array) -> void: var sprite := Image.new() sprite.create(project.size.x, project.size.y, false, Image.FORMAT_RGBA8) sprite.blend_rect(cel.image, used_rect, offset) - project.undo_redo.add_do_property(cel.image, "data", sprite.data) - project.undo_redo.add_undo_property(cel.image, "data", cel.image.data) + Global.undo_redo_compress_images({cel.image: sprite.data}, {cel.image: cel.image.data}) project.undo_redo.add_undo_method(Global, "undo_or_redo", true) project.undo_redo.add_do_method(Global, "undo_or_redo", false) project.undo_redo.commit_action() @@ -524,8 +521,7 @@ func crop_image() -> void: if not cel is PixelCel: continue var sprite: Image = cel.image.get_rect(used_rect) - Global.current_project.undo_redo.add_do_property(cel.image, "data", sprite.data) - Global.current_project.undo_redo.add_undo_property(cel.image, "data", cel.image.data) + Global.undo_redo_compress_images({cel.image: sprite.data}, {cel.image: cel.image.data}) general_undo_scale() @@ -533,18 +529,17 @@ func crop_image() -> void: func resize_canvas(width: int, height: int, offset_x: int, offset_y: int) -> void: general_do_scale(width, height) for f in Global.current_project.frames: - for c in f.cels: - if not c is PixelCel: + for cel in f.cels: + if not cel is PixelCel: continue var sprite := Image.new() sprite.create(width, height, false, Image.FORMAT_RGBA8) sprite.blend_rect( - c.image, + cel.image, Rect2(Vector2.ZERO, Global.current_project.size), Vector2(offset_x, offset_y) ) - Global.current_project.undo_redo.add_do_property(c.image, "data", sprite.data) - Global.current_project.undo_redo.add_undo_property(c.image, "data", c.image.data) + Global.undo_redo_compress_images({cel.image: sprite.data}, {cel.image: cel.image.data}) general_undo_scale() diff --git a/src/Autoload/Global.gd b/src/Autoload/Global.gd index 0e1d0a470..a4b6ac133 100644 --- a/src/Autoload/Global.gd +++ b/src/Autoload/Global.gd @@ -480,7 +480,8 @@ func undo_or_redo( "Center Frames", "Merge Layer", "Link Cel", - "Unlink Cel" + "Unlink Cel", + "Replaced Cel" ] ): if layer_index > -1 and frame_index > -1: @@ -619,5 +620,44 @@ func convert_dictionary_values(dict: Dictionary) -> void: dict[key] = str2var("Vector3" + dict[key]) -func undo_redo_draw_op(image: Object, compressed_image_data: Dictionary, buffer_size: int) -> void: - image["data"]["data"] = compressed_image_data["data"].decompress(buffer_size) +func undo_redo_compress_images(redo_data: Dictionary, undo_data: Dictionary, project := current_project) -> void: + for image in redo_data: + if not image is Image: + continue + var new_image: Dictionary = redo_data[image] + var new_size := Vector2(new_image["width"], new_image["height"]) + var buffer_size: int = new_image["data"].size() + var compressed_data: PoolByteArray = new_image["data"].compress() + project.undo_redo.add_do_method( + self, "undo_redo_draw_op", image, new_size, compressed_data, buffer_size + ) + image.unlock() + for image in undo_data: + if not image is Image: + continue + var new_image: Dictionary = undo_data[image] + var new_size := Vector2(new_image["width"], new_image["height"]) + var buffer_size: int = new_image["data"].size() + var compressed_data: PoolByteArray = new_image["data"].compress() + project.undo_redo.add_undo_method( + self, "undo_redo_draw_op", image, new_size, compressed_data, buffer_size + ) + + +func undo_redo_draw_op( + image: Image, new_size: Vector2, compressed_image_data: PoolByteArray, buffer_size: int +) -> void: + var decompressed := compressed_image_data.decompress(buffer_size) + image.crop(new_size.x, new_size.y) + image.data["data"] = decompressed + + + +## Used by the Move tool for undo/redo, moves all of the Images in the images array +## by diff pixels. +func undo_redo_move(diff: Vector2, images: Array) -> void: + 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, Rect2(Vector2.ZERO, image.get_size()), diff) diff --git a/src/Autoload/OpenSave.gd b/src/Autoload/OpenSave.gd index 0624c0b7a..e95695f81 100644 --- a/src/Autoload/OpenSave.gd +++ b/src/Autoload/OpenSave.gd @@ -544,8 +544,9 @@ func open_image_at_cel(image: Image, layer_index := 0, frame_index := 0) -> void cel_image.create(project_width, project_height, false, Image.FORMAT_RGBA8) cel_image.blit_rect(image, Rect2(Vector2.ZERO, image.get_size()), Vector2.ZERO) var cel: PixelCel = project.frames[i].cels[layer_index] - project.undo_redo.add_do_property(cel, "image", cel_image) - project.undo_redo.add_undo_property(cel, "image", cel.image) + Global.undo_redo_compress_images( + {cel.image: cel_image.data}, {cel.image: cel.image.data}, project + ) project.undo_redo.add_do_property(project, "selected_cels", []) project.undo_redo.add_do_method(project, "change_cel", frame_index, layer_index) diff --git a/src/Classes/ImageEffect.gd b/src/Classes/ImageEffect.gd index d7a2f9310..32641a4c3 100644 --- a/src/Classes/ImageEffect.gd +++ b/src/Classes/ImageEffect.gd @@ -152,10 +152,7 @@ func _commit_undo(action: String, undo_data: Dictionary, project: Project) -> vo var redo_data := _get_undo_data(project) project.undos += 1 project.undo_redo.create_action(action) - for image in redo_data: - project.undo_redo.add_do_property(image, "data", redo_data[image]) - for image in undo_data: - project.undo_redo.add_undo_property(image, "data", undo_data[image]) + Global.undo_redo_compress_images(redo_data, undo_data, project) project.undo_redo.add_do_method(Global, "undo_or_redo", false, -1, -1, project) project.undo_redo.add_undo_method(Global, "undo_or_redo", true, -1, -1, project) project.undo_redo.commit_action() diff --git a/src/Main.tscn b/src/Main.tscn index 9987e7039..a65a16884 100644 --- a/src/Main.tscn +++ b/src/Main.tscn @@ -156,5 +156,5 @@ visible = false [connection signal="custom_action" from="Dialogs/QuitAndSaveDialog" to="." method="_on_QuitAndSaveDialog_custom_action"] [connection signal="popup_hide" from="Dialogs/QuitAndSaveDialog" to="." method="_can_draw_true"] [connection signal="popup_hide" from="Dialogs/ErrorDialog" to="." method="_can_draw_true"] -[connection signal="popup_hide" from="Dialogs/BackupConfirmation" to="." method="_on_BackupConfirmation_popup_hide"] [connection signal="popup_hide" from="Dialogs/BackupConfirmation" to="." method="_can_draw_true"] +[connection signal="popup_hide" from="Dialogs/BackupConfirmation" to="." method="_on_BackupConfirmation_popup_hide"] diff --git a/src/Tools/Bucket.gd b/src/Tools/Bucket.gd index 71dd4b10b..86a25d523 100644 --- a/src/Tools/Bucket.gd +++ b/src/Tools/Bucket.gd @@ -485,11 +485,7 @@ func commit_undo(action: String, undo_data: Dictionary) -> void: project.undos += 1 project.undo_redo.create_action(action) - for image in redo_data: - project.undo_redo.add_do_property(image, "data", redo_data[image]) - image.unlock() - for image in undo_data: - project.undo_redo.add_undo_property(image, "data", undo_data[image]) + Global.undo_redo_compress_images(redo_data, undo_data, project) project.undo_redo.add_do_method(Global, "undo_or_redo", false, frame, layer) project.undo_redo.add_undo_method(Global, "undo_or_redo", true, frame, layer) project.undo_redo.commit_action() diff --git a/src/Tools/Draw.gd b/src/Tools/Draw.gd index 1a6f09a9c..99f9475ae 100644 --- a/src/Tools/Draw.gd +++ b/src/Tools/Draw.gd @@ -209,21 +209,7 @@ func commit_undo() -> void: layer = project.current_layer project.undos += 1 - for image in redo_data: - var compressed_data = redo_data[image] - var buffer_size = compressed_data["data"].size() - compressed_data["data"] = compressed_data["data"].compress() - project.undo_redo.add_do_method( - Global, "undo_redo_draw_op", image, compressed_data, buffer_size - ) - image.unlock() - for image in _undo_data: - var compressed_data = _undo_data[image] - var buffer_size = compressed_data["data"].size() - compressed_data["data"] = compressed_data["data"].compress() - project.undo_redo.add_undo_method( - Global, "undo_redo_draw_op", image, compressed_data, buffer_size - ) + Global.undo_redo_compress_images(redo_data, _undo_data, project) project.undo_redo.add_do_method(Global, "undo_or_redo", false, frame, layer) project.undo_redo.add_undo_method(Global, "undo_or_redo", true, frame, layer) project.undo_redo.commit_action() diff --git a/src/Tools/Move.gd b/src/Tools/Move.gd index 4563ec0aa..1e8e80ab8 100644 --- a/src/Tools/Move.gd +++ b/src/Tools/Move.gd @@ -1,6 +1,5 @@ extends BaseTool -var _undo_data := {} var _start_pos: Vector2 var _offset: Vector2 @@ -42,7 +41,6 @@ func draw_start(position: Vector2) -> void: return _start_pos = position _offset = position - _undo_data = _get_undo_data() if Global.current_project.has_selection: selection_node.transform_content_start() _content_transformation_check = selection_node.is_moving_content @@ -83,14 +81,7 @@ func draw_end(position: Vector2) -> void: else: var pixel_diff: Vector2 = position - _start_pos Global.canvas.move_preview_location = Vector2.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, Rect2(Vector2.ZERO, project.size), pixel_diff) - - commit_undo("Draw") + commit_undo("Draw", pixel_diff) _start_pos = Vector2.INF _snap_to_grid = false @@ -123,45 +114,18 @@ func _snap_position(position: Vector2) -> Vector2: return position -func commit_undo(action: String) -> void: - var redo_data := _get_undo_data() +func commit_undo(action: String, diff: Vector2) -> void: var project: Project = Global.current_project var frame := -1 var layer := -1 if Global.animation_timer.is_stopped() and project.selected_cels.size() == 1: frame = project.current_frame layer = project.current_layer - + var images := _get_selected_draw_images() project.undos += 1 project.undo_redo.create_action(action) - for image in redo_data: - project.undo_redo.add_do_property(image, "data", redo_data[image]) - image.unlock() - for image in _undo_data: - project.undo_redo.add_undo_property(image, "data", _undo_data[image]) + project.undo_redo.add_do_method(Global, "undo_redo_move", diff, images) project.undo_redo.add_do_method(Global, "undo_or_redo", false, frame, layer) + project.undo_redo.add_undo_method(Global, "undo_redo_move", -diff, images) project.undo_redo.add_undo_method(Global, "undo_or_redo", true, frame, layer) project.undo_redo.commit_action() - - _undo_data.clear() - - -func _get_undo_data() -> Dictionary: - var data := {} - var project: Project = Global.current_project - var cels := [] # Array of Cels - if Global.animation_timer.is_stopped(): - for cel_index in project.selected_cels: - cels.append(project.frames[cel_index[0]].cels[cel_index[1]]) - else: - for frame in project.frames: - var cel: PixelCel = frame.cels[project.current_layer] - cels.append(cel) - for cel in cels: - if not cel is PixelCel: - continue - var image: Image = cel.image - image.unlock() - data[image] = image.data - image.lock() - return data diff --git a/src/UI/Dialogs/ImageEffects/FlipImageDialog.gd b/src/UI/Dialogs/ImageEffects/FlipImageDialog.gd index aee63a937..4040e91ee 100644 --- a/src/UI/Dialogs/ImageEffects/FlipImageDialog.gd +++ b/src/UI/Dialogs/ImageEffects/FlipImageDialog.gd @@ -55,39 +55,34 @@ func _commit_undo(action: String, undo_data: Dictionary, project: Project) -> vo var redo_data := _get_undo_data(project) project.undos += 1 project.undo_redo.create_action(action) - project.undo_redo.add_do_property(project, "selection_map", redo_data["selection_map"]) - project.undo_redo.add_do_property(project, "selection_offset", redo_data["outline_offset"]) - project.undo_redo.add_undo_property(project, "selection_map", undo_data["selection_map"]) - project.undo_redo.add_undo_property(project, "selection_offset", undo_data["outline_offset"]) - - for image in redo_data: - if not image is Image: - continue - project.undo_redo.add_do_property(image, "data", redo_data[image]) - image.unlock() - for image in undo_data: - if not image is Image: - continue - project.undo_redo.add_undo_property(image, "data", undo_data[image]) + Global.undo_redo_compress_images(redo_data, undo_data, project) + if redo_data.has("outline_offset"): + project.undo_redo.add_do_property(project, "selection_map", redo_data["selection_map"]) + project.undo_redo.add_do_property(project, "selection_offset", redo_data["outline_offset"]) + project.undo_redo.add_undo_property(project, "selection_map", undo_data["selection_map"]) + project.undo_redo.add_undo_property( + project, "selection_offset", undo_data["outline_offset"] + ) + project.undo_redo.add_do_method(project, "selection_map_changed") + project.undo_redo.add_undo_method(project, "selection_map_changed") project.undo_redo.add_do_method(Global, "undo_or_redo", false, -1, -1, project) - project.undo_redo.add_do_method(project, "selection_map_changed") project.undo_redo.add_undo_method(Global, "undo_or_redo", true, -1, -1, project) - project.undo_redo.add_undo_method(project, "selection_map_changed") project.undo_redo.commit_action() func _get_undo_data(project: Project) -> Dictionary: - var bitmap_image := SelectionMap.new() - bitmap_image.copy_from(project.selection_map) + var affect_selection := selection_checkbox.pressed and project.has_selection var data := {} - data["selection_map"] = bitmap_image - data["outline_offset"] = project.selection_offset + if affect_selection: + var bitmap_image := SelectionMap.new() + bitmap_image.copy_from(project.selection_map) + data["selection_map"] = bitmap_image + data["outline_offset"] = project.selection_offset var images := _get_selected_draw_images(project) for image in images: image.unlock() data[image] = image.data - return data diff --git a/src/UI/Timeline/AnimationTimeline.gd b/src/UI/Timeline/AnimationTimeline.gd index cde038f23..3d4d40266 100644 --- a/src/UI/Timeline/AnimationTimeline.gd +++ b/src/UI/Timeline/AnimationTimeline.gd @@ -870,8 +870,11 @@ func _on_MergeDownLayer_pressed() -> void: project.undo_redo.add_do_property(bottom_cel, "image", bottom_image) project.undo_redo.add_undo_property(bottom_cel, "image", bottom_cel.image) else: - project.undo_redo.add_do_property(bottom_cel.image, "data", bottom_image.data) - project.undo_redo.add_undo_property(bottom_cel.image, "data", bottom_cel.image.data) + Global.undo_redo_compress_images( + {bottom_cel.image: bottom_image.data}, + {bottom_cel.image: bottom_cel.image.data}, + project + ) project.undo_redo.add_do_method(project, "remove_layers", [top_layer.index]) project.undo_redo.add_undo_method(