1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-03-12 22:35:18 +00:00

Implement @azagaya's rotation with smear shader

This commit is contained in:
Emmanouil Papadeas 2022-07-28 01:04:24 +03:00
parent b424319746
commit 559da5414b
4 changed files with 378 additions and 47 deletions

View file

@ -0,0 +1,127 @@
// Original shader written by Azagaya
shader_type canvas_item;
render_mode unshaded;
uniform sampler2D selection_tex;
uniform float initial_angle = 0.0;
uniform float ending_angle = 0.0;
uniform float tolerance : hint_range(0.0, 255.0) = 100.0;
uniform vec2 origin = vec2(0.0, 0.0);
uniform vec2 position = vec2(0.0, 0.0);
bool similarColors(vec4 c1, vec4 c2) {
return (distance(c1 * 255.0, c2 * 255.0) < tolerance);
}
vec4 rotate(sampler2D tex, vec2 uv, vec2 tex_pixel_size) {
vec4 color = vec4(0.0);
vec2 center = origin / tex_pixel_size;
vec2 size = 1.0 / tex_pixel_size;
vec2 coord = uv / tex_pixel_size + position;
int dx = 3 * (int(coord.x) - int(center.x));
int dy = 3 * (int(coord.y) - int(center.y));
int k = 0;
int ox, oy;
float dir;
float mag;
float init_angle = initial_angle < ending_angle ? initial_angle : ending_angle;
for (float angle = ending_angle; angle >= init_angle && color.a == 0.0; angle -= 1.0) {
float ang = angle*3.1416/180.0;
for (k = 0; k < 9; k++) {
int i = -1 + int(k % 3);
int j = -1 + k / 3;
dir = atan(float(dy + j), float(dx + i));
mag = sqrt(pow(float(dx + i), 2.0) + pow(float(dy + j), 2.0));
dir -= ang;
ox = int(round(3.0 * center.x + 1.0 + mag * cos(dir)));
oy = int(round(3.0 * center.y + 1.0 + mag * sin(dir)));
if (int(size.x) % 2 != 0) {
ox += 1;
}
if (int(size.y) % 2 != 0) {
oy += 1;
}
if (ox >= 0 && ox < 3 * int(size.x) && oy >= 0 && oy < 3 * int(size.y)) {
break;
}
}
if (k == 9){
continue;
}
int row = int(oy % 3);
int col = int(ox % 3);
int index = col + 3 * row;
ox = int(round(float(ox - 1) / 3.0));
oy = int(round(float(oy - 1) / 3.0));
if (ox == 0 || ox == int(size.x) - 1 || oy == 0 || oy == int(size.y) - 1) {
color = texture(tex, vec2(float(ox), float(oy)) * tex_pixel_size);
}
else {
vec4 a = texture(tex, vec2(float(ox - 1), float(oy - 1)) * tex_pixel_size);
vec4 b = texture(tex, vec2(float(ox), float(oy - 1)) * tex_pixel_size);
vec4 c = texture(tex, vec2(float(ox + 1), float(oy - 1)) * tex_pixel_size);
vec4 d = texture(tex, vec2(float(ox - 1), float(oy)) * tex_pixel_size);
vec4 e = texture(tex, vec2(float(ox), float(oy)) * tex_pixel_size);
vec4 f = texture(tex, vec2(float(ox + 1), float(oy)) * tex_pixel_size);
vec4 g = texture(tex, vec2(float(ox - 1), float(oy + 1)) * tex_pixel_size);
vec4 h = texture(tex, vec2(float(ox), float(oy + 1)) * tex_pixel_size);
vec4 l = texture(tex, vec2(float(ox + 1), float(oy + 1)) * tex_pixel_size);
if (index == 0) {
color = similarColors(d,b) && !similarColors(d,h) && !similarColors(b,f) ? d : e;
}
else if (index == 1) {
color = (similarColors(d,b) && !similarColors(d,h) && !similarColors(b,f) && !similarColors(e,c))
|| (similarColors(b,f) && !similarColors(d,b) && !similarColors(f,h) && !similarColors(e,a)) ? b : e;
}
else if (index == 2) {
color = similarColors(b,f) && !similarColors(d,b) && !similarColors(f,h) ? f : e;
}
else if (index == 3) {
color = (similarColors(d,h) && !similarColors(f,h) && !similarColors(d,b) && !similarColors(e,a))
|| (similarColors(d,b) && !similarColors(d,h) && !similarColors(b,f) && !similarColors(e,g)) ? d : e;
}
else if (index == 4) {
color = e;
}
else if (index == 5) {
color = (similarColors(b,f) && !similarColors(d,b) && !similarColors(f,h) && !similarColors(e,l))
|| (similarColors(f,h) && !similarColors(b,f) && !similarColors(d,h) && !similarColors(e,c)) ? f : e;
}
else if (index == 6) {
color = similarColors(d,h) && !similarColors(f,h) && !similarColors(d,b) ? d : e;
}
else if (index == 7) {
color = (similarColors(f,h) && !similarColors(f,b)&& !similarColors(d,h) && !similarColors(e,g))
|| (similarColors(d,h) && !similarColors(f,h) && !similarColors(d,b) && !similarColors(e,l)) ? h : e;
}
else if (index == 8) {
color = similarColors(f,h) && !similarColors(f,b) && !similarColors(d,h)? f : e;
}
}
color.a *= step(ending_angle, initial_angle) +
step(initial_angle ,ending_angle) * (angle - initial_angle) / (ending_angle - initial_angle + 0.01);
}
return color;
}
void fragment() {
vec4 original = texture(TEXTURE, UV);
vec4 selection = texture(selection_tex, UV);
vec4 rotated_selection = rotate(selection_tex, UV, TEXTURE_PIXEL_SIZE);
vec4 rotated = rotate(TEXTURE, UV, TEXTURE_PIXEL_SIZE);
rotated = mix(vec4(0.0), rotated, rotated_selection.a);
// Combine original and rotated image only when intersecting, otherwise just pure rotated image.
COLOR.rgb = mix(mix(original.rgb, rotated.rgb, rotated.a), rotated.rgb, selection.a);
COLOR.a = mix(original.a, 0.0, selection.a); // Remove alpha on the selected area
COLOR.a = mix(COLOR.a, 1.0, rotated.a); // Combine alpha of original image and rotated
}

