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).
|
||||
- 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",
|
||||
"path": "res://src/UI/ReferenceImages/ReferencesPanel.gd"
|
||||
}, {
|
||||
"base": "Reference",
|
||||
"class": "RegionUnpacker",
|
||||
"language": "GDScript",
|
||||
"path": "res://addons/SmartSlicer/Classes/RegionUnpacker.gd"
|
||||
}, {
|
||||
"base": "Image",
|
||||
"class": "SelectionMap",
|
||||
"language": "GDScript",
|
||||
|
@ -313,6 +318,7 @@ _global_script_class_icons={
|
|||
"Project": "",
|
||||
"ReferenceImage": "",
|
||||
"ReferencesPanel": "",
|
||||
"RegionUnpacker": "",
|
||||
"SelectionMap": "",
|
||||
"SelectionTool": "",
|
||||
"ShaderImageEffect": "",
|
||||
|
|
|
@ -470,14 +470,35 @@ func open_image_as_new_tab(path: String, image: Image) -> void:
|
|||
set_new_imported_tab(project, path)
|
||||
|
||||
|
||||
func open_image_as_spritesheet_tab(path: String, image: Image, horiz: int, vert: int) -> void:
|
||||
var project := Project.new([], path.get_file())
|
||||
func open_image_as_spritesheet_tab_smart(
|
||||
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))
|
||||
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)
|
||||
vert = min(vert, image.get_size().y)
|
||||
var frame_width := image.get_size().x / horiz
|
||||
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 xx in range(horiz):
|
||||
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)
|
||||
|
||||
|
||||
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(
|
||||
_path: String, image: Image, file_name: String, horizontal: int, vertical: int, start_frame: int
|
||||
) -> void:
|
||||
|
@ -547,12 +645,14 @@ func open_image_as_spritesheet_layer(
|
|||
# Slice spritesheet
|
||||
var xx: int = (f - start_frame) % horizontal
|
||||
var yy: int = (f - start_frame) / horizontal
|
||||
image.convert(Image.FORMAT_RGBA8)
|
||||
var cropped_image := Image.new()
|
||||
cropped_image = image.get_rect(
|
||||
Rect2(frame_width * xx, frame_height * yy, frame_width, frame_height)
|
||||
cropped_image.create(project_width, project_height, false, Image.FORMAT_RGBA8)
|
||||
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))
|
||||
else:
|
||||
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():
|
||||
if i == frame_index:
|
||||
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]
|
||||
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_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():
|
||||
if i == layer_index:
|
||||
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:
|
||||
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():
|
||||
if i == frame_index:
|
||||
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:
|
||||
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 image: Image
|
||||
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_vertical := 1
|
||||
var brush_type: int = BrushTypes.FILE
|
||||
var opened_once = false
|
||||
var is_master: 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 image_size_label: Label = $VBoxContainer/SizeContainer/ImageSizeLabel
|
||||
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 new_frame_options = $VBoxContainer/HBoxContainer/NewFrameOptions
|
||||
onready var replace_cel_options = $VBoxContainer/HBoxContainer/ReplaceCelOptions
|
||||
|
@ -62,11 +73,11 @@ func _on_PreviewDialog_about_to_show() -> void:
|
|||
var img_texture := ImageTexture.new()
|
||||
img_texture.create_from_image(image, 0)
|
||||
texture_rect.texture = img_texture
|
||||
spritesheet_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 = min(
|
||||
spritesheet_manual_tab_options.get_node("HorizontalFrames").max_value, image.get_size().x
|
||||
)
|
||||
spritesheet_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 = min(
|
||||
spritesheet_manual_tab_options.get_node("VerticalFrames").max_value, image.get_size().y
|
||||
)
|
||||
image_size_label.text = (
|
||||
tr("Image Size")
|
||||
|
@ -115,20 +126,39 @@ func _on_PreviewDialog_confirmed() -> void:
|
|||
OpenSave.open_image_as_new_tab(path, image)
|
||||
|
||||
elif current_import_option == ImageImportOptions.SPRITESHEET_TAB:
|
||||
OpenSave.open_image_as_spritesheet_tab(
|
||||
path, image, spritesheet_horizontal, spritesheet_vertical
|
||||
)
|
||||
if smart_slice:
|
||||
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:
|
||||
var frame_index: int = spritesheet_lay_opt.get_node("AtFrameSpinbox").value - 1
|
||||
OpenSave.open_image_as_spritesheet_layer(
|
||||
path,
|
||||
image,
|
||||
path.get_basename().get_file(),
|
||||
spritesheet_horizontal,
|
||||
spritesheet_vertical,
|
||||
frame_index
|
||||
)
|
||||
if smart_slice:
|
||||
if !recycle_last_slice_result:
|
||||
obtain_sliced_data()
|
||||
OpenSave.open_image_as_spritesheet_layer_smart(
|
||||
path,
|
||||
image,
|
||||
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:
|
||||
var layer_index: int = new_frame_options.get_node("AtLayerOption").get_selected_id()
|
||||
|
@ -200,11 +230,13 @@ func synchronize() -> void:
|
|||
id == ImageImportOptions.SPRITESHEET_TAB
|
||||
or id == ImageImportOptions.SPRITESHEET_LAYER
|
||||
):
|
||||
dialog.spritesheet_tab_options.get_node("HorizontalFrames").value = min(
|
||||
spritesheet_tab_options.get_node("HorizontalFrames").value, image.get_size().x
|
||||
dialog.spritesheet_manual_tab_options.get_node("HorizontalFrames").value = min(
|
||||
spritesheet_manual_tab_options.get_node("HorizontalFrames").value,
|
||||
image.get_size().x
|
||||
)
|
||||
dialog.spritesheet_tab_options.get_node("VerticalFrames").value = min(
|
||||
spritesheet_tab_options.get_node("VerticalFrames").value, image.get_size().y
|
||||
dialog.spritesheet_manual_tab_options.get_node("VerticalFrames").value = min(
|
||||
spritesheet_manual_tab_options.get_node("VerticalFrames").value,
|
||||
image.get_size().y
|
||||
)
|
||||
if id == ImageImportOptions.SPRITESHEET_LAYER:
|
||||
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:
|
||||
current_import_option = id
|
||||
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_lay_opt.visible = false
|
||||
new_frame_options.visible = false
|
||||
|
@ -249,22 +284,21 @@ func _on_ImportOption_item_selected(id: int) -> void:
|
|||
new_brush_options.visible = false
|
||||
texture_rect.get_child(0).visible = false
|
||||
texture_rect.get_child(1).visible = false
|
||||
rect_size.x = 550
|
||||
|
||||
if id == ImageImportOptions.SPRITESHEET_TAB:
|
||||
frame_size_label.visible = true
|
||||
smart_slice_checkbox.visible = true
|
||||
spritesheet_tab_options.visible = true
|
||||
texture_rect.get_child(0).visible = true
|
||||
texture_rect.get_child(1).visible = true
|
||||
rect_size.x = spritesheet_tab_options.rect_size.x
|
||||
|
||||
elif id == ImageImportOptions.SPRITESHEET_LAYER:
|
||||
frame_size_label.visible = true
|
||||
spritesheet_tab_options.visible = true
|
||||
smart_slice_checkbox.visible = true
|
||||
spritesheet_lay_opt.visible = true
|
||||
spritesheet_tab_options.visible = true
|
||||
texture_rect.get_child(0).visible = true
|
||||
texture_rect.get_child(1).visible = true
|
||||
rect_size.x = spritesheet_lay_opt.rect_size.x
|
||||
|
||||
elif id == ImageImportOptions.NEW_FRAME:
|
||||
new_frame_options.visible = true
|
||||
|
@ -306,59 +340,68 @@ func _on_ImportOption_item_selected(id: int) -> void:
|
|||
elif id == ImageImportOptions.BRUSH:
|
||||
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:
|
||||
spritesheet_horizontal = value
|
||||
for child in texture_rect.get_node("HorizLines").get_children():
|
||||
child.queue_free()
|
||||
|
||||
spritesheet_frame_value_changed(value, false)
|
||||
spritesheet_frame_value_changed()
|
||||
|
||||
|
||||
func _on_VerticalFrames_value_changed(value: int) -> void:
|
||||
spritesheet_vertical = value
|
||||
for child in texture_rect.get_node("VerticalLines").get_children():
|
||||
child.queue_free()
|
||||
|
||||
spritesheet_frame_value_changed(value, true)
|
||||
spritesheet_frame_value_changed()
|
||||
|
||||
|
||||
func spritesheet_frame_value_changed(value: int, vertical: bool) -> 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)
|
||||
|
||||
func spritesheet_frame_value_changed() -> void:
|
||||
var frame_width = floor(image.get_size().x / spritesheet_horizontal)
|
||||
var frame_height = floor(image.get_size().y / spritesheet_vertical)
|
||||
frame_size_label.text = tr("Frame Size") + ": " + str(frame_width) + "×" + str(frame_height)
|
||||
update()
|
||||
|
||||
|
||||
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
|
||||
name = temp_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/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"]
|
||||
margin_right = 550.0
|
||||
margin_bottom = 410.0
|
||||
margin_right = 629.0
|
||||
margin_bottom = 513.0
|
||||
rect_min_size = Vector2( 550, 70 )
|
||||
popup_exclusive = true
|
||||
window_title = "Import Options"
|
||||
resizable = true
|
||||
script = ExtResource( 1 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
anchor_right = 1.0
|
||||
|
@ -21,23 +21,16 @@ margin_left = 8.0
|
|||
margin_top = 8.0
|
||||
margin_right = -8.0
|
||||
margin_bottom = -36.0
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="CenterContainer" type="CenterContainer" parent="VBoxContainer"]
|
||||
margin_right = 534.0
|
||||
margin_bottom = 324.0
|
||||
[node name="CenterContainer" type="AspectRatioContainer" parent="VBoxContainer"]
|
||||
margin_right = 613.0
|
||||
margin_bottom = 395.0
|
||||
size_flags_vertical = 3
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="VBoxContainer/CenterContainer"]
|
||||
margin_left = 117.0
|
||||
margin_top = 12.0
|
||||
margin_right = 417.0
|
||||
margin_bottom = 312.0
|
||||
margin_left = 109.0
|
||||
margin_right = 504.0
|
||||
margin_bottom = 395.0
|
||||
rect_min_size = Vector2( 300, 300 )
|
||||
expand = true
|
||||
stretch_mode = 6
|
||||
|
@ -45,12 +38,15 @@ __meta__ = {
|
|||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="HorizLines" type="Control" parent="VBoxContainer/CenterContainer/TextureRect"]
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
[node name="RowColumnLines" type="Control" parent="VBoxContainer/CenterContainer/TextureRect"]
|
||||
unique_name_in_owner = true
|
||||
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"]
|
||||
visible = false
|
||||
|
@ -60,9 +56,9 @@ margin_bottom = 324.0
|
|||
text = "Apply to all"
|
||||
|
||||
[node name="SizeContainer" type="HBoxContainer" parent="VBoxContainer"]
|
||||
margin_top = 328.0
|
||||
margin_right = 534.0
|
||||
margin_bottom = 342.0
|
||||
margin_top = 399.0
|
||||
margin_right = 613.0
|
||||
margin_bottom = 413.0
|
||||
custom_constants/separation = 32
|
||||
|
||||
[node name="ImageSizeLabel" type="Label" parent="VBoxContainer/SizeContainer"]
|
||||
|
@ -78,14 +74,14 @@ margin_bottom = 14.0
|
|||
text = "Frame size: 64×64"
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
|
||||
margin_top = 346.0
|
||||
margin_right = 534.0
|
||||
margin_bottom = 366.0
|
||||
margin_top = 417.0
|
||||
margin_right = 613.0
|
||||
margin_bottom = 469.0
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer"]
|
||||
margin_top = 3.0
|
||||
margin_right = 66.0
|
||||
margin_bottom = 17.0
|
||||
margin_bottom = 14.0
|
||||
size_flags_vertical = 0
|
||||
text = "Import as:"
|
||||
|
||||
[node name="ImportOption" type="OptionButton" parent="VBoxContainer/HBoxContainer"]
|
||||
|
@ -93,21 +89,27 @@ margin_left = 70.0
|
|||
margin_right = 151.0
|
||||
margin_bottom = 20.0
|
||||
mouse_default_cursor_shape = 2
|
||||
size_flags_vertical = 0
|
||||
text = "New tab"
|
||||
|
||||
[node name="SpritesheetTabOptions" type="HBoxContainer" parent="VBoxContainer/HBoxContainer"]
|
||||
visible = false
|
||||
[node name="SpritesheetTabOptions" type="GridContainer" parent="VBoxContainer/HBoxContainer"]
|
||||
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
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer/SpritesheetTabOptions"]
|
||||
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer/SpritesheetTabOptions/Manual"]
|
||||
margin_top = 5.0
|
||||
margin_right = 118.0
|
||||
margin_bottom = 19.0
|
||||
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_right = 196.0
|
||||
margin_bottom = 24.0
|
||||
|
@ -115,14 +117,14 @@ mouse_default_cursor_shape = 2
|
|||
min_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_top = 5.0
|
||||
margin_right = 300.0
|
||||
margin_bottom = 19.0
|
||||
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_right = 378.0
|
||||
margin_bottom = 24.0
|
||||
|
@ -130,11 +132,47 @@ mouse_default_cursor_shape = 2
|
|||
min_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"]
|
||||
visible = false
|
||||
margin_left = 155.0
|
||||
margin_right = 307.0
|
||||
margin_bottom = 24.0
|
||||
size_flags_vertical = 0
|
||||
|
||||
[node name="Label3" type="Label" parent="VBoxContainer/HBoxContainer/SpritesheetLayerOptions"]
|
||||
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="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="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="value_changed" from="VBoxContainer/HBoxContainer/SpritesheetTabOptions/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/HorizontalFrames" to="." method="_on_HorizontalFrames_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"]
|
||||
|
|
Loading…
Reference in a new issue