diff --git a/project.godot b/project.godot index 7977f5254..3e63d1c31 100644 --- a/project.godot +++ b/project.godot @@ -99,6 +99,11 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://src/Classes/Project.gd" }, { +"base": "Reference", +"class": "ShaderImageEffect", +"language": "GDScript", +"path": "res://src/Classes/ShaderImageEffect.gd" +}, { "base": "Guide", "class": "SymmetryGuide", "language": "GDScript", @@ -123,6 +128,7 @@ _global_script_class_icons={ "PaletteSwatch": "", "Patterns": "", "Project": "", +"ShaderImageEffect": "", "SymmetryGuide": "" } diff --git a/src/Autoload/DrawingAlgos.gd b/src/Autoload/DrawingAlgos.gd index 6e62a92fe..a6320cdab 100644 --- a/src/Autoload/DrawingAlgos.gd +++ b/src/Autoload/DrawingAlgos.gd @@ -359,45 +359,47 @@ func general_undo_centralize() -> void: project.undo_redo.commit_action() -func invert_image_colors(image : Image, affect_selection : bool, project : Project, red := true, green := true, blue := true, alpha := false) -> void: - image.lock() - for x in project.size.x: - for y in project.size.y: - var pos := Vector2(x, y) - if affect_selection and !project.can_pixel_get_drawn(pos): - continue - var px_color := image.get_pixelv(pos) - # Manually invert each color channel - if red: - px_color.r = 1.0 - px_color.r - if green: - px_color.g = 1.0 - px_color.g - if blue: - px_color.b = 1.0 - px_color.b - if alpha: - px_color.a = 1.0 - px_color.a - image.set_pixelv(pos, px_color) +# TO BE REMOVED +# func invert_image_colors(image : Image, affect_selection : bool, project : Project, red := true, green := true, blue := true, alpha := false) -> void: +# image.lock() +# for x in project.size.x: +# for y in project.size.y: +# var pos := Vector2(x, y) +# if affect_selection and !project.can_pixel_get_drawn(pos): +# continue +# var px_color := image.get_pixelv(pos) +# # Manually invert each color channel +# if red: +# px_color.r = 1.0 - px_color.r +# if green: +# px_color.g = 1.0 - px_color.g +# if blue: +# px_color.b = 1.0 - px_color.b +# if alpha: +# px_color.a = 1.0 - px_color.a +# image.set_pixelv(pos, px_color) -func desaturate_image(image : Image, affect_selection : bool, project : Project, red := true, green := true, blue := true, alpha := false) -> void: - image.lock() - for x in project.size.x: - for y in project.size.y: - var pos := Vector2(x, y) - if affect_selection and !project.can_pixel_get_drawn(pos): - continue - var px_color := image.get_pixelv(pos) - var gray = px_color.v - if red: - px_color.r = gray - if green: - px_color.g = gray - if blue: - px_color.b = gray - if alpha: - px_color.a = gray +# TO BE REMOVED +# func desaturate_image(image : Image, affect_selection : bool, project : Project, red := true, green := true, blue := true, alpha := false) -> void: +# image.lock() +# for x in project.size.x: +# for y in project.size.y: +# var pos := Vector2(x, y) +# if affect_selection and !project.can_pixel_get_drawn(pos): +# continue +# var px_color := image.get_pixelv(pos) +# var gray = px_color.v +# if red: +# px_color.r = gray +# if green: +# px_color.g = gray +# if blue: +# px_color.b = gray +# if alpha: +# px_color.a = gray - image.set_pixelv(pos, px_color) +# image.set_pixelv(pos, px_color) func generate_outline(image : Image, affect_selection : bool, project : Project, outline_color : Color, thickness : int, diagonal : bool, inside_image : bool) -> void: @@ -539,41 +541,42 @@ func generate_outline(image : Image, affect_selection : bool, project : Project, image.copy_from(new_image) -func adjust_hsv(img: Image, delta_h : float, delta_s : float, delta_v : float, affect_selection : bool, project : Project) -> void: - img.lock() - for x in project.size.x: - for y in project.size.y: - var pos := Vector2(x, y) - if affect_selection and !project.can_pixel_get_drawn(pos): - continue - var c : Color = img.get_pixelv(pos) - # Hue - var hue = range_lerp(c.h,0,1,-180,180) - hue = hue + delta_h +# TO BE REMOVED +# func adjust_hsv(img: Image, delta_h : float, delta_s : float, delta_v : float, affect_selection : bool, project : Project) -> void: +# img.lock() +# for x in project.size.x: +# for y in project.size.y: +# var pos := Vector2(x, y) +# if affect_selection and !project.can_pixel_get_drawn(pos): +# continue +# var c : Color = img.get_pixelv(pos) +# # Hue +# var hue = range_lerp(c.h,0,1,-180,180) +# hue = hue + delta_h - while(hue >= 180): - hue -= 360 - while(hue < -180): - hue += 360 +# while(hue >= 180): +# hue -= 360 +# while(hue < -180): +# hue += 360 - # Saturation - var sat = c.s - if delta_s > 0: - sat = range_lerp(delta_s,0,100,c.s,1) - elif delta_s < 0: - sat = range_lerp(delta_s,-100,0,0,c.s) +# # Saturation +# var sat = c.s +# if delta_s > 0: +# sat = range_lerp(delta_s,0,100,c.s,1) +# elif delta_s < 0: +# sat = range_lerp(delta_s,-100,0,0,c.s) - # Value - var val = c.v - if delta_v > 0: - val = range_lerp(delta_v,0,100,c.v,1) - elif delta_v < 0: - val = range_lerp(delta_v,-100,0,0,c.v) +# # Value +# var val = c.v +# if delta_v > 0: +# val = range_lerp(delta_v,0,100,c.v,1) +# elif delta_v < 0: +# val = range_lerp(delta_v,-100,0,0,c.v) - c.h = range_lerp(hue,-180,180,0,1) - c.s = sat - c.v = val - img.set_pixelv(pos, c) +# c.h = range_lerp(hue,-180,180,0,1) +# c.s = sat +# c.v = val +# img.set_pixelv(pos, c) func generate_gradient(image : Image, colors : Array, steps : int, direction : int, affect_selection : bool, project : Project) -> void: diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index ce23fc0a3..eac9a4d54 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -639,12 +639,15 @@ func resize_bitmap(bitmap : BitMap, new_size : Vector2) -> BitMap: # Unexposed BitMap class function - https://github.com/godotengine/godot/blob/master/scene/resources/bit_map.cpp#L622 -func bitmap_to_image(bitmap : BitMap) -> Image: +func bitmap_to_image(bitmap : BitMap, square := true) -> Image: var image := Image.new() var width := bitmap.get_size().x var height := bitmap.get_size().y - var square_size = max(width, height) - image.create(square_size, square_size, false, Image.FORMAT_LA8) + if square: + var square_size = max(width, height) + image.create(square_size, square_size, false, Image.FORMAT_LA8) + else: + image.create(width, height, false, Image.FORMAT_LA8) image.lock() for x in width: for y in height: diff --git a/src/Classes/ShaderImageEffect.gd b/src/Classes/ShaderImageEffect.gd new file mode 100644 index 000000000..5cfcc5418 --- /dev/null +++ b/src/Classes/ShaderImageEffect.gd @@ -0,0 +1,45 @@ +class_name ShaderImageEffect extends Reference +# Helper class to generate image effects using shaders +signal done + +func generate_image(_img : Image,_shaderpath: String, _params : Dictionary , size : Vector2 = Global.current_project.size): + var shader = load(_shaderpath) + _img.unlock() + var viewport_texture := Image.new() + var vp = VisualServer.viewport_create() + var canvas = VisualServer.canvas_create() + VisualServer.viewport_attach_canvas(vp, canvas) + VisualServer.viewport_set_size(vp, size.x, size.y) + VisualServer.viewport_set_disable_3d(vp, true) + VisualServer.viewport_set_usage(vp, VisualServer.VIEWPORT_USAGE_2D) + VisualServer.viewport_set_hdr(vp, true) + VisualServer.viewport_set_active(vp, true) + VisualServer.viewport_set_transparent_background(vp, true) + + var ci_rid = VisualServer.canvas_item_create() + VisualServer.viewport_set_canvas_transform(vp, canvas, Transform()) + VisualServer.canvas_item_set_parent(ci_rid, canvas) + var texture = ImageTexture.new() + texture.create_from_image(_img) + VisualServer.canvas_item_add_texture_rect(ci_rid, Rect2(Vector2(0, 0), size), texture) + + var mat_rid = VisualServer.material_create() + VisualServer.material_set_shader(mat_rid, shader.get_rid()) + VisualServer.canvas_item_set_material(ci_rid, mat_rid) + for key in _params: + VisualServer.material_set_param(mat_rid, key, _params[key]) + + VisualServer.viewport_set_update_mode(vp, VisualServer.VIEWPORT_UPDATE_ONCE) + VisualServer.viewport_set_vflip(vp, true) + VisualServer.force_draw(false) + viewport_texture = VisualServer.texture_get_data(VisualServer.viewport_get_texture(vp)) + VisualServer.free_rid(vp) + VisualServer.free_rid(canvas) + VisualServer.free_rid(ci_rid) + VisualServer.free_rid(mat_rid) + viewport_texture.convert(Image.FORMAT_RGBA8) + #Global.canvas.handle_undo("Draw") + _img.copy_from(viewport_texture) + #Global.canvas.handle_redo("Draw") + _img.lock() + emit_signal("done") diff --git a/src/Shaders/Desaturate.shader b/src/Shaders/Desaturate.shader new file mode 100644 index 000000000..9dc246a74 --- /dev/null +++ b/src/Shaders/Desaturate.shader @@ -0,0 +1,62 @@ +shader_type canvas_item; +render_mode unshaded; + +uniform bool red; +uniform bool blue; +uniform bool green; +uniform bool alpha; +uniform sampler2D selection; +uniform bool affect_selection; +uniform bool has_selection; + +vec3 rgb2hsb(vec3 c){ + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + vec4 p = mix(vec4(c.bg, K.wz), + vec4(c.gb, K.xy), + step(c.b, c.g)); + vec4 q = mix(vec4(p.xyw, c.r), + vec4(c.r, p.yzx), + step(p.x, c.r)); + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), + d / (q.x + e), + q.x); +} + +vec3 hsb2rgb(vec3 c){ + vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0,4.0,2.0), + 6.0)-3.0)-1.0, + 0.0, + 1.0 ); + rgb = rgb*rgb*(3.0-2.0*rgb); + return c.z * mix(vec3(1.0), rgb, c.y); +} + + +void fragment() { + // Get color from the sprite texture at the current pixel we are rendering + vec4 original_color = texture(TEXTURE, UV); + vec4 selection_color = texture(selection, UV); + + vec3 col = original_color.rgb; + vec3 hsb = rgb2hsb(col); + float gray = hsb.z; + if (red) + col.x = gray; + if (green) + col.y = gray; + if (blue) + col.z = gray; + + vec3 output; + if(affect_selection && has_selection) + output = mix(original_color.rgb, col, selection_color.a); + else + output = col; + if (alpha) + COLOR = vec4(output.rgb, gray); + else + COLOR = vec4(output.rgb, original_color.a); + +} \ No newline at end of file diff --git a/src/Shaders/HSV.shader b/src/Shaders/HSV.shader new file mode 100644 index 000000000..9e03989ca --- /dev/null +++ b/src/Shaders/HSV.shader @@ -0,0 +1,71 @@ + +shader_type canvas_item; +render_mode unshaded; + +uniform float hue_shift_amount : hint_range(-1, 1); +uniform float sat_shift_amount : hint_range(-1, 1); +uniform float val_shift_amount : hint_range(-1, 1); +uniform sampler2D selection; +uniform bool affect_selection; +uniform bool has_selection; + +vec3 rgb2hsb(vec3 c){ + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + vec4 p = mix(vec4(c.bg, K.wz), + vec4(c.gb, K.xy), + step(c.b, c.g)); + vec4 q = mix(vec4(p.xyw, c.r), + vec4(c.r, p.yzx), + step(p.x, c.r)); + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), + d / (q.x + e), + q.x); +} + +vec3 hsb2rgb(vec3 c){ + vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0,4.0,2.0), + 6.0)-3.0)-1.0, + 0.0, + 1.0 ); + rgb = rgb*rgb*(3.0-2.0*rgb); + return c.z * mix(vec3(1.0), rgb, c.y); +} + +void fragment() { + // Get color from the sprite texture at the current pixel we are rendering + vec4 original_color = texture(TEXTURE, UV); + vec4 selection_color = texture(selection, UV); + + vec3 col = original_color.rgb; + vec3 hsb = rgb2hsb(col); + // If not greyscale + if(col[0] != col[1] || col[1] != col[2]) { + // Shift the color by shift_amount, but rolling over the value goes over 1 + hsb.x = mod(hsb.x + hue_shift_amount, 1.0); + } + if(sat_shift_amount > 0.0) { + hsb.y = mix(hsb.y, 1 , sat_shift_amount); + } + else if (sat_shift_amount < 0.0) { + hsb.y = mix(0, hsb.y , 1f - abs(sat_shift_amount)); + } + + if(val_shift_amount > 0.0) { + hsb.z = mix(hsb.z, 1 , val_shift_amount); + } + else if (val_shift_amount < 0.0) { + hsb.z = mix(0, hsb.z , 1f - abs(val_shift_amount)); + } + + + col = hsb2rgb(hsb); + vec3 output; + if(affect_selection && has_selection) + output = mix(original_color.rgb, col, selection_color.a); + else + output = col; + COLOR = vec4(output.rgb, original_color.a); + +} \ No newline at end of file diff --git a/src/Shaders/Invert.shader b/src/Shaders/Invert.shader new file mode 100644 index 000000000..8024450b7 --- /dev/null +++ b/src/Shaders/Invert.shader @@ -0,0 +1,34 @@ +shader_type canvas_item; +render_mode unshaded; + +uniform bool red; +uniform bool blue; +uniform bool green; +uniform bool alpha; +uniform sampler2D selection; +uniform bool affect_selection; +uniform bool has_selection; + + +void fragment() { + // Get color from the sprite texture at the current pixel we are rendering + vec4 original_color = texture(TEXTURE, UV); + vec4 selection_color = texture(selection, UV); + vec4 col = original_color; + if (red) + col.r = 1f - col.r; + if (green) + col.g = 1f - col.g; + if (blue) + col.b = 1f - col.b; + if (alpha) + col.a = 1f - col.a; + + vec4 output; + if(affect_selection && has_selection) + output = mix(original_color.rgba, col, selection_color.a); + else + output = col; + + COLOR = output; +} \ No newline at end of file diff --git a/src/UI/Dialogs/ImageEffects/DesaturateDialog.gd b/src/UI/Dialogs/ImageEffects/DesaturateDialog.gd index ad73205f6..7fd14cac6 100644 --- a/src/UI/Dialogs/ImageEffects/DesaturateDialog.gd +++ b/src/UI/Dialogs/ImageEffects/DesaturateDialog.gd @@ -6,6 +6,15 @@ var green := true var blue := true var alpha := false +var shaderPath : String = "res://src/Shaders/Desaturate.shader" + +var confirmed: bool = false +func _about_to_show(): + var sm : ShaderMaterial = ShaderMaterial.new() + sm.shader = load(shaderPath) + preview.set_material(sm) + ._about_to_show() + func set_nodes() -> void: preview = $VBoxContainer/Preview @@ -13,8 +22,36 @@ func set_nodes() -> void: affect_option_button = $VBoxContainer/OptionsContainer/AffectOptionButton +func _confirmed() -> void: + confirmed = true + ._confirmed() + func commit_action(_cel : Image, _project : Project = Global.current_project) -> void: - DrawingAlgos.desaturate_image(_cel, selection_checkbox.pressed, _project, red, green, blue, alpha) + var selection = _project.bitmap_to_image(_project.selection_bitmap, false) + var selection_tex = ImageTexture.new() + selection_tex.create_from_image(selection) + + if !confirmed: + preview.material.set_shader_param("red", red) + preview.material.set_shader_param("blue", blue) + preview.material.set_shader_param("green", green) + preview.material.set_shader_param("alpha", alpha) + preview.material.set_shader_param("selection", selection_tex) + preview.material.set_shader_param("affect_selection", selection_checkbox.pressed) + preview.material.set_shader_param("has_selection", _project.has_selection) + else: + var params = { + "red": red, + "blue": blue, + "green": green, + "alpha": alpha, + "selection": selection_tex, + "affect_selection": selection_checkbox.pressed, + "has_selection": _project.has_selection + } + var gen: ShaderImageEffect = ShaderImageEffect.new() + gen.generate_image(_cel, shaderPath, params, _project.size) + yield(gen, "done") func _on_RButton_toggled(button_pressed : bool) -> void: diff --git a/src/UI/Dialogs/ImageEffects/HSVDialog.gd b/src/UI/Dialogs/ImageEffects/HSVDialog.gd index 2ab3b4a03..f6aec1e00 100644 --- a/src/UI/Dialogs/ImageEffects/HSVDialog.gd +++ b/src/UI/Dialogs/ImageEffects/HSVDialog.gd @@ -9,6 +9,16 @@ onready var hue_spinbox = $VBoxContainer/HBoxContainer/TextBoxes/Hue onready var sat_spinbox = $VBoxContainer/HBoxContainer/TextBoxes/Saturation onready var val_spinbox = $VBoxContainer/HBoxContainer/TextBoxes/Value +var shaderPath : String = "res://src/Shaders/HSV.shader" + +var confirmed: bool = false +func _about_to_show(): + reset() + var sm : ShaderMaterial = ShaderMaterial.new() + sm.shader = load(shaderPath) + preview.set_material(sm) + ._about_to_show() + func set_nodes() -> void: preview = $VBoxContainer/Preview @@ -17,12 +27,35 @@ func set_nodes() -> void: func _confirmed() -> void: + confirmed = true ._confirmed() reset() func commit_action(_cel : Image, _project : Project = Global.current_project) -> void: - DrawingAlgos.adjust_hsv(_cel, hue_slider.value, sat_slider.value, val_slider.value, selection_checkbox.pressed, _project) + var selection = _project.bitmap_to_image(_project.selection_bitmap, false) + var selection_tex = ImageTexture.new() + selection_tex.create_from_image(selection) + + if !confirmed: + preview.material.set_shader_param("hue_shift_amount", hue_slider.value /360) + preview.material.set_shader_param("sat_shift_amount", sat_slider.value /100) + preview.material.set_shader_param("val_shift_amount", val_slider.value /100) + preview.material.set_shader_param("selection", selection_tex) + preview.material.set_shader_param("affect_selection", selection_checkbox.pressed) + preview.material.set_shader_param("has_selection", _project.has_selection) + else: + var params = { + "hue_shift_amount": hue_slider.value /360, + "sat_shift_amount": sat_slider.value /100, + "val_shift_amount": val_slider.value /100, + "selection": selection_tex, + "affect_selection": selection_checkbox.pressed, + "has_selection": _project.has_selection + } + var gen: ShaderImageEffect = ShaderImageEffect.new() + gen.generate_image(_cel, shaderPath, params, _project.size) + yield(gen, "done") func reset() -> void: @@ -34,6 +67,7 @@ func reset() -> void: sat_spinbox.value = 0 val_spinbox.value = 0 reconnect_signals() + confirmed = false func disconnect_signals() -> void: diff --git a/src/UI/Dialogs/ImageEffects/InvertColorsDialog.gd b/src/UI/Dialogs/ImageEffects/InvertColorsDialog.gd index a1cd387e3..5bb335f28 100644 --- a/src/UI/Dialogs/ImageEffects/InvertColorsDialog.gd +++ b/src/UI/Dialogs/ImageEffects/InvertColorsDialog.gd @@ -6,6 +6,14 @@ var green := true var blue := true var alpha := false +var shaderPath : String = "res://src/Shaders/Invert.shader" + +var confirmed: bool = false +func _about_to_show(): + var sm : ShaderMaterial = ShaderMaterial.new() + sm.shader = load(shaderPath) + preview.set_material(sm) + ._about_to_show() func set_nodes() -> void: preview = $VBoxContainer/Preview @@ -13,8 +21,36 @@ func set_nodes() -> void: affect_option_button = $VBoxContainer/OptionsContainer/AffectOptionButton +func _confirmed() -> void: + confirmed = true + ._confirmed() + func commit_action(_cel : Image, _project : Project = Global.current_project) -> void: - DrawingAlgos.invert_image_colors(_cel, selection_checkbox.pressed, _project, red, green, blue, alpha) + var selection = _project.bitmap_to_image(_project.selection_bitmap, false) + var selection_tex = ImageTexture.new() + selection_tex.create_from_image(selection) + + if !confirmed: + preview.material.set_shader_param("red", red) + preview.material.set_shader_param("blue", blue) + preview.material.set_shader_param("green", green) + preview.material.set_shader_param("alpha", alpha) + preview.material.set_shader_param("selection", selection_tex) + preview.material.set_shader_param("affect_selection", selection_checkbox.pressed) + preview.material.set_shader_param("has_selection", _project.has_selection) + else: + var params = { + "red": red, + "blue": blue, + "green": green, + "alpha": alpha, + "selection": selection_tex, + "affect_selection": selection_checkbox.pressed, + "has_selection": _project.has_selection + } + var gen: ShaderImageEffect = ShaderImageEffect.new() + gen.generate_image(_cel, shaderPath, params, _project.size) + yield(gen, "done") func _on_RButton_toggled(button_pressed : bool) -> void: