diff --git a/src/Classes/Cels/CelTileMap.gd b/src/Classes/Cels/CelTileMap.gd index 5ab399658..3b91c5008 100644 --- a/src/Classes/Cels/CelTileMap.gd +++ b/src/Classes/Cels/CelTileMap.gd @@ -187,6 +187,125 @@ func transform_tile( return transformed_tile +## Given a [param selection_map] and a [param selection_rect], +## the method finds the cells that are currently selected and returns them +## in the form of a 2D array that contains the serialiazed data +##of the selected cells in the form of [Dictionary]. +func get_selected_cells(selection_map: SelectionMap, selection_rect: Rect2i) -> Array[Array]: + var selected_cells: Array[Array] = [] + for x in range(0, selection_rect.size.x, tileset.tile_size.x): + selected_cells.append([]) + for y in range(0, selection_rect.size.y, tileset.tile_size.y): + var pos := Vector2i(x, y) + selection_rect.position + var x_index := x / tileset.tile_size.x + if selection_map.is_pixel_selected(pos): + var cell_pos := get_cell_position(pos) + selected_cells[x_index].append(cells[cell_pos].serialize()) + else: + # If it's not selected, append the transparent tile 0. + selected_cells[x_index].append( + {"index": 0, "flip_h": false, "flip_v": false, "transpose": false} + ) + return selected_cells + + +## Resizes [param selected_indices], which is an array of arrays of [Dictionary], +## to [param horizontal_size] and [param vertical_size]. +## This method is used when resizing a selection and draw tiles mode is enabled. +func resize_selection( + selected_cells: Array[Array], horizontal_size: int, vertical_size: int +) -> Array[Array]: + var resized_cells: Array[Array] = [] + var current_columns := selected_cells.size() + if current_columns == 0: + return resized_cells + var current_rows := selected_cells[0].size() + if current_rows == 0: + return resized_cells + resized_cells.resize(horizontal_size) + for x in horizontal_size: + resized_cells[x] = [] + resized_cells[x].resize(vertical_size) + var column_middles := current_columns - 2 + if current_columns == 1: + for x in horizontal_size: + _resize_rows(selected_cells[0], resized_cells[x], current_rows, vertical_size) + else: + for x in horizontal_size: + if x == 0: + _resize_rows(selected_cells[0], resized_cells[x], current_rows, vertical_size) + elif x == horizontal_size - 1: + _resize_rows(selected_cells[-1], resized_cells[x], current_rows, vertical_size) + else: + if x < current_columns - 1: + _resize_rows(selected_cells[x], resized_cells[x], current_rows, vertical_size) + else: + if column_middles == 0: + _resize_rows( + selected_cells[-1], resized_cells[x], current_rows, vertical_size + ) + else: + var x_index := x - (column_middles * ((x - 1) / column_middles)) + _resize_rows( + selected_cells[x_index], resized_cells[x], current_rows, vertical_size + ) + return resized_cells + + +## Helper method of [method resize_selection]. +func _resize_rows( + selected_cells: Array, resized_cells: Array, current_rows: int, vertical_size: int +) -> void: + var row_middles := current_rows - 2 + if current_rows == 1: + for y in vertical_size: + resized_cells[y] = selected_cells[0] + else: + for y in vertical_size: + if y == 0: + resized_cells[y] = selected_cells[0] + elif y == vertical_size - 1: + resized_cells[y] = selected_cells[-1] + else: + if y < current_rows - 1: + resized_cells[y] = selected_cells[y] + else: + if row_middles == 0: + resized_cells[y] = selected_cells[-1] + else: + var y_index := y - (row_middles * ((y - 1) / row_middles)) + resized_cells[y] = selected_cells[y_index] + + +## Applies the [param selected_cells] data to [param target_image] data, +## offset by [param selection_rect]. The target image needs to be resized first. +## This method is used when resizing a selection and draw tiles mode is enabled. +func apply_resizing_to_image( + target_image: Image, selected_cells: Array[Array], selection_rect: Rect2i +) -> void: + for x in selected_cells.size(): + for y in selected_cells[x].size(): + var pos := Vector2i(x, y) * tileset.tile_size + selection_rect.position + var cell_pos := get_cell_position(pos) + var coords := get_cell_coords_in_image(cell_pos) - selection_rect.position + var rect := Rect2i(coords, tileset.tile_size) + var image_portion := target_image.get_region(rect) + var cell_data := Cell.new() + cell_data.deserialize(selected_cells[x][y]) + var index := cell_data.index + if index >= tileset.tiles.size(): + index = 0 + var current_tile := tileset.tiles[index].image + var transformed_tile := transform_tile( + current_tile, cell_data.flip_h, cell_data.flip_v, cell_data.transpose + ) + if image_portion.get_data() != transformed_tile.get_data(): + var tile_size := transformed_tile.get_size() + target_image.blit_rect(transformed_tile, Rect2i(Vector2i.ZERO, tile_size), coords) + if target_image is ImageExtended: + target_image.convert_rgb_to_indexed() + + ## Appends data to a [Dictionary] to be used for undo/redo. func serialize_undo_data() -> Dictionary: var dict := {} diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index fb4967081..e602ffaad 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -28,7 +28,7 @@ var big_bounding_rectangle := Rect2i(): if slot.tool_node is BaseSelectionTool: slot.tool_node.set_spinbox_values() _update_gizmos() -var image_current_pixel := Vector2.ZERO ## The ACTUAL pixel coordinate of image +var image_current_pixel := Vector2.ZERO ## The pixel coordinates of the cursor var temp_rect := Rect2() var rect_aspect_ratio := 0.0 @@ -38,6 +38,7 @@ var original_big_bounding_rectangle := Rect2i() var original_preview_image := Image.new() var original_bitmap := SelectionMap.new() var original_offset := Vector2.ZERO +var original_selected_tilemap_cells: Array[Array] var preview_image := Image.new() var preview_image_texture := ImageTexture.new() @@ -317,20 +318,22 @@ func _update_on_zoom() -> void: func _gizmo_resize() -> void: - if Tools.is_placing_tiles(): - return var dir := dragged_gizmo.direction + var mouse_pos := image_current_pixel + if Tools.is_placing_tiles(): + var tilemap := Global.current_project.get_current_cel() as CelTileMap + mouse_pos = mouse_pos.snapped(tilemap.tileset.tile_size) if Input.is_action_pressed("shape_center"): # Code inspired from https://github.com/GDQuest/godot-open-rpg if dir.x != 0 and dir.y != 0: # Border gizmos - temp_rect.size = ((image_current_pixel - temp_rect_pivot) * 2.0 * Vector2(dir)) + temp_rect.size = ((mouse_pos - temp_rect_pivot) * 2.0 * Vector2(dir)) elif dir.y == 0: # Center left and right gizmos - temp_rect.size.x = (image_current_pixel.x - temp_rect_pivot.x) * 2.0 * dir.x + temp_rect.size.x = (mouse_pos.x - temp_rect_pivot.x) * 2.0 * dir.x elif dir.x == 0: # Center top and bottom gizmos - temp_rect.size.y = (image_current_pixel.y - temp_rect_pivot.y) * 2.0 * dir.y + temp_rect.size.y = (mouse_pos.y - temp_rect_pivot.y) * 2.0 * dir.y temp_rect = Rect2(-1.0 * temp_rect.size / 2 + temp_rect_pivot, temp_rect.size) else: - _resize_rect(image_current_pixel, dir) + _resize_rect(mouse_pos, dir) if Input.is_action_pressed("shape_perfect") or resize_keep_ratio: # Maintain aspect ratio var end_y := temp_rect.end.y @@ -386,14 +389,28 @@ func resize_selection() -> void: Global.current_project.selection_map.copy_from(original_bitmap) if is_moving_content: preview_image.copy_from(original_preview_image) - if not Tools.is_placing_tiles(): + if Tools.is_placing_tiles(): + for cel in _get_selected_draw_cels(): + if cel is not CelTileMap: + continue + var tilemap := cel as CelTileMap + var horizontal_size := size.x / tilemap.tileset.tile_size.x + var vertical_size := size.y / tilemap.tileset.tile_size.y + var selected_cells := tilemap.resize_selection( + original_selected_tilemap_cells, horizontal_size, vertical_size + ) + preview_image.crop(size.x, size.y) + tilemap.apply_resizing_to_image( + preview_image, selected_cells, big_bounding_rectangle + ) + else: content_pivot = original_big_bounding_rectangle.size / 2.0 DrawingAlgos.nn_rotate(preview_image, angle, content_pivot) preview_image.resize(size.x, size.y, Image.INTERPOLATE_NEAREST) - if temp_rect.size.x < 0: - preview_image.flip_x() - if temp_rect.size.y < 0: - preview_image.flip_y() + if temp_rect.size.x < 0: + preview_image.flip_x() + if temp_rect.size.y < 0: + preview_image.flip_y() preview_image_texture = ImageTexture.create_from_image(preview_image) Global.current_project.selection_map.copy_from(original_bitmap) @@ -495,9 +512,15 @@ func transform_content_start() -> void: undo_data = get_undo_data(false) return is_moving_content = true - original_bitmap.copy_from(Global.current_project.selection_map) + var project := Global.current_project + original_bitmap.copy_from(project.selection_map) original_big_bounding_rectangle = big_bounding_rectangle - original_offset = Global.current_project.selection_offset + original_offset = project.selection_offset + var current_cel := project.get_current_cel() + if current_cel is CelTileMap: + original_selected_tilemap_cells = (current_cel as CelTileMap).get_selected_cells( + project.selection_map, big_bounding_rectangle + ) queue_redraw() canvas.queue_redraw() @@ -517,21 +540,40 @@ func transform_content_confirm() -> void: if not is_pasting: src.copy_from(cel.transformed_content) cel.transformed_content = null - DrawingAlgos.nn_rotate(src, angle, content_pivot) - src.resize( - preview_image.get_width(), preview_image.get_height(), Image.INTERPOLATE_NEAREST - ) - if temp_rect.size.x < 0: - src.flip_x() - if temp_rect.size.y < 0: - src.flip_y() + if Tools.is_placing_tiles(): + if cel is not CelTileMap: + continue + var tilemap := cel as CelTileMap + var horizontal_size := preview_image.get_width() / tilemap.tileset.tile_size.x + var vertical_size := preview_image.get_height() / tilemap.tileset.tile_size.y + var selected_cells := tilemap.resize_selection( + original_selected_tilemap_cells, horizontal_size, vertical_size + ) + src.crop(preview_image.get_width(), preview_image.get_height()) + tilemap.apply_resizing_to_image(src, selected_cells, big_bounding_rectangle) + else: + DrawingAlgos.nn_rotate(src, angle, content_pivot) + src.resize( + preview_image.get_width(), preview_image.get_height(), Image.INTERPOLATE_NEAREST + ) + if temp_rect.size.x < 0: + src.flip_x() + if temp_rect.size.y < 0: + src.flip_y() - cel_image.blit_rect_mask( - src, - src, - Rect2i(Vector2i.ZERO, project.selection_map.get_size()), - big_bounding_rectangle.position - ) + if Tools.is_placing_tiles(): + cel_image.blit_rect( + src, + Rect2i(Vector2i.ZERO, project.selection_map.get_size()), + big_bounding_rectangle.position + ) + else: + cel_image.blit_rect_mask( + src, + src, + 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) @@ -539,6 +581,7 @@ func transform_content_confirm() -> void: original_preview_image = Image.new() preview_image = Image.new() original_bitmap = SelectionMap.new() + original_selected_tilemap_cells.clear() is_moving_content = false is_pasting = false angle = 0.0 @@ -573,6 +616,7 @@ func transform_content_cancel() -> void: original_preview_image = Image.new() preview_image = Image.new() original_bitmap = SelectionMap.new() + original_selected_tilemap_cells.clear() is_pasting = false angle = 0.0 content_pivot = Vector2.ZERO