mirror of
https://github.com/Orama-Interactive/Pixelorama.git
synced 2025-01-31 07:29:49 +00:00
Added proper circle brush - Bresenham's Circle Algorithm
The circle's radius is the brush's size. Respects image/selection boundaries, works with mirror. A special plot_circle() method is found on Global, to calculate the rectangles used by the mouse cursor/position indicator.
This commit is contained in:
parent
7942463b7d
commit
06e0d74c14
Before Width: | Height: | Size: 109 B After Width: | Height: | Size: 109 B |
13
Assets/Graphics/circle_9x9.png.import
Normal file
13
Assets/Graphics/circle_9x9.png.import
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="image"
|
||||||
|
type="Image"
|
||||||
|
path="res://.import/circle_9x9.png-cfd9cf56bdd7391c1c12df315cfd78e2.image"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://Assets/Graphics/circle_9x9.png"
|
||||||
|
dest_files=[ "res://.import/circle_9x9.png-cfd9cf56bdd7391c1c12df315cfd78e2.image" ]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,7 +1,7 @@
|
||||||
extends BaseButton
|
extends BaseButton
|
||||||
|
|
||||||
var brush_type = Global.BRUSH_TYPES.PIXEL
|
export var brush_type = Global.BRUSH_TYPES.PIXEL
|
||||||
var custom_brush_index := -1
|
export var custom_brush_index := -2
|
||||||
|
|
||||||
func _on_BrushButton_pressed() -> void:
|
func _on_BrushButton_pressed() -> void:
|
||||||
# Change left brush
|
# Change left brush
|
||||||
|
@ -15,9 +15,12 @@ func _on_BrushButton_pressed() -> void:
|
||||||
Global.left_brush_type_label.text = tr("Custom brush")
|
Global.left_brush_type_label.text = tr("Custom brush")
|
||||||
else:
|
else:
|
||||||
Global.left_brush_type_label.text = tr("Brush:") + " %s" % hint_tooltip
|
Global.left_brush_type_label.text = tr("Brush:") + " %s" % hint_tooltip
|
||||||
else: #Pixel brush
|
elif custom_brush_index == -2: # Pixel brush
|
||||||
Global.left_color_interpolation_container.visible = false
|
Global.left_color_interpolation_container.visible = false
|
||||||
Global.left_brush_type_label.text = tr("Brush: Pixel")
|
Global.left_brush_type_label.text = tr("Brush: Pixel")
|
||||||
|
elif custom_brush_index == -1: # Circle brush
|
||||||
|
Global.left_color_interpolation_container.visible = false
|
||||||
|
Global.left_brush_type_label.text = tr("Brush: Circle")
|
||||||
|
|
||||||
Global.update_left_custom_brush()
|
Global.update_left_custom_brush()
|
||||||
|
|
||||||
|
@ -31,21 +34,24 @@ func _on_BrushButton_pressed() -> void:
|
||||||
Global.right_brush_type_label.text = tr("Custom brush")
|
Global.right_brush_type_label.text = tr("Custom brush")
|
||||||
else:
|
else:
|
||||||
Global.right_brush_type_label.text = tr("Brush:") + " %s" % hint_tooltip
|
Global.right_brush_type_label.text = tr("Brush:") + " %s" % hint_tooltip
|
||||||
else: #Pixel brush
|
elif custom_brush_index == -2: # Pixel brush
|
||||||
Global.right_color_interpolation_container.visible = false
|
Global.right_color_interpolation_container.visible = false
|
||||||
Global.right_brush_type_label.text = tr("Brush: Pixel")
|
Global.right_brush_type_label.text = tr("Brush: Pixel")
|
||||||
|
elif custom_brush_index == -1: # Circle brush
|
||||||
|
Global.right_color_interpolation_container.visible = false
|
||||||
|
Global.right_brush_type_label.text = tr("Brush: Circle")
|
||||||
|
|
||||||
Global.update_right_custom_brush()
|
Global.update_right_custom_brush()
|
||||||
|
|
||||||
func _on_DeleteButton_pressed() -> void:
|
func _on_DeleteButton_pressed() -> void:
|
||||||
if brush_type == Global.BRUSH_TYPES.CUSTOM:
|
if brush_type == Global.BRUSH_TYPES.CUSTOM:
|
||||||
if Global.custom_left_brush_index == custom_brush_index:
|
if Global.custom_left_brush_index == custom_brush_index:
|
||||||
Global.custom_left_brush_index = -1
|
Global.custom_left_brush_index = -2
|
||||||
Global.current_left_brush_type = Global.BRUSH_TYPES.PIXEL
|
Global.current_left_brush_type = Global.BRUSH_TYPES.PIXEL
|
||||||
Global.left_brush_type_label.text = "Brush: Pixel"
|
Global.left_brush_type_label.text = "Brush: Pixel"
|
||||||
Global.update_left_custom_brush()
|
Global.update_left_custom_brush()
|
||||||
if Global.custom_right_brush_index == custom_brush_index:
|
if Global.custom_right_brush_index == custom_brush_index:
|
||||||
Global.custom_right_brush_index = -1
|
Global.custom_right_brush_index = -2
|
||||||
Global.current_right_brush_type = Global.BRUSH_TYPES.PIXEL
|
Global.current_right_brush_type = Global.BRUSH_TYPES.PIXEL
|
||||||
Global.right_brush_type_label.text = "Brush: Pixel"
|
Global.right_brush_type_label.text = "Brush: Pixel"
|
||||||
Global.update_right_custom_brush()
|
Global.update_right_custom_brush()
|
||||||
|
|
|
@ -31,8 +31,8 @@ func _ready() -> void:
|
||||||
if layers.empty():
|
if layers.empty():
|
||||||
var sprite := Image.new()
|
var sprite := Image.new()
|
||||||
sprite.create(size.x, size.y, false, Image.FORMAT_RGBA8)
|
sprite.create(size.x, size.y, false, Image.FORMAT_RGBA8)
|
||||||
|
|
||||||
sprite.lock()
|
sprite.lock()
|
||||||
|
|
||||||
var tex := ImageTexture.new()
|
var tex := ImageTexture.new()
|
||||||
tex.create_from_image(sprite, 0)
|
tex.create_from_image(sprite, 0)
|
||||||
|
|
||||||
|
@ -442,24 +442,34 @@ func _draw() -> void:
|
||||||
var mouse_pos := get_local_mouse_position() + location
|
var mouse_pos := get_local_mouse_position() + location
|
||||||
if point_in_rectangle(mouse_pos, location, location + size):
|
if point_in_rectangle(mouse_pos, location, location + size):
|
||||||
mouse_pos = mouse_pos.floor()
|
mouse_pos = mouse_pos.floor()
|
||||||
if Global.left_square_indicator_visible and Global.can_draw:
|
if Global.left_square_indicator_visible && Global.can_draw:
|
||||||
if Global.current_left_brush_type == Global.BRUSH_TYPES.PIXEL:
|
if Global.current_left_brush_type == Global.BRUSH_TYPES.PIXEL || Global.current_left_tool == "LightenDarken":
|
||||||
if Global.current_left_tool == "Pencil" || Global.current_left_tool == "Eraser" || Global.current_left_tool == "LightenDarken":
|
if Global.current_left_tool == "Pencil" || Global.current_left_tool == "Eraser" || Global.current_left_tool == "LightenDarken":
|
||||||
var start_pos_x = mouse_pos.x - (Global.left_brush_size >> 1)
|
var start_pos_x = mouse_pos.x - (Global.left_brush_size >> 1)
|
||||||
var start_pos_y = mouse_pos.y - (Global.left_brush_size >> 1)
|
var start_pos_y = mouse_pos.y - (Global.left_brush_size >> 1)
|
||||||
draw_rect(Rect2(start_pos_x, start_pos_y, Global.left_brush_size, Global.left_brush_size), Color.blue, false)
|
draw_rect(Rect2(start_pos_x, start_pos_y, Global.left_brush_size, Global.left_brush_size), Color.blue, false)
|
||||||
|
elif Global.current_left_brush_type == Global.BRUSH_TYPES.CIRCLE:
|
||||||
|
if Global.current_left_tool == "Pencil" || Global.current_left_tool == "Eraser":
|
||||||
|
draw_set_transform(mouse_pos, 0, Vector2.ONE)
|
||||||
|
for rect in Global.left_circle_points:
|
||||||
|
draw_rect(Rect2(rect, Vector2.ONE), Color.blue, false)
|
||||||
else:
|
else:
|
||||||
if Global.current_left_tool == "Pencil" || Global.current_left_tool == "Eraser":
|
if Global.current_left_tool == "Pencil" || Global.current_left_tool == "Eraser":
|
||||||
var custom_brush_size = Global.custom_left_brush_image.get_size() - Vector2.ONE
|
var custom_brush_size = Global.custom_left_brush_image.get_size() - Vector2.ONE
|
||||||
var dst := rectangle_center(mouse_pos, custom_brush_size)
|
var dst := rectangle_center(mouse_pos, custom_brush_size)
|
||||||
draw_texture(Global.custom_left_brush_texture, dst)
|
draw_texture(Global.custom_left_brush_texture, dst)
|
||||||
|
|
||||||
if Global.right_square_indicator_visible and Global.can_draw:
|
if Global.right_square_indicator_visible && Global.can_draw:
|
||||||
if Global.current_right_brush_type == Global.BRUSH_TYPES.PIXEL:
|
if Global.current_right_brush_type == Global.BRUSH_TYPES.PIXEL || Global.current_right_tool == "LightenDarken":
|
||||||
if Global.current_right_tool == "Pencil" || Global.current_right_tool == "Eraser" || Global.current_left_tool == "LightenDarken":
|
if Global.current_right_tool == "Pencil" || Global.current_right_tool == "Eraser" || Global.current_right_tool == "LightenDarken":
|
||||||
var start_pos_x = mouse_pos.x - (Global.right_brush_size >> 1)
|
var start_pos_x = mouse_pos.x - (Global.right_brush_size >> 1)
|
||||||
var start_pos_y = mouse_pos.y - (Global.right_brush_size >> 1)
|
var start_pos_y = mouse_pos.y - (Global.right_brush_size >> 1)
|
||||||
draw_rect(Rect2(start_pos_x, start_pos_y, Global.right_brush_size, Global.right_brush_size), Color.red, false)
|
draw_rect(Rect2(start_pos_x, start_pos_y, Global.right_brush_size, Global.right_brush_size), Color.red, false)
|
||||||
|
elif Global.current_right_brush_type == Global.BRUSH_TYPES.CIRCLE:
|
||||||
|
if Global.current_right_tool == "Pencil" || Global.current_right_tool == "Eraser":
|
||||||
|
draw_set_transform(mouse_pos, 0, Vector2.ONE)
|
||||||
|
for rect in Global.right_circle_points:
|
||||||
|
draw_rect(Rect2(rect, Vector2.ONE), Color.red, false)
|
||||||
else:
|
else:
|
||||||
if Global.current_right_tool == "Pencil" || Global.current_right_tool == "Eraser":
|
if Global.current_right_tool == "Pencil" || Global.current_right_tool == "Eraser":
|
||||||
var custom_brush_size = Global.custom_right_brush_image.get_size() - Vector2.ONE
|
var custom_brush_size = Global.custom_right_brush_image.get_size() - Vector2.ONE
|
||||||
|
@ -613,6 +623,21 @@ func draw_pixel(pos : Vector2, color : Color, current_mouse_button : String, cur
|
||||||
layers[current_layer_index][0].set_pixel(mirror_x, mirror_y, color)
|
layers[current_layer_index][0].set_pixel(mirror_x, mirror_y, color)
|
||||||
sprite_changed_this_frame = true
|
sprite_changed_this_frame = true
|
||||||
|
|
||||||
|
elif brush_type == Global.BRUSH_TYPES.CIRCLE:
|
||||||
|
plot_circle(layers[current_layer_index][0], pos.x, pos.y, brush_size, color)
|
||||||
|
|
||||||
|
# Handle mirroring
|
||||||
|
var mirror_x := east_limit + west_limit - pos.x
|
||||||
|
var mirror_y := south_limit + north_limit - pos.y
|
||||||
|
if horizontal_mirror:
|
||||||
|
plot_circle(layers[current_layer_index][0], mirror_x, pos.y, brush_size, color)
|
||||||
|
if vertical_mirror:
|
||||||
|
plot_circle(layers[current_layer_index][0], pos.x, mirror_y, brush_size, color)
|
||||||
|
if horizontal_mirror && vertical_mirror:
|
||||||
|
plot_circle(layers[current_layer_index][0], mirror_x, mirror_y, brush_size, color)
|
||||||
|
|
||||||
|
sprite_changed_this_frame = true
|
||||||
|
|
||||||
else:
|
else:
|
||||||
var custom_brush_size := custom_brush_image.get_size() - Vector2.ONE
|
var custom_brush_size := custom_brush_image.get_size() - Vector2.ONE
|
||||||
pos = pos.floor()
|
pos = pos.floor()
|
||||||
|
@ -755,8 +780,41 @@ func flood_fill(pos : Vector2, target_color : Color, replace_color : Color) -> v
|
||||||
q.append(south)
|
q.append(south)
|
||||||
sprite_changed_this_frame = true
|
sprite_changed_this_frame = true
|
||||||
|
|
||||||
#I wish GDScript supported function overloading, I could add more versions of these scripts...
|
# Algorithm based on http://members.chello.at/easyfilter/bresenham.html
|
||||||
#...but with a Rect2() parameter instead of 2 Vector2()s
|
func plot_circle(sprite : Image, xm : int, ym : int, r : int, color : Color) -> void:
|
||||||
|
var west_limit := location.x
|
||||||
|
var east_limit := location.x + size.x
|
||||||
|
var north_limit := location.y
|
||||||
|
var south_limit := location.y + size.y
|
||||||
|
if Global.selected_pixels.size() != 0:
|
||||||
|
west_limit = max(west_limit, Global.selection_rectangle.polygon[0].x)
|
||||||
|
east_limit = min(east_limit, Global.selection_rectangle.polygon[2].x)
|
||||||
|
north_limit = max(north_limit, Global.selection_rectangle.polygon[0].y)
|
||||||
|
south_limit = min(south_limit, Global.selection_rectangle.polygon[2].y)
|
||||||
|
|
||||||
|
var x := -r
|
||||||
|
var y := 0
|
||||||
|
var err := 2 - r * 2 # II. Quadrant
|
||||||
|
while x < 0:
|
||||||
|
var quadrant_1 := Vector2(xm - x, ym + y)
|
||||||
|
var quadrant_2 := Vector2(xm - y, ym - x)
|
||||||
|
var quadrant_3 := Vector2(xm + x, ym - y)
|
||||||
|
var quadrant_4 := Vector2(xm + y, ym + x)
|
||||||
|
if point_in_rectangle(quadrant_1, Vector2(west_limit - 1, north_limit - 1), Vector2(east_limit, south_limit)):
|
||||||
|
sprite.set_pixelv(quadrant_1, color)
|
||||||
|
if point_in_rectangle(quadrant_2, Vector2(west_limit - 1, north_limit - 1), Vector2(east_limit, south_limit)):
|
||||||
|
sprite.set_pixelv(quadrant_2, color)
|
||||||
|
if point_in_rectangle(quadrant_3, Vector2(west_limit - 1, north_limit - 1), Vector2(east_limit, south_limit)):
|
||||||
|
sprite.set_pixelv(quadrant_3, color)
|
||||||
|
if point_in_rectangle(quadrant_4, Vector2(west_limit - 1, north_limit - 1), Vector2(east_limit, south_limit)):
|
||||||
|
sprite.set_pixelv(quadrant_4, color)
|
||||||
|
r = err
|
||||||
|
if r <= y:
|
||||||
|
y += 1
|
||||||
|
err += y * 2 + 1
|
||||||
|
if r > x || err > y:
|
||||||
|
x += 1
|
||||||
|
err += x * 2 + 1
|
||||||
|
|
||||||
# Checks if a point is inside a rectangle
|
# Checks if a point is inside a rectangle
|
||||||
func point_in_rectangle(p : Vector2, coord1 : Vector2, coord2 : Vector2) -> bool:
|
func point_in_rectangle(p : Vector2, coord1 : Vector2, coord2 : Vector2) -> bool:
|
||||||
|
|
|
@ -83,7 +83,7 @@ var onion_skinning_future_rate := 0
|
||||||
var onion_skinning_blue_red := false
|
var onion_skinning_blue_red := false
|
||||||
|
|
||||||
#Brushes
|
#Brushes
|
||||||
enum BRUSH_TYPES {PIXEL, FILE, CUSTOM}
|
enum BRUSH_TYPES {PIXEL, CIRCLE, FILE, CUSTOM}
|
||||||
# warning-ignore:unused_class_variable
|
# warning-ignore:unused_class_variable
|
||||||
var left_brush_size := 1
|
var left_brush_size := 1
|
||||||
# warning-ignore:unused_class_variable
|
# warning-ignore:unused_class_variable
|
||||||
|
@ -94,6 +94,8 @@ var current_left_brush_type = BRUSH_TYPES.PIXEL
|
||||||
var current_right_brush_type = BRUSH_TYPES.PIXEL
|
var current_right_brush_type = BRUSH_TYPES.PIXEL
|
||||||
# warning-ignore:unused_class_variable
|
# warning-ignore:unused_class_variable
|
||||||
var brush_type_window_position := "left"
|
var brush_type_window_position := "left"
|
||||||
|
var left_circle_points := []
|
||||||
|
var right_circle_points := []
|
||||||
|
|
||||||
var brushes_from_files := 0
|
var brushes_from_files := 0
|
||||||
# warning-ignore:unused_class_variable
|
# warning-ignore:unused_class_variable
|
||||||
|
@ -472,7 +474,13 @@ func update_left_custom_brush() -> void:
|
||||||
var pixel := Image.new()
|
var pixel := Image.new()
|
||||||
pixel = preload("res://Assets/Graphics/pixel_image.png")
|
pixel = preload("res://Assets/Graphics/pixel_image.png")
|
||||||
pixel = blend_image_with_color(pixel, left_color_picker.color, 1)
|
pixel = blend_image_with_color(pixel, left_color_picker.color, 1)
|
||||||
left_brush_type_button.get_child(0).texture.create_from_image(pixel)
|
left_brush_type_button.get_child(0).texture.create_from_image(pixel, 0)
|
||||||
|
elif current_left_brush_type == BRUSH_TYPES.CIRCLE:
|
||||||
|
var pixel := Image.new()
|
||||||
|
pixel = preload("res://Assets/Graphics/circle_9x9.png")
|
||||||
|
pixel = blend_image_with_color(pixel, left_color_picker.color, 1)
|
||||||
|
left_brush_type_button.get_child(0).texture.create_from_image(pixel, 0)
|
||||||
|
left_circle_points = plot_circle(left_brush_size)
|
||||||
else:
|
else:
|
||||||
var custom_brush := Image.new()
|
var custom_brush := Image.new()
|
||||||
custom_brush.copy_from(custom_brushes[custom_left_brush_index])
|
custom_brush.copy_from(custom_brushes[custom_left_brush_index])
|
||||||
|
@ -488,7 +496,13 @@ func update_right_custom_brush() -> void:
|
||||||
var pixel := Image.new()
|
var pixel := Image.new()
|
||||||
pixel = preload("res://Assets/Graphics/pixel_image.png")
|
pixel = preload("res://Assets/Graphics/pixel_image.png")
|
||||||
pixel = blend_image_with_color(pixel, right_color_picker.color, 1)
|
pixel = blend_image_with_color(pixel, right_color_picker.color, 1)
|
||||||
right_brush_type_button.get_child(0).texture.create_from_image(pixel)
|
right_brush_type_button.get_child(0).texture.create_from_image(pixel, 0)
|
||||||
|
elif current_right_brush_type == BRUSH_TYPES.CIRCLE:
|
||||||
|
var pixel := Image.new()
|
||||||
|
pixel = preload("res://Assets/Graphics/circle_9x9.png")
|
||||||
|
pixel = blend_image_with_color(pixel, right_color_picker.color, 1)
|
||||||
|
right_brush_type_button.get_child(0).texture.create_from_image(pixel, 0)
|
||||||
|
right_circle_points = plot_circle(right_brush_size)
|
||||||
else:
|
else:
|
||||||
var custom_brush := Image.new()
|
var custom_brush := Image.new()
|
||||||
custom_brush.copy_from(custom_brushes[custom_right_brush_index])
|
custom_brush.copy_from(custom_brushes[custom_right_brush_index])
|
||||||
|
@ -515,6 +529,30 @@ func blend_image_with_color(image : Image, color : Color, interpolate_factor : f
|
||||||
blended_image.set_pixel(xx, yy, Color(0, 0, 0, 0))
|
blended_image.set_pixel(xx, yy, Color(0, 0, 0, 0))
|
||||||
return blended_image
|
return blended_image
|
||||||
|
|
||||||
|
# Algorithm based on http://members.chello.at/easyfilter/bresenham.html
|
||||||
|
# This is not used for drawing, rather for finding the points required
|
||||||
|
# for the mouse cursor/position indicator
|
||||||
|
func plot_circle(r : int) -> Array:
|
||||||
|
var circle_points := []
|
||||||
|
var xm := 0
|
||||||
|
var ym := 0
|
||||||
|
var x := -r
|
||||||
|
var y := 0
|
||||||
|
var err := 2 - r * 2
|
||||||
|
while x < 0:
|
||||||
|
circle_points.append(Vector2(xm - x, ym + y))
|
||||||
|
circle_points.append(Vector2(xm - y, ym - x))
|
||||||
|
circle_points.append(Vector2(xm + x, ym - y))
|
||||||
|
circle_points.append(Vector2(xm + y, ym + x))
|
||||||
|
r = err
|
||||||
|
if r <= y:
|
||||||
|
y += 1
|
||||||
|
err += y * 2 + 1
|
||||||
|
if r > x || err > y:
|
||||||
|
x += 1
|
||||||
|
err += x * 2 + 1
|
||||||
|
return circle_points
|
||||||
|
|
||||||
func _exit_tree() -> void:
|
func _exit_tree() -> void:
|
||||||
config_cache.set_value("window", "screen", OS.current_screen)
|
config_cache.set_value("window", "screen", OS.current_screen)
|
||||||
config_cache.set_value("window", "maximized", OS.window_maximized || OS.window_fullscreen)
|
config_cache.set_value("window", "maximized", OS.window_maximized || OS.window_fullscreen)
|
||||||
|
|
Loading…
Reference in a new issue