1
0
Fork 0
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:
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 _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