diff --git a/.gitignore b/.gitignore index 841dbac..852a042 100644 --- a/.gitignore +++ b/.gitignore @@ -16,7 +16,6 @@ mono_crash.*.json *.x86_64 godot.*.template_release.* dist/* -!dist/.gitkeep screenshot.png.import diff --git a/README.md b/README.md index ef9ed9b..73014ea 100644 --- a/README.md +++ b/README.md @@ -21,12 +21,16 @@ You can find the latest releases [here](https://github.com/ChaoticByte/Fragmente ## Usage -With Fragemented, you are editing images by writing GDShaders. This brings almost endless opportunities to create unique art. +With Fragemented, you are processing images with GDShaders. This brings almost endless opportunities to create unique art. If you want to learn GDShader, take a look at the [Godot docs](https://docs.godotengine.org/en/stable/tutorials/shaders/). -The repo also includes examples. You can use them as a starting-point to write your own filters. +**The builtin editor got removed** from Fragmented with version **v9.0**. I advise you to write your shaders directly in the Godot Editor. -Besides the regular GDShader stuff, Fragmented also has so-called directives. Those allow to further control the behaviour of the application. The most important directive is `//!load` to load an image. +**To get started, use the project template (see the Releases section of this repo) and open it in Godot.** + +The template includes many examples. You can use them as a starting-point to write your own stuff. + +Besides the regular GDShader stuff, Fragmented has so-called directives. Those allow to further control the behaviour of the application. **The most important directive is `//!load` to load an image.** ### Load TEXTURE using the `//!load` directive @@ -58,6 +62,8 @@ Example: //!load ... //!steps 5 +uniform int STEP; // this is mandatory! + void fragment() { if (STEP == 0) { ... @@ -101,20 +107,33 @@ You can run Fragmented from the commandline or scripts. ### Usage ``` -./Fragmented cmd --shader PATH [--load-image PATH] +~ Fragmented CLI ~ +-================- - --shader PATH The path to the shader - --output PATH Where to write the resulting image to. - In batch mode, this must be a folder. - --load-image PATH The path to the image. This will overwrite the - load directive of the shader file. - Passing a folder activates batch mode. - (optional) +Usage: + +./Fragmented + +Commands: + + help + + | Shows this help text. + + apply --shader PATH [--load-image PATH] + + | Applies a shader file. + + --shader PATH The path to the shader + --output PATH Where to write the resulting image to. + In batch mode, this must be a folder. + --load-image PATH The path to the image. This will overwrite the + load directive of the shader file. + Passing a folder activates batch mode. + (optional) ``` -You can also run `./Fragmented cmd help` to show the help message. - ### Batch Mode Since version v8.0, you can pass a directory to `--load-image` and `--output`. This will process all images in the input directory and write the output to the output directory. @@ -124,11 +143,11 @@ Since version v8.0, you can pass a directory to `--load-image` and `--output`. T #### Examples ``` -./Fragmented cmd --shader ./examples/oklab.gdshader --output ./output.png +./Fragmented apply --shader ./examples/oklab.gdshader --output ./output.png ``` ``` -./Fragmented cmd --shader ./examples/oklab.gdshader --load-image ~/Pictures/test.png --output ./output.png +./Fragmented apply --shader ./examples/oklab.gdshader --load-image ~/Pictures/test.png --output ./output.png ``` ## Known Issues diff --git a/build-template/build.sh b/build-template/build.sh index 5541b7c..feff902 100755 --- a/build-template/build.sh +++ b/build-template/build.sh @@ -3,12 +3,12 @@ set -e function log { - echo -e "\033[1;36m* $@\033[0m" + echo -e "\033[1;36m***** $@ *****\033[0m" } -log +log " " log "Fragmented - Godot Build Template Builder" -log +log " " cd $(dirname $0) log Switched to $(pwd) diff --git a/dist.sh b/dist.sh new file mode 100755 index 0000000..3b44438 --- /dev/null +++ b/dist.sh @@ -0,0 +1,30 @@ +set -e + +function log { + echo -e "\033[1;36m***** $@ *****\033[0m" +} + +mkdir -p dist + +log Building application + +VERSION="$(godot --headless --no-header -s tools/get_version.gd)" + +godot --headless --export-release "Linux/X11" "dist/Fragmented-${VERSION}.x86_64" + +log Packing shaderlib + +ZIP_PATH_SHADERLIB=$(realpath "dist/Fragmented-${VERSION}_shaderlib.zip") + +zip -r "${ZIP_PATH_SHADERLIB}" shaderlib/ + +log Packing project template + +ZIP_PATH_PROJECT_TEMPLATE=$(realpath "dist/Fragmented-${VERSION}_project_template.zip") + +rm -f "${ZIP_PATH_PROJECT_TEMPLATE}" +( + cd examples/ + mv project.godot_ project.godot && trap "mv project.godot project.godot_" EXIT + zip -r "${ZIP_PATH_PROJECT_TEMPLATE}" * +) diff --git a/dist/.gitkeep b/dist/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/0_empty.tscn b/examples/0_empty.tscn new file mode 100644 index 0000000..5fa71b9 --- /dev/null +++ b/examples/0_empty.tscn @@ -0,0 +1,3 @@ +[gd_scene format=3 uid="uid://db2rhq8rwv5wo"] + +[node name="Node" type="Node"] diff --git a/examples/blur.gdshader b/examples/blur.gdshader index 8d08069..fa4d87d 100644 --- a/examples/blur.gdshader +++ b/examples/blur.gdshader @@ -1,7 +1,7 @@ shader_type canvas_item; //!load ./images/swamp.jpg -#include "res://shaderlib/blur.gdshaderinc" +#include "./shaderlib/blur.gdshaderinc" void fragment() { COLOR = gaussian_blur(TEXTURE, UV, 48, 24.0); diff --git a/examples/color_and_pixelate.gdshader b/examples/color_and_pixelate.gdshader index dfb5e7b..f490db4 100644 --- a/examples/color_and_pixelate.gdshader +++ b/examples/color_and_pixelate.gdshader @@ -1,7 +1,7 @@ shader_type canvas_item; -#include "res://shaderlib/hsv.gdshaderinc" -#include "res://shaderlib/pixelate.gdshaderinc" +#include "./shaderlib/hsv.gdshaderinc" +#include "./shaderlib/pixelate.gdshaderinc" //!load ./images/swamp.jpg diff --git a/examples/denoise.gdshader b/examples/denoise.gdshader index 09dabb6..229a8da 100644 --- a/examples/denoise.gdshader +++ b/examples/denoise.gdshader @@ -2,7 +2,7 @@ shader_type canvas_item; //!load ./images/noisy.png -#include "res://shaderlib/denoise.gdshaderinc" +#include "./shaderlib/denoise.gdshaderinc" void fragment() { COLOR = smart_denoise(TEXTURE, UV, 12.0, 1.0, .12); diff --git a/examples/kuwahara.gdshader b/examples/kuwahara.gdshader index 345f826..246e162 100644 --- a/examples/kuwahara.gdshader +++ b/examples/kuwahara.gdshader @@ -2,8 +2,8 @@ shader_type canvas_item; //!load ./images/mountain.jpg -#include "res://shaderlib/kuwahara.gdshaderinc" -#include "res://shaderlib/hsv.gdshaderinc" +#include "./shaderlib/kuwahara.gdshaderinc" +#include "./shaderlib/hsv.gdshaderinc" void fragment() { // Kuwahara diff --git a/examples/multistep_distort.gdshader b/examples/multistep_distort.gdshader index cea638f..fd7883e 100644 --- a/examples/multistep_distort.gdshader +++ b/examples/multistep_distort.gdshader @@ -3,6 +3,8 @@ shader_type canvas_item; //!steps 9 //!load ./images/swamp.jpg +uniform int STEP; + const float strength = 0.01; void fragment() { diff --git a/examples/oklab.gdshader b/examples/oklab.gdshader index 8018695..08f46b5 100644 --- a/examples/oklab.gdshader +++ b/examples/oklab.gdshader @@ -1,6 +1,6 @@ shader_type canvas_item; -#include "res://shaderlib/oklab.gdshaderinc" +#include "./shaderlib/oklab.gdshaderinc" //!load ./images/swamp.jpg diff --git a/examples/place_texture.gdshader b/examples/place_texture.gdshader index 76d01fb..b0c50ca 100644 --- a/examples/place_texture.gdshader +++ b/examples/place_texture.gdshader @@ -1,7 +1,7 @@ shader_type canvas_item; -#include "res://shaderlib/place_texture.gdshaderinc" -#include "res://shaderlib/common.gdshaderinc" +#include "./shaderlib/place_texture.gdshaderinc" +#include "./shaderlib/common.gdshaderinc" //!load ./images/swamp.jpg //!load+ img2 ./images/grass.png diff --git a/examples/project.godot_ b/examples/project.godot_ new file mode 100644 index 0000000..bd350d3 --- /dev/null +++ b/examples/project.godot_ @@ -0,0 +1,16 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="Fragmented Project" +config/features=PackedStringArray("4.3", "Forward Plus") +run/main_scene="res://0_empty.tscn" + diff --git a/examples/shaderlib b/examples/shaderlib new file mode 120000 index 0000000..dedec01 --- /dev/null +++ b/examples/shaderlib @@ -0,0 +1 @@ +../shaderlib \ No newline at end of file diff --git a/export_presets.cfg b/export_presets.cfg index f4ce4eb..4b10743 100644 --- a/export_presets.cfg +++ b/export_presets.cfg @@ -8,7 +8,7 @@ dedicated_server=false custom_features="" export_filter="all_resources" include_filter="" -exclude_filter="screenshot.png, examples/*" +exclude_filter="screenshot.png, examples/*, shaderlib/*, tools/*, build-template/*" export_path="dist/Fragmented.x86_64" encryption_include_filters="" encryption_exclude_filters="" diff --git a/project.godot b/project.godot index b5573aa..4b605af 100644 --- a/project.godot +++ b/project.godot @@ -11,11 +11,11 @@ config_version=5 [application] config/name="Fragmented" -config/version="v8.2" -run/main_scene="res://scenes/main.tscn" +config/version="v9.0" +run/main_scene="res://src/scenes/main.tscn" config/features=PackedStringArray("4.3", "Mobile") run/low_processor_mode=true -config/icon="res://assets/icon.png" +config/icon="res://src/assets/icon.png" [autoload] @@ -24,8 +24,8 @@ ShaderDirectiveParser="*res://src/ShaderDirectiveParser.gd" [display] -window/size/viewport_width=704 -window/size/viewport_height=704 +window/size/viewport_width=640 +window/size/viewport_height=672 window/energy_saving/keep_screen_on=false window/subwindows/embed_subwindows=false window/vsync/vsync_mode=0 diff --git a/scenes/main.tscn b/scenes/main.tscn deleted file mode 100644 index 2d104a3..0000000 --- a/scenes/main.tscn +++ /dev/null @@ -1,46 +0,0 @@ -[gd_scene load_steps=10 format=3 uid="uid://bjah7k4bxo044"] - -[ext_resource type="Script" path="res://src/Main.gd" id="1_2625y"] -[ext_resource type="Script" path="res://src/ImageCompositor.gd" id="2_4thch"] -[ext_resource type="Shader" path="res://src/shader/ivd_outline.gdshader" id="3_6xihe"] -[ext_resource type="Script" path="res://src/ImageViewportDisplay.gd" id="3_n4itb"] -[ext_resource type="Script" path="res://src/UIWindow.gd" id="6_8k0ha"] -[ext_resource type="PackedScene" uid="uid://btgits2mfup0h" path="res://scenes/ui_container.tscn" id="7_5ci0e"] -[ext_resource type="Script" path="res://src/Camera.gd" id="8_mls06"] - -[sub_resource type="ShaderMaterial" id="ShaderMaterial_y2ea0"] -shader = ExtResource("3_6xihe") -shader_parameter/zoom_level = Vector2(1, 1) - -[sub_resource type="ViewportTexture" id="ViewportTexture_lct1c"] -viewport_path = NodePath("Compositor") - -[node name="Main" type="Node2D"] -script = ExtResource("1_2625y") - -[node name="Compositor" type="SubViewport" parent="."] -unique_name_in_owner = true -script = ExtResource("2_4thch") - -[node name="ImageViewportDisplay" type="Sprite2D" parent="."] -unique_name_in_owner = true -material = SubResource("ShaderMaterial_y2ea0") -texture = SubResource("ViewportTexture_lct1c") -script = ExtResource("3_n4itb") - -[node name="Camera" type="Camera2D" parent="."] -unique_name_in_owner = true -script = ExtResource("8_mls06") - -[node name="EditorWindow" type="Window" parent="."] -unique_name_in_owner = true -disable_3d = true -position = Vector2i(48, 36) -size = Vector2i(704, 704) -visible = false -script = ExtResource("6_8k0ha") - -[node name="UserInterfaceContainer" parent="EditorWindow" instance=ExtResource("7_5ci0e")] -unique_name_in_owner = true - -[connection signal="close_requested" from="EditorWindow" to="EditorWindow" method="_on_close_requested"] diff --git a/scenes/ui_container.tscn b/scenes/ui_container.tscn deleted file mode 100644 index 6be4c94..0000000 --- a/scenes/ui_container.tscn +++ /dev/null @@ -1,204 +0,0 @@ -[gd_scene load_steps=3 format=3 uid="uid://btgits2mfup0h"] - -[ext_resource type="Script" path="res://src/UIAppVersion.gd" id="1_5qvnb"] -[ext_resource type="Script" path="res://src/Editor.gd" id="2_haub5"] - -[node name="UserInterfaceContainer" type="Control"] -layout_mode = 3 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 - -[node name="AppName" type="Label" parent="."] -layout_mode = 0 -offset_left = 24.0 -offset_top = 16.0 -offset_right = 208.0 -offset_bottom = 48.0 -theme_override_font_sizes/font_size = 20 -text = "Fragmented" -vertical_alignment = 2 - -[node name="AppVersion" type="Label" parent="."] -layout_mode = 0 -offset_left = 152.0 -offset_top = 17.0 -offset_right = 208.0 -offset_bottom = 47.0 -theme_override_font_sizes/font_size = 14 -text = "v0 -" -vertical_alignment = 2 -script = ExtResource("1_5qvnb") - -[node name="Editor" type="Control" parent="."] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_left = 24.0 -offset_top = 64.0 -offset_right = -24.0 -offset_bottom = -16.0 -grow_horizontal = 2 -grow_vertical = 2 -script = ExtResource("2_haub5") - -[node name="OpenShaderDialog" type="FileDialog" parent="Editor"] -unique_name_in_owner = true -auto_translate_mode = 1 -title = "Load Shader" -size = Vector2i(521, 175) -ok_button_text = "Open" -mode_overrides_title = false -file_mode = 0 -access = 2 -filters = PackedStringArray("*.gdshader") -use_native_dialog = true - -[node name="SaveShaderDialog" type="FileDialog" parent="Editor"] -unique_name_in_owner = true -auto_translate_mode = 1 -title = "Save Shader" -size = Vector2i(661, 175) -ok_button_text = "Save" -mode_overrides_title = false -access = 2 -filters = PackedStringArray("*.gdshader") -use_native_dialog = true - -[node name="SaveImageDialog" type="FileDialog" parent="Editor"] -unique_name_in_owner = true -auto_translate_mode = 1 -title = "Export Image" -size = Vector2i(661, 175) -ok_button_text = "Save" -mode_overrides_title = false -access = 2 -filters = PackedStringArray("*.png") -use_native_dialog = true - -[node name="CodeEdit" type="CodeEdit" parent="Editor"] -unique_name_in_owner = true -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_top = 48.0 -grow_horizontal = 2 -grow_vertical = 2 -theme_override_font_sizes/font_size = 14 -placeholder_text = "// Test" -wrap_mode = 1 -minimap_draw = true -minimap_width = 40 -caret_blink = true -draw_control_chars = true -draw_tabs = true -draw_spaces = true -line_length_guidelines = Array[int]([80]) -gutters_draw_line_numbers = true -code_completion_enabled = true -indent_automatic = true -auto_brace_completion_enabled = true -auto_brace_completion_highlight_matching = true - -[node name="NewShaderButton" type="Button" parent="Editor"] -layout_mode = 1 -offset_right = 48.0 -offset_bottom = 32.0 -text = "New" - -[node name="OpenShaderButton" type="Button" parent="Editor"] -layout_mode = 1 -offset_left = 56.0 -offset_right = 112.0 -offset_bottom = 32.0 -text = "Open" - -[node name="SaveShaderButton" type="Button" parent="Editor"] -layout_mode = 1 -offset_left = 120.0 -offset_top = -1.0 -offset_right = 176.0 -offset_bottom = 31.0 -text = "Save" - -[node name="SaveShaderAsButton" type="Button" parent="Editor"] -layout_mode = 1 -offset_left = 184.0 -offset_right = 263.0 -offset_bottom = 32.0 -text = "Save As" - -[node name="SaveImageButton" type="Button" parent="Editor"] -unique_name_in_owner = true -layout_mode = 1 -anchors_preset = 1 -anchor_left = 1.0 -anchor_right = 1.0 -offset_left = -72.0 -offset_bottom = 32.0 -grow_horizontal = 0 -disabled = true -text = "Export" - -[node name="FitImageButton" type="Button" parent="Editor"] -layout_mode = 1 -anchors_preset = 1 -anchor_left = 1.0 -anchor_right = 1.0 -offset_left = -128.0 -offset_right = -80.0 -offset_bottom = 32.0 -grow_horizontal = 0 -text = "Fit" - -[node name="ApplyShaderButton" type="Button" parent="Editor"] -layout_mode = 1 -anchors_preset = 1 -anchor_left = 1.0 -anchor_right = 1.0 -offset_left = -232.0 -offset_right = -136.0 -offset_bottom = 31.0 -grow_horizontal = 0 -text = "Apply (F5)" - -[node name="StatusIndicator" type="TextureButton" parent="Editor"] -unique_name_in_owner = true -layout_mode = 1 -anchors_preset = 1 -anchor_left = 1.0 -anchor_right = 1.0 -offset_left = -76.0 -offset_top = 56.0 -offset_right = -56.0 -offset_bottom = 76.0 -grow_horizontal = 0 -disabled = true -ignore_texture_size = true -stretch_mode = 0 - -[node name="ErrorMessageDialog" type="AcceptDialog" parent="Editor"] -unique_name_in_owner = true -title = "Status" -initial_position = 2 -size = Vector2i(256, 128) -popup_window = true -ok_button_text = "Close" - -[connection signal="file_selected" from="Editor/OpenShaderDialog" to="Editor" method="_on_open_shader_dialog_file_selected"] -[connection signal="file_selected" from="Editor/SaveShaderDialog" to="Editor" method="_on_save_shader_dialog_file_selected"] -[connection signal="file_selected" from="Editor/SaveImageDialog" to="Editor" method="_on_save_image_dialog_file_selected"] -[connection signal="code_completion_requested" from="Editor/CodeEdit" to="Editor" method="_on_code_edit_code_completion_requested"] -[connection signal="pressed" from="Editor/NewShaderButton" to="Editor" method="_on_new_shader_button_pressed"] -[connection signal="pressed" from="Editor/OpenShaderButton" to="Editor" method="_on_open_shader_button_pressed"] -[connection signal="pressed" from="Editor/SaveShaderButton" to="Editor" method="_on_save_shader_button_pressed"] -[connection signal="pressed" from="Editor/SaveShaderAsButton" to="Editor" method="_on_save_shader_as_button_pressed"] -[connection signal="pressed" from="Editor/SaveImageButton" to="Editor" method="_on_save_image_button_pressed"] -[connection signal="pressed" from="Editor/FitImageButton" to="Editor" method="_on_fit_image_button_pressed"] -[connection signal="pressed" from="Editor/ApplyShaderButton" to="Editor" method="_on_apply_shader_button_pressed"] -[connection signal="pressed" from="Editor/StatusIndicator" to="Editor" method="_on_status_indicator_pressed"] diff --git a/screenshot.png b/screenshot.png index 19fb490..4fcaee5 100644 Binary files a/screenshot.png and b/screenshot.png differ diff --git a/shaderlib/oklab.gdshaderinc b/shaderlib/oklab.gdshaderinc index 96b6f34..833db27 100644 --- a/shaderlib/oklab.gdshaderinc +++ b/shaderlib/oklab.gdshaderinc @@ -9,7 +9,7 @@ The fourth value is always alpha. */ -#include "res://shaderlib/common.gdshaderinc" +#include "./common.gdshaderinc" vec4 rgb2oklab(vec4 c) { float l = 0.4122214708f * c.r + 0.5363325363f * c.g + 0.0514459929f * c.b; diff --git a/src/Camera.gd b/src/Camera.gd index 5ad2855..9ca1b84 100644 --- a/src/Camera.gd +++ b/src/Camera.gd @@ -1,9 +1,9 @@ extends Camera2D -var drag = false - @onready var image_viewport_display = %ImageViewportDisplay +var drag = false + func _input(event): if event.is_action_pressed("zoom_out"): zoom_out() @@ -20,9 +20,11 @@ func fit_image(): if Filesystem.original_image != null: var image_size = Filesystem.original_image.get_size() var viewport_size = get_viewport_rect().size - var zoomf = viewport_size.x / image_size.x / 1.1 - if zoomf * image_size.y > viewport_size.y: - zoomf = viewport_size.y / image_size.y / 1.1 + var zoomf = 1.0 + if viewport_size.x / image_size.x * image_size.y > viewport_size.y: + zoomf = viewport_size.y / image_size.y / 1.25 + else: + zoomf = viewport_size.x / image_size.x / 1.2 self.zoom = Vector2(zoomf, zoomf) self.global_position = Vector2(0, 0) diff --git a/src/Editor.gd b/src/Editor.gd deleted file mode 100644 index 2d29232..0000000 --- a/src/Editor.gd +++ /dev/null @@ -1,307 +0,0 @@ -extends Control - -@onready var code_editor = %CodeEdit - -@onready var open_shader_dialog = %OpenShaderDialog -@onready var save_shader_dialog = %SaveShaderDialog -@onready var ui_control_filesave = %SaveImageDialog - -@onready var save_image_button = %SaveImageButton - -@onready var status_indicator = %StatusIndicator -@onready var error_msg_dialog = %ErrorMessageDialog - -@onready var main = get_tree().root.get_node("Main") -@onready var compositor = main.get_node("%Compositor") -@onready var camera = main.get_node("%Camera") - -# - -var status_okay_texture: CompressedTexture2D = preload("uid://m1omb6g45vst") -var status_error_texture: CompressedTexture2D = preload("uid://04iv1gogpuhu") - -# # # # # # # # # # # -# GDShader keywords # -# https://github.com/godotengine/godot/blob/e96ad5af98547df71b50c4c4695ac348638113e0/servers/rendering/shader_language.cpp -# https://github.com/godotengine/godot/blob/e96ad5af98547df71b50c4c4695ac348638113e0/servers/rendering/shader_types.cpp -# -const gdshader_boolean_values = [ - "true", "false" -] -const gdshader_datatypes = [ - "void", - "bool", "bvec2", "bvec3", "bvec4", - "int", "ivec2", "ivec3", "ivec4", - "uint", "uvec2", "uvec3", "uvec4", - "float", "vec2", "vec3", "vec4", - "mat2", "mat3", "mat4", - "sampler2D", "isampler2D", "usampler2D", - "sampler2DArray", "isampler2DArray", "usampler2DArray", -] -const gdshader_precision_modifiers = [ - "lowp", "mediump", "heighp" -] -const gdshader_global_space_keywords = [ - "uniform", "group_uniforms", "varying", "const", - "struct", "shader_type", "render_mode" -] -const gdshader_uniform_qualifiers = [ - "instance", "global" -] -const gdshader_block_keywords = [ - "if", "else", - "for", "while", "do", - "switch", "case", - "default", "break", "continue", - "return", "discard" -] -const gdshader_function_specifier_keywords = [ - "in", "out", "inout" -] -const gdshader_hints = [ - "source_color", "hint_range", "instance_index" -] -const gdshader_sampler_hints = [ - "hint_normal", - "hint_default_white", "hint_default_black", "hint_default_transparent", - "hint_anisotropy", - "hint_roughness_r", "hint_roughness_g", "hint_roughness_b", "hint_roughness_a", - "hint_roughness_normal", "hint_roughness_gray", - "hint_screen_texture", "hint_normal_roughness_texture", - "hint_depth_texture", - "filter_nearest", "filter_linear", - "filter_nearest_mipmap", "filter_linear_mipmap", - "filter_nearest_mipmap_anisotropic", "filter_linear_mipmap_anisotropic", - "repeat_enable", "repeat_disable" -] -const gdshader_builtin_functions = [ - "radians", "degrees", - "sin", "cos", "tan", "asin", "acos", "atan", "sinh", "cosh", "tanh", - "asinh", "acosh", "atanh", - "pow", "exp", "exp2", "log", "log2", "sqrt", "inversesqrt", - "abs", "sign", "floor", "trunc", "round", "roundEven", "ceil", "fract", - "mod", "modf", "min", "max", "clamp", - "mix", "step", "smoothstep", - "isnan", "isinf", - "floatBitsToInt", "floatBitsToUint", "intBitsToFloat", "uintBitsToFloat", - "length", "distance", - "dot", "cross", - "normalize", "reflect", "refract", "faceforward", - "matrixCompMult", "outerProduct", "transpose", - "determinant", "inverse", - "lessThan", "greaterThan", "lessThanEqual", "greaterThanEqual", - "equal", "notEqual", - "any", "all", "not", - "textureSize", "texture", "textureProj", "textureLod", "texelFetch", - "textureProjLod", "textureGrad", "textureProjGrad", "textureGather", - "textureQueryLod", "textureQueryLevels", - "dFdx", "dFdxCoarse", "dFdxFine", "dFdy", "dFdyCoarse", "dFdyFine", - "fwidth", "fwidthCoarse", "fwidthFine" -] -const gdshader_sub_functions = [ - "length", "fma", - "packHalf2x16", "packUnorm2x16", "packSnorm2x16", "packUnorm4x8", "packSnorm4x8", - "unpackHalf2x16", "unpackUnorm2x16", "unpackSnorm2x16", "unpackUnorm4x8", "unpackSnorm4x8", - "bitfieldExtract", "bitfieldInsert", "bitfieldReverse", "bitCount", - "findLSB", "findMSB", - "umulExtended", "imulExtended", - "uaddCarry", "usubBorrow", - "ldexp", "frexp" -] -const gdshader_builtins = [ - "TIME", "PI", "TAU", "E", - "VERTEX", - "UV", - "COLOR", - "POINT_SIZE", - "AT_LIGHT_PASS", - "TEXTURE_PIXEL_SIZE", - "SHADOW_VERTEX", "LIGHT_VERTEX", - "FRAGCOORD", - "NORMAL", "NORMAL_MAP", "NORMAL_MAP_DEPTH", - "TEXTURE", - "POINT_COORD", - "SPECULAR_SHININESS" -] -const gdshader_preprocessor = [ - "define", "undef", "include", "pragma", - "if", "elif", "ifdef", "ifndef", "else", "endif" -] -# shaderlib -var shaderlib_regex = {} # auto-generated -const shaderlib_functions = { - "blur": ["gaussian_blur"], - "common": ["alpha_blend", "cbrt"], - "denoise": ["smart_denoise"], - "hsv": ["rgb2hsv", "hsv2rgb",], - "kuwahara": ["kuwahara",], - "oklab": ["oklab2rgb", "rgb2oklab", "oklab2oklch", "oklch2oklab"], - "pixelate": ["pixelate"], - "place_texture": ["place_texture"], -} -# -# configure Highlighter -# -class ShaderSyntaxHighlighter extends CodeHighlighter: - func _init(): - add_color_region("//", "", Color.WEB_GRAY, true) - add_color_region("/*", "*/", Color.WEB_GRAY, false) - function_color = Color.INDIAN_RED - for k in gdshader_boolean_values: - keyword_colors[k] = Color.INDIAN_RED - for k in ( gdshader_datatypes - + gdshader_hints - + gdshader_sampler_hints - + gdshader_global_space_keywords - + gdshader_function_specifier_keywords - + gdshader_precision_modifiers - + gdshader_uniform_qualifiers): - keyword_colors[k] = Color.ORCHID; - for k in gdshader_block_keywords: - keyword_colors[k] = Color.CORAL - for k in gdshader_builtins: - keyword_colors[k] = Color.DARK_TURQUOISE - member_variable_color = Color.LIGHT_BLUE - number_color = Color.AQUA - symbol_color = Color.GRAY -# -# and code completion -# -func _on_code_edit_code_completion_requested(): - for k in gdshader_boolean_values: - code_editor.code_completion_prefixes.append(k) - code_editor.add_code_completion_option(CodeEdit.KIND_PLAIN_TEXT, k, k, Color.INDIAN_RED) - for k in ( gdshader_datatypes - + gdshader_hints - + gdshader_sampler_hints - + gdshader_global_space_keywords - + gdshader_function_specifier_keywords - + gdshader_precision_modifiers - + gdshader_uniform_qualifiers): - code_editor.code_completion_prefixes.append(k) - code_editor.add_code_completion_option(CodeEdit.KIND_CLASS, k, k, Color.ORCHID) - for k in gdshader_block_keywords: - code_editor.code_completion_prefixes.append(k) - code_editor.add_code_completion_option(CodeEdit.KIND_PLAIN_TEXT, k, k, Color.CORAL) - for k in gdshader_builtins: - code_editor.code_completion_prefixes.append(k) - code_editor.add_code_completion_option(CodeEdit.KIND_CONSTANT, k, k, Color.DARK_TURQUOISE) - for k in gdshader_builtin_functions + gdshader_sub_functions: - code_editor.code_completion_prefixes.append(k) - code_editor.add_code_completion_option(CodeEdit.KIND_FUNCTION, k, k+"(", Color.INDIAN_RED) - for k in gdshader_preprocessor: - code_editor.code_completion_prefixes.append(k) - code_editor.add_code_completion_option(CodeEdit.KIND_PLAIN_TEXT, "#" + k, k) - # shaderlib # - var shader_code = code_editor.text - for key in shaderlib_regex: - if shaderlib_regex[key].search(shader_code) != null: - if key in shaderlib_functions: - for k in shaderlib_functions[key]: - code_editor.code_completion_prefixes.append(k) - code_editor.add_code_completion_option(CodeEdit.KIND_FUNCTION, k, k+"(", Color.INDIAN_RED) - # # # # # # # - code_editor.update_code_completion_options(true) -# -# # # # # # # # # # # # - -func _ready(): - # generate regexes - for k in shaderlib_functions: - shaderlib_regex[k] = RegEx.create_from_string( - r'\s*\#include\s+\"res\:\/\/shaderlib\/' + k + r'\.gdshaderinc\"') - # - code_editor.code_completion_enabled = true - code_editor.syntax_highlighter = ShaderSyntaxHighlighter.new() - self.update_code_edit() - -func _input(event): - if event.is_action_pressed("apply_shader"): - _on_apply_shader_button_pressed() - elif event.is_action_pressed("save_shader"): - accept_event() # Event is now handled. - _on_save_shader_button_pressed() - -func update_code_edit(): - code_editor.text = Filesystem.shader_code - -enum Status {OKAY, ERROR, UNKNOWN = -1} - -func update_status(status: Status, msg: String = ""): - error_msg_dialog.dialog_text = msg - error_msg_dialog.reset_size() - if status == Status.OKAY: - status_indicator.texture_normal = status_okay_texture - elif status == Status.ERROR: - status_indicator.texture_normal = status_error_texture - else: - status_indicator.texture_normal = null - if msg == "": - status_indicator.disabled = true - else: - status_indicator.disabled = false - -# - -func _on_new_shader_button_pressed(): - main.update_title() - Filesystem.reset() - self.update_code_edit() - compositor.update() - update_status(Status.UNKNOWN) - -func _on_open_shader_button_pressed(): - open_shader_dialog.show() - -func _on_save_shader_button_pressed(): - Filesystem.shader_code = code_editor.text - if Filesystem.last_shader_savepath == "": - _on_save_shader_as_button_pressed() - else: - _on_save_shader_dialog_file_selected(Filesystem.last_shader_savepath) - -func _on_save_shader_as_button_pressed() -> void: - Filesystem.shader_code = code_editor.text - if Filesystem.last_shader_savepath == "": - save_shader_dialog.current_file = "filter.gdshader" - else: - save_shader_dialog.current_path = Filesystem.last_shader_savepath - save_shader_dialog.show() - -func _on_fit_image_button_pressed(): - camera.fit_image() - -func _on_apply_shader_button_pressed(): - save_image_button.disabled = true - Filesystem.shader_code = code_editor.text - var errors = await compositor.update() - if len(errors) > 0: - update_status(Status.ERROR, "\n".join(errors)) - else: - update_status(Status.OKAY) - save_image_button.disabled = false - -func _on_save_image_button_pressed(): - if Filesystem.result != null: - ui_control_filesave.current_path = Filesystem.last_image_savepath - ui_control_filesave.show() - -# - -func _on_open_shader_dialog_file_selected(path: String): - Filesystem.load_shader(path) - main.update_title(path.split("/")[-1]) - self.update_code_edit() - self._on_apply_shader_button_pressed() - -func _on_save_shader_dialog_file_selected(path): - Filesystem.save_shader(path) - main.update_title(path.split("/")[-1]) - -func _on_save_image_dialog_file_selected(path): - Filesystem.save_result(path) - -# - -func _on_status_indicator_pressed() -> void: - error_msg_dialog.show() diff --git a/src/Filesystem.gd b/src/Filesystem.gd index 0fb9507..cb1ac7d 100644 --- a/src/Filesystem.gd +++ b/src/Filesystem.gd @@ -1,25 +1,31 @@ extends Node -@onready var template_shader: Shader = load("res://src/shader/template.gdshader") -@onready var shader_code: String = template_shader.code +var cwd = "." + +var shader_path = "": + get(): + return shader_path + set(v): + var old = shader_path + shader_path = v + if "/" in v: # update current working directory + cwd = v.substr(0, v.rfind("/")) + if old != shader_path: + store_last_opened_file() + +var shader: Shader: + get(): + print("Load ", shader_path) + return load(shader_path) var original_image: ImageTexture + var additional_images: Dictionary var result: Image -var cwd = "." var last_image_savepath = "" -var last_shader_savepath = "" var last_original_image_path = "" -func reset(): - self.shader_code = self.template_shader.code - self.last_image_savepath = "" - self.last_shader_savepath = "" - self.last_original_image_path = "" - self.original_image = null - self.result = null - func get_absolute_path(p: String) -> String: # this only works on Linux! if !p.begins_with("/"): @@ -27,14 +33,14 @@ func get_absolute_path(p: String) -> String: return p func load_original_image(path: String) -> String: # returns an error message + print("Load ", path) var img = Image.new() var err = img.load(path) if err == OK: original_image = ImageTexture.create_from_image(img) - if path != self.last_original_image_path: - self.last_original_image_path = path - if self.last_image_savepath == "": + if self.last_image_savepath == "" or path != self.last_original_image_path: self.last_image_savepath = path + self.last_original_image_path = path return "" return error_string(err) + " " + path @@ -42,6 +48,7 @@ func clear_additional_images(): additional_images.clear() func load_additional_image(key: String, path: String) -> String: # returns Error Message String + print("Load ", path) var img = Image.new() var err = img.load(path) if err == OK: @@ -58,33 +65,13 @@ func save_result(path: String): else: self.last_image_savepath = path -func load_shader(path: String): - print("Load ", path) - var file = FileAccess.open(path, FileAccess.READ) - if file != null: - self.shader_code = file.get_as_text() - if "/" in path: # update current working directory - self.cwd = path.substr(0, path.rfind("/")) - self.last_shader_savepath = path - store_last_opened_file() - -func save_shader(path: String): - print("Save ", path) - var file = FileAccess.open(path, FileAccess.WRITE) - file.store_string(self.shader_code) - file.flush() - if "/" in path: # update current working directory - self.cwd = path.substr(0, path.rfind("/")) - self.last_shader_savepath = path - store_last_opened_file() - func store_last_opened_file(): var f = FileAccess.open("user://last_opened", FileAccess.WRITE) if f != null: - f.store_pascal_string(last_shader_savepath) + f.store_pascal_string(shader_path) f.flush() func remember_last_opened_file(): var f = FileAccess.open("user://last_opened", FileAccess.READ) if f != null: - last_shader_savepath = f.get_pascal_string() + shader_path = f.get_pascal_string() diff --git a/src/ImageCompositor.gd b/src/ImageCompositor.gd index 5999b88..ad1ffb5 100644 --- a/src/ImageCompositor.gd +++ b/src/ImageCompositor.gd @@ -37,17 +37,19 @@ func validate_shader_compilation(shader: Shader) -> bool: # test if uniform list is empty -> if it is empty, the shader compilation failed return len(shader.get_shader_uniform_list()) > 0 -func inject_step_uniform(shader_code: String) -> Shader: - var shader = Shader.new() - # this should run after validate_shader_compilation() - var fragment_function_match = _fragment_function_regex.search(shader_code) - shader.code = shader_code.insert(fragment_function_match.get_start(), "\nuniform int STEP;") - return shader +func shader_has_step_uniform(shader: Shader) -> bool: + for u in shader.get_shader_uniform_list(): + if u["name"] == "STEP" && u["type"] == 2: + return true + return false func update(overwrite_image_path: String = "") -> Array: # returns error messages (strings) - # inject STEP uniform & get number of steps - var shader: Shader = inject_step_uniform(Filesystem.shader_code) + var shader = Filesystem.shader # read from disk + if shader == null: + return ["No shader opened!"] + # get number of steps & check if code has STEP uniform var steps: int = ShaderDirectiveParser.parse_steps_directive(shader.code) + var has_step_uniform: bool = shader_has_step_uniform(shader) # validate shader if not validate_shader_compilation(shader): return ["Shader compilation failed!"] @@ -94,8 +96,9 @@ func update(overwrite_image_path: String = "") -> Array: # returns error message image_sprite.material = mat # iterate n times for i in range(steps): - # set STEP param - mat.set_shader_parameter("STEP", i) + if has_step_uniform: + # set STEP param + mat.set_shader_parameter("STEP", i) # Get viewport texture await RenderingServer.frame_post_draw # wait for next frame to get drawn Filesystem.result = get_texture().get_image() diff --git a/src/Main.gd b/src/Main.gd index 71e6bd3..dcf32b3 100644 --- a/src/Main.gd +++ b/src/Main.gd @@ -4,21 +4,24 @@ const BATCH_MODE_SUPPORTED_EXTS = [ ".bmp", ".dds", ".exr", ".hdr", ".jpeg", ".jpg", ".ktx", ".png", ".svg", ".webp" ] -@onready var editor_window = %EditorWindow -@onready var ui_container = %UserInterfaceContainer @onready var app_name = ProjectSettings.get_setting("application/config/name") func show_help(): print( "Usage:\n\n", - "./Fragmented cmd --shader PATH [--load-image PATH]\n\n", - " --shader PATH The path to the shader\n", - " --output PATH Where to write the resulting image to.\n", - " In batch mode, this must be a folder.\n", - " --load-image PATH The path to the image. This will overwrite the\n", - " load directive of the shader file.\n", - " Passing a folder activates batch mode.\n", - " (optional)\n") + "./Fragmented \n\n", + "Commands:\n\n", + " help\n\n", + " | Shows this help text.\n\n", + " apply --shader PATH [--load-image PATH]\n\n", + " | Applies a shader file.\n\n", + " --shader PATH The path to the shader\n", + " --output PATH Where to write the resulting image to.\n", + " In batch mode, this must be a folder.\n", + " --load-image PATH The path to the image. This will overwrite the\n", + " load directive of the shader file.\n", + " Passing a folder activates batch mode.\n", + " (optional)\n") func parse_custom_cmdline(args: PackedStringArray): var kwargs: Dictionary = {"--shader": null, "--output": null, "--load-image": null} @@ -41,83 +44,83 @@ func cli_handle_errors(errors: Array) -> int: printerr(e) return n_errors -func _ready(): - var args = OS.get_cmdline_args() - if "cmd" in args: # commandline interface - if "help" in args: - show_help() - get_tree().quit(1) - return - var kwargs: Dictionary = parse_custom_cmdline(args) - if kwargs["--shader"] == null or kwargs["--output"] == null: - show_help() - get_tree().quit(1) - return - var batch_mode = false - var load_image_dir: DirAccess - if kwargs["--load-image"] != null: - load_image_dir = DirAccess.open(kwargs["--load-image"]) - if load_image_dir != null: - # batch mode - if DirAccess.open(kwargs["--output"]) == null: - printerr("If --load-image is a directory, --output has to be one too.\n") - show_help() +func cli(args: PackedStringArray): + print( + "~ Fragmented CLI ~\n", + "-================-\n") + if "help" in args: + show_help() + get_tree().quit(1) + return + var kwargs: Dictionary = parse_custom_cmdline(args) + if kwargs["--shader"] == null or kwargs["--output"] == null: + show_help() + get_tree().quit(1) + return + var batch_mode = false + var load_image_dir: DirAccess + if kwargs["--load-image"] != null: + load_image_dir = DirAccess.open(kwargs["--load-image"]) + if load_image_dir != null: + # batch mode + if DirAccess.open(kwargs["--output"]) == null: + printerr("If --load-image is a directory, --output has to be one too.\n") + show_help() + get_tree().quit(1) + return + else: + batch_mode = true + # + Filesystem.shader_path = kwargs["--shader"] + # + if batch_mode: + var in_dir_path = load_image_dir.get_current_dir() + var out_dir_path: String = kwargs["--output"].rstrip("/") + for f in load_image_dir.get_files(): + var supported = false + for e in BATCH_MODE_SUPPORTED_EXTS: + if f.ends_with(e): + supported = true + break + if supported: + f = in_dir_path + "/" + f + print(f) + var errors = await $Compositor.update(f) + if cli_handle_errors(errors) == 0: + var filename = out_dir_path + "/" + f.substr(f.rfind("/"), -1) + Filesystem.save_result(filename) + else: get_tree().quit(1) return - else: - batch_mode = true - # - Filesystem.load_shader(kwargs["--shader"]) - # - if batch_mode: - var in_dir_path = load_image_dir.get_current_dir() - var out_dir_path: String = kwargs["--output"].rstrip("/") - for f in load_image_dir.get_files(): - var supported = false - for e in BATCH_MODE_SUPPORTED_EXTS: - if f.ends_with(e): - supported = true - break - if supported: - f = in_dir_path + "/" + f - print(f) - var errors = await $Compositor.update(f) - if cli_handle_errors(errors) == 0: - var filename = out_dir_path + "/" + f.substr(f.rfind("/"), -1) - Filesystem.save_result(filename) - else: - get_tree().quit(1) - return + get_tree().quit(0) + else: + var errors = [] + if kwargs["--load-image"] == null: + errors = await $Compositor.update() + else: + errors = await $Compositor.update(kwargs["--load-image"]) + if cli_handle_errors(errors) == 0: + Filesystem.save_result(kwargs["--output"]) get_tree().quit(0) else: - var errors = [] - if kwargs["--load-image"] == null: - errors = await $Compositor.update() - else: - errors = await $Compositor.update(kwargs["--load-image"]) - if cli_handle_errors(errors) == 0: - Filesystem.save_result(kwargs["--output"]) - get_tree().quit(0) - else: - get_tree().quit(1) + get_tree().quit(1) + +func prepare_gui(): + update_title() + # Load last opened file + Filesystem.remember_last_opened_file() + %MainUI._on_apply_shader_button_pressed() + +func _ready(): + var args = OS.get_cmdline_args() + if len(args) > 0 and args[0] in ["apply", "help"]: + # use the commandline interface + cli(args) else: - update_title() - # position windows - get_window().position = Vector2i( - editor_window.position.x + editor_window.size.x + 50, - editor_window.position.y) - get_window().min_size = Vector2i(400, 400) - editor_window.min_size = Vector2i(560, 400) - editor_window.show() - # Load last opened file - Filesystem.remember_last_opened_file() - if Filesystem.last_shader_savepath != "": - ui_container.get_node("Editor")._on_open_shader_dialog_file_selected(Filesystem.last_shader_savepath) + prepare_gui() func update_title(current_file: String = ""): if current_file == "": get_window().title = app_name + " - Viewer" - editor_window.title = app_name + " - Editor" else: get_window().title = current_file + " - " + app_name + " - Viewer" - editor_window.title = current_file + " - " + app_name + " - Editor" diff --git a/src/MainUI.gd b/src/MainUI.gd new file mode 100644 index 0000000..3727f08 --- /dev/null +++ b/src/MainUI.gd @@ -0,0 +1,99 @@ +extends Control + +@onready var open_shader_dialog = %OpenShaderDialog +@onready var save_image_dialog = %SaveImageDialog + +@onready var open_shader_button = %OpenShaderButton +@onready var save_image_button = %SaveImageButton +@onready var fit_image_button = %FitImageButton +@onready var apply_shader_button = %ApplyShaderButton + +@onready var status_indicator = %StatusIndicator +@onready var error_msg_dialog = %ErrorMessageDialog + +@onready var main = get_tree().root.get_node("Main") +@onready var compositor = %Compositor +@onready var camera = %Camera + +var status_okay_texture: CompressedTexture2D = preload("uid://m1omb6g45vst") +var status_error_texture: CompressedTexture2D = preload("uid://04iv1gogpuhu") + +enum Status {OKAY, ERROR, UNKNOWN = -1} + +# + +func _input(event): + if event.is_action_pressed("apply_shader"): + _on_apply_shader_button_pressed() + elif event.is_action_pressed("save_shader"): + accept_event() # Event is now handled. + +# + +func set_buttons_disabled(disabled: bool): + for b in [open_shader_button, save_image_button, fit_image_button, apply_shader_button, status_indicator]: + b.disabled = disabled + +func _on_open_shader_button_pressed(): + set_buttons_disabled(true) + open_shader_dialog.show() + +func _on_fit_image_button_pressed(): + camera.fit_image() + +func _on_apply_shader_button_pressed(): + set_buttons_disabled(true) + var errors = await compositor.update() + set_buttons_disabled(false) + if len(errors) > 0: + update_status(Status.ERROR, "\n".join(errors)) + else: + update_status(Status.OKAY) + status_indicator.disabled = true + +func _on_save_image_button_pressed(): + if Filesystem.result != null: + set_buttons_disabled(true) + save_image_dialog.current_path = Filesystem.last_image_savepath + save_image_dialog.show() + +# + +func _on_open_shader_dialog_file_selected(path: String): + Filesystem.shader_path = path + main.update_title(path.split("/")[-1]) + self._on_apply_shader_button_pressed() + +func _on_open_shader_dialog_canceled() -> void: + set_buttons_disabled(false) + +func _on_open_shader_dialog_confirmed() -> void: + set_buttons_disabled(false) + +func _on_save_image_dialog_file_selected(path): + Filesystem.save_result(path) + +func _on_save_image_dialog_canceled() -> void: + set_buttons_disabled(false) + +func _on_save_image_dialog_confirmed() -> void: + set_buttons_disabled(false) + +# + +func update_status(status: Status, msg: String = ""): + error_msg_dialog.dialog_text = msg + error_msg_dialog.reset_size() + if status == Status.OKAY: + status_indicator.texture_normal = status_okay_texture + elif status == Status.ERROR: + status_indicator.texture_normal = status_error_texture + else: + status_indicator.texture_normal = null + if msg == "": + status_indicator.disabled = true + else: + status_indicator.disabled = false + +func _on_status_indicator_pressed() -> void: + error_msg_dialog.show() diff --git a/src/UIAppVersion.gd b/src/UIAppVersion.gd deleted file mode 100644 index 60fe35a..0000000 --- a/src/UIAppVersion.gd +++ /dev/null @@ -1,4 +0,0 @@ -extends Label - -func _ready(): - text = ProjectSettings.get_setting("application/config/version") diff --git a/src/UIWindow.gd b/src/UIWindow.gd deleted file mode 100644 index 72836ce..0000000 --- a/src/UIWindow.gd +++ /dev/null @@ -1,4 +0,0 @@ -extends Window - -func _on_close_requested() -> void: - get_tree().quit() diff --git a/src/VersionLabel.gd b/src/VersionLabel.gd new file mode 100644 index 0000000..c3c07e7 --- /dev/null +++ b/src/VersionLabel.gd @@ -0,0 +1,8 @@ +extends Label + +func _ready(): + text = ProjectSettings.get_setting("application/config/name") \ + + " " \ + + ProjectSettings.get_setting("application/config/version") \ + + " | Godot " \ + + Engine.get_version_info()["string"] diff --git a/assets/error.svg b/src/assets/error.svg similarity index 100% rename from assets/error.svg rename to src/assets/error.svg diff --git a/assets/error.svg.import b/src/assets/error.svg.import similarity index 76% rename from assets/error.svg.import rename to src/assets/error.svg.import index 07666be..368b3a5 100644 --- a/assets/error.svg.import +++ b/src/assets/error.svg.import @@ -3,15 +3,15 @@ importer="texture" type="CompressedTexture2D" uid="uid://04iv1gogpuhu" -path="res://.godot/imported/error.svg-75fe5f417585e01e99de8885f1f45c3b.ctex" +path="res://.godot/imported/error.svg-28fb29635cf59d39cabf7052619f602f.ctex" metadata={ "vram_texture": false } [deps] -source_file="res://assets/error.svg" -dest_files=["res://.godot/imported/error.svg-75fe5f417585e01e99de8885f1f45c3b.ctex"] +source_file="res://src/assets/error.svg" +dest_files=["res://.godot/imported/error.svg-28fb29635cf59d39cabf7052619f602f.ctex"] [params] diff --git a/assets/icon.png b/src/assets/icon.png similarity index 100% rename from assets/icon.png rename to src/assets/icon.png diff --git a/assets/icon.png.import b/src/assets/icon.png.import similarity index 73% rename from assets/icon.png.import rename to src/assets/icon.png.import index ce38ae5..d1a6196 100644 --- a/assets/icon.png.import +++ b/src/assets/icon.png.import @@ -3,15 +3,15 @@ importer="texture" type="CompressedTexture2D" uid="uid://kqwc4avs2xdp" -path="res://.godot/imported/icon.png-b6a7fb2db36edd3d95dc42f1dc8c1c5d.ctex" +path="res://.godot/imported/icon.png-d8298ab6eda392a806be6bb7eec65b9c.ctex" metadata={ "vram_texture": false } [deps] -source_file="res://assets/icon.png" -dest_files=["res://.godot/imported/icon.png-b6a7fb2db36edd3d95dc42f1dc8c1c5d.ctex"] +source_file="res://src/assets/icon.png" +dest_files=["res://.godot/imported/icon.png-d8298ab6eda392a806be6bb7eec65b9c.ctex"] [params] diff --git a/assets/okay.svg b/src/assets/okay.svg similarity index 100% rename from assets/okay.svg rename to src/assets/okay.svg diff --git a/assets/okay.svg.import b/src/assets/okay.svg.import similarity index 76% rename from assets/okay.svg.import rename to src/assets/okay.svg.import index 6b6c11f..e1631da 100644 --- a/assets/okay.svg.import +++ b/src/assets/okay.svg.import @@ -3,15 +3,15 @@ importer="texture" type="CompressedTexture2D" uid="uid://m1omb6g45vst" -path="res://.godot/imported/okay.svg-7f6df15523471a86f27b6817e9528a4e.ctex" +path="res://.godot/imported/okay.svg-de66a022ef37753b085371b7c60aefd1.ctex" metadata={ "vram_texture": false } [deps] -source_file="res://assets/okay.svg" -dest_files=["res://.godot/imported/okay.svg-7f6df15523471a86f27b6817e9528a4e.ctex"] +source_file="res://src/assets/okay.svg" +dest_files=["res://.godot/imported/okay.svg-de66a022ef37753b085371b7c60aefd1.ctex"] [params] diff --git a/src/scenes/main.tscn b/src/scenes/main.tscn new file mode 100644 index 0000000..e6580b3 --- /dev/null +++ b/src/scenes/main.tscn @@ -0,0 +1,174 @@ +[gd_scene load_steps=12 format=3 uid="uid://bjah7k4bxo044"] + +[ext_resource type="Script" uid="uid://n6vumf2emghl" path="res://src/Main.gd" id="1_64y3g"] +[ext_resource type="Script" path="res://src/ImageCompositor.gd" id="2_4ykh7"] +[ext_resource type="Shader" uid="uid://on6oju7dt0dj" path="res://src/shader/ivd_outline.gdshader" id="3_0fllm"] +[ext_resource type="Script" uid="uid://c51lc0hv2d7uq" path="res://src/ImageViewportDisplay.gd" id="4_pbpx2"] +[ext_resource type="Script" uid="uid://t71vu44i5cr5" path="res://src/Camera.gd" id="5_hkdq6"] +[ext_resource type="Theme" uid="uid://cwqlns34rj3vx" path="res://src/theme.tres" id="6_rjp5f"] +[ext_resource type="Script" uid="uid://e5gf0r42elmx" path="res://src/MainUI.gd" id="7_5puhk"] +[ext_resource type="Script" uid="uid://b254xv4j2uexg" path="res://src/VersionLabel.gd" id="8_kod8x"] + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_y2ea0"] +shader = ExtResource("3_0fllm") +shader_parameter/zoom_level = Vector2(1, 1) + +[sub_resource type="ViewportTexture" id="ViewportTexture_lct1c"] +viewport_path = NodePath("Compositor") + +[sub_resource type="LabelSettings" id="LabelSettings_6o860"] +font_size = 12 +font_color = Color(1, 1, 1, 0.509804) +shadow_color = Color(0, 0, 0, 1) + +[node name="Main" type="Node2D"] +script = ExtResource("1_64y3g") + +[node name="Compositor" type="SubViewport" parent="."] +unique_name_in_owner = true +script = ExtResource("2_4ykh7") + +[node name="ImageViewportDisplay" type="Sprite2D" parent="."] +unique_name_in_owner = true +material = SubResource("ShaderMaterial_y2ea0") +texture = SubResource("ViewportTexture_lct1c") +script = ExtResource("4_pbpx2") + +[node name="Camera" type="Camera2D" parent="."] +unique_name_in_owner = true +offset = Vector2(0, -64) +script = ExtResource("5_hkdq6") + +[node name="CanvasLayer" type="CanvasLayer" parent="."] + +[node name="MainUI" type="Control" parent="CanvasLayer"] +unique_name_in_owner = true +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +pivot_offset = Vector2(320, 320) +theme = ExtResource("6_rjp5f") +script = ExtResource("7_5puhk") + +[node name="OpenShaderDialog" type="FileDialog" parent="CanvasLayer/MainUI"] +unique_name_in_owner = true +auto_translate_mode = 1 +title = "Load Shader" +size = Vector2i(521, 175) +ok_button_text = "Open" +mode_overrides_title = false +file_mode = 0 +access = 2 +filters = PackedStringArray("*.gdshader") +use_native_dialog = true + +[node name="SaveImageDialog" type="FileDialog" parent="CanvasLayer/MainUI"] +unique_name_in_owner = true +auto_translate_mode = 1 +title = "Export Image" +size = Vector2i(661, 175) +ok_button_text = "Save" +mode_overrides_title = false +access = 2 +filters = PackedStringArray("*.png") +use_native_dialog = true + +[node name="ErrorMessageDialog" type="AcceptDialog" parent="CanvasLayer/MainUI"] +unique_name_in_owner = true +auto_translate_mode = 1 +title = "Status" +initial_position = 2 +size = Vector2i(256, 128) +popup_window = true +ok_button_text = "Close" + +[node name="OpenShaderButton" type="Button" parent="CanvasLayer/MainUI"] +unique_name_in_owner = true +layout_mode = 1 +offset_left = 16.0 +offset_top = 16.0 +offset_right = 128.0 +offset_bottom = 48.0 +text = "Open Shader" + +[node name="SaveImageButton" type="Button" parent="CanvasLayer/MainUI"] +unique_name_in_owner = true +layout_mode = 1 +offset_left = 144.0 +offset_top = 16.0 +offset_right = 216.0 +offset_bottom = 48.0 +disabled = true +text = "Export" + +[node name="FitImageButton" type="Button" parent="CanvasLayer/MainUI"] +unique_name_in_owner = true +layout_mode = 1 +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -176.0 +offset_top = 16.0 +offset_right = -128.0 +offset_bottom = 48.0 +grow_horizontal = 0 +text = "Fit" + +[node name="ApplyShaderButton" type="Button" parent="CanvasLayer/MainUI"] +unique_name_in_owner = true +layout_mode = 1 +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -112.0 +offset_top = 16.0 +offset_right = -16.0 +offset_bottom = 48.0 +grow_horizontal = 0 +text = "Apply (F5)" + +[node name="StatusIndicator" type="TextureButton" parent="CanvasLayer/MainUI"] +unique_name_in_owner = true +layout_mode = 1 +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -220.0 +offset_top = 21.0 +offset_right = -196.0 +offset_bottom = 45.0 +grow_horizontal = 0 +disabled = true +ignore_texture_size = true +stretch_mode = 0 + +[node name="VersionLabel" type="Label" parent="CanvasLayer/MainUI"] +layout_mode = 1 +anchors_preset = 12 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 16.0 +offset_top = -24.0 +offset_right = -16.0 +grow_horizontal = 2 +grow_vertical = 0 +label_settings = SubResource("LabelSettings_6o860") +horizontal_alignment = 1 +vertical_alignment = 1 +script = ExtResource("8_kod8x") + +[connection signal="canceled" from="CanvasLayer/MainUI/OpenShaderDialog" to="CanvasLayer/MainUI" method="_on_open_shader_dialog_canceled"] +[connection signal="confirmed" from="CanvasLayer/MainUI/OpenShaderDialog" to="CanvasLayer/MainUI" method="_on_open_shader_dialog_confirmed"] +[connection signal="file_selected" from="CanvasLayer/MainUI/OpenShaderDialog" to="CanvasLayer/MainUI" method="_on_open_shader_dialog_file_selected"] +[connection signal="canceled" from="CanvasLayer/MainUI/SaveImageDialog" to="CanvasLayer/MainUI" method="_on_save_image_dialog_canceled"] +[connection signal="confirmed" from="CanvasLayer/MainUI/SaveImageDialog" to="CanvasLayer/MainUI" method="_on_save_image_dialog_confirmed"] +[connection signal="file_selected" from="CanvasLayer/MainUI/SaveImageDialog" to="CanvasLayer/MainUI" method="_on_save_image_dialog_file_selected"] +[connection signal="pressed" from="CanvasLayer/MainUI/OpenShaderButton" to="CanvasLayer/MainUI" method="_on_open_shader_button_pressed"] +[connection signal="pressed" from="CanvasLayer/MainUI/SaveImageButton" to="CanvasLayer/MainUI" method="_on_save_image_button_pressed"] +[connection signal="pressed" from="CanvasLayer/MainUI/FitImageButton" to="CanvasLayer/MainUI" method="_on_fit_image_button_pressed"] +[connection signal="pressed" from="CanvasLayer/MainUI/ApplyShaderButton" to="CanvasLayer/MainUI" method="_on_apply_shader_button_pressed"] +[connection signal="pressed" from="CanvasLayer/MainUI/StatusIndicator" to="CanvasLayer/MainUI" method="_on_status_indicator_pressed"] diff --git a/src/shader/template.gdshader b/src/shader/template.gdshader deleted file mode 100644 index be6c5cc..0000000 --- a/src/shader/template.gdshader +++ /dev/null @@ -1,7 +0,0 @@ -shader_type canvas_item; - -//!load /path/to/your/image - -void fragment() { - // Called for every pixel the material is visible on. -} diff --git a/src/theme.tres b/src/theme.tres new file mode 100644 index 0000000..782d3e9 --- /dev/null +++ b/src/theme.tres @@ -0,0 +1,57 @@ +[gd_resource type="Theme" load_steps=4 format=3 uid="uid://cwqlns34rj3vx"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_bm5o2"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.1, 0.1, 0.1, 0.3) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_width_bottom = 1 +border_color = Color(1, 1, 1, 0.27451) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 +corner_detail = 5 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_l0k8a"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.225, 0.225, 0.225, 0.6) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_width_bottom = 1 +border_color = Color(1, 1, 1, 0.784314) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 +corner_detail = 5 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1dkyv"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.1, 0.1, 0.1, 0.6) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_width_bottom = 1 +border_color = Color(1, 1, 1, 0.509804) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 +corner_detail = 5 + +[resource] +Button/styles/disabled = SubResource("StyleBoxFlat_bm5o2") +Button/styles/hover = SubResource("StyleBoxFlat_l0k8a") +Button/styles/normal = SubResource("StyleBoxFlat_1dkyv") diff --git a/tools/get_version.gd b/tools/get_version.gd new file mode 100644 index 0000000..8384f1b --- /dev/null +++ b/tools/get_version.gd @@ -0,0 +1,7 @@ +extends SceneTree + +# godot --headless --no-header -s tools/get_version.gd + +func _init() -> void: + print(ProjectSettings.get_setting("application/config/version")) + quit(0)