View file

@ -1,7 +1,8 @@
extends ImageEffect
var live_preview: bool = true
var shader: Shader = preload("res://src/Shaders/Rotation.shader")
var rotxel_shader: Shader
var nn_shader: Shader = preload("res://src/Shaders/Rotation/NearestNeightbour.shader")
var pivot := Vector2.INF
var drag_pivot := false
@ -11,12 +12,20 @@ onready var x_pivot: SpinBox = $VBoxContainer/TitleButtons/XPivot
onready var y_pivot: SpinBox = $VBoxContainer/TitleButtons/YPivot
onready var angle_hslider: HSlider = $VBoxContainer/AngleOptions/AngleHSlider
onready var angle_spinbox: SpinBox = $VBoxContainer/AngleOptions/AngleSpinBox
onready var smear_options: Container = $VBoxContainer/SmearOptions
onready var init_angle_hslider: HSlider = smear_options.get_node("AngleOptions/InitialAngleHSlider")
onready var init_angle_spinbox: SpinBox = smear_options.get_node("AngleOptions/InitialAngleSpinBox")
onready var tolerance_hslider: HSlider = smear_options.get_node("Tolerance/ToleranceHSlider")
onready var tolerance_spinbox: SpinBox = smear_options.get_node("Tolerance/ToleranceSpinBox")
onready var wait_apply_timer: Timer = $WaitApply
onready var wait_time_spinbox: SpinBox = $VBoxContainer/WaitSettings/WaitTime
func _ready() -> void:
# Algorithms are arranged according to their speed
if OS.get_name() != "HTML5":
type_option_button.add_item("Rotxel with Smear")
rotxel_shader = load("res://src/Shaders/Rotation/SmearRotxel.shader")
type_option_button.add_item("Nearest neighbour (Shader)")
type_option_button.add_item("Nearest neighbour")
type_option_button.add_item("Rotxel")
@ -83,7 +92,7 @@ func commit_action(cel: Image, _project: Project = Global.current_project) -> vo
var selection: Image = _project.bitmap_to_image(_project.selection_bitmap)
selection_tex.create_from_image(selection, 0)
if type_option_button.text != "Nearest neighbour (Shader)":
if !_type_is_shader():
image.lock()
cel.lock()
for x in _project.size.x:
@ -96,12 +105,23 @@ func commit_action(cel: Image, _project: Project = Global.current_project) -> vo
image.unlock()
cel.unlock()
match type_option_button.text:
"Rotxel":
DrawingAlgos.rotxel(image, angle, pivot)
"Nearest neighbour":
DrawingAlgos.nn_rotate(image, angle, pivot)
"Upscale, Rotate and Downscale":
DrawingAlgos.fake_rotsprite(image, angle, pivot)
"Rotxel with Smear":
var params := {
"initial_angle": init_angle_hslider.value,
"ending_angle": angle_hslider.value,
"tolerance": tolerance_hslider.value,
"selection_tex": selection_tex,
"origin": pivot / cel.get_size(),
"selection_size": selection_size
}
if !confirmed:
for param in params:
preview.material.set_shader_param(param, params[param])
else:
var gen := ShaderImageEffect.new()
gen.generate_image(cel, rotxel_shader, params, _project.size)
yield(gen, "done")
"Nearest neighbour (Shader)":
var params := {
"angle": angle,
@ -114,19 +134,45 @@ func commit_action(cel: Image, _project: Project = Global.current_project) -> vo
preview.material.set_shader_param(param, params[param])
else:
var gen := ShaderImageEffect.new()
gen.generate_image(cel, shader, params, _project.size)
gen.generate_image(cel, nn_shader, params, _project.size)
yield(gen, "done")
"Rotxel":
DrawingAlgos.rotxel(image, angle, pivot)
"Nearest neighbour":
DrawingAlgos.nn_rotate(image, angle, pivot)
"Upscale, Rotate and Downscale":
DrawingAlgos.fake_rotsprite(image, angle, pivot)
if (
_project.has_selection
and selection_checkbox.pressed
and type_option_button.text != "Nearest neighbour (Shader)"
):
if _project.has_selection and selection_checkbox.pressed and !_type_is_shader():
cel.blend_rect(image, Rect2(Vector2.ZERO, image.get_size()), Vector2.ZERO)
else:
cel.blit_rect(image, Rect2(Vector2.ZERO, image.get_size()), Vector2.ZERO)
func _type_is_shader() -> bool:
return (
type_option_button.text == "Nearest neighbour (Shader)"
or type_option_button.text == "Rotxel with Smear"
)
func _on_TypeOptionButton_item_selected(_id: int) -> void:
if type_option_button.text == "Rotxel with Smear":
var sm := ShaderMaterial.new()
sm.shader = rotxel_shader
preview.set_material(sm)
smear_options.visible = true
elif type_option_button.text == "Nearest neighbour (Shader)":
var sm := ShaderMaterial.new()
sm.shader = nn_shader
preview.set_material(sm)
smear_options.visible = false
else:
preview.set_material(null)
smear_options.visible = false
update_preview()
func _on_AngleHSlider_value_changed(_value: float) -> void:
angle_spinbox.value = angle_hslider.value
if live_preview:
@ -139,14 +185,28 @@ func _on_AngleSpinBox_value_changed(_value: float) -> void:
angle_hslider.value = angle_spinbox.value
func _on_TypeOptionButton_item_selected(_id: int) -> void:
if type_option_button.text == "Nearest neighbour (Shader)":
var sm := ShaderMaterial.new()
sm.shader = shader
preview.set_material(sm)
func _on_InitialAngleHSlider_value_changed(_value: float) -> void:
init_angle_spinbox.value = init_angle_hslider.value
if live_preview:
update_preview()
else:
preview.set_material(null)
update_preview()
wait_apply_timer.start()
func _on_InitialAngleSpinBox_value_changed(_value: float) -> void:
init_angle_hslider.value = init_angle_spinbox.value
func _on_ToleranceHSlider_value_changed(_value: float) -> void:
tolerance_spinbox.value = tolerance_hslider.value
if live_preview:
update_preview()
else:
wait_apply_timer.start()
func _on_ToleranceSpinBox_value_changed(_value: float) -> void:
tolerance_hslider.value = tolerance_spinbox.value
func _on_WaitApply_timeout() -> void:
@ -178,6 +238,19 @@ func _on_quick_change_angle_pressed(angle_value: int) -> void:
angle_hslider.value = new_angle
func _on_quick_change_init_angle_pressed(angle_value: int) -> void:
var current_angle := init_angle_hslider.value
var new_angle := current_angle + angle_value
if angle_value == 0:
new_angle = 0
if new_angle < 0:
new_angle = new_angle + 360
elif new_angle >= 360:
new_angle = new_angle - 360
init_angle_hslider.value = new_angle
func _on_Centre_pressed() -> void:
decide_pivot()

View file

@ -21,13 +21,13 @@ margin_bottom = -36.0
[node name="AspectRatioContainer" type="AspectRatioContainer" parent="VBoxContainer"]
margin_right = 326.0
margin_bottom = 252.0
margin_bottom = 200.0
size_flags_vertical = 3
[node name="Preview" type="TextureRect" parent="VBoxContainer/AspectRatioContainer"]
margin_left = 37.0
margin_right = 289.0
margin_bottom = 252.0
margin_left = 63.0
margin_right = 263.0
margin_bottom = 200.0
rect_min_size = Vector2( 200, 200 )
expand = true
stretch_mode = 5
@ -40,15 +40,15 @@ margin_right = 0.0
margin_bottom = 0.0
[node name="Indicator" type="Control" parent="VBoxContainer/AspectRatioContainer"]
margin_left = 37.0
margin_right = 289.0
margin_bottom = 252.0
margin_left = 63.0
margin_right = 263.0
margin_bottom = 200.0
mouse_default_cursor_shape = 2
[node name="LiveSettings" type="HBoxContainer" parent="VBoxContainer"]
margin_top = 256.0
margin_top = 204.0
margin_right = 326.0
margin_bottom = 280.0
margin_bottom = 228.0
alignment = 1
[node name="LiveCheckbox" type="CheckBox" parent="VBoxContainer/LiveSettings"]
@ -86,9 +86,9 @@ editable = false
suffix = "msec"
[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer"]
margin_top = 284.0
margin_top = 232.0
margin_right = 326.0
margin_bottom = 304.0
margin_bottom = 252.0
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer2"]
margin_top = 3.0
@ -105,14 +105,14 @@ size_flags_horizontal = 3
size_flags_vertical = 3
[node name="HSeparator" type="HSeparator" parent="VBoxContainer"]
margin_top = 308.0
margin_top = 256.0
margin_right = 326.0
margin_bottom = 312.0
margin_bottom = 260.0
[node name="TitleButtons" type="HBoxContainer" parent="VBoxContainer"]
margin_top = 316.0
margin_top = 264.0
margin_right = 326.0
margin_bottom = 340.0
margin_bottom = 288.0
[node name="Label" type="Label" parent="VBoxContainer/TitleButtons"]
margin_top = 5.0
@ -155,14 +155,14 @@ mouse_default_cursor_shape = 2
text = "Center"
[node name="HSeparator2" type="HSeparator" parent="VBoxContainer"]
margin_top = 344.0
margin_top = 292.0
margin_right = 326.0
margin_bottom = 348.0
margin_bottom = 296.0
[node name="AngleOptions" type="HBoxContainer" parent="VBoxContainer"]
margin_top = 352.0
margin_top = 300.0
margin_right = 326.0
margin_bottom = 376.0
margin_bottom = 324.0
[node name="Label" type="Label" parent="VBoxContainer/AngleOptions"]
margin_top = 5.0
@ -191,9 +191,9 @@ max_value = 359.0
suffix = "°"
[node name="QuickRotations" type="HBoxContainer" parent="VBoxContainer"]
margin_top = 380.0
margin_top = 328.0
margin_right = 326.0
margin_bottom = 400.0
margin_bottom = 348.0
alignment = 1
[node name="Deduct90" type="Button" parent="VBoxContainer/QuickRotations"]
@ -231,15 +231,137 @@ margin_bottom = 20.0
mouse_default_cursor_shape = 2
text = "+90"
[node name="HSeparator3" type="HSeparator" parent="VBoxContainer"]
margin_top = 404.0
[node name="SmearOptions" type="VBoxContainer" parent="VBoxContainer"]
visible = false
margin_top = 352.0
margin_right = 326.0
margin_bottom = 408.0
margin_bottom = 454.0
[node name="HSeparator" type="HSeparator" parent="VBoxContainer/SmearOptions"]
margin_right = 326.0
margin_bottom = 4.0
[node name="Label" type="Label" parent="VBoxContainer/SmearOptions"]
margin_top = 8.0
margin_right = 326.0
margin_bottom = 22.0
text = "Smear options:"
[node name="Tolerance" type="HBoxContainer" parent="VBoxContainer/SmearOptions"]
margin_top = 26.0
margin_right = 326.0
margin_bottom = 50.0
[node name="Label" type="Label" parent="VBoxContainer/SmearOptions/Tolerance"]
margin_top = 5.0
margin_right = 66.0
margin_bottom = 19.0
text = "Tolerance:"
[node name="ToleranceHSlider" type="HSlider" parent="VBoxContainer/SmearOptions/Tolerance"]
margin_left = 70.0
margin_right = 248.0
margin_bottom = 24.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
size_flags_vertical = 3
max_value = 255.0
value = 100.0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="ToleranceSpinBox" type="SpinBox" parent="VBoxContainer/SmearOptions/Tolerance"]
margin_left = 252.0
margin_right = 326.0
margin_bottom = 24.0
mouse_default_cursor_shape = 2
max_value = 255.0
value = 100.0
[node name="AngleOptions" type="HBoxContainer" parent="VBoxContainer/SmearOptions"]
margin_top = 54.0
margin_right = 326.0
margin_bottom = 78.0
[node name="Label" type="Label" parent="VBoxContainer/SmearOptions/AngleOptions"]
margin_top = 5.0
margin_right = 79.0
margin_bottom = 19.0
text = "Initial angle:"
[node name="InitialAngleHSlider" type="HSlider" parent="VBoxContainer/SmearOptions/AngleOptions"]
margin_left = 83.0
margin_right = 248.0
margin_bottom = 24.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
size_flags_vertical = 3
max_value = 359.0
value = 359.0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="InitialAngleSpinBox" type="SpinBox" parent="VBoxContainer/SmearOptions/AngleOptions"]
margin_left = 252.0
margin_right = 326.0
margin_bottom = 24.0
mouse_default_cursor_shape = 2
max_value = 359.0
value = 359.0
suffix = "°"
[node name="QuickRotations" type="HBoxContainer" parent="VBoxContainer/SmearOptions"]
margin_top = 82.0
margin_right = 326.0
margin_bottom = 102.0
alignment = 1
[node name="Deduct90" type="Button" parent="VBoxContainer/SmearOptions/QuickRotations"]
margin_left = 76.0
margin_right = 109.0
margin_bottom = 20.0
mouse_default_cursor_shape = 2
text = "-90"
[node name="Deduct45" type="Button" parent="VBoxContainer/SmearOptions/QuickRotations"]
margin_left = 113.0
margin_right = 146.0
margin_bottom = 20.0
mouse_default_cursor_shape = 2
text = "-45"
[node name="Zero" type="Button" parent="VBoxContainer/SmearOptions/QuickRotations"]
margin_left = 150.0
margin_right = 170.0
margin_bottom = 20.0
mouse_default_cursor_shape = 2
text = "0"
[node name="Add45" type="Button" parent="VBoxContainer/SmearOptions/QuickRotations"]
margin_left = 174.0
margin_right = 210.0
margin_bottom = 20.0
mouse_default_cursor_shape = 2
text = "+45"
[node name="Add90" type="Button" parent="VBoxContainer/SmearOptions/QuickRotations"]
margin_left = 214.0
margin_right = 250.0
margin_bottom = 20.0
mouse_default_cursor_shape = 2
text = "+90"
[node name="HSeparator3" type="HSeparator" parent="VBoxContainer"]
margin_top = 458.0
margin_right = 326.0
margin_bottom = 462.0
[node name="OptionsContainer" type="HBoxContainer" parent="VBoxContainer"]
margin_top = 412.0
margin_top = 466.0
margin_right = 326.0
margin_bottom = 436.0
margin_bottom = 490.0
[node name="SelectionCheckBox" type="CheckBox" parent="VBoxContainer/OptionsContainer"]
margin_right = 160.0
@ -274,4 +396,13 @@ selected = 0
[connection signal="pressed" from="VBoxContainer/QuickRotations/Zero" to="." method="_on_quick_change_angle_pressed" binds= [ 0 ]]
[connection signal="pressed" from="VBoxContainer/QuickRotations/Add45" to="." method="_on_quick_change_angle_pressed" binds= [ 45 ]]
[connection signal="pressed" from="VBoxContainer/QuickRotations/Add90" to="." method="_on_quick_change_angle_pressed" binds= [ 90 ]]
[connection signal="value_changed" from="VBoxContainer/SmearOptions/Tolerance/ToleranceHSlider" to="." method="_on_ToleranceHSlider_value_changed"]
[connection signal="value_changed" from="VBoxContainer/SmearOptions/Tolerance/ToleranceSpinBox" to="." method="_on_ToleranceSpinBox_value_changed"]
[connection signal="value_changed" from="VBoxContainer/SmearOptions/AngleOptions/InitialAngleHSlider" to="." method="_on_InitialAngleHSlider_value_changed"]
[connection signal="value_changed" from="VBoxContainer/SmearOptions/AngleOptions/InitialAngleSpinBox" to="." method="_on_InitialAngleSpinBox_value_changed"]
[connection signal="pressed" from="VBoxContainer/SmearOptions/QuickRotations/Deduct90" to="." method="_on_quick_change_init_angle_pressed" binds= [ -90 ]]
[connection signal="pressed" from="VBoxContainer/SmearOptions/QuickRotations/Deduct45" to="." method="_on_quick_change_init_angle_pressed" binds= [ -45 ]]
[connection signal="pressed" from="VBoxContainer/SmearOptions/QuickRotations/Zero" to="." method="_on_quick_change_init_angle_pressed" binds= [ 0 ]]
[connection signal="pressed" from="VBoxContainer/SmearOptions/QuickRotations/Add45" to="." method="_on_quick_change_init_angle_pressed" binds= [ 45 ]]
[connection signal="pressed" from="VBoxContainer/SmearOptions/QuickRotations/Add90" to="." method="_on_quick_change_init_angle_pressed" binds= [ 90 ]]
[connection signal="timeout" from="WaitApply" to="." method="_on_WaitApply_timeout"]