Skip to content

Commit bb3eefa

Browse files
committed
Add saving to the application
1 parent f1355ec commit bb3eefa

3 files changed

Lines changed: 110 additions & 11 deletions

File tree

scripts/Application.gd

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ var currently_loaded_files: Array[ProcessedImage] = []
2424
var current_loading_index := 0
2525
var currently_active_item: ProcessedImage
2626

27-
var output_path: String = ""
27+
var output_path: String = "./"
2828
var current_file_format : FILE_FORMATS = FILE_FORMATS.SAME_AS_INPUT
2929

3030
# Possible entries {directory_tree}, {file},{extension}, {index}, {width}, {height}, {rotations}, {rect.x}, {rect.y}, {rect.width}, {rect.height}
@@ -45,6 +45,11 @@ var current_active_index := -1 :
4545
currently_active_item = currently_loaded_files[current_active_index]
4646
active_item_changed.emit(currently_active_item)
4747
currently_active_item.is_active = true
48+
return
49+
50+
# If we are at the last item, emit it again so that it gets saved
51+
if value == currently_loaded_files.size():
52+
active_item_changed.emit(currently_active_item)
4853

4954
var thread_polling_timer: Timer
5055
var thread_workers: Array[ImageThreadWorker] = []
@@ -130,6 +135,14 @@ func threaded_crop_image(image: Image, size: Rect2i, callback: Callable) -> void
130135
})
131136
var worker := select_worker()
132137
worker.add_item_to_queue(worktask)
138+
139+
func threaded_save_image(image: Image, path: String, callback: Callable) -> void:
140+
141+
var worktask := ImageThreadWorkerTask.new(callback, path, ImageThreadWorkerTask.WORKER_TASKS.STORE, {
142+
"image": image,
143+
})
144+
var worker := select_worker()
145+
worker.add_item_to_queue(worktask)
133146

134147
func select_worker() -> ImageThreadWorker:
135148
# Find a non working thread
@@ -218,6 +231,9 @@ class ImageThreadWorker extends Resource:
218231
ImageThreadWorkerTask.WORKER_TASKS.CROP:
219232
var image := thread.wait_to_finish() as Image
220233
current_work_item.callback.call(image)
234+
ImageThreadWorkerTask.WORKER_TASKS.STORE:
235+
var status := thread.wait_to_finish() as int
236+
current_work_item.callback.call(status)
221237

222238
current_work_item = null
223239
check_for_work()
@@ -237,6 +253,12 @@ class ImageThreadWorker extends Resource:
237253
current_work_item.data_playload["size"],
238254
current_work_item.data_playload["crop_size"],
239255
))
256+
257+
ImageThreadWorkerTask.WORKER_TASKS.STORE:
258+
thread.start(save_image.bind(
259+
current_work_item.data_playload["image"],
260+
current_work_item.path
261+
))
240262

241263

242264
func load_image_texture(path: String) -> ImageTexture:
@@ -261,3 +283,22 @@ class ImageThreadWorker extends Resource:
261283
new_image.resize(size.x, size.y, Image.INTERPOLATE_LANCZOS)
262284

263285
return new_image
286+
287+
func save_image(image: Image, path: String) -> int:
288+
289+
# Get the extension of our image
290+
var extension := path.get_extension().to_lower()
291+
292+
# Recursively ensure that the directory exists
293+
DirAccess.make_dir_recursive_absolute(path.get_base_dir())
294+
295+
# Based on the extension call the different saving routines
296+
match extension:
297+
"png":
298+
return image.save_png(path)
299+
"jpg", "jpeg":
300+
return image.save_jpg(path, 0.8)
301+
"webp":
302+
return image.save_webp(path)
303+
304+
return 0

scripts/ImageScrollManager.gd

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,16 @@ func _ready() -> void:
2828

2929
resized.connect(func(): old_scroll_position = -1)
3030

31-
Application.active_item_changed.connect(func(item: ProcessedImage): scroll_horizontal = (item.index - (visible_items / 2)) * item_width)
31+
Application.active_item_changed.connect(on_active_item_changed)
3232

