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

Replaces bucket same color pixels with a shader and adds a similarity parameter #649

Merged
merged 9 commits into from
Feb 15, 2022
37 changes: 37 additions & 0 deletions src/Shaders/ColorReplace.shader
Original file line number Diff line number Diff line change
@@ -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);
}
64 changes: 56 additions & 8 deletions src/Tools/Bucket.gd
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
extends BaseTool

const ColorReplaceShader := preload("res://src/Shaders/ColorReplace.shader")

var _prev_mode := 0
var _pattern: Patterns.Pattern
var _similarity := 100
var _fill_area := 0
var _fill_with := 0
var _offset_x := 0
Expand All @@ -20,9 +23,11 @@ func _input(event: InputEvent) -> void:
if event.is_action("ctrl"):
options.selected = _prev_mode ^ 1
_fill_area = options.selected
$Similarity.visible = (_fill_area == 1)
if event.is_action_released("ctrl"):
options.selected = _prev_mode
_fill_area = options.selected
$Similarity.visible = (_fill_area == 1)


func _on_FillAreaOptions_item_selected(index: int) -> void:
Expand All @@ -33,6 +38,20 @@ func _on_FillAreaOptions_item_selected(index: int) -> void:

func _on_FillWithOptions_item_selected(index: int) -> void:
_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()
save_config()

Expand Down Expand Up @@ -64,11 +83,12 @@ func _on_PatternOffsetY_value_changed(value: float) -> void:

func get_config() -> Dictionary:
if !_pattern:
return {}
return {"fill_area": _fill_area, "fill_with": _fill_with, "similarity": _similarity}
return {
"pattern_index": _pattern.index,
"fill_area": _fill_area,
"fill_with": _fill_with,
"similarity": _similarity,
"offset_x": _offset_x,
"offset_y": _offset_y,
}
Expand All @@ -80,6 +100,7 @@ func set_config(config: Dictionary) -> void:
_pattern = Global.patterns_popup.get_pattern(index)
_fill_area = config.get("fill_area", _fill_area)
_fill_with = config.get("fill_with", _fill_with)
_similarity = config.get("similarity", _similarity)
_offset_x = config.get("offset_x", _offset_x)
_offset_y = config.get("offset_y", _offset_y)
update_pattern()
Expand All @@ -88,6 +109,9 @@ func set_config(config: Dictionary) -> void:
func update_config() -> void:
$FillAreaOptions.selected = _fill_area
$FillWithOptions.selected = _fill_with
$Similarity.visible = (_fill_area == 1)
$Similarity/Value.value = _similarity
$Similarity/Slider.value = _similarity
$Mirror.visible = _fill_area == 0
$FillPattern.visible = _fill_with == 1
$FillPattern/XOffset/OffsetX.value = _offset_x
Expand Down Expand Up @@ -151,13 +175,37 @@ func fill_in_color(position: Vector2) -> void:
if tool_slot.color.is_equal_approx(color):
return

for x in Global.current_project.size.x:
for y in Global.current_project.size.y:
var pos := Vector2(x, y)
if project.has_selection and not project.can_pixel_get_drawn(pos):
continue
if image.get_pixelv(pos).is_equal_approx(color):
_set_pixel(image, x, y, tool_slot.color)
var selection: Image
var selection_tex := ImageTexture.new()
if project.has_selection:
selection = project.bitmap_to_image(project.selection_bitmap, false)
else:
selection = Image.new()
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:
Expand Down
66 changes: 59 additions & 7 deletions src/Tools/Bucket.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ corner_radius_bottom_left = 5
anti_aliasing = false

[node name="ToolOptions" instance=ExtResource( 2 )]
margin_right = 138.0
margin_bottom = 105.0
script = ExtResource( 3 )

[node name="Label" parent="." index="0"]
Expand All @@ -43,18 +45,66 @@ margin_bottom = 56.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 4
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

[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_top = 60.0
margin_right = 92.0
margin_bottom = 74.0
size_flags_horizontal = 4
text = "Fill with:"

[node name="FillWithOptions" type="OptionButton" parent="." index="4"]
[node name="FillWithOptions" type="OptionButton" parent="." index="5"]
margin_left = 5.0
margin_top = 78.0
margin_right = 126.0
Expand All @@ -65,7 +115,7 @@ text = "Selected Color"
items = [ "Selected Color", null, false, 0, null, "Pattern", null, false, 1, null ]
selected = 0

[node name="FillPattern" type="VBoxContainer" parent="." index="5"]
[node name="FillPattern" type="VBoxContainer" parent="." index="6"]
visible = false
margin_left = 22.0
margin_top = 102.0
Expand Down Expand Up @@ -137,16 +187,16 @@ margin_right = 85.0
margin_bottom = 24.0
mouse_default_cursor_shape = 2

[node name="PixelPerfect" parent="." index="6"]
[node name="PixelPerfect" parent="." index="7"]
visible = false

[node name="EmptySpacer" parent="." index="7"]
[node name="EmptySpacer" parent="." index="8"]
visible = false
margin_top = 212.0
margin_right = 131.0
margin_bottom = 224.0

[node name="Mirror" parent="." index="8"]
[node name="Mirror" parent="." index="9"]
visible = false
margin_top = 212.0
margin_right = 131.0
Expand All @@ -161,6 +211,8 @@ margin_left = 86.0
margin_right = 103.0

[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="pressed" from="FillPattern/Type" to="." method="_on_PatternType_pressed"]
[connection signal="value_changed" from="FillPattern/XOffset/OffsetX" to="." method="_on_PatternOffsetX_value_changed"]
Expand Down