mirror of
https://github.com/Orama-Interactive/Pixelorama.git
synced 2025-01-18 17:19:50 +00:00
Basic tablet pen pressure sensitivity, brush alpha now gets blended
Instead of replacing the pixels with the new color's alpha value, the alpha values of the selected color and the current pixel color get blended together. This means that, if you have a pixel with 50% alpha and you draw a color over it with 25% alpha, the final result will have 75% alpha, instead of 25% as it used to be. The pressure sensitivity is still experimental and may not work properly. Works only with Godot 3.2 and above. draw_pixel() has also been renamed to draw_brush()
This commit is contained in:
parent
5d0b39f14c
commit
f28a3a4405
|
@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Image layer rotation! Choose between 2 rotation algorithms, Rotxel and Nearest Neighbour - Thanks to azagaya!
|
- Image layer rotation! Choose between 2 rotation algorithms, Rotxel and Nearest Neighbour - Thanks to azagaya!
|
||||||
|
- Tablet pen pressure sensitivity!
|
||||||
- Crowdin integration for contributing translations!
|
- Crowdin integration for contributing translations!
|
||||||
- Spanish translation - thanks to azagaya!
|
- Spanish translation - thanks to azagaya!
|
||||||
- Chinese Simplified translation - thanks to wcxu21!
|
- Chinese Simplified translation - thanks to wcxu21!
|
||||||
|
@ -16,13 +17,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Lanczos scaling interpolation. (Added because of the Godot 3.2 update)
|
- Lanczos scaling interpolation. (Added because of the Godot 3.2 update)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Updates to the Greek, Russian and Traditional Chinese translations.
|
- Major changes to alpha blending behavior. The alpha values now get added/blended together instead of just replacing the pixel with the new value.
|
||||||
- Replaced some OS alerts with a custom made error dialog.
|
- Replaced some OS alerts with a custom made error dialog.
|
||||||
- Made the zooming smoother, is toggleable in Preferences whether to keep the new zooming or the old one.
|
- Made the zooming smoother, is toggleable in Preferences whether to keep the new zooming or the old one.
|
||||||
- Made the "X" button on the custom brushes a little smaller.
|
- Made the "X" button on the custom brushes a little smaller.
|
||||||
- The color picker will now have a small white triangle on the top left of the color preview if at least one of its RGB values are above 1 in Raw mode. (Added automatically because of the Godot 3.2 update)
|
- The color picker will now have a small white triangle on the top left of the color preview if at least one of its RGB values are above 1 in Raw mode. (Added automatically because of the Godot 3.2 update)
|
||||||
- You can now toggle the visibility of hidden items on and off in the file dialogs. (Added automatically because of the Godot 3.2 update)
|
- You can now toggle the visibility of hidden items on and off in the file dialogs. (Added automatically because of the Godot 3.2 update)
|
||||||
- The language buttons in the preferences have their localized names in their hint tooltips. For example, if you hover over the "English" button while the language is Greek, the hint tooltip will be "Αγγλικά", which is the Greek word for English.
|
- The language buttons in the preferences have their localized names in their hint tooltips. For example, if you hover over the "English" button while the language is Greek, the hint tooltip will be "Αγγλικά", which is the Greek word for English.
|
||||||
|
- Translation updates.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Delay the splash screen popup so it shows properly centered (thanks to YeldhamDev)
|
- Delay the splash screen popup so it shows properly centered (thanks to YeldhamDev)
|
||||||
|
|
|
@ -22,11 +22,12 @@ var north_limit := location.y
|
||||||
var south_limit := location.y + size.y
|
var south_limit := location.y + size.y
|
||||||
var mouse_inside_canvas := false # used for undo
|
var mouse_inside_canvas := false # used for undo
|
||||||
var sprite_changed_this_frame := false # for optimization purposes
|
var sprite_changed_this_frame := false # for optimization purposes
|
||||||
var lighten_darken_pixels := [] # Cleared after mouse release
|
var mouse_press_pixels := [] # Cleared after mouse release
|
||||||
var is_making_line := false
|
var is_making_line := false
|
||||||
var made_line := false
|
var made_line := false
|
||||||
var is_making_selection := "None"
|
var is_making_selection := "None"
|
||||||
var line_2d : Line2D
|
var line_2d : Line2D
|
||||||
|
var pen_pressure := 1.0 # For tablet pressure sensitivity
|
||||||
|
|
||||||
# Called when the node enters the scene tree for the first time.
|
# Called when the node enters the scene tree for the first time.
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
@ -119,6 +120,13 @@ func _input(event : InputEvent) -> void:
|
||||||
if Global.has_focus:
|
if Global.has_focus:
|
||||||
update()
|
update()
|
||||||
|
|
||||||
|
# Godot 3.2 and above only code
|
||||||
|
if Engine.get_version_info().major == 3 && Engine.get_version_info().minor >= 2:
|
||||||
|
if event is InputEventMouseMotion:
|
||||||
|
pen_pressure = event.pressure
|
||||||
|
if pen_pressure == 0: # Aka drawing with mouse
|
||||||
|
pen_pressure = 1
|
||||||
|
|
||||||
sprite_changed_this_frame = false
|
sprite_changed_this_frame = false
|
||||||
var mouse_pos := current_pixel
|
var mouse_pos := current_pixel
|
||||||
var mouse_pos_floored := mouse_pos.floor()
|
var mouse_pos_floored := mouse_pos.floor()
|
||||||
|
@ -203,16 +211,16 @@ func _input(event : InputEvent) -> void:
|
||||||
handle_undo("Draw")
|
handle_undo("Draw")
|
||||||
elif (Input.is_action_just_released("left_mouse") && !Input.is_action_pressed("right_mouse")) || (Input.is_action_just_released("right_mouse") && !Input.is_action_pressed("left_mouse")):
|
elif (Input.is_action_just_released("left_mouse") && !Input.is_action_pressed("right_mouse")) || (Input.is_action_just_released("right_mouse") && !Input.is_action_pressed("left_mouse")):
|
||||||
made_line = false
|
made_line = false
|
||||||
lighten_darken_pixels.clear()
|
mouse_press_pixels.clear()
|
||||||
if can_handle || Global.undos == Global.undo_redo.get_version():
|
if can_handle || Global.undos == Global.undo_redo.get_version():
|
||||||
if previous_action != "None" && previous_action != "RectSelect" && current_action != "ColorPicker":
|
if previous_action != "None" && previous_action != "RectSelect" && current_action != "ColorPicker":
|
||||||
handle_redo("Draw")
|
handle_redo("Draw")
|
||||||
|
|
||||||
match current_action: # Handle current tool
|
match current_action: # Handle current tool
|
||||||
"Pencil":
|
"Pencil":
|
||||||
pencil_and_eraser(mouse_pos, current_color, current_mouse_button)
|
pencil_and_eraser(mouse_pos, current_color, current_mouse_button, current_action)
|
||||||
"Eraser":
|
"Eraser":
|
||||||
pencil_and_eraser(mouse_pos, Color(0, 0, 0, 0), current_mouse_button)
|
pencil_and_eraser(mouse_pos, Color(0, 0, 0, 0), current_mouse_button, current_action)
|
||||||
"Bucket":
|
"Bucket":
|
||||||
if can_handle:
|
if can_handle:
|
||||||
if fill_area == 0: # Paint the specific area of the same color
|
if fill_area == 0: # Paint the specific area of the same color
|
||||||
|
@ -539,19 +547,19 @@ func pencil_and_eraser(mouse_pos : Vector2, color : Color, current_mouse_button
|
||||||
return
|
return
|
||||||
if is_making_line:
|
if is_making_line:
|
||||||
fill_gaps(line_2d.points[1], previous_mouse_pos_for_lines, color, current_mouse_button, current_action)
|
fill_gaps(line_2d.points[1], previous_mouse_pos_for_lines, color, current_mouse_button, current_action)
|
||||||
draw_pixel(line_2d.points[1], color, current_mouse_button, current_action)
|
draw_brush(line_2d.points[1], color, current_mouse_button, current_action)
|
||||||
made_line = true
|
made_line = true
|
||||||
else:
|
else:
|
||||||
if point_in_rectangle(mouse_pos, location, location + size):
|
if point_in_rectangle(mouse_pos, location, location + size):
|
||||||
mouse_inside_canvas = true
|
mouse_inside_canvas = true
|
||||||
# Draw
|
# Draw
|
||||||
draw_pixel(mouse_pos, color, current_mouse_button, current_action)
|
draw_brush(mouse_pos, color, current_mouse_button, current_action)
|
||||||
fill_gaps(mouse_pos, previous_mouse_pos, color, current_mouse_button, current_action) #Fill the gaps
|
fill_gaps(mouse_pos, previous_mouse_pos, color, current_mouse_button, current_action) #Fill the gaps
|
||||||
# If mouse is not inside bounds but it used to be, fill the gaps
|
# If mouse is not inside bounds but it used to be, fill the gaps
|
||||||
elif point_in_rectangle(previous_mouse_pos, location, location + size):
|
elif point_in_rectangle(previous_mouse_pos, location, location + size):
|
||||||
fill_gaps(mouse_pos, previous_mouse_pos, color, current_mouse_button, current_action)
|
fill_gaps(mouse_pos, previous_mouse_pos, color, current_mouse_button, current_action)
|
||||||
|
|
||||||
func draw_pixel(pos : Vector2, color : Color, current_mouse_button : String, current_action := "None") -> void:
|
func draw_brush(pos : Vector2, color : Color, current_mouse_button : String, current_action := "None") -> void:
|
||||||
if Global.can_draw && Global.has_focus:
|
if Global.can_draw && Global.has_focus:
|
||||||
var brush_size := 1
|
var brush_size := 1
|
||||||
var brush_type = Global.BRUSH_TYPES.PIXEL
|
var brush_type = Global.BRUSH_TYPES.PIXEL
|
||||||
|
@ -561,6 +569,11 @@ func draw_pixel(pos : Vector2, color : Color, current_mouse_button : String, cur
|
||||||
var vertical_mirror := false
|
var vertical_mirror := false
|
||||||
var ld := 0
|
var ld := 0
|
||||||
var ld_amount := 0.1
|
var ld_amount := 0.1
|
||||||
|
if Global.pressure_sensitivity_mode == Global.PRESSURE_SENSITIVITY.ALPHA:
|
||||||
|
if current_action == "Pencil":
|
||||||
|
color.a *= pen_pressure
|
||||||
|
elif current_action == "Eraser":
|
||||||
|
color.a *= (1.0 - pen_pressure)
|
||||||
if current_mouse_button == "left_mouse":
|
if current_mouse_button == "left_mouse":
|
||||||
brush_size = Global.left_brush_size
|
brush_size = Global.left_brush_size
|
||||||
brush_type = Global.current_left_brush_type
|
brush_type = Global.current_left_brush_type
|
||||||
|
@ -618,17 +631,22 @@ func draw_pixel(pos : Vector2, color : Color, current_mouse_button : String, cur
|
||||||
var pos_floored := Vector2(cur_pos_x, cur_pos_y).floor()
|
var pos_floored := Vector2(cur_pos_x, cur_pos_y).floor()
|
||||||
# Don't draw the same pixel over and over and don't re-lighten/darken it
|
# Don't draw the same pixel over and over and don't re-lighten/darken it
|
||||||
var current_pixel_color : Color = layers[current_layer_index][0].get_pixel(cur_pos_x, cur_pos_y)
|
var current_pixel_color : Color = layers[current_layer_index][0].get_pixel(cur_pos_x, cur_pos_y)
|
||||||
if current_pixel_color != color && !(pos_floored in lighten_darken_pixels):
|
var _c := color
|
||||||
if current_action == "LightenDarken":
|
if current_action == "Pencil" && color.a < 1:
|
||||||
color = current_pixel_color
|
# Blend alpha
|
||||||
if color.a > 0:
|
_c.a = color.a + current_pixel_color.a * (1 - color.a)
|
||||||
if ld == 0: # Lighten
|
|
||||||
color = current_pixel_color.lightened(ld_amount)
|
|
||||||
else: # Darken
|
|
||||||
color = current_pixel_color.darkened(ld_amount)
|
|
||||||
lighten_darken_pixels.append(pos_floored)
|
|
||||||
|
|
||||||
layers[current_layer_index][0].set_pixel(cur_pos_x, cur_pos_y, color)
|
if current_pixel_color != _c && !(pos_floored in mouse_press_pixels):
|
||||||
|
if current_action == "LightenDarken":
|
||||||
|
_c = current_pixel_color
|
||||||
|
if _c.a > 0:
|
||||||
|
if ld == 0: # Lighten
|
||||||
|
_c = current_pixel_color.lightened(ld_amount)
|
||||||
|
else: # Darken
|
||||||
|
_c = current_pixel_color.darkened(ld_amount)
|
||||||
|
|
||||||
|
mouse_press_pixels.append(pos_floored)
|
||||||
|
layers[current_layer_index][0].set_pixel(cur_pos_x, cur_pos_y, _c)
|
||||||
sprite_changed_this_frame = true
|
sprite_changed_this_frame = true
|
||||||
|
|
||||||
# Handle mirroring
|
# Handle mirroring
|
||||||
|
@ -636,39 +654,39 @@ func draw_pixel(pos : Vector2, color : Color, current_mouse_button : String, cur
|
||||||
var mirror_y := south_limit + north_limit - cur_pos_y - 1
|
var mirror_y := south_limit + north_limit - cur_pos_y - 1
|
||||||
if horizontal_mirror:
|
if horizontal_mirror:
|
||||||
current_pixel_color = layers[current_layer_index][0].get_pixel(mirror_x, cur_pos_y)
|
current_pixel_color = layers[current_layer_index][0].get_pixel(mirror_x, cur_pos_y)
|
||||||
if current_pixel_color != color: # don't draw the same pixel over and over
|
if current_pixel_color != _c: # don't draw the same pixel over and over
|
||||||
if current_action == "LightenDarken":
|
if current_action == "LightenDarken":
|
||||||
if ld == 0: # Lighten
|
if ld == 0: # Lighten
|
||||||
color = current_pixel_color.lightened(ld_amount)
|
_c = current_pixel_color.lightened(ld_amount)
|
||||||
else:
|
else:
|
||||||
color = current_pixel_color.darkened(ld_amount)
|
_c = current_pixel_color.darkened(ld_amount)
|
||||||
lighten_darken_pixels.append(pos_floored)
|
|
||||||
|
|
||||||
layers[current_layer_index][0].set_pixel(mirror_x, cur_pos_y, color)
|
mouse_press_pixels.append(pos_floored)
|
||||||
|
layers[current_layer_index][0].set_pixel(mirror_x, cur_pos_y, _c)
|
||||||
sprite_changed_this_frame = true
|
sprite_changed_this_frame = true
|
||||||
if vertical_mirror:
|
if vertical_mirror:
|
||||||
current_pixel_color = layers[current_layer_index][0].get_pixel(cur_pos_x, mirror_y)
|
current_pixel_color = layers[current_layer_index][0].get_pixel(cur_pos_x, mirror_y)
|
||||||
if current_pixel_color != color: # don't draw the same pixel over and over
|
if current_pixel_color != _c: # don't draw the same pixel over and over
|
||||||
if current_action == "LightenDarken":
|
if current_action == "LightenDarken":
|
||||||
if ld == 0: # Lighten
|
if ld == 0: # Lighten
|
||||||
color = current_pixel_color.lightened(ld_amount)
|
_c = current_pixel_color.lightened(ld_amount)
|
||||||
else:
|
else:
|
||||||
color = current_pixel_color.darkened(ld_amount)
|
_c = current_pixel_color.darkened(ld_amount)
|
||||||
lighten_darken_pixels.append(pos_floored)
|
|
||||||
|
|
||||||
layers[current_layer_index][0].set_pixel(cur_pos_x, mirror_y, color)
|
mouse_press_pixels.append(pos_floored)
|
||||||
|
layers[current_layer_index][0].set_pixel(cur_pos_x, mirror_y, _c)
|
||||||
sprite_changed_this_frame = true
|
sprite_changed_this_frame = true
|
||||||
if horizontal_mirror && vertical_mirror:
|
if horizontal_mirror && vertical_mirror:
|
||||||
current_pixel_color = layers[current_layer_index][0].get_pixel(mirror_x, mirror_y)
|
current_pixel_color = layers[current_layer_index][0].get_pixel(mirror_x, mirror_y)
|
||||||
if current_pixel_color != color: # don't draw the same pixel over and over
|
if current_pixel_color != _c: # don't draw the same pixel over and over
|
||||||
if current_action == "LightenDarken":
|
if current_action == "LightenDarken":
|
||||||
if ld == 0: # Lighten
|
if ld == 0: # Lighten
|
||||||
color = current_pixel_color.lightened(ld_amount)
|
_c = current_pixel_color.lightened(ld_amount)
|
||||||
else:
|
else:
|
||||||
color = current_pixel_color.darkened(ld_amount)
|
_c = current_pixel_color.darkened(ld_amount)
|
||||||
lighten_darken_pixels.append(pos_floored)
|
|
||||||
|
|
||||||
layers[current_layer_index][0].set_pixel(mirror_x, mirror_y, color)
|
mouse_press_pixels.append(pos_floored)
|
||||||
|
layers[current_layer_index][0].set_pixel(mirror_x, mirror_y, _c)
|
||||||
sprite_changed_this_frame = true
|
sprite_changed_this_frame = true
|
||||||
|
|
||||||
elif brush_type == Global.BRUSH_TYPES.CIRCLE || brush_type == Global.BRUSH_TYPES.FILLED_CIRCLE:
|
elif brush_type == Global.BRUSH_TYPES.CIRCLE || brush_type == Global.BRUSH_TYPES.FILLED_CIRCLE:
|
||||||
|
@ -773,7 +791,7 @@ func fill_gaps(mouse_pos : Vector2, prev_mouse_pos : Vector2, color : Color, cur
|
||||||
var x = previous_mouse_pos_floored.x
|
var x = previous_mouse_pos_floored.x
|
||||||
var y = previous_mouse_pos_floored.y
|
var y = previous_mouse_pos_floored.y
|
||||||
while !(x == mouse_pos_floored.x && y == mouse_pos_floored.y):
|
while !(x == mouse_pos_floored.x && y == mouse_pos_floored.y):
|
||||||
draw_pixel(Vector2(x, y), color, current_mouse_button, current_action)
|
draw_brush(Vector2(x, y), color, current_mouse_button, current_action)
|
||||||
e2 = err << 1
|
e2 = err << 1
|
||||||
if e2 >= dy:
|
if e2 >= dy:
|
||||||
err += dy
|
err += dy
|
||||||
|
@ -830,14 +848,11 @@ func plot_circle(sprite : Image, xm : int, ym : int, r : int, color : Color, fil
|
||||||
var quadrant_2 := Vector2(xm - y, ym - x)
|
var quadrant_2 := Vector2(xm - y, ym - x)
|
||||||
var quadrant_3 := Vector2(xm + x, ym - y)
|
var quadrant_3 := Vector2(xm + x, ym - y)
|
||||||
var quadrant_4 := Vector2(xm + y, ym + x)
|
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)):
|
draw_pixel_blended(sprite, quadrant_1, color)
|
||||||
sprite.set_pixelv(quadrant_1, color)
|
draw_pixel_blended(sprite, quadrant_2, color)
|
||||||
if point_in_rectangle(quadrant_2, Vector2(west_limit - 1, north_limit - 1), Vector2(east_limit, south_limit)):
|
draw_pixel_blended(sprite, quadrant_3, color)
|
||||||
sprite.set_pixelv(quadrant_2, color)
|
draw_pixel_blended(sprite, quadrant_4, 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
|
r = err
|
||||||
if r <= y:
|
if r <= y:
|
||||||
y += 1
|
y += 1
|
||||||
|
@ -851,8 +866,14 @@ func plot_circle(sprite : Image, xm : int, ym : int, r : int, color : Color, fil
|
||||||
for i in range (-radius, radius + 1):
|
for i in range (-radius, radius + 1):
|
||||||
if i * i + j * j <= radius * radius:
|
if i * i + j * j <= radius * radius:
|
||||||
var draw_pos := Vector2(i + xm, j + ym)
|
var draw_pos := Vector2(i + xm, j + ym)
|
||||||
if point_in_rectangle(draw_pos, Vector2(west_limit - 1, north_limit - 1), Vector2(east_limit, south_limit)):
|
draw_pixel_blended(sprite, draw_pos, color)
|
||||||
sprite.set_pixelv(draw_pos, color)
|
|
||||||
|
func draw_pixel_blended(sprite : Image, pos : Vector2, color : Color) -> void:
|
||||||
|
if point_in_rectangle(pos, Vector2(west_limit - 1, north_limit - 1), Vector2(east_limit, south_limit)):
|
||||||
|
if color.a > 0 && color.a < 1:
|
||||||
|
# Blend alpha
|
||||||
|
color.a = color.a + sprite.get_pixelv(pos).a * (1 - color.a)
|
||||||
|
sprite.set_pixelv(pos, color)
|
||||||
|
|
||||||
# Checks if a point is inside a rectangle
|
# Checks if a point is inside a rectangle
|
||||||
func point_in_rectangle(p : Vector2, coord1 : Vector2, coord2 : Vector2) -> bool:
|
func point_in_rectangle(p : Vector2, coord1 : Vector2, coord2 : Vector2) -> bool:
|
||||||
|
|
|
@ -16,7 +16,7 @@ var canvases := []
|
||||||
# warning-ignore:unused_class_variable
|
# warning-ignore:unused_class_variable
|
||||||
var hidden_canvases := []
|
var hidden_canvases := []
|
||||||
enum PRESSURE_SENSITIVITY {NONE, ALPHA, SIZE}
|
enum PRESSURE_SENSITIVITY {NONE, ALPHA, SIZE}
|
||||||
var pressure_sensitivity_mode = PRESSURE_SENSITIVITY.NONE
|
var pressure_sensitivity_mode = PRESSURE_SENSITIVITY.ALPHA
|
||||||
var smooth_zoom := true
|
var smooth_zoom := true
|
||||||
var left_cursor_tool_texture : ImageTexture
|
var left_cursor_tool_texture : ImageTexture
|
||||||
var right_cursor_tool_texture : ImageTexture
|
var right_cursor_tool_texture : ImageTexture
|
||||||
|
|
Loading…
Reference in a new issue