diff --git a/Translations/Translations.pot b/Translations/Translations.pot
index 437a7b783..c6645bfe8 100644
--- a/Translations/Translations.pot
+++ b/Translations/Translations.pot
@@ -1465,6 +1465,10 @@ msgstr ""
 msgid "Dynamics"
 msgstr ""
 
+#. Found in the Dynamics options menu. A stabilizer is a feature that, when enabled, helps artists create smooth lines as they draw.
+msgid "Stabilizer"
+msgstr ""
+
 #. Found in the Dynamics options menu. Pressure refers to tablet pen pressure.
 msgid "Pressure"
 msgstr ""
diff --git a/src/Autoload/Tools.gd b/src/Autoload/Tools.gd
index 4fc1a9ce6..a4edd80f9 100644
--- a/src/Autoload/Tools.gd
+++ b/src/Autoload/Tools.gd
@@ -9,6 +9,8 @@ var vertical_mirror := false
 var pixel_perfect := false
 
 # Dynamics
+var stabilizer_enabled := false
+var stabilizer_value := 16
 var dynamics_alpha: int = Dynamics.NONE
 var dynamics_size: int = Dynamics.NONE
 var pen_pressure := 1.0
diff --git a/src/Tools/BaseTool.gd b/src/Tools/BaseTool.gd
index 0557b8122..0ebc83207 100644
--- a/src/Tools/BaseTool.gd
+++ b/src/Tools/BaseTool.gd
@@ -6,6 +6,7 @@ var kname: String
 var tool_slot: Tools.Slot = null
 var cursor_text := ""
 var _cursor := Vector2i(Vector2.INF)
+var _stabilizer_center := Vector2.ZERO
 
 var _draw_cache: Array[Vector2i] = []  ## For storing already drawn pixels
 @warning_ignore("unused_private_class_variable")
@@ -54,6 +55,7 @@ func update_config() -> void:
 
 
 func draw_start(pos: Vector2i) -> void:
+	_stabilizer_center = pos
 	_draw_cache = []
 	is_moving = true
 	Global.current_project.can_undo = false
