1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-01-19 01:29:49 +00:00

Reworked draw_tool (#705)

* Refactored `draw_tool`

In draw.gd, efactored the `draw_tool(position)` function to memorize a
few preliminary steps that may be skipped on every pixel.

* Reformat

* Reformat

* Improved _set_pixel

Memorized a few results per-stroke rather than recomputing them on each
pixel.

* Refactor

* Improved draw_tool_circle

Memorized the shape of the circle as a vector of displacements from
the center to avoid recomputing the whole circle at every position.

* Refactor

* Reworked drat tool methods

I changed the implementations to use private functions that return the
array of points that would be affected by the tool. This way, when we
are drawing a stroke of a tool, we can get all points affected by the
stroke, filter out duplicates, and only set pixels once.

* refactor
This commit is contained in:
Matteo Piovanelli 2022-06-10 15:30:08 +02:00 committed by GitHub
parent e4607a46bc
commit 872ac62722
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -22,6 +22,13 @@ var _indicator := BitMap.new()
var _polylines := []
var _line_polylines := []
# Memorize some stuff when doing brush strokes
var _stroke_project: Project
var _stroke_images := [] # Array of Images
var _tile_mode_rect: Rect2
var _is_mask_size_zero := true
var _circle_tool_shortcut: PoolVector2Array
func _ready() -> void:
Tools.connect("color_changed", self, "_on_Color_changed")
@ -154,6 +161,7 @@ func update_mask(can_skip := true) -> void:
_mask = PoolByteArray()
return
var size: Vector2 = Global.current_project.size
_is_mask_size_zero = false
# Faster than zeroing PoolByteArray directly.
# See: https://github.com/Orama-Interactive/Pixelorama/pull/439
var nulled_array := []
@ -195,26 +203,64 @@ func commit_undo() -> void:
func draw_tool(position: Vector2) -> void:
_prepare_tool()
var coords_to_draw = _draw_tool(position)
for coord in coords_to_draw:
_set_pixel_no_cache(coord)
func _prepare_tool() -> void:
if !Global.current_project.layers[Global.current_project.current_layer].can_layer_get_drawn():
return
var strength := _strength
if Global.pressure_sensitivity_mode == Global.PressureSensitivity.ALPHA:
strength *= Tools.pen_pressure
_drawer.pixel_perfect = Tools.pixel_perfect if _brush_size == 1 else false
_drawer.horizontal_mirror = Tools.horizontal_mirror
_drawer.vertical_mirror = Tools.vertical_mirror
_drawer.color_op.strength = strength
# Memorize current project
_stroke_project = Global.current_project
# Memorize the frame/layer we are drawing on rather than fetching it on every pixel
_stroke_images = _get_selected_draw_images()
# Memorize current tile mode
_tile_mode_rect = _stroke_project.get_tile_mode_rect()
# This may prevent a few tests when setting pixels
_is_mask_size_zero = _mask.size() == 0
match _brush.type:
Brushes.CIRCLE:
_prepare_circle_tool(false)
Brushes.FILLED_CIRCLE:
_prepare_circle_tool(true)
func _prepare_circle_tool(fill: bool) -> void:
_circle_tool_shortcut = PoolVector2Array()
var circle_tool_map = _create_circle_indicator(_brush_size, fill)
# Go through that BitMap and build an Array of the "displacement" from the center of the bits
# that are true.
var diameter = 2 * _brush_size + 1
for n in range(0, diameter):
for m in range(0, diameter):
if circle_tool_map.get_bit(Vector2(m, n)):
_circle_tool_shortcut.append(Vector2(m - _brush_size, n - _brush_size))
# Make sure to alway have invoked _prepare_tool() before this. This computes the coordinates to be
# drawn if it can (except for the generic brush, when it's actually drawing them)
func _draw_tool(position: Vector2) -> PoolVector2Array:
if !Global.current_project.layers[Global.current_project.current_layer].can_layer_get_drawn():
return PoolVector2Array() # empty fallback
match _brush.type:
Brushes.PIXEL:
draw_tool_pixel(position)
return _compute_draw_tool_pixel(position)
Brushes.CIRCLE:
draw_tool_circle(position, false)
return _compute_draw_tool_circle(position, false)
Brushes.FILLED_CIRCLE:
draw_tool_circle(position, true)
return _compute_draw_tool_circle(position, true)
_:
draw_tool_brush(position)
return PoolVector2Array() # empty fallback
# Bresenham's Algorithm
@ -228,6 +274,8 @@ func draw_fill_gap(start: Vector2, end: Vector2) -> void:
var sy = 1 if start.y < end.y else -1
var x = start.x
var y = start.y
_prepare_tool()
var coords_to_draw = {}
while !(x == end.x && y == end.y):
e2 = err << 1
if e2 >= dy:
@ -236,33 +284,55 @@ func draw_fill_gap(start: Vector2, end: Vector2) -> void:
if e2 <= dx:
err += dx
y += sy
draw_tool(Vector2(x, y))
#coords_to_draw.append_array(_draw_tool(Vector2(x, y)))
for coord in _draw_tool(Vector2(x, y)):
coords_to_draw[coord] = 0
for c in coords_to_draw.keys():
_set_pixel_no_cache(c)
func draw_tool_pixel(position: Vector2) -> void:
for coord in _compute_draw_tool_pixel(position):
_set_pixel_no_cache(coord)
# Compute the array of coordinates that should be drawn
func _compute_draw_tool_pixel(position: Vector2) -> PoolVector2Array:
var result = PoolVector2Array()
var start := position - Vector2.ONE * (_brush_size >> 1)
var end := start + Vector2.ONE * _brush_size
for y in range(start.y, end.y):
for x in range(start.x, end.x):
_set_pixel(Vector2(x, y))
result.append(Vector2(x, y))
return result
# Algorithm based on http://members.chello.at/easyfilter/bresenham.html
func draw_tool_circle(position: Vector2, fill := false) -> void:
for coord in _compute_draw_tool_circle(position, fill):
_set_pixel_no_cache(coord)
# Compute the array of coordinates that should be drawn
func _compute_draw_tool_circle(position: Vector2, fill := false) -> PoolVector2Array:
if _circle_tool_shortcut:
return _draw_tool_circle_from_map(position)
else:
var result = PoolVector2Array()
var r := _brush_size
var x := -r
var y := 0
var err := 2 - r * 2
var draw := true
if fill:
_set_pixel(position)
result.append(position)
while x < 0:
if draw:
for i in range(1 if fill else -x, -x + 1):
_set_pixel(position + Vector2(-i, y))
_set_pixel(position + Vector2(-y, -i))
_set_pixel(position + Vector2(i, -y))
_set_pixel(position + Vector2(y, i))
result.append(position + Vector2(-i, y))
result.append(position + Vector2(-y, -i))
result.append(position + Vector2(i, -y))
result.append(position + Vector2(y, i))
draw = not fill
r = err
if r <= y:
@ -272,6 +342,14 @@ func draw_tool_circle(position: Vector2, fill := false) -> void:
if r > x || err > y:
x += 1
err += x * 2 + 1
return result
func _draw_tool_circle_from_map(position: Vector2) -> PoolVector2Array:
var result = PoolVector2Array()
for displacement in _circle_tool_shortcut:
result.append(position + displacement)
return result
func draw_tool_brush(position: Vector2) -> void:
@ -363,22 +441,29 @@ func draw_indicator_at(position: Vector2, offset: Vector2, color: Color) -> void
func _set_pixel(position: Vector2, ignore_mirroring := false) -> void:
if position in _draw_cache and _for_frame == Global.current_project.current_frame:
if position in _draw_cache and _for_frame == _stroke_project.current_frame:
return
if _draw_cache.size() > _cache_limit or _for_frame != Global.current_project.current_frame:
if _draw_cache.size() > _cache_limit or _for_frame != _stroke_project.current_frame:
_draw_cache = []
_for_frame = Global.current_project.current_frame
_for_frame = _stroke_project.current_frame
_draw_cache.append(position) # Store the position of pixel
# Invoke uncached version to actually draw the pixel
_set_pixel_no_cache(position, ignore_mirroring)
var project: Project = Global.current_project
if project.tile_mode and project.get_tile_mode_rect().has_point(position):
position = position.posmodv(project.size)
if !project.can_pixel_get_drawn(position):
func _set_pixel_no_cache(position: Vector2, ignore_mirroring := false) -> void:
if _stroke_project.tile_mode and _tile_mode_rect.has_point(position):
position = position.posmodv(_stroke_project.size)
if !_stroke_project.can_pixel_get_drawn(position):
return
var images := _get_selected_draw_images()
var i := int(position.x + position.y * project.size.x)
var images := _stroke_images
if _is_mask_size_zero:
for image in images:
_drawer.set_pixel(image, position, tool_slot.color, ignore_mirroring)
else:
var i := int(position.x + position.y * _stroke_project.size.x)
if _mask.size() >= i + 1:
if _mask[i] < Tools.pen_pressure:
_mask[i] = Tools.pen_pressure