3333
image_update_timer = Timer.new()
3434
image_update_timer.wait_time = 0.25
3535
add_child(image_update_timer)
3636
image_update_timer.timeout.connect(load_and_unload_images)
3737
image_update_timer.start()
38+
39+
func on_active_item_changed(item: ProcessedImage) -> void:
40+
scroll_horizontal = (item.index - (visible_items / 2)) * item_width
3841

3942
func load_and_unload_images():
4043

scripts/ProcessedImage.gd

Lines changed: 64 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ signal was_unloaded
88
signal active_changed
99

1010
var source_path: String
11-
var processed_path: String
11+
var processed_path: String = ""
1212
var is_currently_loaded := false
1313
var was_already_exported := false
1414
var relative_path: String
@@ -19,7 +19,8 @@ var preview_texture : Texture2D = null
1919
var index: int = 0
2020
var total_rotations: int = 0
2121

22-
var is_loading_image: bool = false
22+
var is_loading_images: int = 0
23+
var is_exporting_image: bool = false
2324

2425
var exported_section: Rect2i = Rect2i()
2526

@@ -38,18 +39,32 @@ func load_image() -> void:
3839
if original_texture != null and preview_texture != null:
3940
return
4041

41-
if is_loading_image:
42+
if is_loading_images != 0:
4243
return
4344

44-
is_loading_image = true
45+
if processed_path != "":
46+
is_loading_images = 2
47+
else:
48+
is_loading_images = 1
4549

4650
Application.threaded_load_image(
4751
source_path,
4852
func(image: ImageTexture):
4953
original_texture = image
50-
was_loaded.emit()
51-
is_loading_image = false
54+
is_loading_images -= 1
55+
if is_loading_images == 0:
56+
was_loaded.emit()
5257
)
58+
59+
if processed_path != "":
60+
Application.threaded_load_image(
61+
processed_path,
62+
func(image: ImageTexture):
63+
preview_texture = image
64+
is_loading_images -= 1
65+
if is_loading_images == 0:
66+
was_loaded.emit()
67+
)
5368

5469
func rotate(direction: int) -> void:
5570
if original_texture == null:
@@ -75,18 +90,58 @@ func unload_image() -> bool:
7590

7691
func export_image(position: Rect2i) -> void:
7792

78-
exported_section = position
93+
# If the crop region didn't change, we do not need to do anything
94+
if position == exported_section:
95+
return
96+
97+
if is_exporting_image:
98+
return
7999

80100
# We now have our image section, where we extract a new image
81101
if original_texture != null:
82102

103+
is_exporting_image = true
104+
83105
Application.threaded_crop_image(
84106
original_texture.get_image(),
85107
position,
86108
func(image: Image):
87109
preview_texture = ImageTexture.create_from_image(image)
88-
was_already_exported = true
89-
was_exported.emit()
110+
111+
# Create a formatted file name for the image
112+
var file_name : String = Application.output_template.format({
113+
"directory_tree": relative_path.get_base_dir().path_join(""),
114+
"file": relative_path.get_file().trim_suffix("." + relative_path.get_extension()),
115+
"extension": relative_path.get_extension() if Application.current_file_format == 0 else ["png", "jpg", "webp"][Application.current_file_format - 1],
116+
"index": index,
117+
"width": Application.crop_to_size.x,
118+
"height": Application.crop_to_size.x,
119+
"rotations": total_rotations,
120+
"rect.x": exported_section.position.x,
121+
"rect.y": exported_section.position.y,
122+
"rect.width": exported_section.size.x,
123+
"rect.height": exported_section.size.y,
124+
})
125+
126+
# Remove leading / from the filename and join the path with the output directory
127+
var path := Application.output_path.path_join(file_name.trim_prefix("/"))
128+
129+
exported_section = position
130+
131+
Application.threaded_save_image(
132+
image,
133+
path,
134+
func(status: int):
135+
136+
is_exporting_image = false
137+
138+
if status == OK:
139+
processed_path = path
140+
was_already_exported = true
141+
was_exported.emit()
142+
else:
143+
print("File - " + path + " - was exported unsuccessfully - status: " + error_string(status))
144+
)
90145
)
91146

92147

0 commit comments

Comments
 (0)