mirror of
https://github.com/Orama-Interactive/Pixelorama.git
synced 2025-01-18 09:09:47 +00:00
Spritesheet Autoslicer (#893)
* Added autoSlicer
* Fixed the remaining things (Autoslicer fully functional)
* Update RegionUnpacker.gd
* Formatting
* formatting
* formatting
* neatify
* Update RegionUnpacker.gd
* formatting
* formatting
* Update RegionUnpacker.gd
* Update README.md
* Added region unpacker class
* Optimized the region unpacker, this should now be twice as fast
addon version: f01526e50db98eea6d4d69db3c241d360887af7f
* change Smart Slicer to 5745b26a6e0b7e10bc4a46d07b5f9f0dd8f26c96
5745b26a6e
* Delete addons/SmartSlicer/Shader directory
* removed shader
* Update SmartSlicer Version
278b1c5a80b2c8b89279e405156d556732ce98d2
* Formatting (This is torture LOL)
2578b74ba84289aa109ae715b4a6c90fd5e23126
* Delete addons/SmartSlicer/addons/SmartSlicer/Classes directory
* Formatting
Version remains same
* Delete SmartSlicePreview.gd
* use _draw instead of line2d
and moved SmartSlicerPreview.gd for better organization
* Formatting
* More formatting
* Fix bugs related to import
* fix crash on attempting to open empty image
as new spritesheet tab (smart)
* removed accidental print
* fix empty image warnings
This commit is contained in:
parent
42196cda3b
commit
42428595c5
|
@ -27,3 +27,10 @@ Files extracted from source:
|
||||||
- Version: Based on git commit e5df60ed1d53246e03dba36053ff009846ba5174 with a modification on dockable_container.gd (lines 187-191).
|
- Version: Based on git commit e5df60ed1d53246e03dba36053ff009846ba5174 with a modification on dockable_container.gd (lines 187-191).
|
||||||
- License: [CC0-1.0](https://github.com/gilzoide/godot-dockable-container/blob/main/LICENSE)
|
- License: [CC0-1.0](https://github.com/gilzoide/godot-dockable-container/blob/main/LICENSE)
|
||||||
|
|
||||||
|
## SmartSlicer
|
||||||
|
|
||||||
|
- Upstream: https://github.com/Variable-Interactive/SmartSlicer
|
||||||
|
- Version: Based on git commit 2804e6109f9667022c66522ce88a99a56fd67ca8 with a modification on SmartSlicePreview.gd (lines 31-32). Only the contents of addons folder are used and the script SmartSlicePreview.gd is moved to res://src/UI/Dialogs/HelperScripts/ for better organization
|
||||||
|
- License: [MIT](https://github.com/Variable-Interactive/SmartSlicer/blob/main/LICENSE)
|
||||||
|
|
||||||
|
|
||||||
|
|
251
addons/SmartSlicer/Classes/RegionUnpacker.gd
Normal file
251
addons/SmartSlicer/Classes/RegionUnpacker.gd
Normal file
|
@ -0,0 +1,251 @@
|
||||||
|
class_name RegionUnpacker
|
||||||
|
extends Reference
|
||||||
|
|
||||||
|
# THIS CLASS TAKES INSPIRATION FROM PIXELORAMA'S FLOOD FILL
|
||||||
|
# AND HAS BEEN MODIFIED FOR OPTIMIZATION
|
||||||
|
|
||||||
|
var slice_thread := Thread.new()
|
||||||
|
|
||||||
|
var _include_boundary_threshold: int # the size of rect below which merging accounts for boundaty
|
||||||
|
var _merge_dist: int # after crossing threshold the smaller image will merge with larger image
|
||||||
|
# if it is within the _merge_dist
|
||||||
|
|
||||||
|
# working array used as buffer for segments while flooding
|
||||||
|
var _allegro_flood_segments: Array
|
||||||
|
# results array per image while flooding
|
||||||
|
var _allegro_image_segments: Array
|
||||||
|
|
||||||
|
|
||||||
|
func _init(threshold: int, merge_dist: int) -> void:
|
||||||
|
_include_boundary_threshold = threshold
|
||||||
|
_merge_dist = merge_dist
|
||||||
|
|
||||||
|
|
||||||
|
func get_used_rects(image: Image) -> Dictionary:
|
||||||
|
if OS.get_name() == "HTML5":
|
||||||
|
return get_rects(image)
|
||||||
|
else:
|
||||||
|
# If Thread model is set to "Multi-Threaded" in project settings>threads>thread model
|
||||||
|
if slice_thread.is_active():
|
||||||
|
slice_thread.wait_to_finish()
|
||||||
|
var error = slice_thread.start(self, "get_rects", image)
|
||||||
|
if error == OK:
|
||||||
|
return slice_thread.wait_to_finish()
|
||||||
|
else:
|
||||||
|
return get_rects(image)
|
||||||
|
# If Thread model is set to "Single-Safe" in project settings>threads>thread model then
|
||||||
|
# comment the above code and uncomment below
|
||||||
|
#return get_rects({"image": image})
|
||||||
|
|
||||||
|
|
||||||
|
func get_rects(image: Image) -> Dictionary:
|
||||||
|
# make a smaller image to make the loop shorter
|
||||||
|
var used_rect = image.get_used_rect()
|
||||||
|
if used_rect.size == Vector2.ZERO:
|
||||||
|
return clean_rects([])
|
||||||
|
var test_image = image.get_rect(used_rect)
|
||||||
|
# prepare a bitmap to keep track of previous places
|
||||||
|
var scanned_area := BitMap.new()
|
||||||
|
scanned_area.create(test_image.get_size())
|
||||||
|
test_image.lock()
|
||||||
|
# Scan the image
|
||||||
|
var rects = []
|
||||||
|
var frame_size = Vector2.ZERO
|
||||||
|
for y in test_image.get_size().y:
|
||||||
|
for x in test_image.get_size().x:
|
||||||
|
var position = Vector2(x, y)
|
||||||
|
if test_image.get_pixelv(position).a > 0: # used portion of image detected
|
||||||
|
if !scanned_area.get_bit(position):
|
||||||
|
var rect := _estimate_rect(test_image, position)
|
||||||
|
scanned_area.set_bit_rect(rect, true)
|
||||||
|
rect.position += used_rect.position
|
||||||
|
rects.append(rect)
|
||||||
|
test_image.unlock()
|
||||||
|
var rects_info = clean_rects(rects)
|
||||||
|
rects_info["rects"].sort_custom(self, "sort_rects")
|
||||||
|
return rects_info
|
||||||
|
|
||||||
|
|
||||||
|
func clean_rects(rects: Array) -> Dictionary:
|
||||||
|
var frame_size = Vector2.ZERO
|
||||||
|
for i in rects.size():
|
||||||
|
var target: Rect2 = rects.pop_front()
|
||||||
|
var test_rect = target
|
||||||
|
if (
|
||||||
|
target.size.x < _include_boundary_threshold
|
||||||
|
or target.size.y < _include_boundary_threshold
|
||||||
|
):
|
||||||
|
test_rect.size += Vector2(_merge_dist, _merge_dist)
|
||||||
|
test_rect.position -= Vector2(_merge_dist, _merge_dist) / 2
|
||||||
|
var merged = false
|
||||||
|
for rect_i in rects.size():
|
||||||
|
if test_rect.intersects(rects[rect_i]):
|
||||||
|
rects[rect_i] = target.merge(rects[rect_i])
|
||||||
|
merged = true
|
||||||
|
break
|
||||||
|
if !merged:
|
||||||
|
rects.append(target)
|
||||||
|
|
||||||
|
# calculation for a suitable frame size
|
||||||
|
if target.size.x > frame_size.x:
|
||||||
|
frame_size.x = target.size.x
|
||||||
|
if target.size.y > frame_size.y:
|
||||||
|
frame_size.y = target.size.y
|
||||||
|
return {"rects": rects, "frame_size": frame_size}
|
||||||
|
|
||||||
|
|
||||||
|
func sort_rects(rect_a: Rect2, rect_b: Rect2) -> bool:
|
||||||
|
# After many failed attempts, this version works for some reason (it's best not to disturb it)
|
||||||
|
if rect_a.end.y < rect_b.position.y:
|
||||||
|
return true
|
||||||
|
if rect_a.position.x < rect_b.position.x:
|
||||||
|
# if both lie in the same row
|
||||||
|
var start = rect_a.position
|
||||||
|
var size = Vector2(rect_b.end.x, rect_a.end.y)
|
||||||
|
if Rect2(start, size).intersects(rect_b):
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
func _estimate_rect(image: Image, position: Vector2) -> Rect2:
|
||||||
|
var cel_image := Image.new()
|
||||||
|
cel_image.copy_from(image)
|
||||||
|
cel_image.lock()
|
||||||
|
var small_rect: Rect2 = _flood_fill(position, cel_image)
|
||||||
|
cel_image.unlock()
|
||||||
|
return small_rect
|
||||||
|
|
||||||
|
|
||||||
|
# Add a new segment to the array
|
||||||
|
func _add_new_segment(y: int = 0) -> void:
|
||||||
|
var segment = {}
|
||||||
|
segment.flooding = false
|
||||||
|
segment.todo_above = false
|
||||||
|
segment.todo_below = false
|
||||||
|
segment.left_position = -5 # anything less than -1 is ok
|
||||||
|
segment.right_position = -5
|
||||||
|
segment.y = y
|
||||||
|
segment.next = 0
|
||||||
|
_allegro_flood_segments.append(segment)
|
||||||
|
|
||||||
|
|
||||||
|
# fill an horizontal segment around the specified position, and adds it to the
|
||||||
|
# list of segments filled. Returns the first x coordinate after the part of the
|
||||||
|
# line that has been filled.
|
||||||
|
func _flood_line_around_point(position: Vector2, image: Image) -> int:
|
||||||
|
# this method is called by `_flood_fill` after the required data structures
|
||||||
|
# have been initialized
|
||||||
|
if not image.get_pixelv(position).a > 0:
|
||||||
|
return int(position.x) + 1
|
||||||
|
var west: Vector2 = position
|
||||||
|
var east: Vector2 = position
|
||||||
|
while west.x >= 0 && image.get_pixelv(west).a > 0:
|
||||||
|
west += Vector2.LEFT
|
||||||
|
while east.x < image.get_width() && image.get_pixelv(east).a > 0:
|
||||||
|
east += Vector2.RIGHT
|
||||||
|
# Make a note of the stuff we processed
|
||||||
|
var c = int(position.y)
|
||||||
|
var segment = _allegro_flood_segments[c]
|
||||||
|
# we may have already processed some segments on this y coordinate
|
||||||
|
if segment.flooding:
|
||||||
|
while segment.next > 0:
|
||||||
|
c = segment.next # index of next segment in this line of image
|
||||||
|
segment = _allegro_flood_segments[c]
|
||||||
|
# found last current segment on this line
|
||||||
|
c = _allegro_flood_segments.size()
|
||||||
|
segment.next = c
|
||||||
|
_add_new_segment(int(position.y))
|
||||||
|
segment = _allegro_flood_segments[c]
|
||||||
|
# set the values for the current segment
|
||||||
|
segment.flooding = true
|
||||||
|
segment.left_position = west.x + 1
|
||||||
|
segment.right_position = east.x - 1
|
||||||
|
segment.y = position.y
|
||||||
|
segment.next = 0
|
||||||
|
# Should we process segments above or below this one?
|
||||||
|
# when there is a selected area, the pixels above and below the one we started creating this
|
||||||
|
# segment from may be outside it. It's easier to assume we should be checking for segments
|
||||||
|
# above and below this one than to specifically check every single pixel in it, because that
|
||||||
|
# test will be performed later anyway.
|
||||||
|
# On the other hand, this test we described is the same `project.can_pixel_get_drawn` does if
|
||||||
|
# there is no selection, so we don't need branching here.
|
||||||
|
segment.todo_above = position.y > 0
|
||||||
|
segment.todo_below = position.y < image.get_height() - 1
|
||||||
|
# this is an actual segment we should be coloring, so we add it to the results for the
|
||||||
|
# current image
|
||||||
|
if segment.right_position >= segment.left_position:
|
||||||
|
_allegro_image_segments.append(segment)
|
||||||
|
# we know the point just east of the segment is not part of a segment that should be
|
||||||
|
# processed, else it would be part of this segment
|
||||||
|
return int(east.x) + 1
|
||||||
|
|
||||||
|
|
||||||
|
func _check_flooded_segment(y: int, left: int, right: int, image: Image) -> bool:
|
||||||
|
var ret = false
|
||||||
|
var c: int = 0
|
||||||
|
while left <= right:
|
||||||
|
c = y
|
||||||
|
while true:
|
||||||
|
var segment = _allegro_flood_segments[c]
|
||||||
|
if left >= segment.left_position and left <= segment.right_position:
|
||||||
|
left = segment.right_position + 2
|
||||||
|
break
|
||||||
|
c = segment.next
|
||||||
|
if c == 0: # couldn't find a valid segment, so we draw a new one
|
||||||
|
left = _flood_line_around_point(Vector2(left, y), image)
|
||||||
|
ret = true
|
||||||
|
break
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
func _flood_fill(position: Vector2, image: Image) -> Rect2:
|
||||||
|
# implements the floodfill routine by Shawn Hargreaves
|
||||||
|
# from https://www1.udel.edu/CIS/software/dist/allegro-4.2.1/src/flood.c
|
||||||
|
# init flood data structures
|
||||||
|
_allegro_flood_segments = []
|
||||||
|
_allegro_image_segments = []
|
||||||
|
_compute_segments_for_image(position, image)
|
||||||
|
# now actually color the image: since we have already checked a few things for the points
|
||||||
|
# we'll process here, we're going to skip a bunch of safety checks to speed things up.
|
||||||
|
|
||||||
|
var final_image = Image.new()
|
||||||
|
final_image.copy_from(image)
|
||||||
|
final_image.fill(Color.transparent)
|
||||||
|
final_image.lock()
|
||||||
|
_select_segments(final_image)
|
||||||
|
final_image.unlock()
|
||||||
|
|
||||||
|
return final_image.get_used_rect()
|
||||||
|
|
||||||
|
|
||||||
|
func _compute_segments_for_image(position: Vector2, image: Image) -> void:
|
||||||
|
# initially allocate at least 1 segment per line of image
|
||||||
|
for j in image.get_height():
|
||||||
|
_add_new_segment(j)
|
||||||
|
# start flood algorithm
|
||||||
|
_flood_line_around_point(position, image)
|
||||||
|
# test all segments while also discovering more
|
||||||
|
var done := false
|
||||||
|
while not done:
|
||||||
|
done = true
|
||||||
|
var max_index = _allegro_flood_segments.size()
|
||||||
|
for c in max_index:
|
||||||
|
var p = _allegro_flood_segments[c]
|
||||||
|
if p.todo_below: # check below the segment?
|
||||||
|
p.todo_below = false
|
||||||
|
if _check_flooded_segment(p.y + 1, p.left_position, p.right_position, image):
|
||||||
|
done = false
|
||||||
|
if p.todo_above: # check above the segment?
|
||||||
|
p.todo_above = false
|
||||||
|
if _check_flooded_segment(p.y - 1, p.left_position, p.right_position, image):
|
||||||
|
done = false
|
||||||
|
|
||||||
|
|
||||||
|
func _select_segments(map: Image) -> void:
|
||||||
|
# short circuit for flat colors
|
||||||
|
for c in _allegro_image_segments.size():
|
||||||
|
var p = _allegro_image_segments[c]
|
||||||
|
var rect = Rect2()
|
||||||
|
rect.position = Vector2(p.left_position, p.y)
|
||||||
|
rect.end = Vector2(p.right_position + 1, p.y + 1)
|
||||||
|
map.fill_rect(rect, Color.white)
|
|
@ -224,6 +224,11 @@ _global_script_classes=[ {
|
||||||
"language": "GDScript",
|
"language": "GDScript",
|
||||||
"path": "res://src/UI/ReferenceImages/ReferencesPanel.gd"
|
"path": "res://src/UI/ReferenceImages/ReferencesPanel.gd"
|
||||||
}, {
|
}, {
|
||||||
|
"base": "Reference",
|
||||||
|
"class": "RegionUnpacker",
|
||||||
|
"language": "GDScript",
|
||||||
|
"path": "res://addons/SmartSlicer/Classes/RegionUnpacker.gd"
|
||||||
|
}, {
|
||||||
"base": "Image",
|
"base": "Image",
|
||||||
"class": "SelectionMap",
|
"class": "SelectionMap",
|
||||||
"language": "GDScript",
|
"language": "GDScript",
|
||||||
|
@ -313,6 +318,7 @@ _global_script_class_icons={
|
||||||
"Project": "",
|
"Project": "",
|
||||||
"ReferenceImage": "",
|
"ReferenceImage": "",
|
||||||
"ReferencesPanel": "",
|
"ReferencesPanel": "",
|
||||||
|
"RegionUnpacker": "",
|
||||||
"SelectionMap": "",
|
"SelectionMap": "",
|
||||||
"SelectionTool": "",
|
"SelectionTool": "",
|
||||||
"ShaderImageEffect": "",
|
"ShaderImageEffect": "",
|
||||||
|
|
|
@ -470,14 +470,35 @@ func open_image_as_new_tab(path: String, image: Image) -> void:
|
||||||
set_new_imported_tab(project, path)
|
set_new_imported_tab(project, path)
|
||||||
|
|
||||||
|
|
||||||
func open_image_as_spritesheet_tab(path: String, image: Image, horiz: int, vert: int) -> void:
|
func open_image_as_spritesheet_tab_smart(
|
||||||
var project := Project.new([], path.get_file())
|
path: String, image: Image, sliced_rects: Array, frame_size: Vector2
|
||||||
|
) -> void:
|
||||||
|
if sliced_rects.size() == 0: # Image is empty sprite (manually set data to be consistent)
|
||||||
|
frame_size = image.get_size()
|
||||||
|
sliced_rects.append(Rect2(Vector2.ZERO, frame_size))
|
||||||
|
var project := Project.new([], path.get_file(), frame_size)
|
||||||
project.layers.append(PixelLayer.new(project))
|
project.layers.append(PixelLayer.new(project))
|
||||||
Global.projects.append(project)
|
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.new()
|
||||||
|
cropped_image.create(frame_size.x, frame_size.y, false, Image.FORMAT_RGBA8)
|
||||||
|
cropped_image.blit_rect(image, rect, offset)
|
||||||
|
cropped_image.convert(Image.FORMAT_RGBA8)
|
||||||
|
frame.cels.append(PixelCel.new(cropped_image, 1))
|
||||||
|
project.frames.append(frame)
|
||||||
|
set_new_imported_tab(project, path)
|
||||||
|
|
||||||
|
|
||||||
|
func open_image_as_spritesheet_tab(path: String, image: Image, horiz: int, vert: int) -> void:
|
||||||
horiz = min(horiz, image.get_size().x)
|
horiz = min(horiz, image.get_size().x)
|
||||||
vert = min(vert, image.get_size().y)
|
vert = min(vert, image.get_size().y)
|
||||||
var frame_width := image.get_size().x / horiz
|
var frame_width := image.get_size().x / horiz
|
||||||
var frame_height := image.get_size().y / vert
|
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))
|
||||||
|
Global.projects.append(project)
|
||||||
for yy in range(vert):
|
for yy in range(vert):
|
||||||
for xx in range(horiz):
|
for xx in range(horiz):
|
||||||
var frame := Frame.new()
|
var frame := Frame.new()
|
||||||
|
@ -492,6 +513,83 @@ func open_image_as_spritesheet_tab(path: String, image: Image, horiz: int, vert:
|
||||||
set_new_imported_tab(project, path)
|
set_new_imported_tab(project, path)
|
||||||
|
|
||||||
|
|
||||||
|
func open_image_as_spritesheet_layer_smart(
|
||||||
|
_path: String,
|
||||||
|
image: Image,
|
||||||
|
file_name: String,
|
||||||
|
sliced_rects: Array,
|
||||||
|
start_frame: int,
|
||||||
|
frame_size: Vector2
|
||||||
|
) -> void:
|
||||||
|
# Resize canvas to if "frame_size.x" or "frame_size.y" is too large
|
||||||
|
var project: Project = Global.current_project
|
||||||
|
var project_width: int = max(frame_size.x, project.size.x)
|
||||||
|
var project_height: int = max(frame_size.y, project.size.y)
|
||||||
|
if project.size < Vector2(project_width, project_height):
|
||||||
|
DrawingAlgos.resize_canvas(project_width, project_height, 0, 0)
|
||||||
|
|
||||||
|
# Initialize undo mechanism
|
||||||
|
project.undos += 1
|
||||||
|
project.undo_redo.create_action("Add Spritesheet Layer")
|
||||||
|
|
||||||
|
# Create new frames (if needed)
|
||||||
|
var new_frames_size = max(project.frames.size(), start_frame + sliced_rects.size())
|
||||||
|
var frames := []
|
||||||
|
var frame_indices := []
|
||||||
|
if new_frames_size > project.frames.size():
|
||||||
|
var required_frames = new_frames_size - project.frames.size()
|
||||||
|
frame_indices = range(
|
||||||
|
project.current_frame + 1, project.current_frame + required_frames + 1
|
||||||
|
)
|
||||||
|
for i in required_frames:
|
||||||
|
var new_frame := Frame.new()
|
||||||
|
for l in range(project.layers.size()): # Create as many cels as there are layers
|
||||||
|
new_frame.cels.append(project.layers[l].new_empty_cel())
|
||||||
|
if project.layers[l].new_cels_linked:
|
||||||
|
var prev_cel: BaseCel = project.frames[project.current_frame].cels[l]
|
||||||
|
if prev_cel.link_set == null:
|
||||||
|
prev_cel.link_set = {}
|
||||||
|
project.undo_redo.add_do_method(
|
||||||
|
project.layers[l], "link_cel", prev_cel, prev_cel.link_set
|
||||||
|
)
|
||||||
|
project.undo_redo.add_undo_method(
|
||||||
|
project.layers[l], "link_cel", prev_cel, null
|
||||||
|
)
|
||||||
|
new_frame.cels[l].set_content(prev_cel.get_content(), prev_cel.image_texture)
|
||||||
|
new_frame.cels[l].link_set = prev_cel.link_set
|
||||||
|
frames.append(new_frame)
|
||||||
|
|
||||||
|
# Create new layer for spritesheet
|
||||||
|
var layer := PixelLayer.new(project, file_name)
|
||||||
|
var cels := []
|
||||||
|
for f in new_frames_size:
|
||||||
|
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)
|
||||||
|
var cropped_image := Image.new()
|
||||||
|
cropped_image.create(project_width, project_height, false, Image.FORMAT_RGBA8)
|
||||||
|
cropped_image.blit_rect(image, sliced_rects[f - start_frame], offset)
|
||||||
|
cels.append(PixelCel.new(cropped_image))
|
||||||
|
else:
|
||||||
|
cels.append(layer.new_empty_cel())
|
||||||
|
|
||||||
|
project.undo_redo.add_do_method(project, "add_frames", frames, frame_indices)
|
||||||
|
project.undo_redo.add_do_method(project, "add_layers", [layer], [project.layers.size()], [cels])
|
||||||
|
project.undo_redo.add_do_method(
|
||||||
|
project, "change_cel", new_frames_size - 1, project.layers.size()
|
||||||
|
)
|
||||||
|
project.undo_redo.add_do_method(Global, "undo_or_redo", false)
|
||||||
|
|
||||||
|
project.undo_redo.add_undo_method(project, "remove_layers", [project.layers.size()])
|
||||||
|
project.undo_redo.add_undo_method(project, "remove_frames", frame_indices)
|
||||||
|
project.undo_redo.add_undo_method(
|
||||||
|
project, "change_cel", project.current_frame, project.current_layer
|
||||||
|
)
|
||||||
|
project.undo_redo.add_undo_method(Global, "undo_or_redo", true)
|
||||||
|
project.undo_redo.commit_action()
|
||||||
|
|
||||||
|
|
||||||
func open_image_as_spritesheet_layer(
|
func open_image_as_spritesheet_layer(
|
||||||
_path: String, image: Image, file_name: String, horizontal: int, vertical: int, start_frame: int
|
_path: String, image: Image, file_name: String, horizontal: int, vertical: int, start_frame: int
|
||||||
) -> void:
|
) -> void:
|
||||||
|
@ -547,12 +645,14 @@ func open_image_as_spritesheet_layer(
|
||||||
# Slice spritesheet
|
# Slice spritesheet
|
||||||
var xx: int = (f - start_frame) % horizontal
|
var xx: int = (f - start_frame) % horizontal
|
||||||
var yy: int = (f - start_frame) / horizontal
|
var yy: int = (f - start_frame) / horizontal
|
||||||
|
image.convert(Image.FORMAT_RGBA8)
|
||||||
var cropped_image := Image.new()
|
var cropped_image := Image.new()
|
||||||
cropped_image = image.get_rect(
|
cropped_image.create(project_width, project_height, false, Image.FORMAT_RGBA8)
|
||||||
Rect2(frame_width * xx, frame_height * yy, frame_width, frame_height)
|
cropped_image.blit_rect(
|
||||||
|
image,
|
||||||
|
Rect2(frame_width * xx, frame_height * yy, frame_width, frame_height),
|
||||||
|
Vector2.ZERO
|
||||||
)
|
)
|
||||||
cropped_image.crop(project.size.x, project.size.y)
|
|
||||||
cropped_image.convert(Image.FORMAT_RGBA8)
|
|
||||||
cels.append(PixelCel.new(cropped_image))
|
cels.append(PixelCel.new(cropped_image))
|
||||||
else:
|
else:
|
||||||
cels.append(layer.new_empty_cel())
|
cels.append(layer.new_empty_cel())
|
||||||
|
@ -585,8 +685,11 @@ func open_image_at_cel(image: Image, layer_index := 0, frame_index := 0) -> void
|
||||||
for i in project.frames.size():
|
for i in project.frames.size():
|
||||||
if i == frame_index:
|
if i == frame_index:
|
||||||
image.convert(Image.FORMAT_RGBA8)
|
image.convert(Image.FORMAT_RGBA8)
|
||||||
|
var cel_image = Image.new()
|
||||||
|
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]
|
var cel: PixelCel = project.frames[i].cels[layer_index]
|
||||||
project.undo_redo.add_do_property(cel, "image", image)
|
project.undo_redo.add_do_property(cel, "image", cel_image)
|
||||||
project.undo_redo.add_undo_property(cel, "image", cel.image)
|
project.undo_redo.add_undo_property(cel, "image", cel.image)
|
||||||
|
|
||||||
project.undo_redo.add_do_property(project, "selected_cels", [])
|
project.undo_redo.add_do_property(project, "selected_cels", [])
|
||||||
|
@ -612,7 +715,10 @@ func open_image_as_new_frame(image: Image, layer_index := 0) -> void:
|
||||||
for i in project.layers.size():
|
for i in project.layers.size():
|
||||||
if i == layer_index:
|
if i == layer_index:
|
||||||
image.convert(Image.FORMAT_RGBA8)
|
image.convert(Image.FORMAT_RGBA8)
|
||||||
frame.cels.append(PixelCel.new(image, 1))
|
var cel_image = Image.new()
|
||||||
|
cel_image.create(project_width, project_height, false, Image.FORMAT_RGBA8)
|
||||||
|
cel_image.blit_rect(image, Rect2(Vector2.ZERO, image.get_size()), Vector2.ZERO)
|
||||||
|
frame.cels.append(PixelCel.new(cel_image, 1))
|
||||||
else:
|
else:
|
||||||
frame.cels.append(project.layers[i].new_empty_cel())
|
frame.cels.append(project.layers[i].new_empty_cel())
|
||||||
|
|
||||||
|
@ -644,7 +750,10 @@ func open_image_as_new_layer(image: Image, file_name: String, frame_index := 0)
|
||||||
for i in project.frames.size():
|
for i in project.frames.size():
|
||||||
if i == frame_index:
|
if i == frame_index:
|
||||||
image.convert(Image.FORMAT_RGBA8)
|
image.convert(Image.FORMAT_RGBA8)
|
||||||
cels.append(PixelCel.new(image, 1))
|
var cel_image = Image.new()
|
||||||
|
cel_image.create(project_width, project_height, false, Image.FORMAT_RGBA8)
|
||||||
|
cel_image.blit_rect(image, Rect2(Vector2.ZERO, image.get_size()), Vector2.ZERO)
|
||||||
|
cels.append(PixelCel.new(cel_image, 1))
|
||||||
else:
|
else:
|
||||||
cels.append(layer.new_empty_cel())
|
cels.append(layer.new_empty_cel())
|
||||||
|
|
||||||
|
|
39
src/UI/Dialogs/HelperScripts/RowColumnLines.gd
Normal file
39
src/UI/Dialogs/HelperScripts/RowColumnLines.gd
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
var color: Color = Color("6680ff") # Set this to a theme color later
|
||||||
|
var _spritesheet_vertical
|
||||||
|
var _spritesheet_horizontal
|
||||||
|
|
||||||
|
|
||||||
|
func show_preview(spritesheet_vertical, spritesheet_horizontal) -> void:
|
||||||
|
_spritesheet_vertical = spritesheet_vertical
|
||||||
|
_spritesheet_horizontal = spritesheet_horizontal
|
||||||
|
update()
|
||||||
|
|
||||||
|
|
||||||
|
func _draw() -> void:
|
||||||
|
var texture_rect: TextureRect = get_parent()
|
||||||
|
var image = texture_rect.texture.get_data()
|
||||||
|
var image_size_y = texture_rect.rect_size.y
|
||||||
|
var image_size_x = texture_rect.rect_size.x
|
||||||
|
if image.get_size().x > image.get_size().y:
|
||||||
|
var scale_ratio = image.get_size().x / image_size_x
|
||||||
|
image_size_y = image.get_size().y / scale_ratio
|
||||||
|
else:
|
||||||
|
var scale_ratio = image.get_size().y / image_size_y
|
||||||
|
image_size_x = image.get_size().x / scale_ratio
|
||||||
|
|
||||||
|
var offset_x = (texture_rect.rect_size.x - image_size_x) / 2
|
||||||
|
var offset_y = (texture_rect.rect_size.y - image_size_y) / 2
|
||||||
|
|
||||||
|
var line_distance_vertical = image_size_y / _spritesheet_vertical
|
||||||
|
var line_distance_horizontal = image_size_x / _spritesheet_horizontal
|
||||||
|
|
||||||
|
for i in range(1, _spritesheet_vertical):
|
||||||
|
var from = Vector2(offset_x, i * line_distance_vertical + offset_y)
|
||||||
|
var to = Vector2(image_size_x + offset_x, i * line_distance_vertical + offset_y)
|
||||||
|
draw_line(from, to, color)
|
||||||
|
for i in range(1, _spritesheet_horizontal):
|
||||||
|
var from = Vector2(i * line_distance_horizontal + offset_x, offset_y)
|
||||||
|
var to = Vector2(i * line_distance_horizontal + offset_x, image_size_y + offset_y)
|
||||||
|
draw_line(from, to, color)
|
36
src/UI/Dialogs/HelperScripts/SmartSlicePreview.gd
Normal file
36
src/UI/Dialogs/HelperScripts/SmartSlicePreview.gd
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
# add this as a child of the texturerect that contains the main spritesheet
|
||||||
|
var color: Color = Color("6680ff") # Set this to a theme color later
|
||||||
|
var _sliced_rects: Array
|
||||||
|
var _stretch_amount: float
|
||||||
|
var _offset: Vector2
|
||||||
|
|
||||||
|
|
||||||
|
func show_preview(sliced_rects: Array) -> void:
|
||||||
|
var image = get_parent().texture.get_data()
|
||||||
|
if image.get_size().x > image.get_size().y:
|
||||||
|
_stretch_amount = rect_size.x / image.get_size().x
|
||||||
|
else:
|
||||||
|
_stretch_amount = rect_size.y / image.get_size().y
|
||||||
|
_sliced_rects = sliced_rects.duplicate()
|
||||||
|
_offset = (0.5 * (rect_size - (image.get_size() * _stretch_amount))).floor()
|
||||||
|
update()
|
||||||
|
|
||||||
|
|
||||||
|
func _draw() -> void:
|
||||||
|
draw_set_transform(_offset, 0, Vector2.ONE)
|
||||||
|
for i in _sliced_rects.size():
|
||||||
|
var rect = _sliced_rects[i]
|
||||||
|
var scaled_rect: Rect2 = rect
|
||||||
|
scaled_rect.position = (scaled_rect.position * _stretch_amount)
|
||||||
|
scaled_rect.size *= _stretch_amount
|
||||||
|
draw_rect(scaled_rect, color, false)
|
||||||
|
# show number
|
||||||
|
draw_set_transform(_offset + scaled_rect.position, 0, Vector2.ONE)
|
||||||
|
# var font: Font = Control.new().get_font("font")
|
||||||
|
# replace with font used by pixelorama
|
||||||
|
var font: Font = Global.control.theme.default_font
|
||||||
|
var font_height := font.get_height()
|
||||||
|
draw_string(font, Vector2(1, font_height), str(i))
|
||||||
|
draw_set_transform(_offset, 0, Vector2.ONE)
|
|
@ -17,17 +17,28 @@ enum BrushTypes { FILE, PROJECT, RANDOM }
|
||||||
var path: String
|
var path: String
|
||||||
var image: Image
|
var image: Image
|
||||||
var current_import_option: int = ImageImportOptions.NEW_TAB
|
var current_import_option: int = ImageImportOptions.NEW_TAB
|
||||||
|
var smart_slice = false
|
||||||
|
var recycle_last_slice_result = false # should we recycle the current sliced_rects
|
||||||
|
var sliced_rects: Dictionary
|
||||||
var spritesheet_horizontal := 1
|
var spritesheet_horizontal := 1
|
||||||
var spritesheet_vertical := 1
|
var spritesheet_vertical := 1
|
||||||
var brush_type: int = BrushTypes.FILE
|
var brush_type: int = BrushTypes.FILE
|
||||||
var opened_once = false
|
var opened_once = false
|
||||||
var is_master: bool = false
|
var is_master: bool = false
|
||||||
var hiding: bool = false
|
var hiding: bool = false
|
||||||
|
var _content_offset = rect_size - get_child(0).rect_size # A workaround for a pixelorama bug
|
||||||
|
|
||||||
onready var texture_rect: TextureRect = $VBoxContainer/CenterContainer/TextureRect
|
onready var texture_rect: TextureRect = $VBoxContainer/CenterContainer/TextureRect
|
||||||
onready var image_size_label: Label = $VBoxContainer/SizeContainer/ImageSizeLabel
|
onready var image_size_label: Label = $VBoxContainer/SizeContainer/ImageSizeLabel
|
||||||
onready var frame_size_label: Label = $VBoxContainer/SizeContainer/FrameSizeLabel
|
onready var frame_size_label: Label = $VBoxContainer/SizeContainer/FrameSizeLabel
|
||||||
onready var spritesheet_tab_options = $VBoxContainer/HBoxContainer/SpritesheetTabOptions
|
onready var smart_slice_checkbox = $VBoxContainer/HBoxContainer/SpritesheetTabOptions/SmartSlice
|
||||||
|
onready var merge_threshold = $VBoxContainer/HBoxContainer/SpritesheetTabOptions/Smart/Threshold
|
||||||
|
# gdlint: ignore=max-line-length
|
||||||
|
onready var merge_dist: TextureProgress = $VBoxContainer/HBoxContainer/SpritesheetTabOptions/Smart/MergeDist
|
||||||
|
# gdlint: ignore=max-line-length
|
||||||
|
onready var spritesheet_manual_tab_options = $VBoxContainer/HBoxContainer/SpritesheetTabOptions/Manual
|
||||||
|
onready var spritesheet_smart_tab_options = $VBoxContainer/HBoxContainer/SpritesheetTabOptions/Smart
|
||||||
|
onready var spritesheet_tab_options = spritesheet_smart_tab_options.get_parent()
|
||||||
onready var spritesheet_lay_opt = $VBoxContainer/HBoxContainer/SpritesheetLayerOptions
|
onready var spritesheet_lay_opt = $VBoxContainer/HBoxContainer/SpritesheetLayerOptions
|
||||||
onready var new_frame_options = $VBoxContainer/HBoxContainer/NewFrameOptions
|
onready var new_frame_options = $VBoxContainer/HBoxContainer/NewFrameOptions
|
||||||
onready var replace_cel_options = $VBoxContainer/HBoxContainer/ReplaceCelOptions
|
onready var replace_cel_options = $VBoxContainer/HBoxContainer/ReplaceCelOptions
|
||||||
|
@ -62,11 +73,11 @@ func _on_PreviewDialog_about_to_show() -> void:
|
||||||
var img_texture := ImageTexture.new()
|
var img_texture := ImageTexture.new()
|
||||||
img_texture.create_from_image(image, 0)
|
img_texture.create_from_image(image, 0)
|
||||||
texture_rect.texture = img_texture
|
texture_rect.texture = img_texture
|
||||||
spritesheet_tab_options.get_node("HorizontalFrames").max_value = min(
|
spritesheet_manual_tab_options.get_node("HorizontalFrames").max_value = min(
|
||||||
spritesheet_tab_options.get_node("HorizontalFrames").max_value, image.get_size().x
|
spritesheet_manual_tab_options.get_node("HorizontalFrames").max_value, image.get_size().x
|
||||||
)
|
)
|
||||||
spritesheet_tab_options.get_node("VerticalFrames").max_value = min(
|
spritesheet_manual_tab_options.get_node("VerticalFrames").max_value = min(
|
||||||
spritesheet_tab_options.get_node("VerticalFrames").max_value, image.get_size().y
|
spritesheet_manual_tab_options.get_node("VerticalFrames").max_value, image.get_size().y
|
||||||
)
|
)
|
||||||
image_size_label.text = (
|
image_size_label.text = (
|
||||||
tr("Image Size")
|
tr("Image Size")
|
||||||
|
@ -115,20 +126,39 @@ func _on_PreviewDialog_confirmed() -> void:
|
||||||
OpenSave.open_image_as_new_tab(path, image)
|
OpenSave.open_image_as_new_tab(path, image)
|
||||||
|
|
||||||
elif current_import_option == ImageImportOptions.SPRITESHEET_TAB:
|
elif current_import_option == ImageImportOptions.SPRITESHEET_TAB:
|
||||||
OpenSave.open_image_as_spritesheet_tab(
|
if smart_slice:
|
||||||
path, image, spritesheet_horizontal, spritesheet_vertical
|
if !recycle_last_slice_result:
|
||||||
)
|
obtain_sliced_data()
|
||||||
|
OpenSave.open_image_as_spritesheet_tab_smart(
|
||||||
|
path, image, sliced_rects["rects"], sliced_rects["frame_size"]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
OpenSave.open_image_as_spritesheet_tab(
|
||||||
|
path, image, spritesheet_horizontal, spritesheet_vertical
|
||||||
|
)
|
||||||
|
|
||||||
elif current_import_option == ImageImportOptions.SPRITESHEET_LAYER:
|
elif current_import_option == ImageImportOptions.SPRITESHEET_LAYER:
|
||||||
var frame_index: int = spritesheet_lay_opt.get_node("AtFrameSpinbox").value - 1
|
var frame_index: int = spritesheet_lay_opt.get_node("AtFrameSpinbox").value - 1
|
||||||
OpenSave.open_image_as_spritesheet_layer(
|
if smart_slice:
|
||||||
path,
|
if !recycle_last_slice_result:
|
||||||
image,
|
obtain_sliced_data()
|
||||||
path.get_basename().get_file(),
|
OpenSave.open_image_as_spritesheet_layer_smart(
|
||||||
spritesheet_horizontal,
|
path,
|
||||||
spritesheet_vertical,
|
image,
|
||||||
frame_index
|
path.get_basename().get_file(),
|
||||||
)
|
sliced_rects["rects"],
|
||||||
|
frame_index,
|
||||||
|
sliced_rects["frame_size"]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
OpenSave.open_image_as_spritesheet_layer(
|
||||||
|
path,
|
||||||
|
image,
|
||||||
|
path.get_basename().get_file(),
|
||||||
|
spritesheet_horizontal,
|
||||||
|
spritesheet_vertical,
|
||||||
|
frame_index
|
||||||
|
)
|
||||||
|
|
||||||
elif current_import_option == ImageImportOptions.NEW_FRAME:
|
elif current_import_option == ImageImportOptions.NEW_FRAME:
|
||||||
var layer_index: int = new_frame_options.get_node("AtLayerOption").get_selected_id()
|
var layer_index: int = new_frame_options.get_node("AtLayerOption").get_selected_id()
|
||||||
|
@ -200,11 +230,13 @@ func synchronize() -> void:
|
||||||
id == ImageImportOptions.SPRITESHEET_TAB
|
id == ImageImportOptions.SPRITESHEET_TAB
|
||||||
or id == ImageImportOptions.SPRITESHEET_LAYER
|
or id == ImageImportOptions.SPRITESHEET_LAYER
|
||||||
):
|
):
|
||||||
dialog.spritesheet_tab_options.get_node("HorizontalFrames").value = min(
|
dialog.spritesheet_manual_tab_options.get_node("HorizontalFrames").value = min(
|
||||||
spritesheet_tab_options.get_node("HorizontalFrames").value, image.get_size().x
|
spritesheet_manual_tab_options.get_node("HorizontalFrames").value,
|
||||||
|
image.get_size().x
|
||||||
)
|
)
|
||||||
dialog.spritesheet_tab_options.get_node("VerticalFrames").value = min(
|
dialog.spritesheet_manual_tab_options.get_node("VerticalFrames").value = min(
|
||||||
spritesheet_tab_options.get_node("VerticalFrames").value, image.get_size().y
|
spritesheet_manual_tab_options.get_node("VerticalFrames").value,
|
||||||
|
image.get_size().y
|
||||||
)
|
)
|
||||||
if id == ImageImportOptions.SPRITESHEET_LAYER:
|
if id == ImageImportOptions.SPRITESHEET_LAYER:
|
||||||
dialog.spritesheet_lay_opt.get_node("AtFrameSpinbox").value = (spritesheet_lay_opt.get_node(
|
dialog.spritesheet_lay_opt.get_node("AtFrameSpinbox").value = (spritesheet_lay_opt.get_node(
|
||||||
|
@ -240,7 +272,10 @@ func synchronize() -> void:
|
||||||
func _on_ImportOption_item_selected(id: int) -> void:
|
func _on_ImportOption_item_selected(id: int) -> void:
|
||||||
current_import_option = id
|
current_import_option = id
|
||||||
OpenSave.last_dialog_option = current_import_option
|
OpenSave.last_dialog_option = current_import_option
|
||||||
frame_size_label.visible = false
|
smart_slice_checkbox.pressed = false
|
||||||
|
apply_all.disabled = false
|
||||||
|
smart_slice = false
|
||||||
|
smart_slice_checkbox.visible = false
|
||||||
spritesheet_tab_options.visible = false
|
spritesheet_tab_options.visible = false
|
||||||
spritesheet_lay_opt.visible = false
|
spritesheet_lay_opt.visible = false
|
||||||
new_frame_options.visible = false
|
new_frame_options.visible = false
|
||||||
|
@ -249,22 +284,21 @@ func _on_ImportOption_item_selected(id: int) -> void:
|
||||||
new_brush_options.visible = false
|
new_brush_options.visible = false
|
||||||
texture_rect.get_child(0).visible = false
|
texture_rect.get_child(0).visible = false
|
||||||
texture_rect.get_child(1).visible = false
|
texture_rect.get_child(1).visible = false
|
||||||
rect_size.x = 550
|
|
||||||
|
|
||||||
if id == ImageImportOptions.SPRITESHEET_TAB:
|
if id == ImageImportOptions.SPRITESHEET_TAB:
|
||||||
frame_size_label.visible = true
|
frame_size_label.visible = true
|
||||||
|
smart_slice_checkbox.visible = true
|
||||||
spritesheet_tab_options.visible = true
|
spritesheet_tab_options.visible = true
|
||||||
texture_rect.get_child(0).visible = true
|
texture_rect.get_child(0).visible = true
|
||||||
texture_rect.get_child(1).visible = true
|
texture_rect.get_child(1).visible = true
|
||||||
rect_size.x = spritesheet_tab_options.rect_size.x
|
|
||||||
|
|
||||||
elif id == ImageImportOptions.SPRITESHEET_LAYER:
|
elif id == ImageImportOptions.SPRITESHEET_LAYER:
|
||||||
frame_size_label.visible = true
|
frame_size_label.visible = true
|
||||||
spritesheet_tab_options.visible = true
|
smart_slice_checkbox.visible = true
|
||||||
spritesheet_lay_opt.visible = true
|
spritesheet_lay_opt.visible = true
|
||||||
|
spritesheet_tab_options.visible = true
|
||||||
texture_rect.get_child(0).visible = true
|
texture_rect.get_child(0).visible = true
|
||||||
texture_rect.get_child(1).visible = true
|
texture_rect.get_child(1).visible = true
|
||||||
rect_size.x = spritesheet_lay_opt.rect_size.x
|
|
||||||
|
|
||||||
elif id == ImageImportOptions.NEW_FRAME:
|
elif id == ImageImportOptions.NEW_FRAME:
|
||||||
new_frame_options.visible = true
|
new_frame_options.visible = true
|
||||||
|
@ -306,59 +340,68 @@ func _on_ImportOption_item_selected(id: int) -> void:
|
||||||
elif id == ImageImportOptions.BRUSH:
|
elif id == ImageImportOptions.BRUSH:
|
||||||
new_brush_options.visible = true
|
new_brush_options.visible = true
|
||||||
|
|
||||||
|
rect_size = get_child(0).rect_size + _content_offset
|
||||||
|
update()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_SmartSlice_toggled(button_pressed: bool) -> void:
|
||||||
|
setup_smart_slice(button_pressed)
|
||||||
|
|
||||||
|
|
||||||
|
func setup_smart_slice(enabled: bool) -> void:
|
||||||
|
spritesheet_smart_tab_options.visible = enabled
|
||||||
|
spritesheet_manual_tab_options.visible = !enabled
|
||||||
|
if is_master: # disable apply all (the algorithm is not fast enough for this)
|
||||||
|
apply_all.pressed = false
|
||||||
|
apply_all.disabled = enabled
|
||||||
|
smart_slice = enabled
|
||||||
|
if !recycle_last_slice_result and enabled:
|
||||||
|
slice_preview()
|
||||||
|
update()
|
||||||
|
|
||||||
|
|
||||||
|
func obtain_sliced_data() -> void:
|
||||||
|
var unpak := RegionUnpacker.new(merge_threshold.value, merge_dist.value)
|
||||||
|
sliced_rects = unpak.get_used_rects(texture_rect.texture.get_data())
|
||||||
|
|
||||||
|
|
||||||
|
func slice_preview():
|
||||||
|
sliced_rects.clear()
|
||||||
|
obtain_sliced_data()
|
||||||
|
recycle_last_slice_result = true
|
||||||
|
var size = sliced_rects["frame_size"]
|
||||||
|
frame_size_label.text = tr("Frame Size") + ": " + str(size.x) + "×" + str(size.y)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_Threshold_value_changed(_value: float) -> void:
|
||||||
|
recycle_last_slice_result = false
|
||||||
|
|
||||||
|
|
||||||
|
func _on_MergeDist_value_changed(_value: float) -> void:
|
||||||
|
recycle_last_slice_result = false
|
||||||
|
|
||||||
|
|
||||||
|
func _on_Slice_pressed() -> void:
|
||||||
|
if !recycle_last_slice_result:
|
||||||
|
slice_preview()
|
||||||
|
update()
|
||||||
|
|
||||||
|
|
||||||
func _on_HorizontalFrames_value_changed(value: int) -> void:
|
func _on_HorizontalFrames_value_changed(value: int) -> void:
|
||||||
spritesheet_horizontal = value
|
spritesheet_horizontal = value
|
||||||
for child in texture_rect.get_node("HorizLines").get_children():
|
spritesheet_frame_value_changed()
|
||||||
child.queue_free()
|
|
||||||
|
|
||||||
spritesheet_frame_value_changed(value, false)
|
|
||||||
|
|
||||||
|
|
||||||
func _on_VerticalFrames_value_changed(value: int) -> void:
|
func _on_VerticalFrames_value_changed(value: int) -> void:
|
||||||
spritesheet_vertical = value
|
spritesheet_vertical = value
|
||||||
for child in texture_rect.get_node("VerticalLines").get_children():
|
spritesheet_frame_value_changed()
|
||||||
child.queue_free()
|
|
||||||
|
|
||||||
spritesheet_frame_value_changed(value, true)
|
|
||||||
|
|
||||||
|
|
||||||
func spritesheet_frame_value_changed(value: int, vertical: bool) -> void:
|
func spritesheet_frame_value_changed() -> void:
|
||||||
var image_size_y = texture_rect.rect_size.y
|
|
||||||
var image_size_x = texture_rect.rect_size.x
|
|
||||||
if image.get_size().x > image.get_size().y:
|
|
||||||
var scale_ratio = image.get_size().x / image_size_x
|
|
||||||
image_size_y = image.get_size().y / scale_ratio
|
|
||||||
else:
|
|
||||||
var scale_ratio = image.get_size().y / image_size_y
|
|
||||||
image_size_x = image.get_size().x / scale_ratio
|
|
||||||
|
|
||||||
var offset_x = (texture_rect.rect_size.x - image_size_x) / 2
|
|
||||||
var offset_y = (texture_rect.rect_size.y - image_size_y) / 2
|
|
||||||
|
|
||||||
if value > 1:
|
|
||||||
var line_distance
|
|
||||||
if vertical:
|
|
||||||
line_distance = image_size_y / value
|
|
||||||
else:
|
|
||||||
line_distance = image_size_x / value
|
|
||||||
|
|
||||||
for i in range(1, value):
|
|
||||||
var line_2d := Line2D.new()
|
|
||||||
line_2d.width = 1
|
|
||||||
line_2d.position = Vector2.ZERO
|
|
||||||
if vertical:
|
|
||||||
line_2d.add_point(Vector2(offset_x, i * line_distance + offset_y))
|
|
||||||
line_2d.add_point(Vector2(image_size_x + offset_x, i * line_distance + offset_y))
|
|
||||||
texture_rect.get_node("VerticalLines").add_child(line_2d)
|
|
||||||
else:
|
|
||||||
line_2d.add_point(Vector2(i * line_distance + offset_x, offset_y))
|
|
||||||
line_2d.add_point(Vector2(i * line_distance + offset_x, image_size_y + offset_y))
|
|
||||||
texture_rect.get_node("HorizLines").add_child(line_2d)
|
|
||||||
|
|
||||||
var frame_width = floor(image.get_size().x / spritesheet_horizontal)
|
var frame_width = floor(image.get_size().x / spritesheet_horizontal)
|
||||||
var frame_height = floor(image.get_size().y / spritesheet_vertical)
|
var frame_height = floor(image.get_size().y / spritesheet_vertical)
|
||||||
frame_size_label.text = tr("Frame Size") + ": " + str(frame_width) + "×" + str(frame_height)
|
frame_size_label.text = tr("Frame Size") + ": " + str(frame_width) + "×" + str(frame_height)
|
||||||
|
update()
|
||||||
|
|
||||||
|
|
||||||
func _on_BrushTypeOption_item_selected(index: int) -> void:
|
func _on_BrushTypeOption_item_selected(index: int) -> void:
|
||||||
|
@ -428,3 +471,21 @@ func file_name_replace(name: String, folder: String) -> String:
|
||||||
temp_name += "." + file_ext
|
temp_name += "." + file_ext
|
||||||
name = temp_name
|
name = temp_name
|
||||||
return name
|
return name
|
||||||
|
|
||||||
|
|
||||||
|
func _on_PreviewDialog_item_rect_changed() -> void:
|
||||||
|
update()
|
||||||
|
|
||||||
|
|
||||||
|
func _draw() -> void:
|
||||||
|
$"%SmartSlice".show_preview([])
|
||||||
|
$"%RowColumnLines".show_preview(1, 1)
|
||||||
|
if (
|
||||||
|
current_import_option == ImageImportOptions.SPRITESHEET_TAB
|
||||||
|
or current_import_option == ImageImportOptions.SPRITESHEET_LAYER
|
||||||
|
):
|
||||||
|
if smart_slice:
|
||||||
|
if "rects" in sliced_rects.keys():
|
||||||
|
$"%SmartSlice".show_preview(sliced_rects["rects"])
|
||||||
|
else:
|
||||||
|
$"%RowColumnLines".show_preview(spritesheet_vertical, spritesheet_horizontal)
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
[gd_scene load_steps=2 format=2]
|
[gd_scene load_steps=5 format=2]
|
||||||
|
|
||||||
[ext_resource path="res://src/UI/Dialogs/PreviewDialog.gd" type="Script" id=1]
|
[ext_resource path="res://src/UI/Dialogs/PreviewDialog.gd" type="Script" id=1]
|
||||||
|
[ext_resource path="res://src/UI/Nodes/ValueSlider.tscn" type="PackedScene" id=2]
|
||||||
|
[ext_resource path="res://src/UI/Dialogs/HelperScripts/SmartSlicePreview.gd" type="Script" id=3]
|
||||||
|
[ext_resource path="res://src/UI/Dialogs/HelperScripts/RowColumnLines.gd" type="Script" id=4]
|
||||||
|
|
||||||
[node name="PreviewDialog" type="ConfirmationDialog"]
|
[node name="PreviewDialog" type="ConfirmationDialog"]
|
||||||
margin_right = 550.0
|
margin_right = 629.0
|
||||||
margin_bottom = 410.0
|
margin_bottom = 513.0
|
||||||
rect_min_size = Vector2( 550, 70 )
|
rect_min_size = Vector2( 550, 70 )
|
||||||
popup_exclusive = true
|
popup_exclusive = true
|
||||||
window_title = "Import Options"
|
window_title = "Import Options"
|
||||||
resizable = true
|
resizable = true
|
||||||
script = ExtResource( 1 )
|
script = ExtResource( 1 )
|
||||||
__meta__ = {
|
|
||||||
"_edit_use_anchors_": false
|
|
||||||
}
|
|
||||||
|
|
||||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
|
@ -21,23 +21,16 @@ margin_left = 8.0
|
||||||
margin_top = 8.0
|
margin_top = 8.0
|
||||||
margin_right = -8.0
|
margin_right = -8.0
|
||||||
margin_bottom = -36.0
|
margin_bottom = -36.0
|
||||||
__meta__ = {
|
|
||||||
"_edit_use_anchors_": false
|
|
||||||
}
|
|
||||||
|
|
||||||
[node name="CenterContainer" type="CenterContainer" parent="VBoxContainer"]
|
[node name="CenterContainer" type="AspectRatioContainer" parent="VBoxContainer"]
|
||||||
margin_right = 534.0
|
margin_right = 613.0
|
||||||
margin_bottom = 324.0
|
margin_bottom = 395.0
|
||||||
size_flags_vertical = 3
|
size_flags_vertical = 3
|
||||||
__meta__ = {
|
|
||||||
"_edit_use_anchors_": false
|
|
||||||
}
|
|
||||||
|
|
||||||
[node name="TextureRect" type="TextureRect" parent="VBoxContainer/CenterContainer"]
|
[node name="TextureRect" type="TextureRect" parent="VBoxContainer/CenterContainer"]
|
||||||
margin_left = 117.0
|
margin_left = 109.0
|
||||||
margin_top = 12.0
|
margin_right = 504.0
|
||||||
margin_right = 417.0
|
margin_bottom = 395.0
|
||||||
margin_bottom = 312.0
|
|
||||||
rect_min_size = Vector2( 300, 300 )
|
rect_min_size = Vector2( 300, 300 )
|
||||||
expand = true
|
expand = true
|
||||||
stretch_mode = 6
|
stretch_mode = 6
|
||||||
|
@ -45,12 +38,15 @@ __meta__ = {
|
||||||
"_edit_use_anchors_": false
|
"_edit_use_anchors_": false
|
||||||
}
|
}
|
||||||
|
|
||||||
[node name="HorizLines" type="Control" parent="VBoxContainer/CenterContainer/TextureRect"]
|
[node name="RowColumnLines" type="Control" parent="VBoxContainer/CenterContainer/TextureRect"]
|
||||||
__meta__ = {
|
unique_name_in_owner = true
|
||||||
"_edit_use_anchors_": false
|
script = ExtResource( 4 )
|
||||||
}
|
|
||||||
|
|
||||||
[node name="VerticalLines" type="Control" parent="VBoxContainer/CenterContainer/TextureRect"]
|
[node name="SmartSlice" type="Control" parent="VBoxContainer/CenterContainer/TextureRect"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
script = ExtResource( 3 )
|
||||||
|
|
||||||
[node name="ApplyAll" type="CheckBox" parent="VBoxContainer"]
|
[node name="ApplyAll" type="CheckBox" parent="VBoxContainer"]
|
||||||
visible = false
|
visible = false
|
||||||
|
@ -60,9 +56,9 @@ margin_bottom = 324.0
|
||||||
text = "Apply to all"
|
text = "Apply to all"
|
||||||
|
|
||||||
[node name="SizeContainer" type="HBoxContainer" parent="VBoxContainer"]
|
[node name="SizeContainer" type="HBoxContainer" parent="VBoxContainer"]
|
||||||
margin_top = 328.0
|
margin_top = 399.0
|
||||||
margin_right = 534.0
|
margin_right = 613.0
|
||||||
margin_bottom = 342.0
|
margin_bottom = 413.0
|
||||||
custom_constants/separation = 32
|
custom_constants/separation = 32
|
||||||
|
|
||||||
[node name="ImageSizeLabel" type="Label" parent="VBoxContainer/SizeContainer"]
|
[node name="ImageSizeLabel" type="Label" parent="VBoxContainer/SizeContainer"]
|
||||||
|
@ -78,14 +74,14 @@ margin_bottom = 14.0
|
||||||
text = "Frame size: 64×64"
|
text = "Frame size: 64×64"
|
||||||
|
|
||||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
|
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
|
||||||
margin_top = 346.0
|
margin_top = 417.0
|
||||||
margin_right = 534.0
|
margin_right = 613.0
|
||||||
margin_bottom = 366.0
|
margin_bottom = 469.0
|
||||||
|
|
||||||
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer"]
|
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer"]
|
||||||
margin_top = 3.0
|
|
||||||
margin_right = 66.0
|
margin_right = 66.0
|
||||||
margin_bottom = 17.0
|
margin_bottom = 14.0
|
||||||
|
size_flags_vertical = 0
|
||||||
text = "Import as:"
|
text = "Import as:"
|
||||||
|
|
||||||
[node name="ImportOption" type="OptionButton" parent="VBoxContainer/HBoxContainer"]
|
[node name="ImportOption" type="OptionButton" parent="VBoxContainer/HBoxContainer"]
|
||||||
|
@ -93,21 +89,27 @@ margin_left = 70.0
|
||||||
margin_right = 151.0
|
margin_right = 151.0
|
||||||
margin_bottom = 20.0
|
margin_bottom = 20.0
|
||||||
mouse_default_cursor_shape = 2
|
mouse_default_cursor_shape = 2
|
||||||
|
size_flags_vertical = 0
|
||||||
text = "New tab"
|
text = "New tab"
|
||||||
|
|
||||||
[node name="SpritesheetTabOptions" type="HBoxContainer" parent="VBoxContainer/HBoxContainer"]
|
[node name="SpritesheetTabOptions" type="GridContainer" parent="VBoxContainer/HBoxContainer"]
|
||||||
visible = false
|
|
||||||
margin_left = 155.0
|
margin_left = 155.0
|
||||||
margin_right = 533.0
|
margin_right = 613.0
|
||||||
|
margin_bottom = 52.0
|
||||||
|
rect_min_size = Vector2( 380, 0 )
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="Manual" type="HBoxContainer" parent="VBoxContainer/HBoxContainer/SpritesheetTabOptions"]
|
||||||
|
margin_right = 378.0
|
||||||
margin_bottom = 24.0
|
margin_bottom = 24.0
|
||||||
|
|
||||||
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer/SpritesheetTabOptions"]
|
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer/SpritesheetTabOptions/Manual"]
|
||||||
margin_top = 5.0
|
margin_top = 5.0
|
||||||
margin_right = 118.0
|
margin_right = 118.0
|
||||||
margin_bottom = 19.0
|
margin_bottom = 19.0
|
||||||
text = "Horizontal frames:"
|
text = "Horizontal frames:"
|
||||||
|
|
||||||
[node name="HorizontalFrames" type="SpinBox" parent="VBoxContainer/HBoxContainer/SpritesheetTabOptions"]
|
[node name="HorizontalFrames" type="SpinBox" parent="VBoxContainer/HBoxContainer/SpritesheetTabOptions/Manual"]
|
||||||
margin_left = 122.0
|
margin_left = 122.0
|
||||||
margin_right = 196.0
|
margin_right = 196.0
|
||||||
margin_bottom = 24.0
|
margin_bottom = 24.0
|
||||||
|
@ -115,14 +117,14 @@ mouse_default_cursor_shape = 2
|
||||||
min_value = 1.0
|
min_value = 1.0
|
||||||
value = 1.0
|
value = 1.0
|
||||||
|
|
||||||
[node name="Label2" type="Label" parent="VBoxContainer/HBoxContainer/SpritesheetTabOptions"]
|
[node name="Label2" type="Label" parent="VBoxContainer/HBoxContainer/SpritesheetTabOptions/Manual"]
|
||||||
margin_left = 200.0
|
margin_left = 200.0
|
||||||
margin_top = 5.0
|
margin_top = 5.0
|
||||||
margin_right = 300.0
|
margin_right = 300.0
|
||||||
margin_bottom = 19.0
|
margin_bottom = 19.0
|
||||||
text = "Vertical frames:"
|
text = "Vertical frames:"
|
||||||
|
|
||||||
[node name="VerticalFrames" type="SpinBox" parent="VBoxContainer/HBoxContainer/SpritesheetTabOptions"]
|
[node name="VerticalFrames" type="SpinBox" parent="VBoxContainer/HBoxContainer/SpritesheetTabOptions/Manual"]
|
||||||
margin_left = 304.0
|
margin_left = 304.0
|
||||||
margin_right = 378.0
|
margin_right = 378.0
|
||||||
margin_bottom = 24.0
|
margin_bottom = 24.0
|
||||||
|
@ -130,11 +132,47 @@ mouse_default_cursor_shape = 2
|
||||||
min_value = 1.0
|
min_value = 1.0
|
||||||
value = 1.0
|
value = 1.0
|
||||||
|
|
||||||
|
[node name="Smart" type="HBoxContainer" parent="VBoxContainer/HBoxContainer/SpritesheetTabOptions"]
|
||||||
|
visible = false
|
||||||
|
margin_top = 28.0
|
||||||
|
margin_right = 458.0
|
||||||
|
margin_bottom = 52.0
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="Threshold" parent="VBoxContainer/HBoxContainer/SpritesheetTabOptions/Smart" instance=ExtResource( 2 )]
|
||||||
|
margin_right = 195.0
|
||||||
|
hint_tooltip = "Image having any one side smaller than this value will closs the threshold"
|
||||||
|
min_value = 1.0
|
||||||
|
value = 10.0
|
||||||
|
allow_greater = true
|
||||||
|
prefix = "Threshold:"
|
||||||
|
|
||||||
|
[node name="MergeDist" parent="VBoxContainer/HBoxContainer/SpritesheetTabOptions/Smart" instance=ExtResource( 2 )]
|
||||||
|
margin_left = 199.0
|
||||||
|
margin_right = 394.0
|
||||||
|
hint_tooltip = "image (that crossed the threshold), this distance apart to any other image
|
||||||
|
will get merged with that image"
|
||||||
|
value = 3.0
|
||||||
|
prefix = "Merge distance:"
|
||||||
|
|
||||||
|
[node name="Slice" type="Button" parent="VBoxContainer/HBoxContainer/SpritesheetTabOptions/Smart"]
|
||||||
|
margin_left = 398.0
|
||||||
|
margin_right = 458.0
|
||||||
|
margin_bottom = 24.0
|
||||||
|
text = "Refresh"
|
||||||
|
|
||||||
|
[node name="SmartSlice" type="CheckBox" parent="VBoxContainer/HBoxContainer/SpritesheetTabOptions"]
|
||||||
|
margin_top = 28.0
|
||||||
|
margin_right = 378.0
|
||||||
|
margin_bottom = 52.0
|
||||||
|
text = "Smart Slice"
|
||||||
|
|
||||||
[node name="SpritesheetLayerOptions" type="HBoxContainer" parent="VBoxContainer/HBoxContainer"]
|
[node name="SpritesheetLayerOptions" type="HBoxContainer" parent="VBoxContainer/HBoxContainer"]
|
||||||
visible = false
|
visible = false
|
||||||
margin_left = 155.0
|
margin_left = 155.0
|
||||||
margin_right = 307.0
|
margin_right = 307.0
|
||||||
margin_bottom = 24.0
|
margin_bottom = 24.0
|
||||||
|
size_flags_vertical = 0
|
||||||
|
|
||||||
[node name="Label3" type="Label" parent="VBoxContainer/HBoxContainer/SpritesheetLayerOptions"]
|
[node name="Label3" type="Label" parent="VBoxContainer/HBoxContainer/SpritesheetLayerOptions"]
|
||||||
margin_top = 5.0
|
margin_top = 5.0
|
||||||
|
@ -264,9 +302,14 @@ margin_bottom = 24.0
|
||||||
|
|
||||||
[connection signal="about_to_show" from="." to="." method="_on_PreviewDialog_about_to_show"]
|
[connection signal="about_to_show" from="." to="." method="_on_PreviewDialog_about_to_show"]
|
||||||
[connection signal="confirmed" from="." to="." method="_on_PreviewDialog_confirmed"]
|
[connection signal="confirmed" from="." to="." method="_on_PreviewDialog_confirmed"]
|
||||||
|
[connection signal="item_rect_changed" from="." to="." method="_on_PreviewDialog_item_rect_changed"]
|
||||||
[connection signal="popup_hide" from="." to="." method="_on_PreviewDialog_popup_hide"]
|
[connection signal="popup_hide" from="." to="." method="_on_PreviewDialog_popup_hide"]
|
||||||
[connection signal="toggled" from="VBoxContainer/ApplyAll" to="." method="_on_ApplyAll_toggled"]
|
[connection signal="toggled" from="VBoxContainer/ApplyAll" to="." method="_on_ApplyAll_toggled"]
|
||||||
[connection signal="item_selected" from="VBoxContainer/HBoxContainer/ImportOption" to="." method="_on_ImportOption_item_selected"]
|
[connection signal="item_selected" from="VBoxContainer/HBoxContainer/ImportOption" to="." method="_on_ImportOption_item_selected"]
|
||||||
[connection signal="value_changed" from="VBoxContainer/HBoxContainer/SpritesheetTabOptions/HorizontalFrames" to="." method="_on_HorizontalFrames_value_changed"]
|
[connection signal="value_changed" from="VBoxContainer/HBoxContainer/SpritesheetTabOptions/Manual/HorizontalFrames" to="." method="_on_HorizontalFrames_value_changed"]
|
||||||
[connection signal="value_changed" from="VBoxContainer/HBoxContainer/SpritesheetTabOptions/VerticalFrames" to="." method="_on_VerticalFrames_value_changed"]
|
[connection signal="value_changed" from="VBoxContainer/HBoxContainer/SpritesheetTabOptions/Manual/VerticalFrames" to="." method="_on_VerticalFrames_value_changed"]
|
||||||
|
[connection signal="value_changed" from="VBoxContainer/HBoxContainer/SpritesheetTabOptions/Smart/Threshold" to="." method="_on_Threshold_value_changed"]
|
||||||
|
[connection signal="value_changed" from="VBoxContainer/HBoxContainer/SpritesheetTabOptions/Smart/MergeDist" to="." method="_on_MergeDist_value_changed"]
|
||||||
|
[connection signal="pressed" from="VBoxContainer/HBoxContainer/SpritesheetTabOptions/Smart/Slice" to="." method="_on_Slice_pressed"]
|
||||||
|
[connection signal="toggled" from="VBoxContainer/HBoxContainer/SpritesheetTabOptions/SmartSlice" to="." method="_on_SmartSlice_toggled"]
|
||||||
[connection signal="item_selected" from="VBoxContainer/HBoxContainer/NewBrushOptions/BrushTypeOption" to="." method="_on_BrushTypeOption_item_selected"]
|
[connection signal="item_selected" from="VBoxContainer/HBoxContainer/NewBrushOptions/BrushTypeOption" to="." method="_on_BrushTypeOption_item_selected"]
|
||||||
|
|
Loading…
Reference in a new issue