mirror of
https://github.com/Orama-Interactive/Pixelorama.git
synced 2025-01-19 09:39:48 +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 _polylines := []
|
||||||
var _line_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:
|
func _ready() -> void:
|
||||||
Tools.connect("color_changed", self, "_on_Color_changed")
|
Tools.connect("color_changed", self, "_on_Color_changed")
|
||||||
|
@ -154,6 +161,7 @@ func update_mask(can_skip := true) -> void:
|
||||||
_mask = PoolByteArray()
|
_mask = PoolByteArray()
|
||||||
return
|
return
|
||||||
var size: Vector2 = Global.current_project.size
|
var size: Vector2 = Global.current_project.size
|
||||||
|
_is_mask_size_zero = false
|
||||||
# Faster than zeroing PoolByteArray directly.
|
# Faster than zeroing PoolByteArray directly.
|
||||||
# See: https://github.com/Orama-Interactive/Pixelorama/pull/439
|
# See: https://github.com/Orama-Interactive/Pixelorama/pull/439
|
||||||
var nulled_array := []
|
var nulled_array := []
|
||||||
|
@ -195,26 +203,64 @@ func commit_undo() -> void:
|
||||||
|
|
||||||
|
|
||||||
func draw_tool(position: Vector2) -> 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():
|
if !Global.current_project.layers[Global.current_project.current_layer].can_layer_get_drawn():
|
||||||
return
|
return
|
||||||
var strength := _strength
|
var strength := _strength
|
||||||
if Global.pressure_sensitivity_mode == Global.PressureSensitivity.ALPHA:
|
if Global.pressure_sensitivity_mode == Global.PressureSensitivity.ALPHA:
|
||||||
strength *= Tools.pen_pressure
|
strength *= Tools.pen_pressure
|
||||||
|
|
||||||
_drawer.pixel_perfect = Tools.pixel_perfect if _brush_size == 1 else false
|
_drawer.pixel_perfect = Tools.pixel_perfect if _brush_size == 1 else false
|
||||||
_drawer.horizontal_mirror = Tools.horizontal_mirror
|
_drawer.horizontal_mirror = Tools.horizontal_mirror
|
||||||
_drawer.vertical_mirror = Tools.vertical_mirror
|
_drawer.vertical_mirror = Tools.vertical_mirror
|
||||||
_drawer.color_op.strength = strength
|
_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:
|
match _brush.type:
|
||||||
Brushes.PIXEL:
|
Brushes.PIXEL:
|
||||||
draw_tool_pixel(position)
|
return _compute_draw_tool_pixel(position)
|
||||||
Brushes.CIRCLE:
|
Brushes.CIRCLE:
|
||||||
draw_tool_circle(position, false)
|
return _compute_draw_tool_circle(position, false)
|
||||||
Brushes.FILLED_CIRCLE:
|
Brushes.FILLED_CIRCLE:
|
||||||
draw_tool_circle(position, true)
|
return _compute_draw_tool_circle(position, true)
|
||||||
_:
|
_:
|
||||||
draw_tool_brush(position)
|
draw_tool_brush(position)
|
||||||
|
return PoolVector2Array() # empty fallback
|
||||||
|
|
||||||
|
|
||||||
# Bresenham's Algorithm
|
# 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 sy = 1 if start.y < end.y else -1
|
||||||
var x = start.x
|
var x = start.x
|
||||||
var y = start.y
|
var y = start.y
|
||||||
|
_prepare_tool()
|
||||||
|
var coords_to_draw = {}
|
||||||
while !(x == end.x && y == end.y):
|
while !(x == end.x && y == end.y):
|
||||||
e2 = err << 1
|
e2 = err << 1
|
||||||
if e2 >= dy:
|
if e2 >= dy:
|
||||||
|
@ -236,33 +284,55 @@ func draw_fill_gap(start: Vector2, end: Vector2) -> void:
|
||||||
if e2 <= dx:
|
if e2 <= dx:
|
||||||
err += dx
|
err += dx
|
||||||
y += sy
|
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:
|
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 start := position - Vector2.ONE * (_brush_size >> 1)
|
||||||
var end := start + Vector2.ONE * _brush_size
|
var end := start + Vector2.ONE * _brush_size
|
||||||
for y in range(start.y, end.y):
|
for y in range(start.y, end.y):
|
||||||
for x in range(start.x, end.x):
|
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
|
# Algorithm based on http://members.chello.at/easyfilter/bresenham.html
|
||||||
func draw_tool_circle(position: Vector2, fill := false) -> void:
|
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 r := _brush_size
|
||||||
var x := -r
|
var x := -r
|
||||||
var y := 0
|
var y := 0
|
||||||
var err := 2 - r * 2
|
var err := 2 - r * 2
|
||||||
var draw := true
|
var draw := true
|
||||||
if fill:
|
if fill:
|
||||||
_set_pixel(position)
|
result.append(position)
|
||||||
while x < 0:
|
while x < 0:
|
||||||
if draw:
|
if draw:
|
||||||
for i in range(1 if fill else -x, -x + 1):
|
for i in range(1 if fill else -x, -x + 1):
|
||||||
_set_pixel(position + Vector2(-i, y))
|
result.append(position + Vector2(-i, y))
|
||||||
_set_pixel(position + Vector2(-y, -i))
|
result.append(position + Vector2(-y, -i))
|
||||||
_set_pixel(position + Vector2(i, -y))
|
result.append(position + Vector2(i, -y))
|
||||||
_set_pixel(position + Vector2(y, i))
|
result.append(position + Vector2(y, i))
|
||||||
draw = not fill
|
draw = not fill
|
||||||
r = err
|
r = err
|
||||||
if r <= y:
|
if r <= y:
|
||||||
|
@ -272,6 +342,14 @@ func draw_tool_circle(position: Vector2, fill := false) -> void:
|
||||||
if r > x || err > y:
|
if r > x || err > y:
|
||||||
x += 1
|
x += 1
|
||||||
err += x * 2 + 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:
|
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:
|
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
|
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 = []
|
_draw_cache = []
|
||||||
_for_frame = Global.current_project.current_frame
|
_for_frame = _stroke_project.current_frame
|
||||||
_draw_cache.append(position) # Store the position of pixel
|
_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
|
return
|
||||||
|
|
||||||
var images := _get_selected_draw_images()
|
var images := _stroke_images
|
||||||
var i := int(position.x + position.y * project.size.x)
|
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.size() >= i + 1:
|
||||||
if _mask[i] < Tools.pen_pressure:
|
if _mask[i] < Tools.pen_pressure:
|
||||||
_mask[i] = Tools.pen_pressure
|
_mask[i] = Tools.pen_pressure
|
||||||
|
|
Loading…
Reference in a new issue