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:
parent
e4607a46bc
commit
872ac62722
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue