Compare commits

...

71 commits
v6.1 ... main

Author SHA1 Message Date
54cbddbbba
Bump version to v10.2 2025-04-09 19:18:09 +02:00
65fddc0bd8
Update README regarding STEPS uniform 2025-04-09 19:09:32 +02:00
ebc8ce2cb9
Add STEPS uniform to get the number of steps programmatically 2025-04-09 19:04:11 +02:00
c9c5428dbc
shaderlib: Add rotateUV function to common.gdshader 2025-04-09 19:00:02 +02:00
4d0f7ca538
Bump version to v10.1 2025-04-01 21:42:38 +02:00
3dcc4ca02c
shaderlib: Add multipass pixelsorting 2025-04-01 21:37:32 +02:00
bf1c1327ac
Add better documentation of value ranges in oklab and oklch 2025-03-31 21:24:56 +02:00
560e455cd2
Disable VSync while ImageCompositor.update processes the image 2025-03-31 21:24:07 +02:00
77273f9c1b
Add sobel filter (edge detection) 2025-03-31 15:41:31 +02:00
9d23efea63
Fix: Show and align image viewport before first step 2025-03-31 15:39:36 +02:00
4278cde44b
Disable low_processor_mode in the settings to improve multistep performance 2025-03-31 15:37:55 +02:00
f940061135
Declare shaderlib as stable, bump version to v10.0 2025-03-15 20:45:30 +01:00
4546b9eac0
fix: re-enable buttons after image export 2025-03-15 20:40:11 +01:00
e7bb3c8f16
Update example project and export_presets.cfg for Godot 4.4 2025-03-15 18:16:02 +01:00
46829e72e9
Bump version to v9.1 2025-03-13 21:55:57 +01:00
df85ceb469
Update screenshot 2025-03-13 21:55:27 +01:00
b6f6c3d27e
Change background and fix image viewport outline 2025-03-13 21:41:01 +01:00
9b1efc1513
Update to Godot 4.4 2025-03-13 20:57:31 +01:00
9192e020e3
Update screenshot 2025-02-06 23:39:58 +01:00
f32d10dbaa
Update README 2025-02-06 23:29:10 +01:00
c5c9b9f516
Merge pull request #53 from ChaoticByte/release/v9.0
Release/v9.0
2025-02-06 23:02:03 +01:00
b916f43f8b
Fix shader include relative include by using ResourceLoader load() instead of loading the text from the file - fixes #56 2025-02-06 19:54:20 +01:00
ed10da0604
Fix res:// import in shaderlib/oklab.gdshaderinc 2025-02-05 23:52:32 +01:00
89b82695ac
Added empty scene to the project temlate/examples folder so that Godot shuts up about no scene being opened currently 2025-02-05 18:06:08 +01:00
f7aac231ae
Delete .gitkeep in dist folder, create it on demand 2025-02-05 17:50:41 +01:00
cc59ba9b9e
Fix missing STEP variable by making it mandatory to be defined as a uniform in the shader file 2025-02-05 17:48:49 +01:00
7ad9ef002a
Update README 2025-02-04 19:05:20 +01:00
6a7dc164fe
Revert "CLI dump-shaderlib: Update help message"
This reverts commit d29f14c7c9.
2025-02-04 19:04:10 +01:00
ddba85d17c
Revert "Add a method to dump the shaderlib to the local filesystem - implements #47"
This reverts commit a4a1de4bfe.
2025-02-04 19:00:31 +01:00
da1bd6a58a
Don't include the shaderlib in the application anymore, instead distribute a zip file with each release 2025-02-04 18:59:57 +01:00
1ee3088cce
Update screenshot 2025-02-03 20:40:46 +01:00
6bb6b8b768
Add dist.sh script to automatically build and create project template, Update README 2025-02-03 20:30:27 +01:00
5a8a6e4c61
Fixed commandline: apply 2025-02-03 19:25:48 +01:00
9f5011aaac
Removed all editor functionality and improved new viewer-only UI #54 2025-02-03 17:40:56 +01:00
e40ab18683
Update README regarding commandline interface 2025-01-31 20:27:14 +01:00
d29f14c7c9
CLI dump-shaderlib: Update help message 2025-01-31 20:24:40 +01:00
a4a1de4bfe
Add a method to dump the shaderlib to the local filesystem - implements #47 2025-01-31 20:20:35 +01:00
168cb036be
Clean up Main.gd, improve CLI and rename 'cli' command to 'apply' - implements #48 2025-01-31 19:51:30 +01:00
fee1c64775
Merge pull request #52 from ChaoticByte/release/v8.2
Release/v8.2
2025-01-28 21:46:52 +01:00
60332eadaf
Bump version to 8.2 2025-01-28 21:44:33 +01:00
d376801058
Give credits for example image mountain.jpg 2025-01-27 23:13:21 +01:00
632b4593aa
Add Kuwahara Filter, implements #49 2025-01-27 23:09:07 +01:00
d512005b86
Merge pull request #51 from ChaoticByte/release/v8.1
Release/v8.1
2025-01-27 22:59:37 +01:00
a444cea11a
Bump version to v8.1 2025-01-27 22:58:18 +01:00
f8cfbf91c9
Fixed out-of-date shaderlib autocomplete 2025-01-27 22:57:01 +01:00
b3044f64a6
Merge pull request #46 from ChaoticByte/release/v8.0
Release/v8.0
2025-01-27 18:19:16 +01:00
c0d9e00fe9
Bump version to 8.0 2025-01-27 18:17:01 +01:00
7810a1fa83
cli: added support for processing whole directories (batch mode) - this implements #40; changed build template to support more image formats 2025-01-26 21:38:16 +01:00
40374bd849
Restructured shaderlib by moving functions and renaming files 2025-01-24 22:16:45 +01:00
c70eaed0c4
Readme: Improved the screenshot subtitle and Usage section, added a link to the Godot Shader documentation - fixes #44 2025-01-24 21:56:37 +01:00
acdb52c91e
Readme: add a toc, add a Known Issues section and add a note about screen scaling not being supported (issue #45) 2025-01-24 21:44:41 +01:00
dc325d3e77
Add note about shaderlib API stability 2025-01-22 07:19:56 +01:00
701efcaed6
Merge pull request #43 from ChaoticByte/release/v7.0
Release/v7.0
2025-01-19 13:00:29 +01:00
6419ab1c3c
Bump version to 7.0 and update screenshot 2025-01-19 12:57:31 +01:00
0fad7cb575
Implement a commandline interface that can be used in scripts - implements #42 2025-01-19 12:51:10 +01:00
1a21589fc1
Implement oklab, oklch color space conversion functions, add example, restructure comments in the shaderlib, implements #37 2025-01-17 16:51:17 +01:00
35959290d3
Update LICENSE (fix name) 2025-01-17 15:37:57 +01:00
88e85c19f5
Switch to BSD-3-Clause lincense, closes #41 2025-01-17 15:35:36 +01:00
d08329c750
shaderlib: Implement gaussian_blur(); implements #39 2025-01-11 15:59:58 +01:00
85d33d5d77
Refactored Main/%Compositor into a standalone ImageCompositor class; implements #38 2025-01-10 22:18:43 +01:00
3b47d57353
shaderlib: Remove hsv_multiply and hsv_offset, rename hsv.gdshaderinc to colorspaces.gdshaderinc, update autocomplete and example accordingly; implements #36 2025-01-10 21:58:37 +01:00
50ddf6e6d3
Merge pull request #35 from ChaoticByte/release/v6.2
Release/v6.2
2025-01-10 20:58:28 +01:00
a7da554320
Bump version to 6.2 2025-01-10 20:57:04 +01:00
9b6d5d0a78
shaderlib: Add smart_denoise function, add example, move example images into separate folder; implements #29 2025-01-08 20:53:45 +01:00
7b72274140
Don't try to load the shader code if the file couldn't be opened 2025-01-08 19:43:27 +01:00
4d12ad4432
shaderlib: Implement a simple pixelate function (effects.gdshaderinc), examples/hsv.gdshader -> color_and_pixelate.gdshader; implements #30 2025-01-08 19:41:03 +01:00
6c48c9fe72
shaderlib: Shorten lowpass filter example 2025-01-07 22:50:15 +01:00
974c40fcb4
Remember last opened file and open it on start, implements #33 2025-01-07 22:46:13 +01:00
ec7544cb3b
Add autocomplete support for preprocessor directives, implements #34 2025-01-07 22:19:08 +01:00
71394edbf1
Disable Export Button during Composite applying shader, implements #31 2025-01-07 21:58:20 +01:00
fd16d2a8df
Assign ShaderMaterial outside of loop 2025-01-07 21:53:15 +01:00
102 changed files with 1530 additions and 758 deletions

1
.gitignore vendored
View file

@ -16,7 +16,6 @@ mono_crash.*.json
*.x86_64
godot.*.template_release.*
dist/*
!dist/.gitkeep
screenshot.png.import

41
LICENSE
View file

@ -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.

View file

@ -3,7 +3,15 @@
![screenshot](./screenshot.png)
<p align=center>Create image filters by writing shaders.</p>
<p align=center>An image editing/compositing software for graphics programmers.</p>
## Table of Contents
- [Supported Platforms](#supported-platforms)
- [Usage](#usage)
- [Shaderlib](#shaderlib)
- [Commandline interface](#commandline-interface)
- [Known Issues](#known-issues)
## Supported Platforms
@ -13,8 +21,16 @@ You can find the latest releases [here](https://github.com/ChaoticByte/Fragmente
## Usage
The repo includes examples. You can use them as a starting-point to write your own filters.
Just load an image using `//!load`, edit the shader 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/).
**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
@ -46,13 +62,17 @@ Example:
//!load ...
//!steps 5
uniform int STEP;
uniform int STEPS;
void fragment() {
if (STEP == 0) {
...
...
} else if (STEP == 1) {
...
...
} else if (STEP == STEPS-1) {
...
}
// ... and so on
}
```
@ -66,11 +86,71 @@ Here is an example:
```glsl
shader_type canvas_item;
#include "res://shaderlib/hsv.gdshaderinc"
#include "./shaderlib/oklab.gdshaderinc"
//!load ./swamp.jpg
//!load ./images/swamp.jpg
void fragment() {
COLOR = hsv_offset(COLOR, 0.32, 0.2, 0.0);
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 <command> <args...>
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

View file

@ -7,15 +7,14 @@ FROM docker.io/ubuntu:focal AS os-base
# https://docs.godotengine.org/en/stable/contributing/development/compiling/compiling_for_linuxbsd.html
RUN apt-get update
RUN DEBIAN_FRONTEND=noninteractive apt-get install -yq build-essential scons pkg-config libx11-dev libxcursor-dev libxinerama-dev libgl1-mesa-dev libglu1-mesa-dev libasound2-dev libpulse-dev libudev-dev libxi-dev libxrandr-dev libwayland-dev
RUN DEBIAN_FRONTEND=noninteractive apt-get install -yq git
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.3-stable /godot-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_dds_enabled=no module_enet_enabled=no module_gridmap_enabled=no module_hdr_enabled=no module_jsonrpc_enabled=no module_ktx_enabled=no module_mbedtls_enabled=no module_meshoptimizer_enabled=no module_minimp3_enabled=no module_mobile_vr_enabled=no module_msdfgen_enabled=no module_multiplayer_enabled=no module_navigation_enabled=no module_ogg_enabled=no module_openxr_enabled=no module_raycast_enabled=no module_squish_enabled=no module_svg_enabled=no module_tga_enabled=no module_theora_enabled=no module_tinyexr_enabled=no module_upnp_enabled=no module_vhacd_enabled=no module_vorbis_enabled=no module_webrtc_enabled=no module_websocket_enabled=no module_webxr_enabled=no module_zip_enabled=no arch=x86_64 && strip bin/godot.linuxbsd.template_release.x86_64
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

View file

@ -3,12 +3,12 @@
set -e
function log {
echo -e "\033[1;36m* $@\033[0m"
echo -e "\033[1;36m***** $@ *****\033[0m"
}
log
log " "
log "Fragmented - Godot Build Template Builder"
log
log " "
cd $(dirname $0)
log Switched to $(pwd)

30
dist.sh Executable file
View file

@ -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}" *
)

0
dist/.gitkeep vendored
View file

3
examples/0_empty.tscn Normal file
View file

@ -0,0 +1,3 @@
[gd_scene format=3 uid="uid://db2rhq8rwv5wo"]
[node name="Node" type="Node"]

8
examples/blur.gdshader Normal file
View file

@ -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);
}

View file

@ -0,0 +1 @@
uid://cny8dtukv54wt

View file

@ -1,6 +1,6 @@
shader_type canvas_item;
//!load ./swamp.jpg
//!load ./images/swamp.jpg
const vec2 offset_r = vec2(-0.002, -0.002);
const vec2 offset_g = vec2(0., 0.);

View file

@ -0,0 +1 @@
uid://0efk4fornlg6

View file

@ -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);
}

View file

@ -0,0 +1 @@
uid://gd23hu7ro148

View file

@ -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);
}

View file

@ -0,0 +1 @@
uid://cbwyneu03fki6

View file

@ -1,6 +1,6 @@
shader_type canvas_item;
//!load ./swamp.jpg
//!load ./images/swamp.jpg
void fragment() {
float b = (COLOR.r + COLOR.g + COLOR.b) / 3.0;

View file

@ -0,0 +1 @@
uid://dvarqolt6es27

View file

@ -1,9 +0,0 @@
shader_type canvas_item;
#include "res://shaderlib/hsv.gdshaderinc"
//!load ./swamp.jpg
void fragment() {
COLOR = hsv_multiply(hsv_offset(COLOR, -0.3, 0.9, 0.0), 1.0, 0.44, 1.0);
}

View file

@ -3,3 +3,4 @@
- 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)

View file

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Before After
Before After

View file

@ -3,15 +3,15 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://c1mh1d2f3u4ju"
path="res://.godot/imported/grass.png-30af7213ed2d70b810b4ae788314a9e9.ctex"
path="res://.godot/imported/grass.png-61a458998da568ce60ccb8a0c7caaf6d.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://examples/grass.png"
dest_files=["res://.godot/imported/grass.png-30af7213ed2d70b810b4ae788314a9e9.ctex"]
source_file="res://examples/images/grass.png"
dest_files=["res://.godot/imported/grass.png-61a458998da568ce60ccb8a0c7caaf6d.ctex"]
[params]

Binary file not shown.

After

Width:  |  Height:  |  Size: 814 KiB

View file

@ -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

BIN
examples/images/noisy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

View file

@ -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

View file

Before

Width:  |  Height:  |  Size: 477 KiB

After

Width:  |  Height:  |  Size: 477 KiB

Before After
Before After

View file

@ -3,15 +3,15 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://ckjb0agn5btv7"
path="res://.godot/imported/swamp.jpg-8e3eac7e7aacce65638e712310cdb35c.ctex"
path="res://.godot/imported/swamp.jpg-1dfdcd52a5ef03d42a82a7f06acefa98.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://examples/swamp.jpg"
dest_files=["res://.godot/imported/swamp.jpg-8e3eac7e7aacce65638e712310cdb35c.ctex"]
source_file="res://examples/images/swamp.jpg"
dest_files=["res://.godot/imported/swamp.jpg-1dfdcd52a5ef03d42a82a7f06acefa98.ctex"]
[params]

View file

@ -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);
}

View file

@ -0,0 +1 @@
uid://cdhyuk7u8kxyk

View file

@ -1,15 +1,13 @@
shader_type canvas_item;
//!load ./swamp.jpg
//!load ./images/swamp.jpg
// Settings
const float threshold = 0.5;
const float threshold = 0.6;
//
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.rgb = min(tex.rgb, vec3(threshold));
COLOR.a = tex.a;
}

View file

@ -0,0 +1 @@
uid://dn02xsjm1kok8

View file

@ -1,7 +1,9 @@
shader_type canvas_item;
//!steps 9
//!load ./swamp.jpg
//!load ./images/swamp.jpg
uniform int STEP;
const float strength = 0.01;

View file

@ -0,0 +1 @@
uid://c17u5jx7a7o81

View file

@ -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);
}

View file

@ -0,0 +1 @@
uid://csk0fg4by651b

12
examples/oklab.gdshader Normal file
View file

@ -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));
}

View file

@ -0,0 +1 @@
uid://cu37y8lc0x83

View file

@ -1,10 +1,10 @@
shader_type canvas_item;
#include "res://shaderlib/transform.gdshaderinc"
#include "res://shaderlib/transparency.gdshaderinc"
#include "./shaderlib/place_texture.gdshaderinc"
#include "./shaderlib/common.gdshaderinc"
//!load ./swamp.jpg
//!load+ img2 ./grass.png
//!load ./images/swamp.jpg
//!load+ img2 ./images/grass.png
uniform sampler2D img2: repeat_disable, filter_nearest;

View file

@ -0,0 +1 @@
uid://dybe4t5rbbkc6

15
examples/project.godot_ Normal file
View file

@ -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")

1
examples/shaderlib Symbolic link
View file

@ -0,0 +1 @@
../shaderlib

10
examples/sobel.gdshader Normal file
View file

@ -0,0 +1,10 @@
shader_type canvas_item;
//!load ./images/noisy.png
#include "./shaderlib/sobel.gdshaderinc"
void fragment() {
// Sobel Filter
COLOR = sobel(TEXTURE, UV);
}

View file

@ -0,0 +1 @@
uid://h376mk1fq4ky

View file

@ -8,10 +8,12 @@ dedicated_server=false
custom_features=""
export_filter="all_resources"
include_filter=""
exclude_filter="screenshot.png, examples/*"
exclude_filter="screenshot.png, examples/*, shaderlib/*, tools/*, build-template/*"
export_path="dist/Fragmented.x86_64"
patches=PackedStringArray()
encryption_include_filters=""
encryption_exclude_filters=""
seed=0
encrypt_pck=false
encrypt_directory=false
script_export_mode=2

View file

@ -11,11 +11,10 @@ config_version=5
[application]
config/name="Fragmented"
config/version="v6.1"
run/main_scene="res://scenes/main.tscn"
config/features=PackedStringArray("4.3", "Mobile")
run/low_processor_mode=true
config/icon="res://assets/icon.png"
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]
@ -24,11 +23,10 @@ ShaderDirectiveParser="*res://src/ShaderDirectiveParser.gd"
[display]
window/size/viewport_width=704
window/size/viewport_height=704
window/size/viewport_width=640
window/size/viewport_height=672
window/energy_saving/keep_screen_on=false
window/subwindows/embed_subwindows=false
window/vsync/vsync_mode=0
[editor_plugins]
@ -72,4 +70,4 @@ renderer/rendering_method="mobile"
textures/vram_compression/import_etc2_astc=true
textures/lossless_compression/force_png=true
shader_compiler/shader_cache/enabled=false
environment/defaults/default_clear_color=Color(0, 0, 0, 1)
environment/defaults/default_clear_color=Color(0.501961, 0.501961, 0.501961, 1)

View file

@ -1,52 +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/Compositor.gd" id="2_hvo65"]
[ext_resource type="Shader" path="res://src/shader/ivd_outline.gdshader" id="3_6xihe"]
[ext_resource type="Script" path="res://src/ImageViewportDisplay.gd" id="3_n4itb"]
[ext_resource type="Script" path="res://src/UIWindow.gd" id="6_8k0ha"]
[ext_resource type="PackedScene" uid="uid://btgits2mfup0h" path="res://scenes/ui_container.tscn" id="7_5ci0e"]
[ext_resource type="Script" path="res://src/Camera.gd" id="8_mls06"]
[sub_resource type="ShaderMaterial" id="ShaderMaterial_y2ea0"]
shader = ExtResource("3_6xihe")
shader_parameter/zoom_level = Vector2(1, 1)
[sub_resource type="ViewportTexture" id="ViewportTexture_lct1c"]
viewport_path = NodePath("Compositor")
[node name="Main" type="Node2D"]
script = ExtResource("1_2625y")
[node name="Compositor" type="SubViewport" parent="."]
unique_name_in_owner = true
disable_3d = true
transparent_bg = true
canvas_item_default_texture_filter = 0
render_target_update_mode = 4
script = ExtResource("2_hvo65")
[node name="ImageSprite" type="Sprite2D" parent="Compositor"]
unique_name_in_owner = true
[node name="ImageViewportDisplay" type="Sprite2D" parent="."]
unique_name_in_owner = true
material = SubResource("ShaderMaterial_y2ea0")
texture = SubResource("ViewportTexture_lct1c")
script = ExtResource("3_n4itb")
[node name="Camera" type="Camera2D" parent="."]
unique_name_in_owner = true
script = ExtResource("8_mls06")
[node name="EditorWindow" type="Window" parent="."]
unique_name_in_owner = true
disable_3d = true
position = Vector2i(48, 36)
size = Vector2i(704, 704)
script = ExtResource("6_8k0ha")
[node name="UserInterfaceContainer" parent="EditorWindow" instance=ExtResource("7_5ci0e")]
unique_name_in_owner = true
[connection signal="close_requested" from="EditorWindow" to="EditorWindow" method="_on_close_requested"]

View file

@ -1,202 +0,0 @@
[gd_scene load_steps=3 format=3 uid="uid://btgits2mfup0h"]
[ext_resource type="Script" path="res://src/UIAppVersion.gd" id="1_5qvnb"]
[ext_resource type="Script" path="res://src/Editor.gd" id="2_haub5"]
[node name="UserInterfaceContainer" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="AppName" type="Label" parent="."]
layout_mode = 0
offset_left = 24.0
offset_top = 16.0
offset_right = 208.0
offset_bottom = 48.0
theme_override_font_sizes/font_size = 20
text = "Fragmented"
vertical_alignment = 2
[node name="AppVersion" type="Label" parent="."]
layout_mode = 0
offset_left = 152.0
offset_top = 17.0
offset_right = 208.0
offset_bottom = 47.0
theme_override_font_sizes/font_size = 14
text = "v0
"
vertical_alignment = 2
script = ExtResource("1_5qvnb")
[node name="Editor" type="Control" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 24.0
offset_top = 64.0
offset_right = -24.0
offset_bottom = -16.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("2_haub5")
[node name="OpenShaderDialog" type="FileDialog" parent="Editor"]
unique_name_in_owner = true
auto_translate_mode = 1
title = "Load Shader"
size = Vector2i(521, 175)
ok_button_text = "Open"
mode_overrides_title = false
file_mode = 0
access = 2
filters = PackedStringArray("*.gdshader")
use_native_dialog = true
[node name="SaveShaderDialog" type="FileDialog" parent="Editor"]
unique_name_in_owner = true
auto_translate_mode = 1
title = "Save Shader"
size = Vector2i(661, 175)
ok_button_text = "Save"
mode_overrides_title = false
access = 2
filters = PackedStringArray("*.gdshader")
use_native_dialog = true
[node name="SaveImageDialog" type="FileDialog" parent="Editor"]
unique_name_in_owner = true
auto_translate_mode = 1
title = "Export Image"
size = Vector2i(661, 175)
ok_button_text = "Save"
mode_overrides_title = false
access = 2
filters = PackedStringArray("*.png")
use_native_dialog = true
[node name="CodeEdit" type="CodeEdit" parent="Editor"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_top = 48.0
grow_horizontal = 2
grow_vertical = 2
theme_override_font_sizes/font_size = 14
placeholder_text = "// Test"
wrap_mode = 1
minimap_draw = true
minimap_width = 40
caret_blink = true
draw_control_chars = true
draw_tabs = true
draw_spaces = true
line_length_guidelines = Array[int]([80])
gutters_draw_line_numbers = true
code_completion_enabled = true
indent_automatic = true
auto_brace_completion_enabled = true
auto_brace_completion_highlight_matching = true
[node name="NewShaderButton" type="Button" parent="Editor"]
layout_mode = 1
offset_right = 48.0
offset_bottom = 32.0
text = "New"
[node name="OpenShaderButton" type="Button" parent="Editor"]
layout_mode = 1
offset_left = 56.0
offset_right = 112.0
offset_bottom = 32.0
text = "Open"
[node name="SaveShaderButton" type="Button" parent="Editor"]
layout_mode = 1
offset_left = 120.0
offset_top = -1.0
offset_right = 176.0
offset_bottom = 31.0
text = "Save"
[node name="SaveShaderAsButton" type="Button" parent="Editor"]
layout_mode = 1
offset_left = 184.0
offset_right = 263.0
offset_bottom = 32.0
text = "Save As"
[node name="SaveImageButton" type="Button" parent="Editor"]
layout_mode = 1
anchors_preset = 1
anchor_left = 1.0
anchor_right = 1.0
offset_left = -72.0
offset_bottom = 32.0
grow_horizontal = 0
text = "Export"
[node name="FitImageButton" type="Button" parent="Editor"]
layout_mode = 1
anchors_preset = 1
anchor_left = 1.0
anchor_right = 1.0
offset_left = -128.0
offset_right = -80.0
offset_bottom = 32.0
grow_horizontal = 0
text = "Fit"
[node name="ApplyShaderButton" type="Button" parent="Editor"]
layout_mode = 1
anchors_preset = 1
anchor_left = 1.0
anchor_right = 1.0
offset_left = -232.0
offset_right = -136.0
offset_bottom = 31.0
grow_horizontal = 0
text = "Apply (F5)"
[node name="StatusIndicator" type="TextureButton" parent="Editor"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 1
anchor_left = 1.0
anchor_right = 1.0
offset_left = -76.0
offset_top = 56.0
offset_right = -56.0
offset_bottom = 76.0
grow_horizontal = 0
disabled = true
ignore_texture_size = true
stretch_mode = 0
[node name="ErrorMessageDialog" type="AcceptDialog" parent="Editor"]
unique_name_in_owner = true
title = "Status"
initial_position = 2
size = Vector2i(256, 128)
popup_window = true
ok_button_text = "Close"
[connection signal="file_selected" from="Editor/OpenShaderDialog" to="Editor" method="_on_open_shader_dialog_file_selected"]
[connection signal="file_selected" from="Editor/SaveShaderDialog" to="Editor" method="_on_save_shader_dialog_file_selected"]
[connection signal="file_selected" from="Editor/SaveImageDialog" to="Editor" method="_on_save_image_dialog_file_selected"]
[connection signal="code_completion_requested" from="Editor/CodeEdit" to="Editor" method="_on_code_edit_code_completion_requested"]
[connection signal="pressed" from="Editor/NewShaderButton" to="Editor" method="_on_new_shader_button_pressed"]
[connection signal="pressed" from="Editor/OpenShaderButton" to="Editor" method="_on_open_shader_button_pressed"]
[connection signal="pressed" from="Editor/SaveShaderButton" to="Editor" method="_on_save_shader_button_pressed"]
[connection signal="pressed" from="Editor/SaveShaderAsButton" to="Editor" method="_on_save_shader_as_button_pressed"]
[connection signal="pressed" from="Editor/SaveImageButton" to="Editor" method="_on_save_image_button_pressed"]
[connection signal="pressed" from="Editor/FitImageButton" to="Editor" method="_on_fit_image_button_pressed"]
[connection signal="pressed" from="Editor/ApplyShaderButton" to="Editor" method="_on_apply_shader_button_pressed"]
[connection signal="pressed" from="Editor/StatusIndicator" to="Editor" method="_on_status_indicator_pressed"]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 635 KiB

After

Width:  |  Height:  |  Size: 502 KiB

Before After
Before After

View file

@ -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;
}

View file

@ -0,0 +1 @@
uid://bjtljvcjcu6dr

View file

@ -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);
}

View file

@ -0,0 +1 @@
uid://764b6ekchgb8

View file

@ -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;
}

View file

@ -0,0 +1 @@
uid://b7ksfifyyfcip

View file

@ -1,7 +1,12 @@
// rgb2hsv and hsv2rgb functions adapted
// from https://godotshaders.com/shader/hsv-adjustment/
// original code by https://godotshaders.com/author/al1-ce/
/*
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) {
@ -20,31 +25,3 @@ vec4 hsv2rgb(vec4 c) {
vec3 rgb = c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
return vec4(rgb.r, rgb.g, rgb.b, c.a);
}
// Offset the hue, saturation and brightness of a RGB color
vec4 hsv_offset(
vec4 rgba,
float offset_hue,
float offset_saturation,
float offset_brightness
) {
vec4 c = rgb2hsv(rgba);
c.x += offset_hue;
c.y += offset_saturation;
c.z += offset_brightness;
return hsv2rgb(c);
}
// Multiply the hue, saturation and brightness of a RGB color
vec4 hsv_multiply(
vec4 rgba,
float mult_hue,
float mult_saturation,
float mult_brightness
) {
vec4 c = rgb2hsv(rgba);
c.x *= mult_hue;
c.y *= mult_saturation;
c.z *= mult_brightness;
return hsv2rgb(c);
}

View file

@ -0,0 +1 @@
uid://bbr3tq6mp5qa2

View file

@ -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;
}

View file

@ -0,0 +1 @@
uid://chqh2cni1qiuu

View file

@ -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
);
}

View file

@ -0,0 +1 @@
uid://ckw4nfslk4m6l

View file

@ -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);
}

View file

@ -0,0 +1 @@
uid://dpu5nneo5bgnq

View file

@ -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; }
}
}
}

View file

@ -0,0 +1 @@
uid://doefnwk3vyr0o

View file

@ -1,6 +1,8 @@
// Load in a texture from a sampler2D with an offset and scale
// See examples/place_texture.gdshader
/*
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

View file

@ -0,0 +1 @@
uid://51u2hjq62e5i

View file

@ -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);
}

View file

@ -0,0 +1 @@
uid://bqo1fpunnl05f

View file

@ -1,8 +0,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);
}

View file

@ -1,9 +1,9 @@
extends Camera2D
var drag = false
@onready var image_viewport_display = %ImageViewportDisplay
var drag = false
func _input(event):
if event.is_action_pressed("zoom_out"):
zoom_out()
@ -16,31 +16,35 @@ func _input(event):
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 Filesystem.original_image != null:
var image_size = Filesystem.original_image.get_size()
var viewport_size = get_viewport_rect().size
var zoomf = viewport_size.x / image_size.x / 1.1
if zoomf * image_size.y > viewport_size.y:
zoomf = viewport_size.y / image_size.y / 1.1
var zoomf = 1.0
if viewport_size.x / image_size.x * image_size.y > viewport_size.y:
zoomf = viewport_size.y / image_size.y / 1.25
else:
zoomf = viewport_size.x / image_size.x / 1.2
self.zoom = Vector2(zoomf, zoomf)
self.global_position = Vector2(0, 0)
func update_vd_zoomlevel():
image_viewport_display.update_zoom_texture_filter(self.zoom)
image_viewport_display.material.set_shader_parameter("zoom_level", self.zoom)
func zoom_in():
var old_mouse_pos = get_global_mouse_position()
self.zoom *= 1.2
self.global_position += old_mouse_pos - get_global_mouse_position()
update_vd_zoomlevel()
func zoom_out():
var old_mouse_pos = get_global_mouse_position()
self.zoom *= 1/1.2
self.global_position += old_mouse_pos - get_global_mouse_position()
update_vd_zoomlevel()
func _on_fit_image_button_pressed():
fit_image()

1
src/Camera.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://b6r8rigubdctk

View file

@ -1,290 +0,0 @@
extends Control
@onready var code_editor = %CodeEdit
@onready var open_shader_dialog = %OpenShaderDialog
@onready var save_shader_dialog = %SaveShaderDialog
@onready var ui_control_filesave = %SaveImageDialog
@onready var status_indicator = %StatusIndicator
@onready var error_msg_dialog = %ErrorMessageDialog
@onready var main = get_tree().root.get_node("Main")
@onready var compositor = main.get_node("%Compositor")
@onready var camera = main.get_node("%Camera")
#
var status_okay_texture: CompressedTexture2D = preload("uid://m1omb6g45vst")
var status_error_texture: CompressedTexture2D = preload("uid://04iv1gogpuhu")
# # # # # # # # # # #
# GDShader keywords #
# https://github.com/godotengine/godot/blob/e96ad5af98547df71b50c4c4695ac348638113e0/servers/rendering/shader_language.cpp
# https://github.com/godotengine/godot/blob/e96ad5af98547df71b50c4c4695ac348638113e0/servers/rendering/shader_types.cpp
#
const gdshader_boolean_values = [
"true", "false"
]
const gdshader_datatypes = [
"void",
"bool", "bvec2", "bvec3", "bvec4",
"int", "ivec2", "ivec3", "ivec4",
"uint", "uvec2", "uvec3", "uvec4",
"float", "vec2", "vec3", "vec4",
"mat2", "mat3", "mat4",
"sampler2D", "isampler2D", "usampler2D",
"sampler2DArray", "isampler2DArray", "usampler2DArray",
]
const gdshader_precision_modifiers = [
"lowp", "mediump", "heighp"
]
const gdshader_global_space_keywords = [
"uniform", "group_uniforms", "varying", "const",
"struct", "shader_type", "render_mode"
]
const gdshader_uniform_qualifiers = [
"instance", "global"
]
const gdshader_block_keywords = [
"if", "else",
"for", "while", "do",
"switch", "case",
"default", "break", "continue",
"return", "discard"
]
const gdshader_function_specifier_keywords = [
"in", "out", "inout"
]
const gdshader_hints = [
"source_color", "hint_range", "instance_index"
]
const gdshader_sampler_hints = [
"hint_normal",
"hint_default_white", "hint_default_black", "hint_default_transparent",
"hint_anisotropy",
"hint_roughness_r", "hint_roughness_g", "hint_roughness_b", "hint_roughness_a",
"hint_roughness_normal", "hint_roughness_gray",
"hint_screen_texture", "hint_normal_roughness_texture",
"hint_depth_texture",
"filter_nearest", "filter_linear",
"filter_nearest_mipmap", "filter_linear_mipmap",
"filter_nearest_mipmap_anisotropic", "filter_linear_mipmap_anisotropic",
"repeat_enable", "repeat_disable"
]
const gdshader_builtin_functions = [
"radians", "degrees",
"sin", "cos", "tan", "asin", "acos", "atan", "sinh", "cosh", "tanh",
"asinh", "acosh", "atanh",
"pow", "exp", "exp2", "log", "log2", "sqrt", "inversesqrt",
"abs", "sign", "floor", "trunc", "round", "roundEven", "ceil", "fract",
"mod", "modf", "min", "max", "clamp",
"mix", "step", "smoothstep",
"isnan", "isinf",
"floatBitsToInt", "floatBitsToUint", "intBitsToFloat", "uintBitsToFloat",
"length", "distance",
"dot", "cross",
"normalize", "reflect", "refract", "faceforward",
"matrixCompMult", "outerProduct", "transpose",
"determinant", "inverse",
"lessThan", "greaterThan", "lessThanEqual", "greaterThanEqual",
"equal", "notEqual",
"any", "all", "not",
"textureSize", "texture", "textureProj", "textureLod", "texelFetch",
"textureProjLod", "textureGrad", "textureProjGrad", "textureGather",
"textureQueryLod", "textureQueryLevels",
"dFdx", "dFdxCoarse", "dFdxFine", "dFdy", "dFdyCoarse", "dFdyFine",
"fwidth", "fwidthCoarse", "fwidthFine"
]
const gdshader_sub_functions = [
"length", "fma",
"packHalf2x16", "packUnorm2x16", "packSnorm2x16", "packUnorm4x8", "packSnorm4x8",
"unpackHalf2x16", "unpackUnorm2x16", "unpackSnorm2x16", "unpackUnorm4x8", "unpackSnorm4x8",
"bitfieldExtract", "bitfieldInsert", "bitfieldReverse", "bitCount",
"findLSB", "findMSB",
"umulExtended", "imulExtended",
"uaddCarry", "usubBorrow",
"ldexp", "frexp"
]
const gdshader_builtins = [
"TIME", "PI", "TAU", "E",
"VERTEX",
"UV",
"COLOR",
"POINT_SIZE",
"AT_LIGHT_PASS",
"TEXTURE_PIXEL_SIZE",
"SHADOW_VERTEX", "LIGHT_VERTEX",
"FRAGCOORD",
"NORMAL", "NORMAL_MAP", "NORMAL_MAP_DEPTH",
"TEXTURE",
"POINT_COORD",
"SPECULAR_SHININESS"
]
# shaderlib
var shaderlib_regex = {
"hsv": RegEx.create_from_string(r'\s*\#include\s+\"res\:\/\/shaderlib\/hsv\.gdshaderinc\"'),
"transform": RegEx.create_from_string(r'\s*\#include\s+\"res\:\/\/shaderlib\/transform\.gdshaderinc\"'),
"transparency": RegEx.create_from_string(r'\s*\#include\s+\"res\:\/\/shaderlib\/transparency\.gdshaderinc\"')
}
const shaderlib_functions = {
"hsv": ["rgb2hsv", "hsv2rgb", "hsv_offset", "hsv_multiply"],
"transform": ["place_texture"],
"transparency": ["alpha_blend"],
}
#
# configure Highlighter
#
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)
# shaderlib #
var shader_code = code_editor.text
for key in shaderlib_regex:
if shaderlib_regex[key].search(shader_code) != null:
if key in shaderlib_functions:
for k in shaderlib_functions[key]:
code_editor.code_completion_prefixes.append(k)
code_editor.add_code_completion_option(CodeEdit.KIND_FUNCTION, k, k+"(", Color.INDIAN_RED)
# # # # # # #
code_editor.update_code_completion_options(true)
#
# # # # # # # # # # # #
func _ready():
code_editor.code_completion_enabled = true
code_editor.syntax_highlighter = ShaderSyntaxHighlighter.new()
self.update_code_edit()
func _input(event):
if event.is_action_pressed("apply_shader"):
_on_apply_shader_button_pressed()
elif event.is_action_pressed("save_shader"):
accept_event() # Event is now handled.
_on_save_shader_button_pressed()
func update_code_edit():
code_editor.text = Filesystem.shader_code
enum Status {OKAY, ERROR, UNKNOWN = -1}
func update_status(status: Status, msg: String = ""):
error_msg_dialog.dialog_text = msg
error_msg_dialog.reset_size()
if status == Status.OKAY:
status_indicator.texture_normal = status_okay_texture
elif status == Status.ERROR:
status_indicator.texture_normal = status_error_texture
else:
status_indicator.texture_normal = null
if msg == "":
status_indicator.disabled = true
else:
status_indicator.disabled = false
#
func _on_new_shader_button_pressed():
main.update_title()
Filesystem.reset()
self.update_code_edit()
compositor.update()
update_status(Status.UNKNOWN)
func _on_open_shader_button_pressed():
open_shader_dialog.show()
func _on_save_shader_button_pressed():
Filesystem.shader_code = code_editor.text
if Filesystem.last_shader_savepath == "":
_on_save_shader_as_button_pressed()
else:
_on_save_shader_dialog_file_selected(Filesystem.last_shader_savepath)
func _on_save_shader_as_button_pressed() -> void:
Filesystem.shader_code = code_editor.text
if Filesystem.last_shader_savepath == "":
save_shader_dialog.current_file = "filter.gdshader"
else:
save_shader_dialog.current_path = Filesystem.last_shader_savepath
save_shader_dialog.show()
func _on_fit_image_button_pressed():
camera.fit_image()
func _on_apply_shader_button_pressed():
Filesystem.shader_code = code_editor.text
var errors = await compositor.update()
if len(errors) > 0:
update_status(Status.ERROR, "\n".join(errors))
else:
update_status(Status.OKAY)
func _on_save_image_button_pressed():
if Filesystem.result != null:
ui_control_filesave.current_path = Filesystem.last_image_savepath
ui_control_filesave.show()
#
func _on_open_shader_dialog_file_selected(path: String):
Filesystem.load_shader(path)
main.update_title(path.split("/")[-1])
self.update_code_edit()
self._on_apply_shader_button_pressed()
func _on_save_shader_dialog_file_selected(path):
Filesystem.save_shader(path)
main.update_title(path.split("/")[-1])
func _on_save_image_dialog_file_selected(path):
Filesystem.save_result(path)
#
func _on_status_indicator_pressed() -> void:
error_msg_dialog.show()

View file

@ -1,25 +1,31 @@
extends Node
@onready var template_shader: Shader = load("res://src/shader/template.gdshader")
@onready var shader_code: String = template_shader.code
var cwd = "."
var shader_path = "":
get():
return shader_path
set(v):
var old = shader_path
shader_path = v
if "/" in v: # update current working directory
cwd = v.substr(0, v.rfind("/"))
if old != shader_path:
store_last_opened_file()
var shader: Shader:
get():
print("Load ", shader_path)
return load(shader_path)
var original_image: ImageTexture
var additional_images: Dictionary
var result: Image
var cwd = "."
var last_image_savepath = ""
var last_shader_savepath = ""
var last_original_image_path = ""
func reset():
self.shader_code = self.template_shader.code
self.last_image_savepath = ""
self.last_shader_savepath = ""
self.last_original_image_path = ""
self.original_image = null
self.result = null
func get_absolute_path(p: String) -> String:
# this only works on Linux!
if !p.begins_with("/"):
@ -27,14 +33,14 @@ func get_absolute_path(p: String) -> String:
return p
func load_original_image(path: String) -> String: # returns an error message
print("Load ", path)
var img = Image.new()
var err = img.load(path)
if err == OK:
original_image = ImageTexture.create_from_image(img)
if path != self.last_original_image_path:
self.last_original_image_path = path
if self.last_image_savepath == "":
if self.last_image_savepath == "" or path != self.last_original_image_path:
self.last_image_savepath = path
self.last_original_image_path = path
return ""
return error_string(err) + " " + path
@ -42,6 +48,7 @@ func clear_additional_images():
additional_images.clear()
func load_additional_image(key: String, path: String) -> String: # returns Error Message String
print("Load ", path)
var img = Image.new()
var err = img.load(path)
if err == OK:
@ -58,18 +65,13 @@ func save_result(path: String):
else:
self.last_image_savepath = path
func load_shader(path: String):
print("Load ", path)
var file = FileAccess.open(path, FileAccess.READ)
self.shader_code = file.get_as_text()
if "/" in path: # update current working directory
self.cwd = path.substr(0, path.rfind("/"))
self.last_shader_savepath = path
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 save_shader(path: String):
print("Save ", path)
var file = FileAccess.open(path, FileAccess.WRITE)
file.store_string(self.shader_code)
if "/" in path: # update current working directory
self.cwd = path.substr(0, path.rfind("/"))
self.last_shader_savepath = path
func remember_last_opened_file():
var f = FileAccess.open("user://last_opened", FileAccess.READ)
if f != null:
shader_path = f.get_pascal_string()

1
src/Filesystem.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://rlb041ygdwol

View file

@ -1,9 +1,22 @@
extends SubViewport
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_sprite = %ImageSprite
@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:
@ -24,27 +37,40 @@ func validate_shader_compilation(shader: Shader) -> bool:
# test if uniform list is empty -> if it is empty, the shader compilation failed
return len(shader.get_shader_uniform_list()) > 0
func inject_step_uniform(shader_code: String) -> Shader:
var shader = Shader.new()
# this should run after validate_shader_compilation()
var fragment_function_match = _fragment_function_regex.search(shader_code)
shader.code = shader_code.insert(fragment_function_match.get_start(), "\nuniform int STEP;")
return shader
func shader_has_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 update() -> Array: # returns error messages (strings)
# inject STEP uniform & get number of steps
var shader: Shader = inject_step_uniform(Filesystem.shader_code)
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 m = ShaderDirectiveParser.parse_load_directive(shader.code)
if len(m) < 1:
errors.append("Didn't find a load directive!")
return errors
var original_image_path = Filesystem.get_absolute_path(m[1])
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
@ -65,6 +91,9 @@ func update() -> Array: # returns error messages (strings)
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
@ -73,20 +102,22 @@ func update() -> Array: # returns error messages (strings)
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):
# set STEP param
mat.set_shader_parameter("STEP", i)
# assign material
image_sprite.material = mat
if has_step_uniform:
# set STEP param
mat.set_shader_parameter("STEP", i)
# Get viewport texture
await RenderingServer.frame_post_draw # for good measure
await RenderingServer.frame_post_draw # wait for next frame to get drawn
Filesystem.result = get_texture().get_image()
image_sprite.material = null
image_sprite.texture = ImageTexture.create_from_image(Filesystem.result)
if fit_image:
camera.fit_image()
camera.update_vd_zoomlevel()
image_viewport_display.show()
set_vsync(true) # reenable vsync
image_sprite.material = null
# done
return errors

View file

@ -0,0 +1 @@
uid://d106170kuigl3

View file

@ -0,0 +1 @@
uid://ctc4lhbdsoq7u

View file

@ -1,21 +1,126 @@
extends Node
@onready var editor_window = %EditorWindow
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 _ready():
func show_help():
print(
"Usage:\n\n",
"./Fragmented <command> <args...>\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()
# position windows
get_window().position = Vector2i(
editor_window.position.x + editor_window.size.x + 50,
editor_window.position.y)
get_window().min_size = Vector2i(400, 400)
editor_window.min_size = Vector2i(560, 400)
# Load last opened file
Filesystem.remember_last_opened_file()
%MainUI._on_apply_shader_button_pressed()
func _ready():
var args = OS.get_cmdline_args()
if len(args) > 0 and args[0] in ["apply", "help"]:
# use the commandline interface
cli(args)
else:
prepare_gui()
func update_title(current_file: String = ""):
if current_file == "":
get_window().title = app_name + " - Viewer"
editor_window.title = app_name + " - Editor"
else:
get_window().title = current_file + " - " + app_name + " - Viewer"
editor_window.title = current_file + " - " + app_name + " - Editor"

1
src/Main.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://5sbslwysin5a

100
src/MainUI.gd Normal file
View file

@ -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()

1
src/MainUI.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://bxgmf2ny7yuc8

View file

@ -0,0 +1 @@
uid://dw8bep14j4j3w

View file

@ -1,4 +0,0 @@
extends Label
func _ready():
text = ProjectSettings.get_setting("application/config/version")

View file

@ -1,4 +0,0 @@
extends Window
func _on_close_requested() -> void:
get_tree().quit()

8
src/VersionLabel.gd Normal file
View file

@ -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"]

1
src/VersionLabel.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://bh0gpu3i2p47f

BIN
src/assets/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 B

34
src/assets/bg.png.import Normal file
View file

@ -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

View file

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Before After
Before After

View file

@ -3,15 +3,15 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://04iv1gogpuhu"
path="res://.godot/imported/error.svg-75fe5f417585e01e99de8885f1f45c3b.ctex"
path="res://.godot/imported/error.svg-28fb29635cf59d39cabf7052619f602f.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/error.svg"
dest_files=["res://.godot/imported/error.svg-75fe5f417585e01e99de8885f1f45c3b.ctex"]
source_file="res://src/assets/error.svg"
dest_files=["res://.godot/imported/error.svg-28fb29635cf59d39cabf7052619f602f.ctex"]
[params]

View file

Before

Width:  |  Height:  |  Size: 3 KiB

After

Width:  |  Height:  |  Size: 3 KiB

Before After
Before After

View file

@ -3,15 +3,15 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://kqwc4avs2xdp"
path="res://.godot/imported/icon.png-b6a7fb2db36edd3d95dc42f1dc8c1c5d.ctex"
path="res://.godot/imported/icon.png-d8298ab6eda392a806be6bb7eec65b9c.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/icon.png"
dest_files=["res://.godot/imported/icon.png-b6a7fb2db36edd3d95dc42f1dc8c1c5d.ctex"]
source_file="res://src/assets/icon.png"
dest_files=["res://.godot/imported/icon.png-d8298ab6eda392a806be6bb7eec65b9c.ctex"]
[params]

View file

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Before After
Before After

View file

@ -3,15 +3,15 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://m1omb6g45vst"
path="res://.godot/imported/okay.svg-7f6df15523471a86f27b6817e9528a4e.ctex"
path="res://.godot/imported/okay.svg-de66a022ef37753b085371b7c60aefd1.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/okay.svg"
dest_files=["res://.godot/imported/okay.svg-7f6df15523471a86f27b6817e9528a4e.ctex"]
source_file="res://src/assets/okay.svg"
dest_files=["res://.godot/imported/okay.svg-de66a022ef37753b085371b7c60aefd1.ctex"]
[params]

194
src/scenes/main.tscn Normal file
View file

@ -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"]

View file

@ -11,6 +11,6 @@ void fragment() {
UV.x > 1.0-t.x ||
UV.y > 1.0-t.y
) {
COLOR = mix(COLOR, vec4(1.0), 0.5);
COLOR = mix(COLOR, vec4(0.5), 0.5);
}
}

View file

@ -0,0 +1 @@
uid://ctk7jomfyx0fh

View file

@ -1,7 +0,0 @@
shader_type canvas_item;
//!load /path/to/your/image
void fragment() {
// Called for every pixel the material is visible on.
}

57
src/theme.tres Normal file
View file

@ -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")

Some files were not shown because too many files have changed in this diff Show more