Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shader based rotation #683

Merged
merged 6 commits into from
Apr 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions src/Shaders/Rotation.shader
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
shader_type canvas_item;

uniform float angle;
uniform sampler2D selection_tex;
uniform vec2 selection_pivot;
uniform vec2 selection_size;


vec2 rotate(vec2 uv, vec2 pivot, float ratio) {
// Scale and center image
uv.x -= 0.5;
uv.x *= ratio;
uv.x += 0.5;

// Rotate image
uv -= pivot;
uv = vec2(cos(angle) * uv.x + sin(angle) * uv.y,
-sin(angle) * uv.x + cos(angle) * uv.y);
uv.x /= ratio;
uv += pivot;

return uv;
}


void fragment() {
vec4 original = texture(TEXTURE, UV);
float selection = texture(selection_tex, UV).a;

vec2 tex_size = 1.0 / TEXTURE_PIXEL_SIZE; // Texture size in real pixel coordinates
vec2 pixelated_uv = floor(UV * tex_size) / (tex_size - 1.0); // Pixelate UV to fit resolution
vec2 pivot = selection_pivot / tex_size; // Normalize pivot position
vec2 sel_size = selection_size / tex_size; // Normalize selection size
float ratio = tex_size.x / tex_size.y; // Resolution ratio

// Make a border to prevent stretching pixels on the edge
vec2 border_uv = rotate(pixelated_uv, pivot, ratio);
border_uv -= pivot - sel_size / 2.0; // Move the border to selection position
border_uv /= sel_size; // Set border size to selection size

// Center the border
border_uv -= 0.5;
border_uv *= 2.0;
border_uv = abs(border_uv);

float border = max(border_uv.x, border_uv.y); // This is a rectangular gradient
border = floor(border - TEXTURE_PIXEL_SIZE.x); // Turn the grad into a rectangle shape
border = 1.0 - clamp(border, 0.0, 1.0); // Invert the rectangle

// Mixing
vec4 rotated = texture(TEXTURE, rotate(pixelated_uv, pivot, ratio)); // Rotated image
rotated.a *= texture(selection_tex, rotate(pixelated_uv, pivot, ratio)).a; // Combine with rotated selection mask

COLOR.rgb = mix(original.rgb, rotated.rgb, rotated.a * border); // Mix original image and rotated
COLOR.a = mix(original.a, 0.0, selection); // Remove alpha on the selected area
COLOR.a = mix(COLOR.a, 1.0, rotated.a * border); // Combine alpha of original image and rotated
}
84 changes: 62 additions & 22 deletions src/UI/Dialogs/ImageEffects/RotateImage.gd
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
extends ImageEffect

var live_preview: bool = true
var confirmed := false
var shader: Shader = preload("res://src/Shaders/Rotation.shader")

onready var type_option_button: OptionButton = $VBoxContainer/HBoxContainer2/TypeOptionButton
onready var angle_hslider: HSlider = $VBoxContainer/AngleOptions/AngleHSlider
onready var angle_spinbox: SpinBox = $VBoxContainer/AngleOptions/AngleSpinBox
Expand All @@ -12,6 +15,7 @@ func _ready() -> void:
type_option_button.add_item("Rotxel")
type_option_button.add_item("Upscale, Rotate and Downscale")
type_option_button.add_item("Nearest neighbour")
type_option_button.add_item("Nearest neighbour (Shader)")


func set_nodes() -> void:
Expand All @@ -21,21 +25,27 @@ func set_nodes() -> void:


func _about_to_show() -> void:
confirmed = false
._about_to_show()
wait_apply_timer.wait_time = wait_time_spinbox.value / 1000.0
angle_hslider.value = 0


func commit_action(_cel: Image, _project: Project = Global.current_project) -> void:
var angle: float = deg2rad(angle_hslider.value)
# warning-ignore:integer_division
# warning-ignore:integer_division
# warning-ignore:integer_division
# warning-ignore:integer_division
var pivot = Vector2(_cel.get_width() / 2, _cel.get_height() / 2)

# Pivot correction in case of even size
if _cel.get_width() % 2 == 0:
pivot.x -= 0.5
if _cel.get_height() % 2 == 0:
pivot.y -= 0.5
if type_option_button.text != "Nearest neighbour (Shader)":
if _cel.get_width() % 2 == 0:
pivot.x -= 0.5
if _cel.get_height() % 2 == 0:
pivot.y -= 0.5

var selection_size := _cel.get_size()
var selection_tex := ImageTexture.new()

var image := Image.new()
image.copy_from(_cel)
Expand All @@ -45,36 +55,60 @@ func commit_action(_cel: Image, _project: Project = Global.current_project) -> v
selection_rectangle.position
+ ((selection_rectangle.end - selection_rectangle.position) / 2)
)
# Pivot correction in case of even size
if int(selection_rectangle.end.x - selection_rectangle.position.x) % 2 == 0:
pivot.x -= 0.5
if int(selection_rectangle.end.y - selection_rectangle.position.y) % 2 == 0:
pivot.y -= 0.5
image.lock()
_cel.lock()
for x in _project.size.x:
for y in _project.size.y:
var pos := Vector2(x, y)
if !_project.can_pixel_get_drawn(pos):
image.set_pixelv(pos, Color(0, 0, 0, 0))
else:
_cel.set_pixelv(pos, Color(0, 0, 0, 0))
image.unlock()
_cel.unlock()
selection_size = selection_rectangle.size

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)":
# Pivot correction in case of even size
if int(selection_rectangle.end.x - selection_rectangle.position.x) % 2 == 0:
pivot.x -= 0.5
if int(selection_rectangle.end.y - selection_rectangle.position.y) % 2 == 0:
pivot.y -= 0.5
image.lock()
_cel.lock()
for x in _project.size.x:
for y in _project.size.y:
var pos := Vector2(x, y)
if !_project.can_pixel_get_drawn(pos):
image.set_pixelv(pos, Color(0, 0, 0, 0))
else:
_cel.set_pixelv(pos, Color(0, 0, 0, 0))
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)
"Nearest neighbour (Shader)":
if !confirmed:
preview.material.set_shader_param("angle", angle)
preview.material.set_shader_param("selection_tex", selection_tex)
preview.material.set_shader_param("selection_pivot", pivot)
preview.material.set_shader_param("selection_size", selection_size)
else:
var params = {
"angle": angle,
"selection_tex": selection_tex,
"selection_pivot": pivot,
"selection_size": selection_size
}
var gen := ShaderImageEffect.new()
gen.generate_image(_cel, shader, params, _project.size)
yield(gen, "done")

if _project.has_selection and selection_checkbox.pressed:
_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 _confirmed() -> void:
confirmed = true
._confirmed()
angle_hslider.value = 0

Expand All @@ -92,6 +126,12 @@ func _on_SpinBox_value_changed(_value: float) -> void:


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)
else:
preview.set_material(null)
update_preview()


Expand Down