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(