Skip to content

Commit b419356

Browse files
committed
Generate cropped image, stepping forward + backward, allow image rotate
1 parent 8d33a1a commit b419356

4 files changed

Lines changed: 156 additions & 14 deletions

File tree

MainApplication.tscn

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,26 @@
1-
[gd_scene load_steps=23 format=3 uid="uid://dobe3b68py6dw"]
1+
[gd_scene load_steps=24 format=3 uid="uid://dobe3b68py6dw"]
22

33
[ext_resource type="Theme" uid="uid://bbsoqdy0nn2wf" path="res://Application.theme" id="1_wph70"]
44
[ext_resource type="Script" path="res://scripts/ImageScrollManager.gd" id="2_i2scm"]
55
[ext_resource type="Script" path="res://scripts/ImageViewManager.gd" id="3_ncvqp"]
66
[ext_resource type="FontFile" uid="uid://dinyhiwiyllcs" path="res://assets/fonts/cantarell/Cantarell-Bold.ttf" id="11_ow6c5"]
77
[ext_resource type="Theme" uid="uid://bf7i7i8nhw47o" path="res://FileDialog.theme" id="12_fmmtm"]
88

9+
[sub_resource type="GDScript" id="GDScript_kfh2f"]
10+
script/source = "extends VSplitContainer
11+
12+
13+
# Called when the node enters the scene tree for the first time.
14+
func _ready() -> void:
15+
pass # Replace with function body.
16+
17+
18+
func _gui_input(event: InputEvent) -> void:
19+
# When this part is clicked, unfocus all GUI elements
20+
if get_viewport().gui_get_focus_owner() != null:
21+
get_viewport().gui_get_focus_owner().release_focus()
22+
"
23+
924
[sub_resource type="GDScript" id="GDScript_byb03"]
1025
script/source = "extends AspectRatioContainer
1126

@@ -21,7 +36,7 @@ func _ready() -> void:
2136
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_65bg7"]
2237
bg_color = Color(0.6, 0.6, 0.6, 0)
2338
border_width_left = 1024
24-
border_width_top = 1000
39+
border_width_top = 1024
2540
border_width_right = 1024
2641
border_width_bottom = 1024
2742
border_color = Color(0, 0, 0, 0.568627)
@@ -54,6 +69,12 @@ func _ready() -> void:
5469
func(value : float):
5570
Application.crop_to_size.x = int(value)
5671
)
72+
73+
func _gui_input(event: InputEvent) -> void:
74+
# On Escape release focus if owned
75+
if event is InputEventKey:
76+
if event.keycode == KEY_ESCAPE and has_focus():
77+
release_focus()
5778
"
5879

5980
[sub_resource type="GDScript" id="GDScript_xdud2"]
@@ -71,6 +92,13 @@ func _ready() -> void:
7192
func(value : float):
7293
Application.crop_to_size.y = int(value)
7394
)
95+
96+
97+
func _gui_input(event: InputEvent) -> void:
98+
# On Escape release focus if owned
99+
if event is InputEventKey:
100+
if event.keycode == KEY_ESCAPE and has_focus():
101+
release_focus()
74102
"
75103

76104
[sub_resource type="Image" id="Image_fy5dq"]
@@ -153,6 +181,7 @@ func _ready() -> void:
153181
func(dir: String):
154182
input_path.text = dir
155183
Application.entry_path = dir
184+
release_focus()
156185
)
157186

158187
"
@@ -169,6 +198,12 @@ func _ready() -> void:
169198

170199
toggled.connect(func(value: bool): Application.load_subdirectories = value)
171200

201+
202+
func _gui_input(event: InputEvent) -> void:
203+
# On Escape release focus if owned
204+
if event is InputEventKey:
205+
if event.keycode == KEY_ESCAPE and has_focus():
206+
release_focus()
172207
"
173208

174209
[node name="MainPanel" type="PanelContainer"]
@@ -177,6 +212,7 @@ anchor_right = 1.0
177212
anchor_bottom = 1.0
178213
grow_horizontal = 2
179214
grow_vertical = 2
215+
mouse_filter = 1
180216
theme = ExtResource("1_wph70")
181217

