-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathImageEditor.gd
271 lines (224 loc) · 7.35 KB
/
ImageEditor.gd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
tool
extends Control
enum State { PAN, DRAW, ERASE }
const INTERNAL_TEXTURE_FLAGS = 0
const RECT_MARGIN_FOR_INITIAL_ZOOM = 10
export(ImageTexture) var image_texture = null
var is_standalone = true
var _local_texture = null
var _image = null
var _zoom = 1
var _zoom_altered = false
var _dragging = false
var _drawing_reference_pos = null
var _erasing_reference_pos = null
var _state = null
onready var _texture_rect = find_node("TextureRect")
onready var _scroll_container = find_node("ScrollContainer")
onready var _zoom_label = find_node("ZoomLabel")
onready var _pan_button = find_node("PanButton")
onready var _draw_button = find_node("DrawButton")
onready var _erase_button = find_node("EraseButton")
onready var _color_picker = find_node("ColorPickerButton")
onready var _properties_button = find_node("PropertiesButton")
onready var _properties_window = find_node("ImagePropertiesWindow")
onready var _panel = find_node("Panel")
onready var _stats_label = find_node("StatsLabel")
func _ready():
if Engine.editor_hint and is_standalone:
return
_properties_window.is_standalone = false
_properties_button.connect("pressed", self, "_on_properties_button_pressed")
_properties_window.connect("image_properties_changed", self, "_on_image_properties_changed")
_stats_label.text = ""
if image_texture.get_size() != Vector2.ZERO and image_texture.get_data() != null:
_image = image_texture.get_data()
_initialize()
func _initialize():
_pan_button.connect("pressed", self, "_change_state", [State.PAN])
_draw_button.connect("pressed", self, "_change_state", [State.DRAW])
_erase_button.connect("pressed", self, "_change_state", [State.ERASE])
_texture_rect.connect("gui_input", self, "_on_texture_rect_gui_input")
_panel.connect("gui_input", self, "_on_panel_gui_input")
_panel.connect("resized", self, "_on_panel_resized")
_unlock_controls()
_change_state(State.PAN)
_setup_local_texture()
_update_zoom_label()
_update_stats_label()
func _unlock_controls():
for control in [
_pan_button,
_draw_button,
_erase_button,
_color_picker,
]:
control.disabled = false
func _change_state(new_state):
var state_to_button_mapping = {
State.PAN: _pan_button,
State.DRAW: _draw_button,
State.ERASE: _erase_button,
}
if _state in state_to_button_mapping:
state_to_button_mapping[_state].find_node("Overlay").hide()
_state = new_state
if _state in state_to_button_mapping:
state_to_button_mapping[_state].find_node("Overlay").show()
func _setup_local_texture():
_local_texture = ImageTexture.new()
_local_texture.create_from_image(_image, INTERNAL_TEXTURE_FLAGS)
_texture_rect.texture = _local_texture
func _update_zoom_label():
_zoom_label.text = "Zoom: {0}".format([_zoom])
func _update_stats_label():
var image = _local_texture.get_data()
var image_size = image.get_size()
var image_format_to_str_mapping = {
0: "L8",
1: "LA8",
2: "R8",
3: "RG8",
4: "RGB8",
5: "RGBA8",
6: "RGBA4444",
7: "RGBA5551",
8: "RF",
9: "RGF",
10: "RGBF",
11: "RGBAF",
12: "RH",
13: "RGH",
14: "RGBH",
15: "RGBAH",
16: "RGBE9995",
17: "DXT1",
18: "DXT3",
19: "DXT5",
20: "RGTC_R",
21: "RGTC_RG",
22: "BPTC_RGBA",
23: "BPTC_RGBF",
24: "BPTC_RGBFU",
25: "PVRTC2",
26: "PVRTC2A",
27: "PVRTC4",
28: "PVRTC4A",
29: "ETC",
30: "ETC2_R11",
31: "ETC2_R11S",
32: "ETC2_RG11",
33: "ETC2_RG11S",
34: "ETC2_RGB8",
35: "ETC2_RGBA8",
36: "ETC2_RGB8A1",
}
var image_format_str = image_format_to_str_mapping.get(image.get_format(), "UNKNOWN")
_stats_label.text = "w: {0}\nh: {1}\n{2}".format([image_size.x, image_size.y, image_format_str])
func _event_position_to_pixel_position(event_position):
return (event_position / _zoom).floor()
func _fill_pixel(pixel_position, color):
if (
pixel_position.x < 0
or pixel_position.x >= _image.get_size().x
or pixel_position.y < 0
or pixel_position.y >= _image.get_size().y
):
return
_image.lock()
_image.set_pixelv(pixel_position, color)
_image.unlock()
image_texture.set_data(_image)
_local_texture.set_data(_image)
image_texture.emit_changed()
_local_texture.emit_changed()
func _draw_at_pos(event_position):
var pixel_position = _event_position_to_pixel_position(event_position)
_fill_pixel(pixel_position, _color_picker.color)
func _erase_at_pos(event_position):
var pixel_position = _event_position_to_pixel_position(event_position)
_fill_pixel(pixel_position, Color.transparent)
func _set_zoom(zoom, mark_altered):
var zoom_before = _zoom
_zoom = max(1, zoom)
if mark_altered and _zoom != zoom_before:
_zoom_altered = true
_texture_rect.rect_min_size = _image.get_size() * _zoom
_texture_rect.rect_size = _texture_rect.rect_min_size
_texture_rect.material.set_shader_param("zoom", _zoom)
_update_zoom_label()
func _try_handling_motion_events(event):
if not event is InputEventMouseMotion:
return
if _dragging:
_scroll_container.scroll_horizontal -= event.relative.x
_scroll_container.scroll_vertical -= event.relative.y
elif _drawing_reference_pos != null:
_drawing_reference_pos += event.relative
_draw_at_pos(_drawing_reference_pos)
elif _erasing_reference_pos != null:
_erasing_reference_pos += event.relative
_erase_at_pos(_erasing_reference_pos)
func _try_handling_zoom_events(event):
if (
not event is InputEventMouseButton
or not event.is_pressed()
or not event.button_index in [BUTTON_WHEEL_UP, BUTTON_WHEEL_DOWN]
):
return
_set_zoom(_zoom + (1 if event.button_index == BUTTON_WHEEL_UP else -1), true)
func _on_texture_rect_gui_input(event):
# TODO: refactor (extract)
_try_handling_motion_events(event)
if not event is InputEventMouseButton:
return
if event.is_pressed() and event.button_index == BUTTON_LEFT:
if _state == State.PAN:
_dragging = true
elif _state == State.DRAW:
_draw_at_pos(event.position)
_drawing_reference_pos = event.position
elif _state == State.ERASE:
_erase_at_pos(event.position)
_erasing_reference_pos = event.position
elif not event.is_pressed() and event.button_index == BUTTON_LEFT:
if _state == State.PAN:
_dragging = false
elif _state == State.DRAW:
_drawing_reference_pos = null
elif _state == State.ERASE:
_erasing_reference_pos = null
elif event.is_pressed() and event.button_index == BUTTON_MIDDLE:
_dragging = true
elif not event.is_pressed() and event.button_index == BUTTON_MIDDLE:
_dragging = false
_try_handling_zoom_events(event)
get_tree().set_input_as_handled()
func _on_panel_gui_input(event):
_try_handling_zoom_events(event)
func _on_panel_resized():
if _zoom_altered:
return
var available_rect = _panel.rect_size
if (
available_rect.x <= RECT_MARGIN_FOR_INITIAL_ZOOM * 2
and available_rect.y <= RECT_MARGIN_FOR_INITIAL_ZOOM * 2
):
available_rect -= Vector2(RECT_MARGIN_FOR_INITIAL_ZOOM, RECT_MARGIN_FOR_INITIAL_ZOOM) * 2
var desired_zoom_xy = available_rect / _image.get_size()
var desired_zoom = floor(min(desired_zoom_xy.x, desired_zoom_xy.y))
_set_zoom(desired_zoom, false)
func _on_properties_button_pressed():
_properties_window.image = _image
_properties_window.popup_centered()
func _on_image_properties_changed(image):
var initialization_required = _image == null
_image = image
if initialization_required:
_initialize()
image_texture.create_from_image(_image, image_texture.flags)
_local_texture.create_from_image(_image, _local_texture.flags)
image_texture.emit_changed()
_local_texture.emit_changed()
_set_zoom(_zoom, false)
_update_stats_label()