518 lines
11 KiB
GDScript
518 lines
11 KiB
GDScript
extends Control
|
|
class_name GECanvas
|
|
tool
|
|
|
|
export var pixel_size: float = 16 setget set_pixel_size
|
|
export(int, 1, 2500) var canvas_width = 48 setget set_canvas_width # == pixels
|
|
export(int, 1, 2500) var canvas_height = 28 setget set_canvas_height # == pixels
|
|
export var grid_size = 16 setget set_grid_size
|
|
export var big_grid_size = 10 setget set_big_grid_size
|
|
export var can_draw = true
|
|
|
|
var mouse_in_region
|
|
var mouse_on_top
|
|
|
|
var frame: GEFrame
|
|
|
|
#var layers : Array = [] # Key: layer_name, val: GELayer
|
|
var active_layer: GELayer
|
|
var preview_layer: GELayer
|
|
var tool_layer: GELayer
|
|
var canvas_layers: Control
|
|
|
|
var canvas
|
|
var grid
|
|
var big_grid
|
|
var selected_pixels = []
|
|
|
|
var symmetry_x = false
|
|
var symmetry_y = false
|
|
|
|
|
|
func _ready():
|
|
#-------------------------------
|
|
# Set nodes
|
|
#-------------------------------
|
|
canvas = find_node("Canvas")
|
|
grid = find_node("Grid")
|
|
big_grid = find_node("BigGrid")
|
|
canvas_layers = find_node("CanvasLayers")
|
|
|
|
#-------------------------------
|
|
# setup layers and canvas
|
|
#-------------------------------
|
|
if not is_connected("mouse_entered", self, "_on_mouse_entered"):
|
|
connect("mouse_entered", self, "_on_mouse_entered")
|
|
if not is_connected("mouse_exited", self, "_on_mouse_exited"):
|
|
connect("mouse_exited", self, "_on_mouse_exited")
|
|
|
|
#-------------------------------
|
|
# setup layers and canvas
|
|
#-------------------------------
|
|
#canvas_size = Vector2(int(rect_size.x / grid_size), int(rect_size.y / grid_size))
|
|
#pixel_size = canvas_size
|
|
|
|
create_preview_layer()
|
|
create_tool_layer()
|
|
|
|
set_process(true)
|
|
|
|
|
|
func _process(delta):
|
|
if not is_visible_in_tree():
|
|
return
|
|
var mouse_position = get_local_mouse_position()
|
|
var rect = Rect2(Vector2(0, 0), rect_size)
|
|
mouse_in_region = rect.has_point(mouse_position)
|
|
|
|
|
|
func _draw():
|
|
if preview_layer:
|
|
preview_layer.update_texture()
|
|
if tool_layer:
|
|
tool_layer.update_texture()
|
|
# for layer in layers:
|
|
# layer.update_texture()
|
|
# TODO
|
|
#if frame:
|
|
#frame._draw()
|
|
|
|
|
|
func resize(width: int, height: int):
|
|
if width < 0:
|
|
width = 1
|
|
if height < 0:
|
|
height = 1
|
|
|
|
# TODO move resize to editor.gd -> for all frames
|
|
|
|
set_canvas_width(width)
|
|
set_canvas_height(height)
|
|
|
|
preview_layer.resize(width, height)
|
|
tool_layer.resize(width, height)
|
|
|
|
#frame.resize(width, height)
|
|
|
|
|
|
|
|
func set_frame(new_frame):
|
|
frame = new_frame
|
|
if canvas_layers.get_child_count() > 0:
|
|
canvas_layers.remove_child(canvas_layers.get_child(0))
|
|
canvas_layers.add_child(frame)
|
|
|
|
frame.width = canvas_width
|
|
frame.height = canvas_height
|
|
|
|
frame.anchor_right = 1
|
|
frame.anchor_bottom = 1
|
|
frame.margin_right = 0
|
|
frame.margin_bottom = 0
|
|
|
|
if not frame.layers.empty():
|
|
active_layer = frame.layers[owner.current_layer_idx]
|
|
|
|
|
|
|
|
################################################################
|
|
# Export
|
|
################################################################
|
|
|
|
func get_current_frame_image() -> Image:
|
|
var image = Image.new()
|
|
image.create(canvas_width, canvas_height, true, Image.FORMAT_RGBA8)
|
|
image.lock()
|
|
image.fill(Color.transparent)
|
|
image.unlock()
|
|
image.lock()
|
|
|
|
for layer in frame.layers:
|
|
if not layer.visible:
|
|
continue
|
|
|
|
for x in range(frame.width):
|
|
for y in range(frame.height):
|
|
var color = layer.get_pixel(x, y)
|
|
var image_color = image.get_pixel(x, y)
|
|
|
|
if color.a != 0:
|
|
image.set_pixel(x, y, color)
|
|
else:
|
|
image.set_pixel(x, y, image_color.blend(color))
|
|
image.unlock()
|
|
return image
|
|
|
|
|
|
func get_current_layer_image() -> Image:
|
|
var image = Image.new()
|
|
image.create(canvas_width, canvas_height, true, Image.FORMAT_RGBA8)
|
|
image.lock()
|
|
image.fill(Color.transparent)
|
|
image.unlock()
|
|
image.lock()
|
|
|
|
for layer in frame.layers:
|
|
if layer != active_layer:
|
|
continue
|
|
|
|
for x in range(frame.width):
|
|
for y in range(frame.height):
|
|
var color = layer.get_pixel(x, y)
|
|
var image_color = image.get_pixel(x, y)
|
|
|
|
if color.a != 0:
|
|
image.set_pixel(x, y, color)
|
|
else:
|
|
image.set_pixel(x, y, image_color.blend(color))
|
|
image.unlock()
|
|
return image
|
|
|
|
|
|
|
|
################################################################
|
|
# Pixel/Grid size
|
|
################################################################
|
|
|
|
func set_pixel_size(size: float):
|
|
pixel_size = size
|
|
set_grid_size(grid_size)
|
|
set_big_grid_size(big_grid_size)
|
|
set_canvas_width(canvas_width)
|
|
set_canvas_height(canvas_height)
|
|
|
|
|
|
func set_grid_size(size):
|
|
grid_size = size
|
|
if not find_node("Grid"):
|
|
return
|
|
find_node("Grid").size = size * pixel_size
|
|
|
|
|
|
func set_big_grid_size(size):
|
|
big_grid_size = size
|
|
if not find_node("BigGrid"):
|
|
return
|
|
find_node("BigGrid").size = size * pixel_size
|
|
|
|
|
|
func set_canvas_width(val: int):
|
|
canvas_width = val
|
|
rect_size.x = canvas_width * pixel_size
|
|
|
|
|
|
func set_canvas_height(val: int):
|
|
canvas_height = val
|
|
rect_size.y = canvas_height * pixel_size
|
|
|
|
|
|
|
|
#-------------------------------
|
|
# Layer
|
|
#-------------------------------
|
|
|
|
func toggle_alpha_locked(layer_name: String):
|
|
var layer = find_layer_by_name(layer_name)
|
|
layer.toggle_alpha_locked()
|
|
|
|
|
|
func is_alpha_locked() -> bool:
|
|
return active_layer.alpha_locked
|
|
|
|
|
|
func get_content_margin() -> Rect2:
|
|
return frame.get_content_margin()
|
|
|
|
|
|
func crop_to_content():
|
|
frame.crop_to_content()
|
|
|
|
|
|
func get_active_layer() -> GELayer:
|
|
return active_layer
|
|
|
|
|
|
func get_preview_layer():
|
|
return preview_layer
|
|
|
|
|
|
func clear_active_layer():
|
|
active_layer.clear()
|
|
|
|
|
|
func clear_preview_layer():
|
|
preview_layer.clear()
|
|
|
|
|
|
func clear_layer(layer_name: String):
|
|
for layer in frame.layers:
|
|
if layer.name == layer_name:
|
|
layer.clear()
|
|
break
|
|
|
|
|
|
func remove_layer(layer_name: String):
|
|
# change current layer if the active layer is removed
|
|
var del_layer = find_layer_by_name(layer_name)
|
|
del_layer.clear()
|
|
if del_layer == active_layer:
|
|
for layer in frame.layers:
|
|
if layer == preview_layer or layer == active_layer or layer == tool_layer:
|
|
continue
|
|
active_layer = layer
|
|
print("Select active layer: ", active_layer)
|
|
break
|
|
frame.layers.erase(del_layer)
|
|
return active_layer
|
|
|
|
|
|
func create_preview_layer():
|
|
var layer = GELayer.new()
|
|
layer.create($PreviewLayer, canvas_width, canvas_height)
|
|
preview_layer = layer
|
|
return layer
|
|
|
|
|
|
func create_tool_layer():
|
|
var layer = GELayer.new()
|
|
layer.create($ToolPreviewLayer, canvas_width, canvas_height)
|
|
tool_layer = layer
|
|
return layer
|
|
|
|
|
|
func duplicate_layer(layer: GELayer):
|
|
for existing_layer in frame.layers:
|
|
if layer.name == existing_layer.name:
|
|
return
|
|
|
|
var new_layer: GELayer = GELayer.new()
|
|
new_layer.image.copy_from(layer.image)
|
|
return new_layer
|
|
|
|
|
|
func toggle_layer_visibility(layer_name: String):
|
|
var layer_idx = get_layer_index(layer_name)
|
|
assert(layer_idx != -1, "Layer name not found!")
|
|
var layer = frame.layers[layer_idx]
|
|
layer.visible = not layer.visible
|
|
return layer.visible
|
|
|
|
|
|
func get_layer_index(layer_name: String):
|
|
var idx = 0
|
|
for layer in frame.layers:
|
|
if layer.name == layer_name:
|
|
return idx
|
|
idx += 1
|
|
return -1
|
|
|
|
|
|
func find_layer_by_name(layer_name: String):
|
|
for layer in frame.layers:
|
|
if layer.name == layer_name:
|
|
return layer
|
|
return null
|
|
|
|
|
|
func toggle_lock_layer(layer_name: String):
|
|
find_layer_by_name(layer_name).toggle_lock()
|
|
|
|
|
|
func is_active_layer_locked() -> bool:
|
|
return active_layer.locked
|
|
|
|
|
|
func get_active_layer_index() -> int:
|
|
return get_layer_index(active_layer.name)
|
|
|
|
|
|
func move_layer_forward(layer_name: String):
|
|
var layer = find_layer_by_name(layer_name).texture_rect_ref
|
|
var new_idx = max(layer.get_index() - 1, 0)
|
|
layer.get_parent().move_child(layer, new_idx)
|
|
|
|
|
|
func move_layer_back(layer_name: String):
|
|
var layer = find_layer_by_name(layer_name).texture_rect_ref
|
|
layer.get_parent().move_child(layer, layer.get_index() + 1)
|
|
|
|
|
|
func select_layer(layer: GELayer):
|
|
active_layer = layer
|
|
|
|
|
|
#-------------------------------
|
|
# Check
|
|
#-------------------------------
|
|
|
|
func _on_mouse_entered():
|
|
mouse_on_top = true
|
|
|
|
|
|
func _on_mouse_exited():
|
|
mouse_on_top = false
|
|
|
|
|
|
func is_inside_canvas(x, y):
|
|
if x < 0 or y < 0:
|
|
return false
|
|
if x >= canvas_width or y >= canvas_height:
|
|
return false
|
|
return true
|
|
|
|
|
|
|
|
#-------------------------------
|
|
# Basic pixel-layer options
|
|
#-------------------------------
|
|
|
|
|
|
#Note: Arrays are always passed by reference. To get a copy of an array which
|
|
# can be modified independently of the original array, use duplicate.
|
|
# (https://docs.godotengine.org/en/stable/classes/class_array.html)
|
|
func set_pixel_arr(pixels: Array, color: Color):
|
|
for pixel in pixels:
|
|
_set_pixel(active_layer, pixel.x, pixel.y, color)
|
|
|
|
|
|
func set_pixel_v(pos: Vector2, color: Color):
|
|
set_pixel(pos.x, pos.y, color)
|
|
|
|
|
|
func set_pixel(x: int, y: int, color: Color):
|
|
_set_pixel(active_layer, x, y, color)
|
|
|
|
|
|
func _set_pixel_v(layer: GELayer, v: Vector2, color: Color):
|
|
_set_pixel(layer, v.x, v.y, color)
|
|
|
|
|
|
func _set_pixel(layer: GELayer, x: int, y: int, color: Color):
|
|
if not is_inside_canvas(x, y):
|
|
return
|
|
layer.set_pixel(x, y, color)
|
|
|
|
|
|
func get_pixel_v(pos: Vector2):
|
|
return get_pixel(pos.x, pos.y)
|
|
|
|
|
|
func get_pixel(x: int, y: int):
|
|
if active_layer:
|
|
return active_layer.get_pixel(x, y)
|
|
return null
|
|
|
|
|
|
func set_preview_pixel_v(pos: Vector2, color: Color):
|
|
set_preview_pixel(pos.x, pos.y, color)
|
|
|
|
|
|
func set_preview_pixel(x:int, y: int, color: Color):
|
|
if not is_inside_canvas(x, y):
|
|
return
|
|
preview_layer.set_pixel(x, y, color)
|
|
|
|
|
|
func get_preview_pixel_v(pos: Vector2):
|
|
return get_preview_pixel(pos.x, pos.y)
|
|
|
|
|
|
func get_preview_pixel(x: int, y: int):
|
|
if not preview_layer:
|
|
return null
|
|
return preview_layer.get_pixel(x, y)
|
|
|
|
|
|
|
|
#-------------------------------
|
|
# Grid
|
|
#-------------------------------
|
|
|
|
|
|
func toggle_grid():
|
|
$Grid.visible = not $Grid.visible
|
|
|
|
|
|
func show_grid():
|
|
$Grid.show()
|
|
|
|
|
|
func hide_grid():
|
|
$Grid.hide()
|
|
|
|
|
|
#-------------------------------
|
|
# Handy tools
|
|
#-------------------------------
|
|
|
|
|
|
func select_color(x, y):
|
|
print("???")
|
|
var same_color_pixels = []
|
|
var color = get_pixel(x, y)
|
|
for x in range(active_layer.layer_width):
|
|
for y in range(active_layer.layer_height):
|
|
var pixel_color = active_layer.get_pixel(x, y)
|
|
if pixel_color == color:
|
|
same_color_pixels.append(color)
|
|
return same_color_pixels
|
|
|
|
|
|
func select_same_color(x, y):
|
|
return get_neighbouring_pixels(x, y)
|
|
|
|
|
|
# returns array of Vector2
|
|
# yoinked from
|
|
# https://www.geeksforgeeks.org/flood-fill-algorithm-implement-fill-paint/
|
|
func get_neighbouring_pixels(pos_x: int, pos_y: int) -> Array:
|
|
var pixels = []
|
|
|
|
var to_check_queue = []
|
|
var checked_queue = []
|
|
|
|
to_check_queue.append(GEUtils.to_1D(pos_x, pos_y, canvas_width))
|
|
|
|
var color = get_pixel(pos_x, pos_y)
|
|
|
|
while not to_check_queue.empty():
|
|
var idx = to_check_queue.pop_front()
|
|
var p = GEUtils.to_2D(idx, canvas_width)
|
|
|
|
if idx in checked_queue:
|
|
continue
|
|
|
|
checked_queue.append(idx)
|
|
|
|
if get_pixel(p.x, p.y) != color:
|
|
continue
|
|
|
|
# add to result
|
|
pixels.append(p)
|
|
|
|
# check neighbours
|
|
var x = p.x - 1
|
|
var y = p.y
|
|
if is_inside_canvas(x, y):
|
|
idx = GEUtils.to_1D(x, y, canvas_width)
|
|
to_check_queue.append(idx)
|
|
|
|
x = p.x + 1
|
|
if is_inside_canvas(x, y):
|
|
idx = GEUtils.to_1D(x, y, canvas_width)
|
|
to_check_queue.append(idx)
|
|
|
|
x = p.x
|
|
y = p.y - 1
|
|
if is_inside_canvas(x, y):
|
|
idx = GEUtils.to_1D(x, y, canvas_width)
|
|
to_check_queue.append(idx)
|
|
|
|
y = p.y + 1
|
|
if is_inside_canvas(x, y):
|
|
idx = GEUtils.to_1D(x, y, canvas_width)
|
|
to_check_queue.append(idx)
|
|
|
|
return pixels
|
|
|