182218
[node name="HSplitContainer" type="HSplitContainer" parent="."]
@@ -188,6 +224,7 @@ size_flags_horizontal = 3
188224
theme_override_constants/separation = 8
189225
theme_override_constants/minimum_grab_thickness = 20
190226
theme_override_constants/autohide = 0
227+
script = SubResource("GDScript_kfh2f")
191228

192229
[node name="CenterContainer" type="PanelContainer" parent="HSplitContainer/VSplitContainer"]
193230
clip_contents = true
@@ -206,16 +243,23 @@ layout_mode = 2
206243
mouse_filter = 1
207244

208245
[node name="Sprite2D" type="Sprite2D" parent="HSplitContainer/VSplitContainer/CenterContainer/CenterContainer/Control"]
209-
scale = Vector2(0.322, 0.322)
246+
scale = Vector2(4.272, 4.272)
210247
script = ExtResource("3_ncvqp")
211248

212-
[node name="AspectRatioContainer" type="AspectRatioContainer" parent="HSplitContainer/VSplitContainer/CenterContainer"]
249+
[node name="MarginContainer" type="MarginContainer" parent="HSplitContainer/VSplitContainer/CenterContainer"]
213250
clip_contents = true
214251
layout_mode = 2
252+
theme_override_constants/margin_left = 30
253+
theme_override_constants/margin_top = 30
254+
theme_override_constants/margin_right = 30
255+
theme_override_constants/margin_bottom = 30
256+
257+
[node name="AspectRatioContainer" type="AspectRatioContainer" parent="HSplitContainer/VSplitContainer/CenterContainer/MarginContainer"]
258+
layout_mode = 2
215259
mouse_filter = 2
216260
script = SubResource("GDScript_byb03")
217261

218-
[node name="CutOutArea" type="Panel" parent="HSplitContainer/VSplitContainer/CenterContainer/AspectRatioContainer"]
262+
[node name="CutOutArea" type="Panel" parent="HSplitContainer/VSplitContainer/CenterContainer/MarginContainer/AspectRatioContainer"]
219263
unique_name_in_owner = true
220264
layout_mode = 2
221265
mouse_filter = 2

scripts/Application.gd

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,36 @@ func _ready() -> void:
6868
thread_polling_timer.paused = false
6969
thread_polling_timer.start()
7070

71+
func _unhandled_key_input(e: InputEvent) -> void:
72+
var event: InputEventKey = e as InputEventKey
73+
74+
if event == null:
75+
return
76+
77+
78+
# Do not do these actions when the GUI has something in focus
79+
# So check if currently any GUI has focus
80+
if get_viewport().gui_get_focus_owner() != null:
81+
return
82+
83+
if not event.pressed:
84+
85+
# If the event is either Space or right key, go to the next image
86+
if event.keycode == KEY_SPACE || event.keycode == KEY_RIGHT:
87+
current_active_index += 1
88+
elif event.keycode == KEY_LEFT:
89+
current_active_index -= 1
90+
91+
if currently_active_item:
92+
93+
# Allow rotating an image
94+
if event.keycode == KEY_Q:
95+
currently_active_item.rotate(COUNTERCLOCKWISE)
96+
current_active_index = current_active_index
97+
if event.keycode == KEY_E:
98+
currently_active_item.rotate(CLOCKWISE)
99+
current_active_index = current_active_index
100+
71101
func _poll_threads() -> void:
72102

73103
for worker in thread_workers:
@@ -126,12 +156,19 @@ func clear() -> void:
126156
post_clear.emit()
127157

128158
class ImageThreadWorkerTask extends Resource:
159+
160+
enum WORKER_TASKS {LOAD, STORE, CROP}
161+
129162
var callback: Callable
130163
var path: String
164+
var task: WORKER_TASKS
165+
var data_playload : Dictionary
131166

132-
func _init(callback: Callable, path: String) -> void:
167+
func _init(callback: Callable, path: String, task: WORKER_TASKS = WORKER_TASKS.LOAD, payload: Dictionary = {}) -> void:
133168
self.path = path
134169
self.callback = callback
170+
self.task = task
171+
self.data_playload = payload
135172

136173
class ImageThreadWorker extends Resource:
137174

scripts/ImageViewManager.gd

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,49 @@ func _ready() -> void:
1818
func update_image(image: ProcessedImage) -> void:
1919

