mirror of
https://github.com/Orama-Interactive/Pixelorama.git
synced 2025-01-19 01:29:49 +00:00
Novhack fill shader (#649)
* Get same color pixels shader (#2) * Replace same color pixels bucket code with a shader * Add pattern fill Co-authored-by: novhack <42614907+novhack@users.noreply.github.com> * formatting * some minor changes and fixed a bug * added similarity feature * formatting * Update Bucket.gd * added similarity feature in shader Co-authored-by: novhack <42614907+novhack@users.noreply.github.com>
This commit is contained in:
parent
196c5c2d5d
commit
008683a33a
37
src/Shaders/ColorReplace.shader
Normal file
37
src/Shaders/ColorReplace.shader
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
shader_type canvas_item;
|
||||||
|
render_mode unshaded;
|
||||||
|
|
||||||
|
uniform vec2 size;
|
||||||
|
|
||||||
|
uniform vec4 old_color; //Our description
|
||||||
|
uniform vec4 new_color;
|
||||||
|
uniform float similarity_percent : hint_range(0.0, 100.0);
|
||||||
|
|
||||||
|
// Must be the same size as image
|
||||||
|
// Selected pixels are 1,1,1,1 and unselected 0,0,0,0
|
||||||
|
uniform sampler2D selection;
|
||||||
|
|
||||||
|
uniform bool has_pattern;
|
||||||
|
uniform sampler2D pattern;
|
||||||
|
uniform vec2 pattern_size;
|
||||||
|
uniform vec2 pattern_uv_offset;
|
||||||
|
|
||||||
|
void fragment() { // applies on each pixel seperately
|
||||||
|
vec4 original_color = texture(TEXTURE, UV); // The drawing we have to use on
|
||||||
|
vec4 selection_color = texture(selection, UV); // use its alpha to get portion we can ignore
|
||||||
|
|
||||||
|
vec4 col = original_color; // Innocent till proven Guilty
|
||||||
|
|
||||||
|
float max_diff = distance(original_color, old_color); // How much this pixel matches our description
|
||||||
|
|
||||||
|
float similarity = abs(2.0 - ((similarity_percent/100.0) * 2.0));
|
||||||
|
|
||||||
|
if (max_diff <= similarity) // We found our match and pixel is proven Guilty (small is precise)
|
||||||
|
if (has_pattern)
|
||||||
|
col = textureLod(pattern, UV * (size / pattern_size) + pattern_uv_offset, 0.0);
|
||||||
|
else
|
||||||
|
col = new_color;
|
||||||
|
|
||||||
|
// Mix selects original color if there is selection or col if there is none
|
||||||
|
COLOR = mix(original_color, col, selection_color.a);
|
||||||
|
}
|
|
@ -1,7 +1,10 @@
|
||||||
extends BaseTool
|
extends BaseTool
|
||||||
|
|
||||||
|
const ColorReplaceShader := preload("res://src/Shaders/ColorReplace.shader")
|
||||||
|
|
||||||
var _prev_mode := 0
|
var _prev_mode := 0
|
||||||
var _pattern: Patterns.Pattern
|
var _pattern: Patterns.Pattern
|
||||||
|
var _similarity := 100
|
||||||
var _fill_area := 0
|
var _fill_area := 0
|
||||||
var _fill_with := 0
|
var _fill_with := 0
|
||||||
var _offset_x := 0
|
var _offset_x := 0
|
||||||
|
@ -20,9 +23,11 @@ func _input(event: InputEvent) -> void:
|
||||||
if event.is_action("ctrl"):
|
if event.is_action("ctrl"):
|
||||||
options.selected = _prev_mode ^ 1
|
options.selected = _prev_mode ^ 1
|
||||||
_fill_area = options.selected
|
_fill_area = options.selected
|
||||||
|
$Similarity.visible = (_fill_area == 1)
|
||||||
if event.is_action_released("ctrl"):
|
if event.is_action_released("ctrl"):
|
||||||
options.selected = _prev_mode
|
options.selected = _prev_mode
|
||||||
_fill_area = options.selected
|
_fill_area = options.selected
|
||||||
|
$Similarity.visible = (_fill_area == 1)
|
||||||
|
|
||||||
|
|
||||||
func _on_FillAreaOptions_item_selected(index: int) -> void:
|
func _on_FillAreaOptions_item_selected(index: int) -> void:
|
||||||
|
@ -33,6 +38,20 @@ func _on_FillAreaOptions_item_selected(index: int) -> void:
|
||||||
|
|
||||||
func _on_FillWithOptions_item_selected(index: int) -> void:
|
func _on_FillWithOptions_item_selected(index: int) -> void:
|
||||||
_fill_with = index
|
_fill_with = index
|
||||||
|
$Similarity/Value.value = _similarity
|
||||||
|
update_config()
|
||||||
|
save_config()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_Value_value_changed(value: float) -> void:
|
||||||
|
_similarity = value
|
||||||
|
$Similarity/Slider.value = _similarity
|
||||||
|
update_config()
|
||||||
|
save_config()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_Slider_value_changed(value: float) -> void:
|
||||||
|
_similarity = value
|
||||||
update_config()
|
update_config()
|
||||||
save_config()
|
save_config()
|
||||||
|
|
||||||
|
@ -64,11 +83,12 @@ func _on_PatternOffsetY_value_changed(value: float) -> void:
|
||||||
|
|
||||||
func get_config() -> Dictionary:
|
func get_config() -> Dictionary:
|
||||||
if !_pattern:
|
if !_pattern:
|
||||||
return {}
|
return {"fill_area": _fill_area, "fill_with": _fill_with, "similarity": _similarity}
|
||||||
return {
|
return {
|
||||||
"pattern_index": _pattern.index,
|
"pattern_index": _pattern.index,
|
||||||
"fill_area": _fill_area,
|
"fill_area": _fill_area,
|
||||||
"fill_with": _fill_with,
|
"fill_with": _fill_with,
|
||||||
|
"similarity": _similarity,
|
||||||
"offset_x": _offset_x,
|
"offset_x": _offset_x,
|
||||||
"offset_y": _offset_y,
|
"offset_y": _offset_y,
|
||||||
}
|
}
|
||||||
|
@ -80,6 +100,7 @@ func set_config(config: Dictionary) -> void:
|
||||||
_pattern = Global.patterns_popup.get_pattern(index)
|
_pattern = Global.patterns_popup.get_pattern(index)
|
||||||
_fill_area = config.get("fill_area", _fill_area)
|
_fill_area = config.get("fill_area", _fill_area)
|
||||||
_fill_with = config.get("fill_with", _fill_with)
|
_fill_with = config.get("fill_with", _fill_with)
|
||||||
|
_similarity = config.get("similarity", _similarity)
|
||||||
_offset_x = config.get("offset_x", _offset_x)
|
_offset_x = config.get("offset_x", _offset_x)
|
||||||
_offset_y = config.get("offset_y", _offset_y)
|
_offset_y = config.get("offset_y", _offset_y)
|
||||||
update_pattern()
|
update_pattern()
|
||||||
|
@ -88,6 +109,9 @@ func set_config(config: Dictionary) -> void:
|
||||||
func update_config() -> void:
|
func update_config() -> void:
|
||||||
$FillAreaOptions.selected = _fill_area
|
$FillAreaOptions.selected = _fill_area
|
||||||
$FillWithOptions.selected = _fill_with
|
$FillWithOptions.selected = _fill_with
|
||||||
|
$Similarity.visible = (_fill_area == 1)
|
||||||
|
$Similarity/Value.value = _similarity
|
||||||
|
$Similarity/Slider.value = _similarity
|
||||||
$Mirror.visible = _fill_area == 0
|
$Mirror.visible = _fill_area == 0
|
||||||
$FillPattern.visible = _fill_with == 1
|
$FillPattern.visible = _fill_with == 1
|
||||||
$FillPattern/XOffset/OffsetX.value = _offset_x
|
$FillPattern/XOffset/OffsetX.value = _offset_x
|
||||||
|
@ -151,13 +175,37 @@ func fill_in_color(position: Vector2) -> void:
|
||||||
if tool_slot.color.is_equal_approx(color):
|
if tool_slot.color.is_equal_approx(color):
|
||||||
return
|
return
|
||||||
|
|
||||||
for x in Global.current_project.size.x:
|
var selection: Image
|
||||||
for y in Global.current_project.size.y:
|
var selection_tex := ImageTexture.new()
|
||||||
var pos := Vector2(x, y)
|
if project.has_selection:
|
||||||
if project.has_selection and not project.can_pixel_get_drawn(pos):
|
selection = project.bitmap_to_image(project.selection_bitmap, false)
|
||||||
continue
|
else:
|
||||||
if image.get_pixelv(pos).is_equal_approx(color):
|
selection = Image.new()
|
||||||
_set_pixel(image, x, y, tool_slot.color)
|
selection.create(project.size.x, project.size.y, false, Image.FORMAT_RGBA8)
|
||||||
|
selection.fill(Color(1, 1, 1, 1))
|
||||||
|
|
||||||
|
selection_tex.create_from_image(selection)
|
||||||
|
|
||||||
|
var pattern_tex := ImageTexture.new()
|
||||||
|
if _pattern:
|
||||||
|
pattern_tex.create_from_image(_pattern.image)
|
||||||
|
|
||||||
|
var params := {
|
||||||
|
"size": project.size,
|
||||||
|
"old_color": color,
|
||||||
|
"new_color": tool_slot.color,
|
||||||
|
"similarity_percent": _similarity,
|
||||||
|
"selection": selection_tex,
|
||||||
|
"pattern": pattern_tex,
|
||||||
|
"pattern_size": pattern_tex.get_size(),
|
||||||
|
# pixel offset converted to pattern uv offset
|
||||||
|
"pattern_uv_offset":
|
||||||
|
Vector2.ONE / pattern_tex.get_size() * Vector2(_offset_x, _offset_y),
|
||||||
|
"has_pattern": true if _fill_with == 1 else false
|
||||||
|
}
|
||||||
|
var gen := ShaderImageEffect.new()
|
||||||
|
gen.generate_image(image, ColorReplaceShader, params, project.size)
|
||||||
|
yield(gen, "done")
|
||||||
|
|
||||||
|
|
||||||
func fill_in_area(position: Vector2) -> void:
|
func fill_in_area(position: Vector2) -> void:
|
||||||
|
|
|
@ -22,6 +22,8 @@ corner_radius_bottom_left = 5
|
||||||
anti_aliasing = false
|
anti_aliasing = false
|
||||||
|
|
||||||
[node name="ToolOptions" instance=ExtResource( 2 )]
|
[node name="ToolOptions" instance=ExtResource( 2 )]
|
||||||
|
margin_right = 138.0
|
||||||
|
margin_bottom = 105.0
|
||||||
script = ExtResource( 3 )
|
script = ExtResource( 3 )
|
||||||
|
|
||||||
[node name="Label" parent="." index="0"]
|
[node name="Label" parent="." index="0"]
|
||||||
|
@ -43,10 +45,58 @@ margin_bottom = 56.0
|
||||||
mouse_default_cursor_shape = 2
|
mouse_default_cursor_shape = 2
|
||||||
size_flags_horizontal = 4
|
size_flags_horizontal = 4
|
||||||
text = "Same color area"
|
text = "Same color area"
|
||||||
items = [ "Same color area", null, false, 0, null, "Same color pixels", null, false, 1, null ]
|
items = [ "Same color area", null, false, 0, null, "Similar color pixels", null, false, 1, null ]
|
||||||
selected = 0
|
selected = 0
|
||||||
|
|
||||||
[node name="FillWith" type="Label" parent="." index="3"]
|
[node name="Similarity" type="VBoxContainer" parent="." index="3"]
|
||||||
|
visible = false
|
||||||
|
margin_top = 60.0
|
||||||
|
margin_right = 131.0
|
||||||
|
margin_bottom = 122.0
|
||||||
|
alignment = 1
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="Similarity" index="0"]
|
||||||
|
margin_left = 33.0
|
||||||
|
margin_right = 97.0
|
||||||
|
margin_bottom = 14.0
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
text = "Similarity:"
|
||||||
|
|
||||||
|
[node name="Value" type="SpinBox" parent="Similarity" index="1"]
|
||||||
|
margin_left = 28.0
|
||||||
|
margin_top = 18.0
|
||||||
|
margin_right = 102.0
|
||||||
|
margin_bottom = 42.0
|
||||||
|
hint_tooltip = "How much two colors are Similar/Close together"
|
||||||
|
mouse_default_cursor_shape = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
step = 0.001
|
||||||
|
value = 100.0
|
||||||
|
align = 1
|
||||||
|
suffix = "%"
|
||||||
|
__meta__ = {
|
||||||
|
"_editor_description_": ""
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="Slider" type="HSlider" parent="Similarity" index="2"]
|
||||||
|
margin_left = 19.0
|
||||||
|
margin_top = 46.0
|
||||||
|
margin_right = 111.0
|
||||||
|
margin_bottom = 62.0
|
||||||
|
rect_min_size = Vector2( 92, 0 )
|
||||||
|
hint_tooltip = "How much two colors are Similar/Close together"
|
||||||
|
focus_mode = 0
|
||||||
|
mouse_default_cursor_shape = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
size_flags_vertical = 1
|
||||||
|
step = 0.001
|
||||||
|
value = 100.0
|
||||||
|
ticks_on_borders = true
|
||||||
|
__meta__ = {
|
||||||
|
"_editor_description_": ""
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="FillWith" type="Label" parent="." index="4"]
|
||||||
margin_left = 38.0
|
margin_left = 38.0
|
||||||
margin_top = 60.0
|
margin_top = 60.0
|
||||||
margin_right = 92.0
|
margin_right = 92.0
|
||||||
|
@ -54,7 +104,7 @@ margin_bottom = 74.0
|
||||||
size_flags_horizontal = 4
|
size_flags_horizontal = 4
|
||||||
text = "Fill with:"
|
text = "Fill with:"
|
||||||
|
|
||||||
[node name="FillWithOptions" type="OptionButton" parent="." index="4"]
|
[node name="FillWithOptions" type="OptionButton" parent="." index="5"]
|
||||||
margin_left = 5.0
|
margin_left = 5.0
|
||||||
margin_top = 78.0
|
margin_top = 78.0
|
||||||
margin_right = 126.0
|
margin_right = 126.0
|
||||||
|
@ -65,7 +115,7 @@ text = "Selected Color"
|
||||||
items = [ "Selected Color", null, false, 0, null, "Pattern", null, false, 1, null ]
|
items = [ "Selected Color", null, false, 0, null, "Pattern", null, false, 1, null ]
|
||||||
selected = 0
|
selected = 0
|
||||||
|
|
||||||
[node name="FillPattern" type="VBoxContainer" parent="." index="5"]
|
[node name="FillPattern" type="VBoxContainer" parent="." index="6"]
|
||||||
visible = false
|
visible = false
|
||||||
margin_left = 22.0
|
margin_left = 22.0
|
||||||
margin_top = 102.0
|
margin_top = 102.0
|
||||||
|
@ -137,16 +187,16 @@ margin_right = 85.0
|
||||||
margin_bottom = 24.0
|
margin_bottom = 24.0
|
||||||
mouse_default_cursor_shape = 2
|
mouse_default_cursor_shape = 2
|
||||||
|
|
||||||
[node name="PixelPerfect" parent="." index="6"]
|
[node name="PixelPerfect" parent="." index="7"]
|
||||||
visible = false
|
visible = false
|
||||||
|
|
||||||
[node name="EmptySpacer" parent="." index="7"]
|
[node name="EmptySpacer" parent="." index="8"]
|
||||||
visible = false
|
visible = false
|
||||||
margin_top = 212.0
|
margin_top = 212.0
|
||||||
margin_right = 131.0
|
margin_right = 131.0
|
||||||
margin_bottom = 224.0
|
margin_bottom = 224.0
|
||||||
|
|
||||||
[node name="Mirror" parent="." index="8"]
|
[node name="Mirror" parent="." index="9"]
|
||||||
visible = false
|
visible = false
|
||||||
margin_top = 212.0
|
margin_top = 212.0
|
||||||
margin_right = 131.0
|
margin_right = 131.0
|
||||||
|
@ -161,6 +211,8 @@ margin_left = 86.0
|
||||||
margin_right = 103.0
|
margin_right = 103.0
|
||||||
|
|
||||||
[connection signal="item_selected" from="FillAreaOptions" to="." method="_on_FillAreaOptions_item_selected"]
|
[connection signal="item_selected" from="FillAreaOptions" to="." method="_on_FillAreaOptions_item_selected"]
|
||||||
|
[connection signal="value_changed" from="Similarity/Value" to="." method="_on_Value_value_changed"]
|
||||||
|
[connection signal="value_changed" from="Similarity/Slider" to="." method="_on_Slider_value_changed"]
|
||||||
[connection signal="item_selected" from="FillWithOptions" to="." method="_on_FillWithOptions_item_selected"]
|
[connection signal="item_selected" from="FillWithOptions" to="." method="_on_FillWithOptions_item_selected"]
|
||||||
[connection signal="pressed" from="FillPattern/Type" to="." method="_on_PatternType_pressed"]
|
[connection signal="pressed" from="FillPattern/Type" to="." method="_on_PatternType_pressed"]
|
||||||
[connection signal="value_changed" from="FillPattern/XOffset/OffsetX" to="." method="_on_PatternOffsetX_value_changed"]
|
[connection signal="value_changed" from="FillPattern/XOffset/OffsetX" to="." method="_on_PatternOffsetX_value_changed"]
|
||||||
|
|
Loading…
Reference in a new issue