1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-01-18 17:19:50 +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:
OverloadedOrama 2019-12-25 20:27:25 +02:00
parent 7942463b7d
commit 06e0d74c14
7 changed files with 210 additions and 67 deletions

View file

Before

Width:  |  Height:  |  Size: 109 B

After

Width:  |  Height:  |  Size: 109 B

View 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

File diff suppressed because one or more lines are too long

View file

@ -1,7 +1,7 @@
extends BaseButton
var brush_type = Global.BRUSH_TYPES.PIXEL
var custom_brush_index := -1
export var brush_type = Global.BRUSH_TYPES.PIXEL
export var custom_brush_index := -2
func _on_BrushButton_pressed() -> void:
# Change left brush
@ -15,9 +15,12 @@ func _on_BrushButton_pressed() -> void:
Global.left_brush_type_label.text = tr("Custom brush")
else:
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_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()
@ -31,21 +34,24 @@ func _on_BrushButton_pressed() -> void:
Global.right_brush_type_label.text = tr("Custom brush")
else:
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_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()
func _on_DeleteButton_pressed() -> void:
if brush_type == Global.BRUSH_TYPES.CUSTOM:
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.left_brush_type_label.text = "Brush: Pixel"
Global.update_left_custom_brush()
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.right_brush_type_label.text = "Brush: Pixel"
Global.update_right_custom_brush()

View file

@ -31,8 +31,8 @@ func _ready() -> void:
if layers.empty():
var sprite := Image.new()
sprite.create(size.x, size.y, false, Image.FORMAT_RGBA8)
sprite.lock()
var tex := ImageTexture.new()
tex.create_from_image(sprite, 0)
@ -442,24 +442,34 @@ func _draw() -> void:
var mouse_pos := get_local_mouse_position() + location
if point_in_rectangle(mouse_pos, location, location + size):
mouse_pos = mouse_pos.floor()
if Global.left_square_indicator_visible and Global.can_draw:
if Global.current_left_brush_type == Global.BRUSH_TYPES.PIXEL:
if Global.left_square_indicator_visible && Global.can_draw:
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":
var start_pos_x = mouse_pos.x - (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)
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:
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 dst := rectangle_center(mouse_pos, custom_brush_size)
draw_texture(Global.custom_left_brush_texture, dst)
if Global.right_square_indicator_visible and Global.can_draw:
if Global.current_right_brush_type == Global.BRUSH_TYPES.PIXEL:
if Global.current_right_tool == "Pencil" || Global.current_right_tool == "Eraser" || Global.current_left_tool == "LightenDarken":
if Global.right_square_indicator_visible && Global.can_draw:
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_right_tool == "LightenDarken":
var start_pos_x = mouse_pos.x - (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)
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:
if Global.current_right_tool == "Pencil" || Global.current_right_tool == "Eraser":
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)
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:
var custom_brush_size := custom_brush_image.get_size() - Vector2.ONE
pos = pos.floor()
@ -755,8 +780,41 @@ func flood_fill(pos : Vector2, target_color : Color, replace_color : Color) -> v
q.append(south)
sprite_changed_this_frame = true
#I wish GDScript supported function overloading, I could add more versions of these scripts...
#...but with a Rect2() parameter instead of 2 Vector2()s
# Algorithm based on http://members.chello.at/easyfilter/bresenham.html
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
func point_in_rectangle(p : Vector2, coord1 : Vector2, coord2 : Vector2) -> bool:

View file

@ -83,7 +83,7 @@ var onion_skinning_future_rate := 0
var onion_skinning_blue_red := false
#Brushes
enum BRUSH_TYPES {PIXEL, FILE, CUSTOM}
enum BRUSH_TYPES {PIXEL, CIRCLE, FILE, CUSTOM}
# warning-ignore:unused_class_variable
var left_brush_size := 1
# 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
# warning-ignore:unused_class_variable
var brush_type_window_position := "left"
var left_circle_points := []
var right_circle_points := []
var brushes_from_files := 0
# warning-ignore:unused_class_variable
@ -472,7 +474,13 @@ func update_left_custom_brush() -> void:
var pixel := Image.new()
pixel = preload("res://Assets/Graphics/pixel_image.png")
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:
var custom_brush := Image.new()
custom_brush.copy_from(custom_brushes[custom_left_brush_index])
@ -488,7 +496,13 @@ func update_right_custom_brush() -> void:
var pixel := Image.new()
pixel = preload("res://Assets/Graphics/pixel_image.png")
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:
var custom_brush := Image.new()
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))
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:
config_cache.set_value("window", "screen", OS.current_screen)
config_cache.set_value("window", "maximized", OS.window_maximized || OS.window_fullscreen)