2020
if current_image != null:
21-
current_image.was_loaded.disconnect(update_image.bind(image))
22-
21+
22+
if current_image.was_loaded.is_connected(update_image.bind(image)):
23+
current_image.was_loaded.disconnect(update_image.bind(image))
24+
25+
if current_image != image:
26+
# Store our cropping state in the old image
27+
# For that pass in the Rect2i which would describe the sub section of the image
28+
var image_section_size : Vector2 = cutout_area.size / scale
29+
30+
# Next is our top left position of the rect
31+
# As our internal position always starts of center, we start at the center too, subtract half our size
32+
# and then we offset that by our custom additional offset
33+
var image_position : Vector2 = size/2 - image_section_size/2 + additional_offset/scale
34+
35+
# If the image position is less than 0,0 we clamp it
36+
if image_position.x < 0:
37+
image_position.x = 0
38+
if image_position.y < 0:
39+
image_position.y = 0
40+
41+
42+
# Now pass on that subrect
43+
current_image.export_image(Rect2i(image_position, image_section_size))
44+
45+
2346
current_image = image
2447

2548
if image.original_texture == null:
2649
image.load_image()
2750
image.was_loaded.connect(update_image.bind(image))
2851

2952
texture = image.original_texture
53+
54+
55+
update_positions()
56+
57+
# If we already previously loaded and unloaded the image, restore the zoom position we had
58+
if image.was_already_exported:
59+
scale = cutout_area.size / Vector2(image.exported_section.size)
60+
additional_offset = -(size/2 - Vector2(image.exported_section.size)/2 - Vector2(image.exported_section.position)) * scale
61+
62+
63+
clamp_position()
3064

3165
func update_positions() -> void:
3266
if texture == null:
@@ -49,8 +83,12 @@ func update_positions() -> void:
4983
minimum_zoom = scale_tmp
5084
scale = Vector2(scale_tmp, scale_tmp)
5185

52-
func _input(event: InputEvent) -> void:
86+
func _unhandled_input(event: InputEvent) -> void:
5387

88+
# Get the current mouse pointer and only containue if it is the moving cursor
89+
# (Poverty fix to prevent actions outside this area affecting this)
90+
if Input.get_current_cursor_shape() != Input.CURSOR_MOVE:
91+
return
5492

5593
# Check for mouse events to update the position
5694

@@ -115,5 +153,5 @@ func clamp_position() -> void:
115153
additional_offset.x = space_available_from_center.x * sign(additional_offset.x)
116154
if abs(additional_offset.y) > space_available_from_center.y:
117155
additional_offset.y = space_available_from_center.y * sign(additional_offset.y)
118-
156+
119157
position = -additional_offset

scripts/ProcessedImage.gd

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ var index: int = 0
1919

2020
var is_loading_image: bool = false
2121

22+
var exported_section: Rect2i = Rect2i()
23+
2224
var is_active: bool = false :
2325
set(value):
2426
is_active = value
@@ -45,7 +47,17 @@ func load_image() -> void:
4547
was_loaded.emit()
4648
is_loading_image = false
4749
)
48-
50+
51+
func rotate(direction: int) -> void:
52+
if original_texture == null:
53+
return
54+
55+
var new_image = original_texture.get_image()
56+
new_image.rotate_90(direction)
57+
58+
original_texture = ImageTexture.create_from_image(new_image)
59+
60+
was_loaded.emit()
4961

5062
func unload_image() -> bool:
5163

@@ -56,10 +68,21 @@ func unload_image() -> bool:
5668

5769
return false
5870

59-
func export_image(position: Rect2) -> bool:
71+
func export_image(position: Rect2i) -> bool:
72+
73+
exported_section = position
74+
75+
# We now have our image section, where we extract a new image
76+
if original_texture != null:
77+
var new_image := original_texture.get_image().get_region(position)
78+
79+
# Now we resize that target region into the crop size we actually want
80+
new_image.resize(Application.crop_to_size.x, Application.crop_to_size.y, Image.INTERPOLATE_LANCZOS)
81+
82+
preview_texture = ImageTexture.create_from_image(new_image)
6083

61-
was_already_exported = true
62-
was_exported.emit()
84+
was_already_exported = true
85+
was_exported.emit()
6386
return false
6487

6588
func get_file_name() -> String:

0 commit comments

Comments
 (0)