@@ -260,6 +262,17 @@ func _snap_to_guide(
 	return snap_to
 
 
+func _get_stabilized_position(normal_pos: Vector2) -> Vector2:
+	if not Tools.stabilizer_enabled:
+		return normal_pos
+	var difference := normal_pos - _stabilizer_center
+	var distance := difference.length() / Tools.stabilizer_value
+	var angle := difference.angle()
+	var pos := _stabilizer_center + Vector2(distance, distance) * Vector2.from_angle(angle)
+	_stabilizer_center = pos
+	return pos
+
+
 func _get_draw_rect() -> Rect2i:
 	if Global.current_project.has_selection:
 		return Global.current_project.selection_map.get_used_rect()
diff --git a/src/Tools/Draw.gd b/src/Tools/Draw.gd
index 3a46db26d..26a0cf147 100644
--- a/src/Tools/Draw.gd
+++ b/src/Tools/Draw.gd
@@ -318,7 +318,7 @@ func _draw_tool(pos: Vector2) -> PackedVector2Array:
 
 # Bresenham's Algorithm
 # Thanks to https://godotengine.org/qa/35276/tile-based-line-drawing-algorithm-efficiency
-func draw_fill_gap(start: Vector2, end: Vector2) -> void:
+func draw_fill_gap(start: Vector2i, end: Vector2i) -> void:
 	if Global.mirror_view:
 		# Even brushes are not perfectly centred and are offsetted by 1 px so we add it
 		if int(_stroke_dimensions.x) % 2 == 0:
diff --git a/src/Tools/Eraser.gd b/src/Tools/Eraser.gd
index 2b297ae1b..c0b2598bb 100644
--- a/src/Tools/Eraser.gd
+++ b/src/Tools/Eraser.gd
@@ -66,11 +66,12 @@ func draw_start(pos: Vector2i) -> void:
 	cursor_text = ""
 
 
-func draw_move(pos: Vector2i) -> void:
+func draw_move(pos_i: Vector2i) -> void:
+	var pos := _get_stabilized_position(pos_i)
 	pos = snap_position(pos)
 	super.draw_move(pos)
 	if _picking_color:  # Still return even if we released Alt
-		if Input.is_action_pressed("draw_color_picker"):
+		if Input.is_action_pressed(&"draw_color_picker"):
 			_pick_color(pos)
 		return
 
diff --git a/src/Tools/Pencil.gd b/src/Tools/Pencil.gd
index e6a49f8cb..77897b691 100644
--- a/src/Tools/Pencil.gd
+++ b/src/Tools/Pencil.gd
@@ -131,11 +131,12 @@ func draw_start(pos: Vector2i) -> void:
 	cursor_text = ""
 
 
-func draw_move(pos: Vector2i) -> void:
+func draw_move(pos_i: Vector2i) -> void:
+	var pos := _get_stabilized_position(pos_i)
 	pos = snap_position(pos)
 	super.draw_move(pos)
 	if _picking_color:  # Still return even if we released Alt
-		if Input.is_action_pressed("draw_color_picker"):
+		if Input.is_action_pressed(&"draw_color_picker"):
 			_pick_color(pos)
 		return
 
diff --git a/src/Tools/SelectionTools/Lasso.gd b/src/Tools/SelectionTools/Lasso.gd
index a0645fede..74e2f219e 100644
--- a/src/Tools/SelectionTools/Lasso.gd
+++ b/src/Tools/SelectionTools/Lasso.gd
@@ -12,9 +12,10 @@ func draw_start(pos: Vector2i) -> void:
 		_last_position = pos
 
 
-func draw_move(pos: Vector2i) -> void:
+func draw_move(pos_i: Vector2i) -> void:
 	if selection_node.arrow_key_move:
 		return
+	var pos := _get_stabilized_position(pos_i)
 	pos = snap_position(pos)
 	super.draw_move(pos)
 	if !_move:
@@ -28,8 +29,6 @@ func draw_end(pos: Vector2i) -> void:
 	if selection_node.arrow_key_move:
 		return
 	pos = snap_position(pos)
-	if !_move:
-		_draw_points.append(pos)
 	super.draw_end(pos)
 
 
diff --git a/src/Tools/SelectionTools/PaintSelect.gd b/src/Tools/SelectionTools/PaintSelect.gd
index e924a63b8..9f787fc7e 100644
--- a/src/Tools/SelectionTools/PaintSelect.gd
+++ b/src/Tools/SelectionTools/PaintSelect.gd
@@ -40,9 +40,10 @@ func draw_start(pos: Vector2i) -> void:
 		_last_position = pos
 
 
-func draw_move(pos: Vector2i) -> void:
+func draw_move(pos_i: Vector2i) -> void:
 	if selection_node.arrow_key_move:
 		return
+	var pos := _get_stabilized_position(pos_i)
 	pos = snap_position(pos)
 	super.draw_move(pos)
 	if !_move:
@@ -56,8 +57,6 @@ func draw_end(pos: Vector2i) -> void:
 	if selection_node.arrow_key_move:
 		return
 	pos = snap_position(pos)
-	if !_move:
-		_draw_points.append_array(draw_tool(pos))
 	super.draw_end(pos)
 
 
diff --git a/src/Tools/Shading.gd b/src/Tools/Shading.gd
index 10d2b8450..a61f89825 100644
--- a/src/Tools/Shading.gd
+++ b/src/Tools/Shading.gd
@@ -237,11 +237,12 @@ func draw_start(pos: Vector2i) -> void:
 	cursor_text = ""
 
 
-func draw_move(pos: Vector2i) -> void:
+func draw_move(pos_i: Vector2i) -> void:
+	var pos := _get_stabilized_position(pos_i)
 	pos = snap_position(pos)
 	super.draw_move(pos)
 	if _picking_color:  # Still return even if we released Alt
-		if Input.is_action_pressed("draw_color_picker"):
+		if Input.is_action_pressed(&"draw_color_picker"):
 			_pick_color(pos)
 		return
 
diff --git a/src/UI/GlobalToolOptions/GlobalToolOptions.gd b/src/UI/GlobalToolOptions/GlobalToolOptions.gd
index 3d6766122..6bffb9b4e 100644
--- a/src/UI/GlobalToolOptions/GlobalToolOptions.gd
+++ b/src/UI/GlobalToolOptions/GlobalToolOptions.gd
@@ -153,3 +153,11 @@ func _on_SizeMin_value_changed(value: float) -> void:
 func _on_SizeMax_value_changed(value: float) -> void:
 	Tools.brush_size_max = int(value)
 	dynamics_changed.emit()
+
+
+func _on_enable_stabilizer_toggled(toggled_on: bool) -> void:
+	Tools.stabilizer_enabled = toggled_on
+
+
+func _on_stabilizer_value_value_changed(value: float) -> void:
+	Tools.stabilizer_value = value
diff --git a/src/UI/GlobalToolOptions/GlobalToolOptions.tscn b/src/UI/GlobalToolOptions/GlobalToolOptions.tscn
index 35a4fb694..818a210ac 100644
--- a/src/UI/GlobalToolOptions/GlobalToolOptions.tscn
+++ b/src/UI/GlobalToolOptions/GlobalToolOptions.tscn
@@ -45,7 +45,6 @@ gradient = SubResource("3")
 gradient = SubResource("3")
 
 [node name="Global Tool Options" type="PanelContainer"]
-custom_minimum_size = Vector2(0, 36)
 offset_left = 1.0
 offset_right = 195.0
 offset_bottom = 50.0
@@ -147,12 +146,38 @@ texture = ExtResource("6")
 
 [node name="DynamicsPanel" type="PopupPanel" parent="."]
 canvas_item_default_texture_filter = 0
+size = Vector2i(300, 334)
 
 [node name="VBoxContainer" type="VBoxContainer" parent="DynamicsPanel"]
-offset_left = 4.0
-offset_top = 4.0
-offset_right = 186.0
-offset_bottom = 288.0
+offset_left = 8.0
+offset_top = 8.0
+offset_right = 292.0
+offset_bottom = 326.0
+
+[node name="StabilizerContainer" type="HBoxContainer" parent="DynamicsPanel/VBoxContainer"]
+layout_mode = 2
+
+[node name="EnableStabilizer" type="CheckButton" parent="DynamicsPanel/VBoxContainer/StabilizerContainer"]
+layout_mode = 2
+mouse_default_cursor_shape = 2
+text = "Stabilizer"
+
+[node name="StabilizerValue" type="TextureProgressBar" parent="DynamicsPanel/VBoxContainer/StabilizerContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+focus_mode = 2
+mouse_default_cursor_shape = 2
+theme_type_variation = &"ValueSlider"
+min_value = 1.0
+max_value = 64.0
+value = 16.0
+allow_greater = true
+nine_patch_stretch = true
+stretch_margin_left = 3
+stretch_margin_top = 3
+stretch_margin_right = 3
+stretch_margin_bottom = 3
+script = ExtResource("5")
 
 [node name="DynamicsOptions" type="GridContainer" parent="DynamicsPanel/VBoxContainer"]
 layout_mode = 2
@@ -412,6 +437,8 @@ offset_bottom = 23.0
 [connection signal="toggled" from="ScrollContainer/CenterContainer/GridContainer/Vertical" to="." method="_on_Vertical_toggled"]
 [connection signal="toggled" from="ScrollContainer/CenterContainer/GridContainer/PixelPerfect" to="." method="_on_PixelPerfect_toggled"]
 [connection signal="pressed" from="ScrollContainer/CenterContainer/GridContainer/Dynamics" to="." method="_on_Dynamics_pressed"]
+[connection signal="toggled" from="DynamicsPanel/VBoxContainer/StabilizerContainer/EnableStabilizer" to="." method="_on_enable_stabilizer_toggled"]
+[connection signal="value_changed" from="DynamicsPanel/VBoxContainer/StabilizerContainer/StabilizerValue" to="." method="_on_stabilizer_value_value_changed"]
 [connection signal="value_changed" from="DynamicsPanel/VBoxContainer/LimitContainer/AlphaMin" to="." method="_on_AlphaMin_value_changed"]
 [connection signal="value_changed" from="DynamicsPanel/VBoxContainer/LimitContainer/AlphaMax" to="." method="_on_AlphaMax_value_changed"]
 [connection signal="value_changed" from="DynamicsPanel/VBoxContainer/LimitContainer/SizeMin" to="." method="_on_SizeMin_value_changed"]