diff --git a/.gitignore b/.gitignore index 99ff9ef..852a042 100644 --- a/.gitignore +++ b/.gitignore @@ -13,8 +13,9 @@ data_*/ mono_crash.*.json # Builds +*.x86_64 +godot.*.template_release.* dist/* -!dist/.gitkeep screenshot.png.import diff --git a/LICENSE b/LICENSE index 920dc4f..ba50b01 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,28 @@ -MIT License +BSD 3-Clause License -Copyright (c) 2024 Julian Müller (ChaoticByte) +Copyright (c) 2025, Julian Müller (ChaoticByte) -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index a7dcfea..a12517b 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,156 @@ -

GlitchApp

+

Fragmented

![screenshot](./screenshot.png) -

Create image filters by writing shaders.

+

An image editing/compositing software for graphics programmers.

+ +## Table of Contents + +- [Supported Platforms](#supported-platforms) +- [Usage](#usage) +- [Shaderlib](#shaderlib) +- [Commandline interface](#commandline-interface) +- [Known Issues](#known-issues) ## Supported Platforms -- Linux [tested] -- Windows +- Linux -You can find the latest releases [here](https://github.com/ChaoticByte/GlitchApp/releases/latest). +You can find the latest releases [here](https://github.com/ChaoticByte/Fragmented/releases/latest). ## Usage -The application includes presets. You can use them as a starting-point to write your own filters. -Just load an image, apply a preset, edit the code and hit `F5` to see the changes. +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/). -### Load additional images using the `//!load` directive +**The builtin editor got removed** from Fragmented with version **v9.0**. I advise you to write your shaders directly in the Godot Editor. + +**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 ```glsl -//!load +//!load +``` + +The main image file will be read and available as the sampler2D `TEXTURE`. + +#### Load additional images + +```glsl +//!load+ + uniform sampler2D ; ``` -With this you can load additional images into your shader. -Have a look at the `Mix` preset: +Have a look at the `place_texture.gdshader` example. + +### Have multiple steps with `//!steps n` + +You can apply your shaderfile multiple times. At every additional step, `TEXTURE` is the result of the previous step. This can be used to chain effects that cannot be easily chained otherwise. + +To query the current step index, a `STEP` uniform is automatically injected. If `steps` is set to `0`, your shader won't be applied at all. + +Example: + +```glsl +//!load ... +//!steps 5 + +uniform int STEP; +uniform int STEPS; + +void fragment() { + if (STEP == 0) { + ... + } else if (STEP == 1) { + ... + } else if (STEP == STEPS-1) { + ... + } +} +``` + +## 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; -//!load img2 ./icon.png -uniform sampler2D img2: repeat_enable, filter_nearest; +#include "./shaderlib/oklab.gdshaderinc" + +//!load ./images/swamp.jpg void fragment() { - COLOR = mix(COLOR, texture(img2, UV), .5); + vec4 oklab = rgb2oklab(COLOR); + vec4 oklch = oklab2oklch(oklab); + oklch.z -= 2.0; + COLOR = oklab2rgb(oklch2oklab(oklch)); } ``` + +## Commandline interface + +You can run Fragmented from the commandline or scripts. + +> Note: Headless mode is not supported. Using the commandline interface still opens a window. + +### Usage + +``` +~ Fragmented CLI ~ +-================- + +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) + +``` + +### 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. + +> Note: You *can* use this feature for video frames, but it will take a loooong time. + +#### Examples + +``` +./Fragmented apply --shader ./examples/oklab.gdshader --output ./output.png +``` + +``` +./Fragmented apply --shader ./examples/oklab.gdshader --load-image ~/Pictures/test.png --output ./output.png +``` + +## Known Issues + +- screen scaling is unsupported; Using screen scaling could lead to an either blurry UI, or no scaling at all -> see #45 +- commandline interface: `--headless` is not supported diff --git a/build-template/Containerfile b/build-template/Containerfile new file mode 100644 index 0000000..b1a0c48 --- /dev/null +++ b/build-template/Containerfile @@ -0,0 +1,20 @@ + +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 python3-pip git build-essential 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 pip3 install --system scons + +FROM os-base AS clone-src + +RUN git clone https://github.com/godotengine/godot.git -b 4.4-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_enet_enabled=no module_gridmap_enabled=no module_jsonrpc_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_theora_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 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..feff902 --- /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/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 new file mode 100644 index 0000000..fa4d87d --- /dev/null +++ b/examples/blur.gdshader @@ -0,0 +1,8 @@ +shader_type canvas_item; + +//!load ./images/swamp.jpg +#include "./shaderlib/blur.gdshaderinc" + +void fragment() { + COLOR = gaussian_blur(TEXTURE, UV, 48, 24.0); +} diff --git a/examples/blur.gdshader.uid b/examples/blur.gdshader.uid new file mode 100644 index 0000000..3739afe --- /dev/null +++ b/examples/blur.gdshader.uid @@ -0,0 +1 @@ +uid://cny8dtukv54wt diff --git a/src/presets/shaders/channel_offset.gdshader b/examples/channel_offset.gdshader similarity index 92% rename from src/presets/shaders/channel_offset.gdshader rename to examples/channel_offset.gdshader index e3ac188..a1ad8b5 100644 --- a/src/presets/shaders/channel_offset.gdshader +++ b/examples/channel_offset.gdshader @@ -1,5 +1,7 @@ shader_type canvas_item; +//!load ./images/swamp.jpg + const vec2 offset_r = vec2(-0.002, -0.002); const vec2 offset_g = vec2(0., 0.); const vec2 offset_b = vec2(0.002, 0.002); diff --git a/examples/channel_offset.gdshader.uid b/examples/channel_offset.gdshader.uid new file mode 100644 index 0000000..66e7cef --- /dev/null +++ b/examples/channel_offset.gdshader.uid @@ -0,0 +1 @@ +uid://0efk4fornlg6 diff --git a/examples/color_and_pixelate.gdshader b/examples/color_and_pixelate.gdshader new file mode 100644 index 0000000..f490db4 --- /dev/null +++ b/examples/color_and_pixelate.gdshader @@ -0,0 +1,14 @@ +shader_type canvas_item; + +#include "./shaderlib/hsv.gdshaderinc" +#include "./shaderlib/pixelate.gdshaderinc" + +//!load ./images/swamp.jpg + +void fragment() { + COLOR = pixelate(TEXTURE, UV, 200.0); + vec4 hsv = rgb2hsv(COLOR); + hsv.xyz += vec3(0.65, .42-(hsv.y*.3), -.125); + hsv.xyz *= vec3(1.0, 1.0, 1.25); + COLOR = hsv2rgb(hsv); +} diff --git a/examples/color_and_pixelate.gdshader.uid b/examples/color_and_pixelate.gdshader.uid new file mode 100644 index 0000000..d943067 --- /dev/null +++ b/examples/color_and_pixelate.gdshader.uid @@ -0,0 +1 @@ +uid://gd23hu7ro148 diff --git a/examples/denoise.gdshader b/examples/denoise.gdshader new file mode 100644 index 0000000..229a8da --- /dev/null +++ b/examples/denoise.gdshader @@ -0,0 +1,9 @@ +shader_type canvas_item; + +//!load ./images/noisy.png + +#include "./shaderlib/denoise.gdshaderinc" + +void fragment() { + COLOR = smart_denoise(TEXTURE, UV, 12.0, 1.0, .12); +} diff --git a/examples/denoise.gdshader.uid b/examples/denoise.gdshader.uid new file mode 100644 index 0000000..9337646 --- /dev/null +++ b/examples/denoise.gdshader.uid @@ -0,0 +1 @@ +uid://cbwyneu03fki6 diff --git a/src/presets/shaders/greyscale.gdshader b/examples/greyscale.gdshader similarity index 71% rename from src/presets/shaders/greyscale.gdshader rename to examples/greyscale.gdshader index cd70a78..00aac8b 100644 --- a/src/presets/shaders/greyscale.gdshader +++ b/examples/greyscale.gdshader @@ -1,10 +1,10 @@ shader_type canvas_item; +//!load ./images/swamp.jpg + void fragment() { - vec4 tex = texture(TEXTURE , UV); float b = (COLOR.r + COLOR.g + COLOR.b) / 3.0; COLOR.r = b; COLOR.g = b; COLOR.b = b; - COLOR.a = tex.a; } diff --git a/examples/greyscale.gdshader.uid b/examples/greyscale.gdshader.uid new file mode 100644 index 0000000..b6e1bbd --- /dev/null +++ b/examples/greyscale.gdshader.uid @@ -0,0 +1 @@ +uid://dvarqolt6es27 diff --git a/examples/images/CREDITS.md b/examples/images/CREDITS.md new file mode 100644 index 0000000..d662b8c --- /dev/null +++ b/examples/images/CREDITS.md @@ -0,0 +1,6 @@ + +# Example Images + +- 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/) +- mountain.jpg by [Phghvvcftyyufj on Pixabay](https://pixabay.com/users/phghvvcftyyufj-12646982) diff --git a/examples/images/grass.png b/examples/images/grass.png new file mode 100644 index 0000000..bb7e120 Binary files /dev/null and b/examples/images/grass.png differ diff --git a/examples/images/grass.png.import b/examples/images/grass.png.import new file mode 100644 index 0000000..327f716 --- /dev/null +++ b/examples/images/grass.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c1mh1d2f3u4ju" +path="res://.godot/imported/grass.png-61a458998da568ce60ccb8a0c7caaf6d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://examples/images/grass.png" +dest_files=["res://.godot/imported/grass.png-61a458998da568ce60ccb8a0c7caaf6d.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 diff --git a/examples/images/mountain.jpg b/examples/images/mountain.jpg new file mode 100644 index 0000000..591c06d Binary files /dev/null and b/examples/images/mountain.jpg differ diff --git a/examples/images/mountain.jpg.import b/examples/images/mountain.jpg.import new file mode 100644 index 0000000..e914676 --- /dev/null +++ b/examples/images/mountain.jpg.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ben72llmopgaj" +path="res://.godot/imported/mountain.jpg-c1b7de1e6557b826bc6f9324027e11af.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://examples/images/mountain.jpg" +dest_files=["res://.godot/imported/mountain.jpg-c1b7de1e6557b826bc6f9324027e11af.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 diff --git a/examples/images/noisy.png b/examples/images/noisy.png new file mode 100644 index 0000000..08e0664 Binary files /dev/null and b/examples/images/noisy.png differ diff --git a/examples/images/noisy.png.import b/examples/images/noisy.png.import new file mode 100644 index 0000000..e8bab73 --- /dev/null +++ b/examples/images/noisy.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cfe2d0qes5x87" +path="res://.godot/imported/noisy.png-1b2e79340785c6c0f50d5bad5ce97356.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://examples/images/noisy.png" +dest_files=["res://.godot/imported/noisy.png-1b2e79340785c6c0f50d5bad5ce97356.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 diff --git a/examples/images/swamp.jpg b/examples/images/swamp.jpg new file mode 100644 index 0000000..d7bdf3c Binary files /dev/null and b/examples/images/swamp.jpg differ diff --git a/examples/images/swamp.jpg.import b/examples/images/swamp.jpg.import new file mode 100644 index 0000000..e0efe23 --- /dev/null +++ b/examples/images/swamp.jpg.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ckjb0agn5btv7" +path="res://.godot/imported/swamp.jpg-1dfdcd52a5ef03d42a82a7f06acefa98.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://examples/images/swamp.jpg" +dest_files=["res://.godot/imported/swamp.jpg-1dfdcd52a5ef03d42a82a7f06acefa98.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 diff --git a/examples/kuwahara.gdshader b/examples/kuwahara.gdshader new file mode 100644 index 0000000..246e162 --- /dev/null +++ b/examples/kuwahara.gdshader @@ -0,0 +1,16 @@ +shader_type canvas_item; + +//!load ./images/mountain.jpg + +#include "./shaderlib/kuwahara.gdshaderinc" +#include "./shaderlib/hsv.gdshaderinc" + +void fragment() { + // Kuwahara + COLOR.rgb = kuwahara(TEXTURE, UV, 20, 80.0, 18.0, 0.6, .15, 8); + // A litte bit of color adjustments + vec4 hsv = rgb2hsv(COLOR); + hsv.x += .03; + hsv.y *= 1.4; + COLOR = hsv2rgb(hsv); +} diff --git a/examples/kuwahara.gdshader.uid b/examples/kuwahara.gdshader.uid new file mode 100644 index 0000000..2e0241a --- /dev/null +++ b/examples/kuwahara.gdshader.uid @@ -0,0 +1 @@ +uid://cdhyuk7u8kxyk diff --git a/examples/lowpass.gdshader b/examples/lowpass.gdshader new file mode 100644 index 0000000..a84c2a2 --- /dev/null +++ b/examples/lowpass.gdshader @@ -0,0 +1,13 @@ +shader_type canvas_item; + +//!load ./images/swamp.jpg + +// Settings +const float threshold = 0.6; +// + +void fragment() { + vec4 tex = texture(TEXTURE , UV); + COLOR.rgb = min(tex.rgb, vec3(threshold)); + COLOR.a = tex.a; +} diff --git a/examples/lowpass.gdshader.uid b/examples/lowpass.gdshader.uid new file mode 100644 index 0000000..80b19c7 --- /dev/null +++ b/examples/lowpass.gdshader.uid @@ -0,0 +1 @@ +uid://dn02xsjm1kok8 diff --git a/examples/multistep_distort.gdshader b/examples/multistep_distort.gdshader new file mode 100644 index 0000000..fd7883e --- /dev/null +++ b/examples/multistep_distort.gdshader @@ -0,0 +1,22 @@ +shader_type canvas_item; + +//!steps 9 +//!load ./images/swamp.jpg + +uniform int STEP; + +const float strength = 0.01; + +void fragment() { + float v; + if (STEP % 3 == 0) { + v = COLOR.r; // 3 times + } else if (STEP % 3 == 0) { + v = COLOR.g; // 3 times + } else { + v = COLOR.b; // 3 times + } + vec2 uv = UV; + uv.y -= v * strength; + COLOR = texture(TEXTURE, uv); +} diff --git a/examples/multistep_distort.gdshader.uid b/examples/multistep_distort.gdshader.uid new file mode 100644 index 0000000..22efbc6 --- /dev/null +++ b/examples/multistep_distort.gdshader.uid @@ -0,0 +1 @@ +uid://c17u5jx7a7o81 diff --git a/examples/multistep_pixelsort.gdshader b/examples/multistep_pixelsort.gdshader new file mode 100644 index 0000000..21ba1b6 --- /dev/null +++ b/examples/multistep_pixelsort.gdshader @@ -0,0 +1,20 @@ +shader_type canvas_item; + +#include "./shaderlib/pixelsort.gdshaderinc" + +//!steps 1500 +uniform int STEP; + +//!load ./images/mountain.jpg + +void fragment() { + // pixel sorting works in multiple steps + COLOR = pixelsort_step( + TEXTURE, UV, + DIRECTION_BOTTOM_TO_TOP, + COLOR_MODE_OKLCH, + {true, false, false}, + {-INF, .007, -INF}, + {INF, INF, INF}, + STEP); +} diff --git a/examples/multistep_pixelsort.gdshader.uid b/examples/multistep_pixelsort.gdshader.uid new file mode 100644 index 0000000..47eaaf5 --- /dev/null +++ b/examples/multistep_pixelsort.gdshader.uid @@ -0,0 +1 @@ +uid://csk0fg4by651b diff --git a/examples/oklab.gdshader b/examples/oklab.gdshader new file mode 100644 index 0000000..08f46b5 --- /dev/null +++ b/examples/oklab.gdshader @@ -0,0 +1,12 @@ +shader_type canvas_item; + +#include "./shaderlib/oklab.gdshaderinc" + +//!load ./images/swamp.jpg + +void fragment() { + vec4 oklab = rgb2oklab(COLOR); + vec4 oklch = oklab2oklch(oklab); + oklch.z -= 2.0; + COLOR = oklab2rgb(oklch2oklab(oklch)); +} diff --git a/examples/oklab.gdshader.uid b/examples/oklab.gdshader.uid new file mode 100644 index 0000000..87cef6b --- /dev/null +++ b/examples/oklab.gdshader.uid @@ -0,0 +1 @@ +uid://cu37y8lc0x83 diff --git a/examples/place_texture.gdshader b/examples/place_texture.gdshader new file mode 100644 index 0000000..b0c50ca --- /dev/null +++ b/examples/place_texture.gdshader @@ -0,0 +1,15 @@ +shader_type canvas_item; + +#include "./shaderlib/place_texture.gdshaderinc" +#include "./shaderlib/common.gdshaderinc" + +//!load ./images/swamp.jpg +//!load+ img2 ./images/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/examples/place_texture.gdshader.uid b/examples/place_texture.gdshader.uid new file mode 100644 index 0000000..8ced27b --- /dev/null +++ b/examples/place_texture.gdshader.uid @@ -0,0 +1 @@ +uid://dybe4t5rbbkc6 diff --git a/examples/project.godot_ b/examples/project.godot_ new file mode 100644 index 0000000..5eff383 --- /dev/null +++ b/examples/project.godot_ @@ -0,0 +1,15 @@ +; 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" +run/main_scene="res://0_empty.tscn" +config/features=PackedStringArray("4.4", "Forward Plus") 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/examples/sobel.gdshader b/examples/sobel.gdshader new file mode 100644 index 0000000..3a7a8b5 --- /dev/null +++ b/examples/sobel.gdshader @@ -0,0 +1,10 @@ +shader_type canvas_item; + +//!load ./images/noisy.png + +#include "./shaderlib/sobel.gdshaderinc" + +void fragment() { + // Sobel Filter + COLOR = sobel(TEXTURE, UV); +} diff --git a/examples/sobel.gdshader.uid b/examples/sobel.gdshader.uid new file mode 100644 index 0000000..33b657a --- /dev/null +++ b/examples/sobel.gdshader.uid @@ -0,0 +1 @@ +uid://h376mk1fq4ky diff --git a/export_presets.cfg b/export_presets.cfg index 8f9be37..79a474d 100644 --- a/export_presets.cfg +++ b/export_presets.cfg @@ -1,29 +1,31 @@ [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="" -export_path="dist/GlitchApp.x86_64" +exclude_filter="screenshot.png, examples/*, shaderlib/*, tools/*, build-template/*" +export_path="dist/Fragmented.x86_64" +patches=PackedStringArray() encryption_include_filters="" encryption_exclude_filters="" +seed=0 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,66 +39,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}\"" - -[preset.1] - -name="Windows Desktop" -platform="Windows Desktop" -runnable=true -dedicated_server=false -custom_features="" -export_filter="all_resources" -include_filter="" -exclude_filter="" -export_path="dist/GlitchApp.exe" -encryption_include_filters="" -encryption_exclude_filters="" -encrypt_pck=false -encrypt_directory=false - -[preset.1.options] - -custom_template/debug="" -custom_template/release="" -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 -binary_format/architecture="x86_64" -codesign/enable=false -codesign/timestamp=true -codesign/timestamp_server_url="" -codesign/digest_algorithm=1 -codesign/description="" -codesign/custom_options=PackedStringArray() -application/modify_resources=true -application/icon="" -application/console_wrapper_icon="" -application/icon_interpolation=4 -application/file_version="" -application/product_version="" -application/company_name="" -application/product_name="GlitchApp" -application/file_description="" -application/copyright="ChaoticByte" -application/trademarks="" -application/export_angle=0 -ssh_remote_deploy/enabled=false -ssh_remote_deploy/host="user@host_ip" -ssh_remote_deploy/port="22" -ssh_remote_deploy/extra_args_ssh="" -ssh_remote_deploy/extra_args_scp="" -ssh_remote_deploy/run_script="Expand-Archive -LiteralPath '{temp_dir}\\{archive_name}' -DestinationPath '{temp_dir}' -$action = New-ScheduledTaskAction -Execute '{temp_dir}\\{exe_name}' -Argument '{cmd_args}' -$trigger = New-ScheduledTaskTrigger -Once -At 00:00 -$settings = New-ScheduledTaskSettingsSet -$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings -Register-ScheduledTask godot_remote_debug -InputObject $task -Force:$true -Start-ScheduledTask -TaskName godot_remote_debug -while (Get-ScheduledTask -TaskName godot_remote_debug | ? State -eq running) { Start-Sleep -Milliseconds 100 } -Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue" -ssh_remote_deploy/cleanup_script="Stop-ScheduledTask -TaskName godot_remote_debug -ErrorAction:SilentlyContinue -Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue -Remove-Item -Recurse -Force '{temp_dir}'" diff --git a/project.godot b/project.godot index 6ae1868..95da5af 100644 --- a/project.godot +++ b/project.godot @@ -10,24 +10,23 @@ config_version=5 [application] -config/name="Glitch" -config/version="v3.3" -run/main_scene="res://scenes/main.tscn" -config/features=PackedStringArray("4.2", "Mobile") -run/low_processor_mode=true -config/icon="res://icon.png" +config/name="Fragmented" +config/version="v10.2" +run/main_scene="res://src/scenes/main.tscn" +config/features=PackedStringArray("4.4", "Mobile") +config/icon="res://src/assets/icon.png" [autoload] -ShaderPresets="*res://src/presets/Presets.gd" -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=640 +window/size/viewport_height=672 window/energy_saving/keep_screen_on=false +window/subwindows/embed_subwindows=false [editor_plugins] @@ -38,15 +37,15 @@ enabled=PackedStringArray() zoom_out={ "deadzone": 0.5, "events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":16,"position":Vector2(244, 15),"global_position":Vector2(248, 56),"factor":1.0,"button_index":5,"canceled":false,"pressed":true,"double_click":false,"script":null) -, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":true,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":45,"physical_keycode":0,"key_label":0,"unicode":45,"echo":false,"script":null) -, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":true,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194435,"key_label":0,"unicode":45,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":true,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":45,"physical_keycode":0,"key_label":0,"unicode":45,"location":0,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":true,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194435,"key_label":0,"unicode":45,"location":0,"echo":false,"script":null) ] } zoom_in={ "deadzone": 0.5, "events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":8,"position":Vector2(270, 19),"global_position":Vector2(274, 60),"factor":1.0,"button_index":4,"canceled":false,"pressed":true,"double_click":false,"script":null) -, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":true,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":43,"physical_keycode":0,"key_label":0,"unicode":43,"echo":false,"script":null) -, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":true,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194437,"key_label":0,"unicode":43,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":true,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":43,"physical_keycode":0,"key_label":0,"unicode":43,"location":0,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":true,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194437,"key_label":0,"unicode":43,"location":0,"echo":false,"script":null) ] } drag={ @@ -56,12 +55,12 @@ drag={ } apply_shader={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194336,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194336,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } save_shader={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":true,"meta_pressed":false,"pressed":false,"keycode":83,"physical_keycode":0,"key_label":0,"unicode":115,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":true,"meta_pressed":false,"pressed":false,"keycode":83,"physical_keycode":0,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null) ] } @@ -70,4 +69,5 @@ save_shader={ renderer/rendering_method="mobile" textures/vram_compression/import_etc2_astc=true textures/lossless_compression/force_png=true -environment/defaults/default_clear_color=Color(0, 0, 0, 1) +shader_compiler/shader_cache/enabled=false +environment/defaults/default_clear_color=Color(0.501961, 0.501961, 0.501961, 1) diff --git a/scenes/main.tscn b/scenes/main.tscn deleted file mode 100644 index e49b48c..0000000 --- a/scenes/main.tscn +++ /dev/null @@ -1,254 +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/ImageViewport.gd" id="2_hvo65"] -[ext_resource type="Script" path="res://src/ImageViewportDisplays.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/Camera.gd" id="8_mls06"] - -[sub_resource type="ViewportTexture" id="ViewportTexture_lct1c"] -viewport_path = NodePath("ImageViewport") - -[sub_resource type="ShaderMaterial" id="ShaderMaterial_onhxk"] -shader = ExtResource("4_ty3qx") - -[node name="Main" type="Node2D"] -script = ExtResource("1_2625y") - -[node name="ImageViewport" type="SubViewport" parent="."] -disable_3d = true -canvas_item_default_texture_filter = 0 -render_target_update_mode = 4 -script = ExtResource("2_hvo65") - -[node name="ImageSprite" type="Sprite2D" parent="ImageViewport"] - -[node name="ImageViewportDisplay" type="Sprite2D" parent="."] -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"] -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 = 24.0 -offset_right = 208.0 -offset_bottom = 56.0 -theme_override_font_sizes/font_size = 20 -text = "GlitchApp -" -vertical_alignment = 2 - -[node name="AppVersion" type="Label" parent="UI_Layer/UserInterfaceContainer"] -layout_mode = 0 -offset_left = 128.0 -offset_top = 24.0 -offset_right = 208.0 -offset_bottom = 56.0 -theme_override_font_sizes/font_size = 14 -text = "v0 -" -vertical_alignment = 2 -script = ExtResource("5_o1ggv") - -[node name="OpenImageDialog" type="FileDialog" parent="UI_Layer/UserInterfaceContainer"] -title = "Load Image" -size = Vector2i(521, 159) -ok_button_text = "Open" -mode_overrides_title = false -file_mode = 0 -access = 2 -use_native_dialog = true - -[node name="SaveImageDialog" type="FileDialog" parent="UI_Layer/UserInterfaceContainer"] -title = "Export Image" -size = Vector2i(661, 159) -ok_button_text = "Save" -mode_overrides_title = false -access = 2 -filters = PackedStringArray("*.png") -use_native_dialog = true - -[node name="OpenImageButton" type="Button" parent="UI_Layer/UserInterfaceContainer"] -layout_mode = 1 -anchors_preset = 1 -anchor_left = 1.0 -anchor_right = 1.0 -offset_left = -360.0 -offset_top = 24.0 -offset_right = -248.0 -offset_bottom = 56.0 -grow_horizontal = 0 -text = "Load Image" - -[node name="SaveImageButton" type="Button" parent="UI_Layer/UserInterfaceContainer"] -layout_mode = 1 -anchors_preset = 1 -anchor_left = 1.0 -anchor_right = 1.0 -offset_left = -240.0 -offset_top = 23.0 -offset_right = -120.0 -offset_bottom = 55.0 -grow_horizontal = 0 -text = "Export Image" - -[node name="FitImageButton" type="Button" parent="UI_Layer/UserInterfaceContainer"] -layout_mode = 1 -anchors_preset = 1 -anchor_left = 1.0 -anchor_right = 1.0 -offset_left = -112.0 -offset_top = 24.0 -offset_right = -24.0 -offset_bottom = 56.0 -grow_horizontal = 0 -text = "Fit Image" - -[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 = 80.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"] -title = "Load Shader" -size = Vector2i(521, 159) -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"] -title = "Save Shader" -size = Vector2i(661, 159) -ok_button_text = "Save" -mode_overrides_title = false -access = 2 -filters = PackedStringArray("*.gdshader") -use_native_dialog = true - -[node name="Label" type="Label" parent="UI_Layer/UserInterfaceContainer/Editor"] -layout_mode = 0 -offset_right = 104.0 -offset_bottom = 32.0 -text = "Load Preset: " -vertical_alignment = 1 - -[node name="PresetOptions" type="OptionButton" parent="UI_Layer/UserInterfaceContainer/Editor"] -layout_mode = 0 -offset_left = 104.0 -offset_right = 240.0 -offset_bottom = 32.0 - -[node name="CodeEdit" type="CodeEdit" parent="UI_Layer/UserInterfaceContainer/Editor"] -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 -anchors_preset = 1 -anchor_left = 1.0 -anchor_right = 1.0 -offset_left = -216.0 -offset_right = -160.0 -offset_bottom = 32.0 -grow_horizontal = 0 -text = "Open" - -[node name="SaveShaderButton" type="Button" parent="UI_Layer/UserInterfaceContainer/Editor"] -layout_mode = 1 -anchors_preset = 1 -anchor_left = 1.0 -anchor_right = 1.0 -offset_left = -152.0 -offset_right = -96.0 -offset_bottom = 32.0 -grow_horizontal = 0 -text = "Save" - -[node name="ApplyShaderButton" type="Button" parent="UI_Layer/UserInterfaceContainer/Editor"] -layout_mode = 1 -anchors_preset = 1 -anchor_left = 1.0 -anchor_right = 1.0 -offset_left = -88.0 -offset_bottom = 32.0 -grow_horizontal = 0 -text = "Apply (F5)" - -[node name="Camera" type="Camera2D" parent="."] -script = ExtResource("8_mls06") - -[connection signal="file_selected" from="UI_Layer/UserInterfaceContainer/OpenImageDialog" to="." method="_on_open_image_dialog_file_selected"] -[connection signal="file_selected" from="UI_Layer/UserInterfaceContainer/SaveImageDialog" to="." method="_on_save_image_dialog_file_selected"] -[connection signal="pressed" from="UI_Layer/UserInterfaceContainer/OpenImageButton" to="." method="_on_open_image_button_pressed"] -[connection signal="pressed" from="UI_Layer/UserInterfaceContainer/SaveImageButton" to="." method="_on_save_image_button_pressed"] -[connection signal="pressed" from="UI_Layer/UserInterfaceContainer/FitImageButton" to="Camera" method="_on_fit_image_button_pressed"] -[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="item_selected" from="UI_Layer/UserInterfaceContainer/Editor/PresetOptions" to="UI_Layer/UserInterfaceContainer/Editor" method="_on_preset_options_item_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"] diff --git a/screenshot.png b/screenshot.png index 5595ae5..ce0ac8a 100644 Binary files a/screenshot.png and b/screenshot.png differ diff --git a/shaderlib/blur.gdshaderinc b/shaderlib/blur.gdshaderinc new file mode 100644 index 0000000..339256d --- /dev/null +++ b/shaderlib/blur.gdshaderinc @@ -0,0 +1,32 @@ + +/* + gaussian_blur adapted from https://godotshaders.com/shader/customizable-gausian-blur/ + original code by https://godotshaders.com/author/djbob-gaming-yt/ + maximum radius is 64 +*/ +vec4 gaussian_blur(sampler2D texture, vec2 uv, int radius, float sigma) { + vec2 resolution = 1.0 / vec2(textureSize(texture, 0)); + // calculate kernel + float kernel[64]; + float sum = 0.0; + for (int i = 0; i <= radius; i++) { + kernel[i] = exp(-0.5 * float(i * i) / (sigma * sigma)); + sum += i == 0 ? kernel[i] : 2.0 * kernel[i]; + } + for (int i = 0; i <= radius; i++) { + kernel[i] /= sum; + } + // + vec4 final_color = vec4(0.0); + float total_weight = 0.0; + for (int x = -radius; x <= radius; x++) { + for (int y = -radius; y <= radius; y++) { + float weight = kernel[abs(x)] * kernel[abs(y)]; + vec2 offset = vec2(float(x), float(y)) * resolution; + final_color += texture(texture, uv + offset) * weight; + total_weight += weight; + } + } + final_color /= total_weight; + return final_color; +} diff --git a/shaderlib/blur.gdshaderinc.uid b/shaderlib/blur.gdshaderinc.uid new file mode 100644 index 0000000..d5c8dfd --- /dev/null +++ b/shaderlib/blur.gdshaderinc.uid @@ -0,0 +1 @@ +uid://bjtljvcjcu6dr diff --git a/shaderlib/common.gdshaderinc b/shaderlib/common.gdshaderinc new file mode 100644 index 0000000..9352d6a --- /dev/null +++ b/shaderlib/common.gdshaderinc @@ -0,0 +1,27 @@ + +// inefficient cuberoot function +float cbrt(float x) { + return pow(x, 1.0/3.0); +} + +/* + 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); +} + +/* + Rotate UV +*/ + +vec2 rotateUV(vec2 uv, float rotation, vec2 center) { + float cosRot = cos(rotation); + float sinRot = sin(rotation); + return vec2( + cosRot * (uv.x - center.x) + sinRot * (uv.y - center.y) + center.x, + cosRot * (uv.y - center.y) - sinRot * (uv.x - center.x) + center.y); +} diff --git a/shaderlib/common.gdshaderinc.uid b/shaderlib/common.gdshaderinc.uid new file mode 100644 index 0000000..f43430d --- /dev/null +++ b/shaderlib/common.gdshaderinc.uid @@ -0,0 +1 @@ +uid://764b6ekchgb8 diff --git a/shaderlib/denoise.gdshaderinc b/shaderlib/denoise.gdshaderinc new file mode 100644 index 0000000..585104d --- /dev/null +++ b/shaderlib/denoise.gdshaderinc @@ -0,0 +1,68 @@ + +/* + glslSmartDenoise by Michele Morrone, adapted + original code: https://github.com/BrutPitt/glslSmartDeNoise + license of the original code: + + BSD 2-Clause License + + Copyright (c) 2019-2020 Michele Morrone + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define INV_SQRT_OF_2PI 0.39894228040143267793994605993439 // 1.0/SQRT_OF_2PI +#define INV_PI 0.31830988618379067153776752674503 + +vec4 smart_denoise(sampler2D tex, vec2 uv, float sigma, float kSigma, float threshold) { + float radius = round(kSigma*sigma); + float radQ = radius * radius; + + float invSigmaQx2 = .5 / (sigma * sigma); // 1.0 / (sigma^2 * 2.0) + float invSigmaQx2PI = INV_PI * invSigmaQx2; // 1/(2 * PI * sigma^2) + + float invThresholdSqx2 = .5 / (threshold * threshold); // 1.0 / (sigma^2 * 2.0) + float invThresholdSqrt2PI = INV_SQRT_OF_2PI / threshold; // 1.0 / (sqrt(2*PI) * sigma^2) + + vec4 centrPx = texture(tex,uv); + + float zBuff = 0.0; + vec4 aBuff = vec4(0.0); + vec2 size = vec2(textureSize(tex, 0)); + + for (float dx = -radius; dx <= radius; dx++) { + float pt = sqrt(radQ - dx * dx); // pt = yRadius: have circular trend + for (float dy = -pt; dy <= pt; dy++) { + vec2 d = vec2(dx, dy); + float blurFactor = exp( -dot(d, d) * invSigmaQx2 ) * invSigmaQx2PI; + + vec4 walkPx = texture(tex,uv+d/size); + vec4 dC = walkPx-centrPx; + float deltaFactor = exp(-dot(dC, dC) * invThresholdSqx2) * invThresholdSqrt2PI * blurFactor; + + zBuff += deltaFactor; + aBuff += deltaFactor*walkPx; + } + } + return aBuff/zBuff; +} diff --git a/shaderlib/denoise.gdshaderinc.uid b/shaderlib/denoise.gdshaderinc.uid new file mode 100644 index 0000000..132a166 --- /dev/null +++ b/shaderlib/denoise.gdshaderinc.uid @@ -0,0 +1 @@ +uid://b7ksfifyyfcip diff --git a/shaderlib/hsv.gdshaderinc b/shaderlib/hsv.gdshaderinc new file mode 100644 index 0000000..2a7834d --- /dev/null +++ b/shaderlib/hsv.gdshaderinc @@ -0,0 +1,27 @@ + +/* + rgb2hsv and hsv2rgb functions adapted + from https://godotshaders.com/shader/hsv-adjustment/ + original code by https://godotshaders.com/author/al1-ce/ + + Color space conversion functions always work with vec4. + The fourth value is always alpha. +*/ + +// 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); +} diff --git a/shaderlib/hsv.gdshaderinc.uid b/shaderlib/hsv.gdshaderinc.uid new file mode 100644 index 0000000..771cc38 --- /dev/null +++ b/shaderlib/hsv.gdshaderinc.uid @@ -0,0 +1 @@ +uid://bbr3tq6mp5qa2 diff --git a/shaderlib/kuwahara.gdshaderinc b/shaderlib/kuwahara.gdshaderinc new file mode 100644 index 0000000..ea6e22b --- /dev/null +++ b/shaderlib/kuwahara.gdshaderinc @@ -0,0 +1,116 @@ +/* + Kuwahara Filter, adapted + original code: https://godotshaders.com/shader/generalized-kuwahara/ + original authors: + - https://godotshaders.com/author/firerabbit/ + - https://github.com/GarrettGunnell (Acerola) + license of the original code: + + MIT License + + Copyright (c) 2022 Garrett Gunnell + Copyright (c) 2024 Firerabbit + + Permission is hereby granted, free of charge, to any person obtaining a copy of this + software and associated documentation files (the "Software"), to deal in the Software + without restriction, including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons + to whom the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies + or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. +*/ + +vec3 kuwahara( + sampler2D texture, + vec2 uv, + int kernel_size, // should be > 2 - high values will affect performance + float hardness, // should be in the range of 1.0 - 100.0 + float sharpness, // should be in the range of 1.0 - 18.0 + float zero_crossing, // should be in the range of 0.5 - 2.0 + float zeta, // should be in the range of 0.01 - 3.0 + int n // number of iterations, should be 8, must be <= 8 +) { + vec2 texelSize = vec2(1.0 / vec2(textureSize(texture, 0))); + vec4 m[8]; + vec3 s[8]; + + int kernel_radius = kernel_size / 2; + + float sin_zero_crossing = sin(zero_crossing); + float eta = (zeta + cos(zero_crossing)) / (sin_zero_crossing * sin_zero_crossing); + + for (int k = 0; k < n; ++k) { + m[k] = vec4(0.0f); + s[k] = vec3(0.0f); + } + + for (int y = -kernel_radius; y <= kernel_radius; ++y) { + for (int x = -kernel_radius; x <= kernel_radius; ++x) { + vec2 v = vec2(float(x), float(y)) / float(kernel_radius); + vec3 c = texture(texture, uv + vec2(float(x), float(y)) * texelSize.xy).rgb; + c = clamp(c, 0.0f, 1.0f); + float sum = 0.0f; + float w[8]; + float z, vxx, vyy; + + /* Calculate Polynomial Weights */ + vxx = zeta - eta * v.x * v.x; + vyy = zeta - eta * v.y * v.y; + z = max(0, v.y + vxx); + w[0] = z * z; + sum += w[0]; + z = max(0, -v.x + vyy); + w[2] = z * z; + sum += w[2]; + z = max(0, -v.y + vxx); + w[4] = z * z; + sum += w[4]; + z = max(0, v.x + vyy); + w[6] = z * z; + sum += w[6]; + v = sqrt(2.0f) / 2.0f * vec2(v.x - v.y, v.x + v.y); + vxx = zeta - eta * v.x * v.x; + vyy = zeta - eta * v.y * v.y; + z = max(0, v.y + vxx); + w[1] = z * z; + sum += w[1]; + z = max(0, -v.x + vyy); + w[3] = z * z; + sum += w[3]; + z = max(0, -v.y + vxx); + w[5] = z * z; + sum += w[5]; + z = max(0, v.x + vyy); + w[7] = z * z; + sum += w[7]; + + float g = exp(-3.125f * dot(v,v)) / sum; + + for (int k = 0; k < 8; ++k) { + float wk = w[k] * g; + m[k] += vec4(c * wk, wk); + s[k] += c * c * wk; + } + } + } + + vec4 output = vec4(0.0f); + for (int k = 0; k < n; ++k) { + m[k].rgb /= m[k].w; + s[k] = abs(s[k] / m[k].w - m[k].rgb * m[k].rgb); + float sigma2 = s[k].r + s[k].g + s[k].b; + float w = 1.0f / (1.0f + pow(hardness * 1000.0f * sigma2, 0.5f * sharpness)); + output += vec4(m[k].rgb * w, w); + } + + return clamp(output / output.w, 0.0f, 1.0f).rgb; +} diff --git a/shaderlib/kuwahara.gdshaderinc.uid b/shaderlib/kuwahara.gdshaderinc.uid new file mode 100644 index 0000000..1ff911b --- /dev/null +++ b/shaderlib/kuwahara.gdshaderinc.uid @@ -0,0 +1 @@ +uid://chqh2cni1qiuu diff --git a/shaderlib/oklab.gdshaderinc b/shaderlib/oklab.gdshaderinc new file mode 100644 index 0000000..5a456c7 --- /dev/null +++ b/shaderlib/oklab.gdshaderinc @@ -0,0 +1,70 @@ + +/* + OkLab and OkLCh + For more details on oklab, see + - https://bottosson.github.io/posts/oklab/ + - https://en.wikipedia.org/wiki/Oklab_color_space + + Color space conversion functions always work with vec4. + The fourth value is always alpha. +*/ + +#include "./common.gdshaderinc" + +vec4 rgb2oklab(vec4 c) { + // oklab.x and .y (a and b) should range from -0.5 to 0.5 + + float l = 0.4122214708f * c.r + 0.5363325363f * c.g + 0.0514459929f * c.b; + float m = 0.2119034982f * c.r + 0.6806995451f * c.g + 0.1073969566f * c.b; + float s = 0.0883024619f * c.r + 0.2817188376f * c.g + 0.6299787005f * c.b; + + float l_ = cbrt(l); + float m_ = cbrt(m); + float s_ = cbrt(s); + + return vec4( + 0.2104542553f*l_ + 0.7936177850f*m_ - 0.0040720468f*s_, + 1.9779984951f*l_ - 2.4285922050f*m_ + 0.4505937099f*s_, + 0.0259040371f*l_ + 0.7827717662f*m_ - 0.8086757660f*s_, + c.a + ); +} + +vec4 oklab2rgb(vec4 c) { + // oklab.x and .y (a and b) should range from -0.5 to 0.5 + + float l_ = c.x + 0.3963377774f * c.y + 0.2158037573f * c.z; + float m_ = c.x - 0.1055613458f * c.y - 0.0638541728f * c.z; + float s_ = c.x - 0.0894841775f * c.y - 1.2914855480f * c.z; + + float l = l_*l_*l_; + float m = m_*m_*m_; + float s = s_*s_*s_; + + return vec4( + +4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s, + -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s, + -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s, + c.a + ); +} + +vec4 oklab2oklch(vec4 c) { + // oklch.z (hue) ranges from -3.6 to 3.6 + return vec4( + c.x, + sqrt((c.y * c.y) + (c.z * c.z)), + atan(c.z, c.y), + c.a + ); +} + +vec4 oklch2oklab(vec4 c) { + // oklch.z (hue) ranges from -3.6 to 3.6 + return vec4( + c.x, + c.y * cos(c.z), + c.y * sin(c.z), + c.a + ); +} diff --git a/shaderlib/oklab.gdshaderinc.uid b/shaderlib/oklab.gdshaderinc.uid new file mode 100644 index 0000000..7f0bf5f --- /dev/null +++ b/shaderlib/oklab.gdshaderinc.uid @@ -0,0 +1 @@ +uid://ckw4nfslk4m6l diff --git a/shaderlib/pixelate.gdshaderinc b/shaderlib/pixelate.gdshaderinc new file mode 100644 index 0000000..d9b91f1 --- /dev/null +++ b/shaderlib/pixelate.gdshaderinc @@ -0,0 +1,14 @@ + +// pixelate by lowering uv resolution +vec4 pixelate(sampler2D tex, vec2 uv, float resolution_x) { + vec2 texture_size = vec2(textureSize(tex, 0)); + vec2 ratio; + if (texture_size.x > texture_size.y) { + ratio = vec2(texture_size.x / texture_size.y, 1.0); + } + else { + ratio = vec2(1.0, texture_size.y / texture_size.x); + } + vec2 r = ratio * resolution_x; + return texture(tex, trunc(uv * r) / r); +} diff --git a/shaderlib/pixelate.gdshaderinc.uid b/shaderlib/pixelate.gdshaderinc.uid new file mode 100644 index 0000000..a0a0d4d --- /dev/null +++ b/shaderlib/pixelate.gdshaderinc.uid @@ -0,0 +1 @@ +uid://dpu5nneo5bgnq diff --git a/shaderlib/pixelsort.gdshaderinc b/shaderlib/pixelsort.gdshaderinc new file mode 100644 index 0000000..73ed5da --- /dev/null +++ b/shaderlib/pixelsort.gdshaderinc @@ -0,0 +1,126 @@ + +/* + Pixelsorting using odd-even sort + + I roughly followed https://ciphrd.com/2020/04/08/pixel-sorting-on-shader-using-well-crafted-sorting-filters-glsl/ + - vector fields aren't implemented, diagonal sorting is not supported! +*/ + +#include "./hsv.gdshaderinc" +#include "./oklab.gdshaderinc" + +#define INF (1.0/0.0) + +#define DIRECTION_LEFT_TO_RIGHT vec2(1, 0) +#define DIRECTION_RIGHT_TO_LEFT vec2(-1, 0) +#define DIRECTION_TOP_TO_BOTTOM vec2(0, 1) +#define DIRECTION_BOTTOM_TO_TOP vec2(0, -1) + +#define COLOR_MODE_RGB 0 +#define COLOR_MODE_OKLAB 1 +#define COLOR_MODE_OKLCH 2 +#define COLOR_MODE_HSV 3 + +vec4 pixelsort_step( + sampler2D tex, vec2 uv, + vec2 direction, // e.g. (1, 0) for left-to-right or (0, -1) for bottom-to-top + // see DIRECTION_LEFT_TO_RIGHT, etc. + // note: vertical sorting doesn't work, so using e.g. (1, 1) won't work + int color_mode, // 0 = RGB, 1 = OKLAB, 2 = OKLCH, 3 = HSV + // see COLOR_MODE_RGB, etc. + bool color_channel_mask[3], // which color channel(s) to take into account + float lower_threshold[3], // lower threshold for pixels to be considered sorted + // when in doubt, use {-INF, -INF, -INF} + float upper_threshold[3], // upper threshold; {INF, INF, INF} + int step_ // from STEP +) { + // sanitize inputs + direction = clamp(direction, vec2(-1, -1), vec2(1, 1)); + color_mode = clamp(color_mode, 0, 3); + // get neighbour + vec2 texture_size = vec2(textureSize(tex, 0)); + vec2 a = (mod(floor(uv * texture_size), 2.0) * 2.0 - 1.0) * (mod(float(step_), 2.0) * 2.0 - 1.0); + vec2 neighbour_uv = uv + (direction * a / texture_size); + // + vec4 x = texture(tex, uv); + vec4 y = texture(tex, neighbour_uv); + if ( // stop at borders + neighbour_uv.x > 1.0 || + neighbour_uv.x < 0.0 || + neighbour_uv.y > 1.0 || + neighbour_uv.y < 0.0 + ) { + return x; + } else { + // convert color if necessary + // get value to compare + float vx = 0.0; + float vy = 0.0; + vec3 color_x; + vec3 color_y; + if (color_mode == COLOR_MODE_RGB) { + color_x = x.rgb; + color_y = y.rgb; + } else if (color_mode == COLOR_MODE_OKLAB) { + color_x = rgb2oklab(x).rgb; + color_y = rgb2oklab(y).rgb; + } else if (color_mode == COLOR_MODE_OKLCH) { + color_x = oklab2oklch(rgb2oklab(x)).rgb; + color_y = oklab2oklch(rgb2oklab(y)).rgb; + } else if (color_mode == COLOR_MODE_HSV) { + color_x = rgb2hsv(x).rgb; + color_y = rgb2hsv(y).rgb; + } + float divisor = 0.0; + if (color_channel_mask[0]) { + vx += color_x.r; + vy += color_y.r; + divisor += 1.0; + } + if (color_channel_mask[1]) { + vx += color_x.g; + vy += color_y.g; + divisor += 1.0; + } + if (color_channel_mask[2]) { + vx += color_x.b; + vy += color_y.b; + divisor += 1.0; + } + divisor = max(divisor, 1.0); + vx /= divisor; + vy /= divisor; + // + if ( + (a.x < .0 && abs(direction).y == .0) || + (a.y < .0 && abs(direction).x == .0) + ) { + if ( + vy > vx && + // threshold + color_x.r < upper_threshold[0] && + color_x.g < upper_threshold[1] && + color_x.b < upper_threshold[2] && + color_x.r > lower_threshold[0] && + color_x.g > lower_threshold[1] && + color_x.b > lower_threshold[2] + ) { return y; } + else { return x; } + } else if ( + (a.x > .0 && abs(direction).y == .0) || + (a.y > .0 && abs(direction).x == .0) + ) { + if ( + vx >= vy && + // threshold + color_y.r < upper_threshold[0] && + color_y.g < upper_threshold[1] && + color_y.b < upper_threshold[2] && + color_y.r > lower_threshold[0] && + color_y.g > lower_threshold[1] && + color_y.b > lower_threshold[2] + ) { return y; } + else { return x; } + } + } +} diff --git a/shaderlib/pixelsort.gdshaderinc.uid b/shaderlib/pixelsort.gdshaderinc.uid new file mode 100644 index 0000000..e02f48e --- /dev/null +++ b/shaderlib/pixelsort.gdshaderinc.uid @@ -0,0 +1 @@ +uid://doefnwk3vyr0o diff --git a/shaderlib/place_texture.gdshaderinc b/shaderlib/place_texture.gdshaderinc new file mode 100644 index 0000000..af57cce --- /dev/null +++ b/shaderlib/place_texture.gdshaderinc @@ -0,0 +1,21 @@ + +/* + 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/place_texture.gdshaderinc.uid b/shaderlib/place_texture.gdshaderinc.uid new file mode 100644 index 0000000..f0b6f69 --- /dev/null +++ b/shaderlib/place_texture.gdshaderinc.uid @@ -0,0 +1 @@ +uid://51u2hjq62e5i diff --git a/shaderlib/sobel.gdshaderinc b/shaderlib/sobel.gdshaderinc new file mode 100644 index 0000000..dd665b3 --- /dev/null +++ b/shaderlib/sobel.gdshaderinc @@ -0,0 +1,50 @@ + +/* + Edge Detection (Sobel Filter and Gaussian Blur) by FencerDevLog, adapted + original code: https://godotshaders.com/shader/edge-detection-sobel-filter-and-gaussian-blur/ + license of the original code: CC0 +*/ + +vec3 _convolution(sampler2D tex, vec2 uv, vec2 pixel_size) { + vec3 conv = vec3(0.0); + // Gaussian blur kernel + float gauss[25] = { + 0.00390625, 0.015625, 0.0234375, 0.015625, 0.00390625, + 0.015625, 0.0625, 0.09375, 0.0625, 0.015625, + 0.0234375, 0.09375, 0.140625, 0.09375, 0.0234375, + 0.015625, 0.0625, 0.09375, 0.0625, 0.015625, + 0.00390625, 0.015625, 0.0234375, 0.015625, 0.00390625 + }; + for (int row = 0; row < 5; row++) { + for (int col = 0; col < 5; col++) { + conv += texture(tex, uv + vec2(float(col - 2), float(row - 2)) * pixel_size).rgb * gauss[row * 5 + col]; + } + } + return conv; +} + +vec4 sobel(sampler2D tex, vec2 uv) { + vec2 pixel_size = 1.0/vec2(textureSize(tex, 0)); + vec3 pixels[9]; // Sobel kernel + // [0, 1, 2] + // [3, 4, 5] + // [6, 7, 8] + for (int row = 0; row < 3; row++) { + for (int col = 0; col < 3; col++) { + vec2 uv_ = uv + vec2(float(col - 1), float(row - 1)) * pixel_size; + pixels[row * 3 + col] = _convolution(tex, uv_, pixel_size); + } + } + + // Sobel operator + vec3 gx = ( + pixels[0] * -1.0 + pixels[3] * -2.0 + pixels[6] * -1.0 + + pixels[2] * 1.0 + pixels[5] * 2.0 + pixels[8] * 1.0 + ); + vec3 gy = ( + pixels[0] * -1.0 + pixels[1] * -2.0 + pixels[2] * -1.0 + + pixels[6] * 1.0 + pixels[7] * 2.0 + pixels[8] * 1.0 + ); + vec3 sobel = sqrt(gx * gx + gy * gy); + return vec4(sobel, 1.0); +} \ No newline at end of file diff --git a/shaderlib/sobel.gdshaderinc.uid b/shaderlib/sobel.gdshaderinc.uid new file mode 100644 index 0000000..5753e70 --- /dev/null +++ b/shaderlib/sobel.gdshaderinc.uid @@ -0,0 +1 @@ +uid://bqo1fpunnl05f diff --git a/src/Camera.gd b/src/Camera.gd index 231add8..416e035 100644 --- a/src/Camera.gd +++ b/src/Camera.gd @@ -1,42 +1,50 @@ extends Camera2D +@onready var image_viewport_display = %ImageViewportDisplay + var drag = false -@onready var user_interface_container = get_parent().get_node("UI_Layer/UserInterfaceContainer") -@onready var image_viewport = get_parent().get_node("ImageViewport") - 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 + +var old_zoom = self.zoom + +func _process(_delta: float) -> void: + if self.zoom != old_zoom: + image_viewport_display.update_zoom_texture_filter(self.zoom) + image_viewport_display.material.set_shader_parameter("zoom_level", self.zoom) + old_zoom = 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 - 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) + 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) func zoom_in(): var old_mouse_pos = get_global_mouse_position() - zoom *= 1.2 - global_position += old_mouse_pos - get_global_mouse_position() + self.zoom *= 1.2 + self.global_position += old_mouse_pos - get_global_mouse_position() func zoom_out(): var old_mouse_pos = get_global_mouse_position() - zoom *= 1/1.2 - global_position += old_mouse_pos - get_global_mouse_position() + self.zoom *= 1/1.2 + self.global_position += old_mouse_pos - get_global_mouse_position() func _on_fit_image_button_pressed(): fit_image() diff --git a/src/Camera.gd.uid b/src/Camera.gd.uid new file mode 100644 index 0000000..f25b715 --- /dev/null +++ b/src/Camera.gd.uid @@ -0,0 +1 @@ +uid://b6r8rigubdctk diff --git a/src/Editor.gd b/src/Editor.gd deleted file mode 100644 index c006b86..0000000 --- a/src/Editor.gd +++ /dev/null @@ -1,243 +0,0 @@ -extends Control - -@onready var preset_options = $PresetOptions -@onready var code_editor = $CodeEdit -@onready var open_shader_dialog = $OpenShaderDialog -@onready var save_shader_dialog = $SaveShaderDialog -var selected_preset_name = ShaderPresets.default_preset -var last_save_filepath = "" - -# # # # # # # # # # # -# 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", "log", "exp2", "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", - "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", - "POINT_COORD", -] -# -# 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) - 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() - -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 _on_preset_options_item_selected(index): - selected_preset_name = preset_options.get_item_text(index) - Globals.shader = ShaderPresets.presets[selected_preset_name] - Globals.target_viewport.update() - update() - last_save_filepath = "" - -func update(): - preset_options.clear() - # the following lines are weird af - var presets: Array[String] = [] - var current_p_idx = 0 - for p in ShaderPresets.presets: - presets.append(p) - if p == selected_preset_name: - current_p_idx = len(presets) - 1 - preset_options.add_item(p) - preset_options.select(current_p_idx) - # weirdness ends here - code_editor.text = Globals.shader.code - -func _on_open_shader_button_pressed(): - open_shader_dialog.show() - -func _on_save_shader_button_pressed(): - if last_save_filepath == "": - save_shader_dialog.current_file = selected_preset_name + "_custom.gdshader" - else: - save_shader_dialog.current_path = last_save_filepath - save_shader_dialog.show() - -func _on_open_shader_dialog_file_selected(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 - Globals.target_viewport.update() - update() - last_save_filepath = path - -func _on_save_shader_dialog_file_selected(path): - var file = FileAccess.open(path, FileAccess.WRITE) - var content = Globals.shader.code - file.store_string(content) - last_save_filepath = path - -func _on_apply_shader_button_pressed(): - var shader = Shader.new() - shader.code = code_editor.text - Globals.shader = shader - Globals.target_viewport.update() diff --git a/src/Filesystem.gd b/src/Filesystem.gd new file mode 100644 index 0000000..cb1ac7d --- /dev/null +++ b/src/Filesystem.gd @@ -0,0 +1,77 @@ +extends Node + +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 last_image_savepath = "" +var last_original_image_path = "" + +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 + print("Load ", path) + var img = Image.new() + var err = img.load(path) + if err == OK: + original_image = ImageTexture.create_from_image(img) + 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 + +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: + 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 store_last_opened_file(): + var f = FileAccess.open("user://last_opened", FileAccess.WRITE) + if f != null: + 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: + shader_path = f.get_pascal_string() diff --git a/src/Filesystem.gd.uid b/src/Filesystem.gd.uid new file mode 100644 index 0000000..a89dcab --- /dev/null +++ b/src/Filesystem.gd.uid @@ -0,0 +1 @@ +uid://rlb041ygdwol diff --git a/src/Globals.gd b/src/Globals.gd deleted file mode 100644 index 3315890..0000000 --- a/src/Globals.gd +++ /dev/null @@ -1,5 +0,0 @@ -extends Node - -var camera_freeze = false -@onready var shader: Shader = ShaderPresets.presets[ShaderPresets.default_preset] -var target_viewport: SubViewport diff --git a/src/ImageCompositor.gd b/src/ImageCompositor.gd new file mode 100644 index 0000000..9f3e95a --- /dev/null +++ b/src/ImageCompositor.gd @@ -0,0 +1,123 @@ +class_name ImageCompositor extends SubViewport + +var image_sprite: Sprite2D + +func _init() -> void: + # Overwrite some variables + self.render_target_update_mode = SubViewport.UPDATE_ALWAYS + self.disable_3d = true + self.transparent_bg = true + self.canvas_item_default_texture_filter = Viewport.DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_NEAREST + self.image_sprite = Sprite2D.new() + +@onready var camera = %Camera +@onready var image_viewport_display = %ImageViewportDisplay + +func _ready() -> void: + # Add image sprite as child to be rendered + self.add_child(image_sprite) + +var _fragment_function_regex: RegEx = RegEx.create_from_string(r'\s*void\s+fragment\s*\(\s*\)\s*{\s*') + +func validate_shader_compilation(shader: Shader) -> bool: + # Inject code to validate shader compilation + 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 shader_has_uniform(shader: Shader, name: String, type: int) -> bool: + for u in shader.get_shader_uniform_list(): + if u["name"] == name && u["type"] == type: + return true + return false + +func set_vsync(enabled: bool): + if enabled: + DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_ENABLED) + else: + DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED) + +func update(overwrite_image_path: String = "") -> Array: # returns error messages (strings) + 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_uniform(shader, "STEP", 2) + var has_steps_uniform: bool = shader_has_uniform(shader, "STEPS", 2) + # validate shader + if not validate_shader_compilation(shader): + return ["Shader compilation failed!"] + var errors = [] + # load texture(s) from //!load directive -> TEXTURE + var original_image_path = "" + if overwrite_image_path == "": + var m = ShaderDirectiveParser.parse_load_directive(shader.code) + if len(m) < 1: + errors.append("Didn't find a load directive!") + return errors + original_image_path = Filesystem.get_absolute_path(m[1]) + else: + original_image_path = overwrite_image_path + var fit_image = false + 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 + Filesystem.clear_additional_images() + for n in ShaderDirectiveParser.parse_load_additional_directive(shader.code): + 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() + # already show the image viewport & fit the image + if fit_image: camera.fit_image() + image_viewport_display.show() + # create shader material + var mat = ShaderMaterial.new() + mat.shader = shader + # add images 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 + # iterate n times + set_vsync(false) # speed up processing + if has_steps_uniform: + # set STEPS param + mat.set_shader_parameter("STEPS", steps) + for i in range(steps): + 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() + image_sprite.texture = ImageTexture.create_from_image(Filesystem.result) + set_vsync(true) # reenable vsync + image_sprite.material = null + # done + return errors diff --git a/src/ImageCompositor.gd.uid b/src/ImageCompositor.gd.uid new file mode 100644 index 0000000..2e55fae --- /dev/null +++ b/src/ImageCompositor.gd.uid @@ -0,0 +1 @@ +uid://d106170kuigl3 diff --git a/src/ImageViewport.gd b/src/ImageViewport.gd deleted file mode 100644 index 7b98461..0000000 --- a/src/ImageViewport.gd +++ /dev/null @@ -1,40 +0,0 @@ -extends SubViewport - -@onready var image_sprite = $ImageSprite - -var image_original_tex: ImageTexture -var image_result: Image -var load_uniform_regex: RegEx - -func _ready(): - load_uniform_regex = RegEx.new() - load_uniform_regex.compile(r'\/\/!load\s(\w*)\s(.*)') - -func set_original_image(image: Image): - image_original_tex = ImageTexture.create_from_image(image) - image_sprite.texture = image_original_tex - image_sprite.offset = image_original_tex.get_size() / 2 - size = image_original_tex.get_size() - -func update(): - if image_original_tex != null: - 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_uniform_regex.search_all(Globals.shader.code): - var u_image = Image.load_from_file(m.strings[2]) - 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/ImageViewportDisplays.gd b/src/ImageViewportDisplay.gd similarity index 54% rename from src/ImageViewportDisplays.gd rename to src/ImageViewportDisplay.gd index d4817ca..7a9c827 100644 --- a/src/ImageViewportDisplays.gd +++ b/src/ImageViewportDisplay.gd @@ -1,9 +1,10 @@ extends Sprite2D -@onready var camera = get_parent().get_node("Camera") +func _ready() -> void: + hide() -func _process(_delta): - if camera.zoom.x >= 1.5: +func update_zoom_texture_filter(zoom: Vector2): + if zoom.x >= 1.5: texture_filter = TEXTURE_FILTER_NEAREST_WITH_MIPMAPS else: texture_filter = TEXTURE_FILTER_LINEAR diff --git a/src/ImageViewportDisplay.gd.uid b/src/ImageViewportDisplay.gd.uid new file mode 100644 index 0000000..dbde761 --- /dev/null +++ b/src/ImageViewportDisplay.gd.uid @@ -0,0 +1 @@ +uid://ctc4lhbdsoq7u diff --git a/src/Main.gd b/src/Main.gd index 1f56020..dcf32b3 100644 --- a/src/Main.gd +++ b/src/Main.gd @@ -1,37 +1,126 @@ -extends Node2D +extends Node -@onready var camera = $Camera -@onready var image_viewport = $ImageViewport -@onready var ui_container = $UI_Layer/UserInterfaceContainer -@onready var ui_control_fileopen = $UI_Layer/UserInterfaceContainer/OpenImageDialog -@onready var ui_control_filesave = $UI_Layer/UserInterfaceContainer/SaveImageDialog -var last_save_filepath = "" +const BATCH_MODE_SUPPORTED_EXTS = [ + ".bmp", ".dds", ".exr", ".hdr", ".jpeg", ".jpg", ".ktx", ".png", ".svg", ".webp" +] + +@onready var app_name = ProjectSettings.get_setting("application/config/name") + +func show_help(): + print( + "Usage:\n\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} + var args_len = args.size() + var i = 0 + while i < args_len: + var a = args[i] + if a in kwargs && args_len > i+1: + i += 1 + kwargs[a] = args[i] + i += 1 + return kwargs + +func cli_handle_errors(errors: Array) -> int: + # returns number of errors + var n_errors = errors.size() + if n_errors > 0: + print("One or more errors occurred.") + for e in errors: + printerr(e) + return n_errors + +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 + 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) + +func prepare_gui(): + update_title() + # Load last opened file + Filesystem.remember_last_opened_file() + %MainUI._on_apply_shader_button_pressed() func _ready(): - Globals.target_viewport = image_viewport - -func _on_open_image_button_pressed(): - ui_control_fileopen.show() - -func _on_open_image_dialog_file_selected(path): - var img = Image.new() - var err = img.load(path) - if err == OK: - image_viewport.set_original_image(img) - image_viewport.update() - camera.fit_image() - last_save_filepath = path + var args = OS.get_cmdline_args() + if len(args) > 0 and args[0] in ["apply", "help"]: + # use the commandline interface + cli(args) else: - print("An error occured!") + prepare_gui() -func _on_save_image_button_pressed(): - if image_viewport.get_result() != null: - ui_control_filesave.current_path = last_save_filepath - ui_control_filesave.show() - -func _on_save_image_dialog_file_selected(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" else: - last_save_filepath = path + get_window().title = current_file + " - " + app_name + " - Viewer" diff --git a/src/Main.gd.uid b/src/Main.gd.uid new file mode 100644 index 0000000..2f9a508 --- /dev/null +++ b/src/Main.gd.uid @@ -0,0 +1 @@ +uid://5sbslwysin5a diff --git a/src/MainUI.gd b/src/MainUI.gd new file mode 100644 index 0000000..8a62ff1 --- /dev/null +++ b/src/MainUI.gd @@ -0,0 +1,100 @@ +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) + set_buttons_disabled(false) + +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/MainUI.gd.uid b/src/MainUI.gd.uid new file mode 100644 index 0000000..0077ccd --- /dev/null +++ b/src/MainUI.gd.uid @@ -0,0 +1 @@ +uid://bxgmf2ny7yuc8 diff --git a/src/ShaderDirectiveParser.gd b/src/ShaderDirectiveParser.gd new file mode 100644 index 0000000..c06dc70 --- /dev/null +++ b/src/ShaderDirectiveParser.gd @@ -0,0 +1,24 @@ +extends Node + +var _load_regex: RegEx = RegEx.create_from_string(r'\/\/!load\s(.*)') +var _load_additional_regex: RegEx = RegEx.create_from_string(r'\/\/!load\+\s(\w*)\s(.*)') +var _iterate_regex: RegEx = RegEx.create_from_string(r'\/\/!steps\s([0-9]+)\s*') + +func parse_load_directive(shader_code: String) -> PackedStringArray: + var regex_match = self._load_regex.search(shader_code) + if regex_match == null: + return [] + return regex_match.strings + +func parse_load_additional_directive(shader_code: String) -> Array[PackedStringArray]: + var results : Array[PackedStringArray] = [] + for m in self._load_additional_regex.search_all(shader_code): + results.append(m.strings) + return results + +func parse_steps_directive(shader_code: String) -> int: + var regex_match = self._iterate_regex.search(shader_code) + if regex_match == null: + return 1 + else: + return max(0, int(regex_match.strings[1])) diff --git a/src/ShaderDirectiveParser.gd.uid b/src/ShaderDirectiveParser.gd.uid new file mode 100644 index 0000000..59f7f0e --- /dev/null +++ b/src/ShaderDirectiveParser.gd.uid @@ -0,0 +1 @@ +uid://dw8bep14j4j3w 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/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/src/VersionLabel.gd.uid b/src/VersionLabel.gd.uid new file mode 100644 index 0000000..e731c41 --- /dev/null +++ b/src/VersionLabel.gd.uid @@ -0,0 +1 @@ +uid://bh0gpu3i2p47f diff --git a/src/assets/bg.png b/src/assets/bg.png new file mode 100644 index 0000000..fb67c19 Binary files /dev/null and b/src/assets/bg.png differ diff --git a/src/assets/bg.png.import b/src/assets/bg.png.import new file mode 100644 index 0000000..e61ed86 --- /dev/null +++ b/src/assets/bg.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d2nwchyd6huob" +path="res://.godot/imported/bg.png-7c8713dd1fab321784216191fa747e53.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://src/assets/bg.png" +dest_files=["res://.godot/imported/bg.png-7c8713dd1fab321784216191fa747e53.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 diff --git a/src/assets/error.svg b/src/assets/error.svg new file mode 100644 index 0000000..af2f066 --- /dev/null +++ b/src/assets/error.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/assets/error.svg.import b/src/assets/error.svg.import new file mode 100644 index 0000000..368b3a5 --- /dev/null +++ b/src/assets/error.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://04iv1gogpuhu" +path="res://.godot/imported/error.svg-28fb29635cf59d39cabf7052619f602f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://src/assets/error.svg" +dest_files=["res://.godot/imported/error.svg-28fb29635cf59d39cabf7052619f602f.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/src/assets/icon.png similarity index 100% rename from icon.png rename to src/assets/icon.png diff --git a/icon.png.import b/src/assets/icon.png.import similarity index 73% rename from icon.png.import rename to src/assets/icon.png.import index a0840e4..d1a6196 100644 --- a/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-487276ed1e3a0c39cad0279d744ee560.ctex" +path="res://.godot/imported/icon.png-d8298ab6eda392a806be6bb7eec65b9c.ctex" metadata={ "vram_texture": false } [deps] -source_file="res://icon.png" -dest_files=["res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex"] +source_file="res://src/assets/icon.png" +dest_files=["res://.godot/imported/icon.png-d8298ab6eda392a806be6bb7eec65b9c.ctex"] [params] diff --git a/src/assets/okay.svg b/src/assets/okay.svg new file mode 100644 index 0000000..5668fe8 --- /dev/null +++ b/src/assets/okay.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + diff --git a/src/assets/okay.svg.import b/src/assets/okay.svg.import new file mode 100644 index 0000000..e1631da --- /dev/null +++ b/src/assets/okay.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://m1omb6g45vst" +path="res://.godot/imported/okay.svg-de66a022ef37753b085371b7c60aefd1.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://src/assets/okay.svg" +dest_files=["res://.godot/imported/okay.svg-de66a022ef37753b085371b7c60aefd1.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/src/presets/Presets.gd b/src/presets/Presets.gd deleted file mode 100644 index c009bd2..0000000 --- a/src/presets/Presets.gd +++ /dev/null @@ -1,14 +0,0 @@ -extends Node - -const dir = "res://src/presets/shaders/" - -@onready var presets = { - "Empty": load(dir + "empty.gdshader"), - "Greyscale": load(dir + "greyscale.gdshader"), - "Lowpass": load(dir + "lowpass.gdshader"), - "Channel Offset": load(dir + "channel_offset.gdshader"), - "RGB -> UV Distort": load(dir + "rgb_uv_distort.gdshader"), - "Mix": load(dir + "mix.gdshader") -} - -var default_preset: String = "Empty" diff --git a/src/presets/shaders/empty.gdshader b/src/presets/shaders/empty.gdshader deleted file mode 100644 index 0cb1281..0000000 --- a/src/presets/shaders/empty.gdshader +++ /dev/null @@ -1,5 +0,0 @@ -shader_type canvas_item; - -void fragment() { - // Called for every pixel the material is visible on. -} diff --git a/src/presets/shaders/lowpass.gdshader b/src/presets/shaders/lowpass.gdshader deleted file mode 100644 index 9dc44f7..0000000 --- a/src/presets/shaders/lowpass.gdshader +++ /dev/null @@ -1,13 +0,0 @@ -shader_type canvas_item; - -// Settings -const float threshold = 0.5; -// - -void fragment() { - vec4 tex = texture(TEXTURE , UV); - COLOR.r = min(tex.r, threshold); - COLOR.g = min(tex.g, threshold); - COLOR.b = min(tex.b, threshold); - COLOR.a = tex.a; -} diff --git a/src/presets/shaders/mix.gdshader b/src/presets/shaders/mix.gdshader deleted file mode 100644 index 53ed562..0000000 --- a/src/presets/shaders/mix.gdshader +++ /dev/null @@ -1,8 +0,0 @@ -shader_type canvas_item; - -//!load img2 ./icon.png -uniform sampler2D img2: repeat_enable, filter_nearest; - -void fragment() { - COLOR = mix(COLOR, texture(img2, UV), .5); -} diff --git a/src/presets/shaders/rgb_uv_distort.gdshader b/src/presets/shaders/rgb_uv_distort.gdshader deleted file mode 100644 index c07466b..0000000 --- a/src/presets/shaders/rgb_uv_distort.gdshader +++ /dev/null @@ -1,11 +0,0 @@ -shader_type canvas_item; - -const float strength = 0.1; - -void fragment() { - vec2 uv = UV; - float b = (COLOR.r + COLOR.g + COLOR.b) / 3.0; - uv.x = mix(uv.x, b, strength); - uv.y = mix(uv.y, b, strength); - COLOR = texture(TEXTURE, uv); -} diff --git a/src/scenes/main.tscn b/src/scenes/main.tscn new file mode 100644 index 0000000..14b19bd --- /dev/null +++ b/src/scenes/main.tscn @@ -0,0 +1,194 @@ +[gd_scene load_steps=13 format=3 uid="uid://bjah7k4bxo044"] + +[ext_resource type="Script" uid="uid://5sbslwysin5a" path="res://src/Main.gd" id="1_64y3g"] +[ext_resource type="Script" uid="uid://d106170kuigl3" path="res://src/ImageCompositor.gd" id="2_4ykh7"] +[ext_resource type="Shader" uid="uid://ctk7jomfyx0fh" path="res://src/shader/ivd_outline.gdshader" id="3_0fllm"] +[ext_resource type="Script" uid="uid://ctc4lhbdsoq7u" path="res://src/ImageViewportDisplay.gd" id="4_pbpx2"] +[ext_resource type="Script" uid="uid://b6r8rigubdctk" path="res://src/Camera.gd" id="5_hkdq6"] +[ext_resource type="Texture2D" uid="uid://d2nwchyd6huob" path="res://src/assets/bg.png" id="6_kokaf"] +[ext_resource type="Theme" uid="uid://cwqlns34rj3vx" path="res://src/theme.tres" id="6_rjp5f"] +[ext_resource type="Script" uid="uid://bxgmf2ny7yuc8" path="res://src/MainUI.gd" id="7_5puhk"] +[ext_resource type="Script" uid="uid://bh0gpu3i2p47f" 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 +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="CanvasLayerBg" type="CanvasLayer" parent="."] +layer = -1 + +[node name="Control" type="Control" parent="CanvasLayerBg"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="TextureRect" type="TextureRect" parent="CanvasLayerBg/Control"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("6_kokaf") +stretch_mode = 1 + +[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) +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/ivd_outline.gdshader b/src/shader/ivd_outline.gdshader new file mode 100644 index 0000000..78bfe77 --- /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(0.5), 0.5); + } +} diff --git a/src/shader/ivd_outline.gdshader.uid b/src/shader/ivd_outline.gdshader.uid new file mode 100644 index 0000000..b42ac31 --- /dev/null +++ b/src/shader/ivd_outline.gdshader.uid @@ -0,0 +1 @@ +uid://ctk7jomfyx0fh 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/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; -} 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) diff --git a/tools/get_version.gd.uid b/tools/get_version.gd.uid new file mode 100644 index 0000000..e14829d --- /dev/null +++ b/tools/get_version.gd.uid @@ -0,0 +1 @@ +uid://cdhqbascy6pvy