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:
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
|
||||
|
||||
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()
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue