diff --git a/.gitignore b/.gitignore index 99ff9ef..841dbac 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,8 @@ data_*/ mono_crash.*.json # Builds +*.x86_64 +godot.*.template_release.* dist/* !dist/.gitkeep diff --git a/README.md b/README.md index e2e92db..bf5d780 100644 --- a/README.md +++ b/README.md @@ -27,21 +27,28 @@ The image file will be read and available as the `TEXTURE` variable. #### Load additional images ```glsl -//!load +//!load+ + uniform sampler2D ; ``` -Have a look at the `mix.gdshader` example: +Have a look at the `place_texture.gdshader` example. + +## Shaderlib + +This repo comes with a (still small) shader library including pre-written functions and more. +Have a look at the `shaderlib` folder. + +Here is an example: ```glsl shader_type canvas_item; +#include "res://shaderlib/hsv.gdshaderinc" + //!load ./swamp.jpg -//!load+ img2 ./overlay.jpg -uniform sampler2D img2: repeat_enable, filter_nearest; - void fragment() { - COLOR = mix(COLOR, texture(img2, UV), .2); + COLOR = hsv_offset(COLOR, 0.32, 0.2, 0.0); } ``` diff --git a/assets/error.svg b/assets/error.svg new file mode 100644 index 0000000..af2f066 --- /dev/null +++ b/assets/error.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/error.svg.import b/assets/error.svg.import new file mode 100644 index 0000000..07666be --- /dev/null +++ b/assets/error.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://04iv1gogpuhu" +path="res://.godot/imported/error.svg-75fe5f417585e01e99de8885f1f45c3b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/error.svg" +dest_files=["res://.godot/imported/error.svg-75fe5f417585e01e99de8885f1f45c3b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=2.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/icon.png b/assets/icon.png similarity index 100% rename from icon.png rename to assets/icon.png diff --git a/icon.png.import b/assets/icon.png.import similarity index 73% rename from icon.png.import rename to assets/icon.png.import index a0840e4..ce38ae5 100644 --- a/icon.png.import +++ b/assets/icon.png.import @@ -3,15 +3,15 @@ importer="texture" type="CompressedTexture2D" uid="uid://kqwc4avs2xdp" -path="res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex" +path="res://.godot/imported/icon.png-b6a7fb2db36edd3d95dc42f1dc8c1c5d.ctex" metadata={ "vram_texture": false } [deps] -source_file="res://icon.png" -dest_files=["res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex"] +source_file="res://assets/icon.png" +dest_files=["res://.godot/imported/icon.png-b6a7fb2db36edd3d95dc42f1dc8c1c5d.ctex"] [params] diff --git a/assets/okay.svg b/assets/okay.svg new file mode 100644 index 0000000..5668fe8 --- /dev/null +++ b/assets/okay.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/okay.svg.import b/assets/okay.svg.import new file mode 100644 index 0000000..6b6c11f --- /dev/null +++ b/assets/okay.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://m1omb6g45vst" +path="res://.godot/imported/okay.svg-7f6df15523471a86f27b6817e9528a4e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/okay.svg" +dest_files=["res://.godot/imported/okay.svg-7f6df15523471a86f27b6817e9528a4e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=2.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/build-template/Containerfile b/build-template/Containerfile new file mode 100644 index 0000000..38d31a7 --- /dev/null +++ b/build-template/Containerfile @@ -0,0 +1,21 @@ + +MAINTAINER ChaoticByte + +# Using Ubuntu 20.04 +FROM docker.io/ubuntu:focal AS os-base + +# https://docs.godotengine.org/en/stable/contributing/development/compiling/compiling_for_linuxbsd.html + +RUN apt-get update +RUN DEBIAN_FRONTEND=noninteractive apt-get install -yq build-essential scons pkg-config libx11-dev libxcursor-dev libxinerama-dev libgl1-mesa-dev libglu1-mesa-dev libasound2-dev libpulse-dev libudev-dev libxi-dev libxrandr-dev libwayland-dev + +RUN DEBIAN_FRONTEND=noninteractive apt-get install -yq git + +FROM os-base AS clone-src + +RUN git clone https://github.com/godotengine/godot.git -b 4.3-stable /godot-src + +FROM clone-src + +WORKDIR /godot-src +ENTRYPOINT scons platform=linuxbsd target=template_release lto=full optimize=size disable_3d=yes module_text_server_adv_enabled=no module_text_server_fb_enabled=yes module_basis_universal_enabled=no module_csg_enabled=no module_dds_enabled=no module_enet_enabled=no module_gridmap_enabled=no module_hdr_enabled=no module_jsonrpc_enabled=no module_ktx_enabled=no module_mbedtls_enabled=no module_meshoptimizer_enabled=no module_minimp3_enabled=no module_mobile_vr_enabled=no module_msdfgen_enabled=no module_multiplayer_enabled=no module_navigation_enabled=no module_ogg_enabled=no module_openxr_enabled=no module_raycast_enabled=no module_squish_enabled=no module_svg_enabled=no module_tga_enabled=no module_theora_enabled=no module_tinyexr_enabled=no module_upnp_enabled=no module_vhacd_enabled=no module_vorbis_enabled=no module_webrtc_enabled=no module_websocket_enabled=no module_webxr_enabled=no module_zip_enabled=no arch=x86_64 && strip bin/godot.linuxbsd.template_release.x86_64 diff --git a/build-template/build.sh b/build-template/build.sh new file mode 100755 index 0000000..5541b7c --- /dev/null +++ b/build-template/build.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +set -e + +function log { + echo -e "\033[1;36m* $@\033[0m" +} + +log +log "Fragmented - Godot Build Template Builder" +log + +cd $(dirname $0) +log Switched to $(pwd) + +tmpsuffix=$(date +%s%N) +image_name=fragmented-godot-template-builder +container_name=${image_name}-${tmpsuffix} +output_file=godot.linuxbsd.template_release.x86_64 + +log Building image ${image_name} ... +buildah build -t ${image_name} +log Building godot build template with container ${container_name} ... +podman run --name ${container_name} localhost/${image_name}:latest +log Copying ${output_file} from container to $(realpath ./${output_file}) +podman cp ${container_name}:/godot-src/bin/${output_file} ./${output_file} +log Removing container ${container_name} +podman container rm ${container_name} +log Done :D diff --git a/examples/CREDITS.md b/examples/CREDITS.md index 99df0e2..ed427c8 100644 --- a/examples/CREDITS.md +++ b/examples/CREDITS.md @@ -1,5 +1,5 @@ # Example Images -- swamp.jpg by [clfr21](https://pixabay.com/de/users/clfr21-6530007/) -- overlay.jpg by [Humusak](https://pixabay.com/de/users/humusak-137455/) +- swamp.jpg by [clfr21 on Pixabay](https://pixabay.com/de/users/clfr21-6530007/) +- grass.png by [GDJ on Pixabay](https://pixabay.com/users/gdj-1086657/) diff --git a/examples/grass.png b/examples/grass.png new file mode 100644 index 0000000..bb7e120 Binary files /dev/null and b/examples/grass.png differ diff --git a/examples/overlay.jpg.import b/examples/grass.png.import similarity index 69% rename from examples/overlay.jpg.import rename to examples/grass.png.import index 7d189f0..f9a52a6 100644 --- a/examples/overlay.jpg.import +++ b/examples/grass.png.import @@ -2,16 +2,16 @@ importer="texture" type="CompressedTexture2D" -uid="uid://8101nnc8cx4o" -path="res://.godot/imported/overlay.jpg-b6d13e0def59877969c9eda02fa4244e.ctex" +uid="uid://c1mh1d2f3u4ju" +path="res://.godot/imported/grass.png-30af7213ed2d70b810b4ae788314a9e9.ctex" metadata={ "vram_texture": false } [deps] -source_file="res://examples/overlay.jpg" -dest_files=["res://.godot/imported/overlay.jpg-b6d13e0def59877969c9eda02fa4244e.ctex"] +source_file="res://examples/grass.png" +dest_files=["res://.godot/imported/grass.png-30af7213ed2d70b810b4ae788314a9e9.ctex"] [params] diff --git a/examples/hsv.gdshader b/examples/hsv.gdshader new file mode 100644 index 0000000..b4d9100 --- /dev/null +++ b/examples/hsv.gdshader @@ -0,0 +1,9 @@ +shader_type canvas_item; + +#include "res://shaderlib/hsv.gdshaderinc" + +//!load ./swamp.jpg + +void fragment() { + COLOR = hsv_multiply(hsv_offset(COLOR, -0.3, 0.9, 0.0), 1.0, 0.44, 1.0); +} diff --git a/examples/mix.gdshader b/examples/mix.gdshader deleted file mode 100644 index a043856..0000000 --- a/examples/mix.gdshader +++ /dev/null @@ -1,10 +0,0 @@ -shader_type canvas_item; - -//!load ./swamp.jpg - -//!load+ img2 ./overlay.jpg -uniform sampler2D img2: repeat_enable, filter_nearest; - -void fragment() { - COLOR = mix(COLOR, texture(img2, UV), .2); -} diff --git a/examples/overlay.jpg b/examples/overlay.jpg deleted file mode 100644 index b305379..0000000 Binary files a/examples/overlay.jpg and /dev/null differ diff --git a/examples/place_texture.gdshader b/examples/place_texture.gdshader new file mode 100644 index 0000000..282ee69 --- /dev/null +++ b/examples/place_texture.gdshader @@ -0,0 +1,15 @@ +shader_type canvas_item; + +#include "res://shaderlib/transform.gdshaderinc" +#include "res://shaderlib/transparency.gdshaderinc" + +//!load ./swamp.jpg +//!load+ img2 ./grass.png + +uniform sampler2D img2: repeat_disable, filter_nearest; + +void fragment() { + vec4 grass = place_texture(img2, UV, TEXTURE_PIXEL_SIZE, vec2(0, .47), vec2(1)); + grass.rgb += (vec3(0.02, 0.07, 0.1) - ((UV.y - .8) * 0.15)); // color correction + COLOR = alpha_blend(COLOR, grass); +} diff --git a/export_presets.cfg b/export_presets.cfg index d1be5f0..f4ce4eb 100644 --- a/export_presets.cfg +++ b/export_presets.cfg @@ -1,29 +1,29 @@ [preset.0] name="Linux/X11" -platform="Linux/X11" +platform="Linux" runnable=true +advanced_options=false dedicated_server=false custom_features="" export_filter="all_resources" include_filter="" -exclude_filter="" +exclude_filter="screenshot.png, examples/*" export_path="dist/Fragmented.x86_64" encryption_include_filters="" encryption_exclude_filters="" encrypt_pck=false encrypt_directory=false +script_export_mode=2 [preset.0.options] custom_template/debug="" -custom_template/release="" +custom_template/release="./build-template/godot.linuxbsd.template_release.x86_64" debug/export_console_wrapper=1 binary_format/embed_pck=true -texture_format/bptc=true -texture_format/s3tc=true -texture_format/etc=false -texture_format/etc2=false +texture_format/s3tc_bptc=true +texture_format/etc2_astc=false binary_format/architecture="x86_64" ssh_remote_deploy/enabled=false ssh_remote_deploy/host="user@host_ip" @@ -37,3 +37,7 @@ unzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\" ssh_remote_deploy/cleanup_script="#!/usr/bin/env bash kill $(pgrep -x -f \"{temp_dir}/{exe_name} {cmd_args}\") rm -rf \"{temp_dir}\"" +texture_format/bptc=true +texture_format/s3tc=true +texture_format/etc=false +texture_format/etc2=false diff --git a/project.godot b/project.godot index cee024d..775ef99 100644 --- a/project.godot +++ b/project.godot @@ -11,22 +11,23 @@ config_version=5 [application] config/name="Fragmented" -config/version="v5.0" +config/version="v6.0" run/main_scene="res://scenes/main.tscn" config/features=PackedStringArray("4.3", "Mobile") run/low_processor_mode=true -config/icon="res://icon.png" +config/icon="res://assets/icon.png" [autoload] -Globals="*res://src/Globals.gd" +Filesystem="*res://src/Filesystem.gd" +ShaderDirectiveParser="*res://src/ShaderDirectiveParser.gd" [display] -window/size/viewport_width=1280 -window/size/viewport_height=720 -window/size/mode=2 +window/size/viewport_width=704 +window/size/viewport_height=704 window/energy_saving/keep_screen_on=false +window/subwindows/embed_subwindows=false window/vsync/vsync_mode=0 [editor_plugins] diff --git a/scenes/main.tscn b/scenes/main.tscn index 6792ecb..e0b45f8 100644 --- a/scenes/main.tscn +++ b/scenes/main.tscn @@ -1,23 +1,24 @@ [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/ImageViewport.gd" id="2_hvo65"] +[ext_resource type="Script" path="res://src/Compositor.gd" id="2_hvo65"] +[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="Shader" path="res://src/ui_background.gdshader" id="4_ty3qx"] -[ext_resource type="Script" path="res://src/UIAppVersion.gd" id="5_o1ggv"] -[ext_resource type="Script" path="res://src/Editor.gd" id="7_g8bap"] +[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="ViewportTexture" id="ViewportTexture_lct1c"] -viewport_path = NodePath("ImageViewport") +[sub_resource type="ShaderMaterial" id="ShaderMaterial_y2ea0"] +shader = ExtResource("3_6xihe") +shader_parameter/zoom_level = Vector2(1, 1) -[sub_resource type="ShaderMaterial" id="ShaderMaterial_onhxk"] -shader = ExtResource("4_ty3qx") +[sub_resource type="ViewportTexture" id="ViewportTexture_lct1c"] +viewport_path = NodePath("Compositor") [node name="Main" type="Node2D"] script = ExtResource("1_2625y") -[node name="ImageViewport" type="SubViewport" parent="."] +[node name="Compositor" type="SubViewport" parent="."] unique_name_in_owner = true disable_3d = true transparent_bg = true @@ -25,187 +26,27 @@ canvas_item_default_texture_filter = 0 render_target_update_mode = 4 script = ExtResource("2_hvo65") -[node name="ImageSprite" type="Sprite2D" parent="ImageViewport"] +[node name="ImageSprite" type="Sprite2D" parent="Compositor"] unique_name_in_owner = true [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="UI_Layer" type="CanvasLayer" parent="."] - -[node name="FokusStealer" type="Control" parent="UI_Layer"] -layout_mode = 3 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -focus_mode = 2 - -[node name="UserInterfaceContainer" type="Control" parent="UI_Layer"] -unique_name_in_owner = true -layout_mode = 3 -anchor_right = 0.225 -anchor_bottom = 1.0 -offset_right = 288.0 -grow_horizontal = 2 -grow_vertical = 2 - -[node name="Background" type="ColorRect" parent="UI_Layer/UserInterfaceContainer"] -material = SubResource("ShaderMaterial_onhxk") -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -focus_mode = 2 -color = Color(1, 1, 1, 0) - -[node name="AppName" type="Label" parent="UI_Layer/UserInterfaceContainer"] -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="UI_Layer/UserInterfaceContainer"] -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("5_o1ggv") - -[node name="Editor" type="Control" parent="UI_Layer/UserInterfaceContainer"] -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 = -24.0 -grow_horizontal = 2 -grow_vertical = 2 -script = ExtResource("7_g8bap") - -[node name="OpenShaderDialog" type="FileDialog" parent="UI_Layer/UserInterfaceContainer/Editor"] -unique_name_in_owner = true -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="UI_Layer/UserInterfaceContainer/Editor"] -unique_name_in_owner = true -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="UI_Layer/UserInterfaceContainer/Editor"] -unique_name_in_owner = true -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="UI_Layer/UserInterfaceContainer/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="OpenShaderButton" type="Button" parent="UI_Layer/UserInterfaceContainer/Editor"] -layout_mode = 1 -offset_right = 56.0 -offset_bottom = 32.0 -text = "Open" - -[node name="SaveShaderButton" type="Button" parent="UI_Layer/UserInterfaceContainer/Editor"] -layout_mode = 1 -offset_left = 64.0 -offset_right = 120.0 -offset_bottom = 32.0 -text = "Save" - -[node name="ApplyShaderButton" type="Button" parent="UI_Layer/UserInterfaceContainer/Editor"] -layout_mode = 1 -offset_left = 128.0 -offset_right = 216.0 -offset_bottom = 32.0 -text = "Apply (F5)" - -[node name="SaveImageButton" type="Button" parent="UI_Layer/UserInterfaceContainer/Editor"] -layout_mode = 1 -anchors_preset = 1 -anchor_left = 1.0 -anchor_right = 1.0 -offset_left = -120.0 -offset_bottom = 32.0 -grow_horizontal = 0 -text = "Export Image" - -[node name="FitImageButton" type="Button" parent="UI_Layer/UserInterfaceContainer/Editor"] -layout_mode = 1 -anchors_preset = 1 -anchor_left = 1.0 -anchor_right = 1.0 -offset_left = -216.0 -offset_right = -128.0 -offset_bottom = 32.0 -grow_horizontal = 0 -text = "Fit Image" - [node name="Camera" type="Camera2D" parent="."] unique_name_in_owner = true script = ExtResource("8_mls06") -[connection signal="file_selected" from="UI_Layer/UserInterfaceContainer/Editor/OpenShaderDialog" to="UI_Layer/UserInterfaceContainer/Editor" method="_on_open_shader_dialog_file_selected"] -[connection signal="file_selected" from="UI_Layer/UserInterfaceContainer/Editor/SaveShaderDialog" to="UI_Layer/UserInterfaceContainer/Editor" method="_on_save_shader_dialog_file_selected"] -[connection signal="file_selected" from="UI_Layer/UserInterfaceContainer/Editor/SaveImageDialog" to="." method="_on_save_image_dialog_file_selected"] -[connection signal="code_completion_requested" from="UI_Layer/UserInterfaceContainer/Editor/CodeEdit" to="UI_Layer/UserInterfaceContainer/Editor" method="_on_code_edit_code_completion_requested"] -[connection signal="pressed" from="UI_Layer/UserInterfaceContainer/Editor/OpenShaderButton" to="UI_Layer/UserInterfaceContainer/Editor" method="_on_open_shader_button_pressed"] -[connection signal="pressed" from="UI_Layer/UserInterfaceContainer/Editor/SaveShaderButton" to="UI_Layer/UserInterfaceContainer/Editor" method="_on_save_shader_button_pressed"] -[connection signal="pressed" from="UI_Layer/UserInterfaceContainer/Editor/ApplyShaderButton" to="UI_Layer/UserInterfaceContainer/Editor" method="_on_apply_shader_button_pressed"] -[connection signal="pressed" from="UI_Layer/UserInterfaceContainer/Editor/SaveImageButton" to="." method="_on_save_image_button_pressed"] -[connection signal="pressed" from="UI_Layer/UserInterfaceContainer/Editor/FitImageButton" to="Camera" method="_on_fit_image_button_pressed"] +[node name="EditorWindow" type="Window" parent="."] +unique_name_in_owner = true +disable_3d = true +position = Vector2i(48, 80) +size = Vector2i(704, 704) +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 new file mode 100644 index 0000000..bc8d37f --- /dev/null +++ b/scenes/ui_container.tscn @@ -0,0 +1,202 @@ +[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"] +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 +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 551bf4d..62b1635 100644 Binary files a/screenshot.png and b/screenshot.png differ diff --git a/shaderlib/hsv.gdshaderinc b/shaderlib/hsv.gdshaderinc new file mode 100644 index 0000000..e1d9fdc --- /dev/null +++ b/shaderlib/hsv.gdshaderinc @@ -0,0 +1,50 @@ + +// rgb2hsv and hsv2rgb functions adapted +// from https://godotshaders.com/shader/hsv-adjustment/ +// original code by https://godotshaders.com/author/al1-ce/ + +// Convert RGB to HSV (hue, saturation, brightness) +vec4 rgb2hsv(vec4 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 vec4(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x, c.a); +} + +// Convert HSV back to RGB (red, green, blue) +vec4 hsv2rgb(vec4 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + vec3 rgb = c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); + return vec4(rgb.r, rgb.g, rgb.b, c.a); +} + +// Offset the hue, saturation and brightness of a RGB color +vec4 hsv_offset( + vec4 rgba, + float offset_hue, + float offset_saturation, + float offset_brightness +) { + vec4 c = rgb2hsv(rgba); + c.x += offset_hue; + c.y += offset_saturation; + c.z += offset_brightness; + return hsv2rgb(c); +} + +// Multiply the hue, saturation and brightness of a RGB color +vec4 hsv_multiply( + vec4 rgba, + float mult_hue, + float mult_saturation, + float mult_brightness +) { + vec4 c = rgb2hsv(rgba); + c.x *= mult_hue; + c.y *= mult_saturation; + c.z *= mult_brightness; + return hsv2rgb(c); +} diff --git a/shaderlib/transform.gdshaderinc b/shaderlib/transform.gdshaderinc new file mode 100644 index 0000000..c888732 --- /dev/null +++ b/shaderlib/transform.gdshaderinc @@ -0,0 +1,19 @@ + +// Load in a texture from a sampler2D with an offset and scale +// See examples/place_texture.gdshader +vec4 place_texture(sampler2D sampler, vec2 uv, vec2 texture_pixel_size, vec2 offset, vec2 scale) { + vec2 texture_size = vec2(textureSize(sampler, 0)); + // position of current pixel; sample color c + vec2 pos = (uv - offset) / (texture_size*texture_pixel_size) / scale; + vec4 c = texture(sampler, pos); + // top-left bounds + vec2 a = offset; + // bottom-right bounds + vec2 b = offset + (texture_size*texture_pixel_size) * scale; + // check bounds + if ( + a.x < uv.x && a.y < uv.y + && b.x > uv.x && b.y > uv.y + ) { return c; } // within bounds -> return color + return vec4(0); // not within bounds -> return transparency +} diff --git a/shaderlib/transparency.gdshaderinc b/shaderlib/transparency.gdshaderinc new file mode 100644 index 0000000..220f0ba --- /dev/null +++ b/shaderlib/transparency.gdshaderinc @@ -0,0 +1,8 @@ + +// Alpha Blending a over b after Bruce A. Wallace +// source: https://en.wikipedia.org/wiki/Alpha_compositing +vec4 alpha_blend(vec4 b, vec4 a) { + float alpha = a.a + (b.a * (1.0 - a.a)); + vec3 col = ((a.rgb*a.a) + ((b.rgb*b.a) * (1.0 - a.a)) / alpha); + return vec4(col.r, col.g, col.b, alpha); +} diff --git a/src/Camera.gd b/src/Camera.gd index 1bc16f4..5ad2855 100644 --- a/src/Camera.gd +++ b/src/Camera.gd @@ -2,44 +2,45 @@ extends Camera2D var drag = false -@onready var user_interface_container = %UserInterfaceContainer -@onready var image_viewport = %ImageViewport @onready var image_viewport_display = %ImageViewportDisplay func _input(event): - if event.is_action_pressed("zoom_out") && !Globals.camera_freeze: + if event.is_action_pressed("zoom_out"): zoom_out() - elif event.is_action_pressed("zoom_in") && !Globals.camera_freeze: + elif event.is_action_pressed("zoom_in"): zoom_in() - if event.is_action_pressed("drag") && !Globals.camera_freeze: - drag = true + if event.is_action_pressed("drag"): + self.drag = true elif event.is_action_released("drag"): - drag = false - if drag && event is InputEventMouseMotion: - global_position -= event.relative / zoom + self.drag = false + if self.drag && event is InputEventMouseMotion: + self.global_position -= event.relative / self.zoom func fit_image(): - if image_viewport.image_original_tex != null: - var ui_container_size = user_interface_container.size - var image_size = image_viewport.image_original_tex.get_size() + 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 - ui_container_size.x) / image_size.x / 1.1 + 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 - zoom = Vector2(zoomf, zoomf) - global_position = Vector2(-((ui_container_size.x) / 2 / zoom.x), 0) + self.zoom = Vector2(zoomf, zoomf) + self.global_position = Vector2(0, 0) + +func update_vd_zoomlevel(): + image_viewport_display.update_zoom_texture_filter(self.zoom) + image_viewport_display.material.set_shader_parameter("zoom_level", self.zoom) func zoom_in(): var old_mouse_pos = get_global_mouse_position() - zoom *= 1.2 - global_position += old_mouse_pos - get_global_mouse_position() - image_viewport_display.update_zoom_texture_filter(zoom) + self.zoom *= 1.2 + self.global_position += old_mouse_pos - get_global_mouse_position() + update_vd_zoomlevel() func zoom_out(): var old_mouse_pos = get_global_mouse_position() - zoom *= 1/1.2 - global_position += old_mouse_pos - get_global_mouse_position() - image_viewport_display.update_zoom_texture_filter(zoom) + self.zoom *= 1/1.2 + self.global_position += old_mouse_pos - get_global_mouse_position() + update_vd_zoomlevel() func _on_fit_image_button_pressed(): fit_image() diff --git a/src/Compositor.gd b/src/Compositor.gd new file mode 100644 index 0000000..ecd0c24 --- /dev/null +++ b/src/Compositor.gd @@ -0,0 +1,82 @@ +extends SubViewport + +@onready var camera = %Camera +@onready var image_sprite = %ImageSprite +@onready var image_viewport_display = %ImageViewportDisplay + +var _fragment_function_regex: RegEx + +func _init(): + _fragment_function_regex = RegEx.new() + _fragment_function_regex.compile(r'\s*void\s+fragment\s*\(\s*\)\s*{\s*') + +func validate_shader_compilation() -> bool: + # Inject code to validate shader compilation + var shader: Shader = Filesystem.shader.duplicate() + var shader_code = shader.code; + # -> get position of fragment shader + var fragment_function_match = _fragment_function_regex.search(shader.code) + if fragment_function_match == null: + return false + # -> inject uniform + var uniform_name = "shader_compilation_validate_" + str(randi_range(999999999, 100000000)) + var uniform_code_line = "\nuniform bool " + uniform_name + ";\n" + shader_code = shader_code.insert(fragment_function_match.get_start(), uniform_code_line) + # -> inject variable access to prevent that the uniform gets optimized away + shader_code = shader_code.insert(fragment_function_match.get_end() + len(uniform_code_line), "\n" + uniform_name + ";\n") + # apply shader code + shader.code = shader_code + # test if uniform list is empty -> if it is empty, the shader compilation failed + return len(shader.get_shader_uniform_list()) > 0 + +func update() -> Array: # returns error messages (strings) + if not validate_shader_compilation(): + return ["Shader compilation failed!"] + var errors = [] + var fit_image = false + # load texture(s) + Filesystem.clear_additional_images() + # ... from //!load directive -> TEXTURE + var m = ShaderDirectiveParser.parse_load_directive(Filesystem.shader) + if len(m) < 1: + errors.append("Didn't find a load directive!") + return errors + var original_image_path = Filesystem.get_absolute_path(m[1]) + if original_image_path != Filesystem.last_original_image_path: + fit_image = true + var err = Filesystem.load_original_image(original_image_path) + if err != "": + errors.append(err) + image_viewport_display.hide() + return errors + # ... from //!load+ directives + for n in ShaderDirectiveParser.parse_load_additional_directive(Filesystem.shader): + err = Filesystem.load_additional_image(n[1], Filesystem.get_absolute_path(n[2])) + if err != "": + errors.append(err) + if len(errors) > 0: + return errors + # apply textures + image_sprite.texture = Filesystem.original_image + image_sprite.offset = Filesystem.original_image.get_size() / 2 + self.size = Filesystem.original_image.get_size() + var mat = ShaderMaterial.new() + mat.shader = Filesystem.shader + # ... as shader parameters + for key in Filesystem.additional_images: + mat.set_shader_parameter( + key, # uniform param name + Filesystem.additional_images[key]) + # assign material + image_sprite.material = mat + # Get viewport texture + await RenderingServer.frame_post_draw # for good measure + Filesystem.result = get_texture().get_image() + image_sprite.material = null + image_sprite.texture = ImageTexture.create_from_image(Filesystem.result) + if fit_image: + camera.fit_image() + camera.update_vd_zoomlevel() + image_viewport_display.show() + # done + return errors diff --git a/src/Editor.gd b/src/Editor.gd index ae8ed61..ea0c7fd 100644 --- a/src/Editor.gd +++ b/src/Editor.gd @@ -1,8 +1,22 @@ 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 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 # @@ -62,7 +76,7 @@ const gdshader_builtin_functions = [ "radians", "degrees", "sin", "cos", "tan", "asin", "acos", "atan", "sinh", "cosh", "tanh", "asinh", "acosh", "atanh", - "pow", "exp", "log", "exp2", "log2", "sqrt", "inversesqrt", + "pow", "exp", "exp2", "log", "log2", "sqrt", "inversesqrt", "abs", "sign", "floor", "trunc", "round", "roundEven", "ceil", "fract", "mod", "modf", "min", "max", "clamp", "mix", "step", "smoothstep", @@ -98,19 +112,26 @@ const gdshader_builtins = [ "UV", "COLOR", "POINT_SIZE", - "MODEL_MATRIX", "CANVAS_MATRIX", "SCREEN_MATRIX", - "INSTANCE_CUSTOM", "INSTANCE_ID", - "VERTEX_ID", "AT_LIGHT_PASS", "TEXTURE_PIXEL_SIZE", - "CUSTOM0", "CUSTOM1", "SHADOW_VERTEX", "LIGHT_VERTEX", "FRAGCOORD", "NORMAL", "NORMAL_MAP", "NORMAL_MAP_DEPTH", - "TEXTURE", "NORMAL_TEXTURE", - "SCREEN_UV", "SCREEN_PIXEL_SIZE", + "TEXTURE", "POINT_COORD", + "SPECULAR_SHININESS" ] +# shaderlib +var shaderlib_regex = { + "hsv": RegEx.create_from_string(r'\s*\#include\s+\"res\:\/\/shaderlib\/hsv\.gdshaderinc\"'), + "transform": RegEx.create_from_string(r'\s*\#include\s+\"res\:\/\/shaderlib\/transform\.gdshaderinc\"'), + "transparency": RegEx.create_from_string(r'\s*\#include\s+\"res\:\/\/shaderlib\/transparency\.gdshaderinc\"') +} +const shaderlib_functions = { + "hsv": ["rgb2hsv", "hsv2rgb", "hsv_offset", "hsv_multiply"], + "transform": ["place_texture"], + "transparency": ["alpha_blend"], +} # # configure Highlighter # @@ -161,23 +182,23 @@ func _on_code_edit_code_completion_requested(): 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) + # 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 _camera_freeze(): - Globals.camera_freeze = true - -func _camera_unfreeze(): - Globals.camera_freeze = false - func _ready(): code_editor.code_completion_enabled = true code_editor.syntax_highlighter = ShaderSyntaxHighlighter.new() - for c in get_children(): - c.connect("mouse_entered", _camera_freeze) - c.connect("mouse_exited", _camera_unfreeze) - update() + self.update_code_edit() func _input(event): if event.is_action_pressed("apply_shader"): @@ -186,43 +207,84 @@ func _input(event): accept_event() # Event is now handled. _on_save_shader_button_pressed() -func update(): - code_editor.text = Globals.shader.code +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(): - if Globals.last_shader_savepath == "": - save_shader_dialog.current_file = "shader.gdshader" + Filesystem.shader.code = code_editor.text + if Filesystem.last_shader_savepath == "": + _on_save_shader_as_button_pressed() else: - save_shader_dialog.current_path = Globals.last_shader_savepath + _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_open_shader_dialog_file_selected(path: String): - print("Load ", path) - var file = FileAccess.open(path, FileAccess.READ) - var shader_code = file.get_as_text() - var shader = Shader.new() - shader.code = shader_code - Globals.shader = shader - if "/" in path: # update current working directory - Globals.cwd = path.substr(0, path.rfind("/")) - Globals.target_viewport.update() - update() - Globals.last_shader_savepath = path - -func _on_save_shader_dialog_file_selected(path): - print("Save ", path) - var file = FileAccess.open(path, FileAccess.WRITE) - var content = code_editor.text - file.store_string(content) - if "/" in path: # update current working directory - Globals.cwd = path.substr(0, path.rfind("/")) - Globals.last_shader_savepath = path +func _on_fit_image_button_pressed(): + camera.fit_image() func _on_apply_shader_button_pressed(): - var shader = Shader.new() - shader.code = code_editor.text - Globals.shader = shader - Globals.target_viewport.update() + 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) + +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 new file mode 100644 index 0000000..9f92261 --- /dev/null +++ b/src/Filesystem.gd @@ -0,0 +1,77 @@ +extends Node + +@onready var template_shader: Shader = load("res://src/shader/template.gdshader") +@onready var shader: Shader = template_shader.duplicate() + +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 = self.template_shader.duplicate() + 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("/"): + return self.cwd + "/" + p.lstrip("./") + return p + +func load_original_image(path: String) -> String: # returns an error message + 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 == "": + self.last_image_savepath = path + return "" + return error_string(err) + " " + path + +func clear_additional_images(): + additional_images.clear() + +func load_additional_image(key: String, path: String) -> String: # returns Error Message String + var img = Image.new() + var err = img.load(path) + if err == OK: + additional_images[key] = ImageTexture.create_from_image(img) + return "" + else: + return error_string(err) + " " + path + +func save_result(path: String): + print("Export ", path) + var err = self.result.save_png(path) + if err != OK: + print("An error occured!") + else: + self.last_image_savepath = path + +func load_shader(path: String): + print("Load ", path) + var file = FileAccess.open(path, FileAccess.READ) + var shader_code = file.get_as_text() + self.shader = Shader.new() + shader.code = shader_code + if "/" in path: # update current working directory + self.cwd = path.substr(0, path.rfind("/")) + self.last_shader_savepath = path + +func save_shader(path: String): + print("Save ", path) + var file = FileAccess.open(path, FileAccess.WRITE) + file.store_string(self.shader.code) + if "/" in path: # update current working directory + self.cwd = path.substr(0, path.rfind("/")) + self.last_shader_savepath = path diff --git a/src/Globals.gd b/src/Globals.gd deleted file mode 100644 index 0351452..0000000 --- a/src/Globals.gd +++ /dev/null @@ -1,8 +0,0 @@ -extends Node - -var camera_freeze = false -@onready var shader: Shader = load("res://src/shaders/empty.gdshader") -var target_viewport: SubViewport -var cwd = "." -var last_image_savepath = "" -var last_shader_savepath = "" diff --git a/src/ImageViewport.gd b/src/ImageViewport.gd deleted file mode 100644 index 207d283..0000000 --- a/src/ImageViewport.gd +++ /dev/null @@ -1,71 +0,0 @@ -extends SubViewport - -@onready var camera = %Camera -@onready var image_sprite = %ImageSprite - -var image_original_tex: ImageTexture -var image_result: Image -var load_regex: RegEx -var load_additional_regex: RegEx -var last_tex_path = "" - -func _ready(): - load_regex = RegEx.new() - load_additional_regex = RegEx.new() - load_regex.compile(r'\/\/!load\s(.*)') - load_additional_regex.compile(r'\/\/!load\+\s(\w*)\s(.*)') - -func load_texture(path): - print("Load ", path) - var img = Image.new() - var err = img.load(path) - if err == OK: - image_original_tex = ImageTexture.create_from_image(img) - image_sprite.texture = image_original_tex - image_sprite.offset = image_original_tex.get_size() / 2 - size = image_original_tex.get_size() - else: - print("An error occured!") - -func get_absolute_path(p: String) -> String: - if !p.begins_with("/"): - return Globals.cwd + "/" + p.lstrip("./") - return p - -func update(): - # load images from //!load directive -> TEXTURE - var regex_match = load_regex.search(Globals.shader.code) - if regex_match == null: # Error! - print("Didn't find any load directives!") - return - var tex_path = get_absolute_path(regex_match.strings[1]) - load_texture(tex_path) # load every time - if tex_path != last_tex_path: - camera.fit_image() - last_tex_path = tex_path - if Globals.last_image_savepath == "": - Globals.last_image_savepath = tex_path - image_sprite.texture = image_original_tex - var mat = ShaderMaterial.new() - mat.shader = Globals.shader - # load images from //!load+ directives and apply them to - # the material as shader parameters - for m in load_additional_regex.search_all(Globals.shader.code): - # this only works for Linux! - var img_path = get_absolute_path(m.strings[2]) - # - print("Load ", img_path) - var u_image = Image.load_from_file(img_path) - mat.set_shader_parameter( - m.strings[1], # uniform param name - ImageTexture.create_from_image(u_image)) - # assign material - image_sprite.material = mat - # Get viewport texture - await RenderingServer.frame_post_draw # for good measure - image_result = get_texture().get_image() - image_sprite.material = null - image_sprite.texture = ImageTexture.create_from_image(image_result) - -func get_result(): - return image_result diff --git a/src/ImageViewportDisplay.gd b/src/ImageViewportDisplay.gd index 9edcf83..7a9c827 100644 --- a/src/ImageViewportDisplay.gd +++ b/src/ImageViewportDisplay.gd @@ -1,5 +1,8 @@ extends Sprite2D +func _ready() -> void: + hide() + func update_zoom_texture_filter(zoom: Vector2): if zoom.x >= 1.5: texture_filter = TEXTURE_FILTER_NEAREST_WITH_MIPMAPS diff --git a/src/Main.gd b/src/Main.gd index 42be43b..6c59d07 100644 --- a/src/Main.gd +++ b/src/Main.gd @@ -1,22 +1,21 @@ -extends Node2D - -@onready var image_viewport = %ImageViewport -@onready var ui_control_filesave = %SaveImageDialog +extends Node +@onready var editor_window = %EditorWindow +@onready var app_name = ProjectSettings.get_setting("application/config/name") func _ready(): - DisplayServer.window_set_min_size(Vector2i(900, 500)) - Globals.target_viewport = image_viewport + 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) -func _on_save_image_button_pressed(): - if image_viewport.get_result() != null: - ui_control_filesave.current_path = Globals.last_image_savepath - ui_control_filesave.show() - -func _on_save_image_dialog_file_selected(path): - print("Export ", path) - var err = image_viewport.get_result().save_png(path) - if err != OK: - print("An error occured!") +func update_title(current_file: String = ""): + if current_file == "": + get_window().title = app_name + " - Viewer" + editor_window.title = app_name + " - Editor" else: - Globals.last_image_savepath = path + get_window().title = current_file + " - " + app_name + " - Viewer" + editor_window.title = current_file + " - " + app_name + " - Editor" diff --git a/src/ShaderDirectiveParser.gd b/src/ShaderDirectiveParser.gd new file mode 100644 index 0000000..6dd031b --- /dev/null +++ b/src/ShaderDirectiveParser.gd @@ -0,0 +1,22 @@ +extends Node + +var _load_regex: RegEx +var _load_additional_regex: RegEx + +func _ready(): + self._load_regex = RegEx.new() + self._load_additional_regex = RegEx.new() + self._load_regex.compile(r'\/\/!load\s(.*)') + self._load_additional_regex.compile(r'\/\/!load\+\s(\w*)\s(.*)') + +func parse_load_directive(shader: Shader) -> PackedStringArray: + var regex_match = self._load_regex.search(Filesystem.shader.code) + if regex_match == null: + return [] + return regex_match.strings + +func parse_load_additional_directive(shader: Shader) -> Array[PackedStringArray]: + var results : Array[PackedStringArray] = [] + for m in self._load_additional_regex.search_all(shader.code): + results.append(m.strings) + return results diff --git a/src/UIWindow.gd b/src/UIWindow.gd new file mode 100644 index 0000000..72836ce --- /dev/null +++ b/src/UIWindow.gd @@ -0,0 +1,4 @@ +extends Window + +func _on_close_requested() -> void: + get_tree().quit() diff --git a/src/shader/ivd_outline.gdshader b/src/shader/ivd_outline.gdshader new file mode 100644 index 0000000..14ce6e0 --- /dev/null +++ b/src/shader/ivd_outline.gdshader @@ -0,0 +1,16 @@ +shader_type canvas_item; + +uniform vec2 zoom_level = vec2(1.0); +const float thickness = 3.0; + +void fragment() { + vec2 t = thickness * TEXTURE_PIXEL_SIZE / zoom_level; + if ( + UV.x < t.x || + UV.y < t.y || + UV.x > 1.0-t.x || + UV.y > 1.0-t.y + ) { + COLOR = mix(COLOR, vec4(1.0), 0.5); + } +} diff --git a/src/shaders/empty.gdshader b/src/shader/template.gdshader similarity index 100% rename from src/shaders/empty.gdshader rename to src/shader/template.gdshader diff --git a/src/ui_background.gdshader b/src/ui_background.gdshader deleted file mode 100644 index 400d329..0000000 --- a/src/ui_background.gdshader +++ /dev/null @@ -1,17 +0,0 @@ -shader_type canvas_item; - -uniform sampler2D SCREEN_TEXTURE: hint_screen_texture, repeat_disable, filter_linear_mipmap_anisotropic; - -float rand(vec2 uv) { - return fract(sin(dot(uv, vec2(12.9898, 78.233))) * 43758.5453123); -} - -void fragment() { - vec2 new_uv; - new_uv.x = mix(SCREEN_UV.x, rand(UV), 0.2); - new_uv.y = mix(SCREEN_UV.y, rand(UV), 0.2); - new_uv.x = mix(new_uv.x, rand(UV), -0.2); - new_uv.y = mix(new_uv.y, rand(UV), -0.2); - COLOR.rgb = clamp(textureLod(SCREEN_TEXTURE, new_uv, 10.0).rgb, 0.05, 0.7); - COLOR.a = 1.0; -}