Compare commits

..

45 commits
v8.0 ... 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
85 changed files with 1097 additions and 771 deletions

1
.gitignore vendored
View file

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

View file

@ -21,12 +21,16 @@ You can find the latest releases [here](https://github.com/ChaoticByte/Fragmente
## Usage
With Fragemented, you are editing images by writing GDShaders. This brings almost endless opportunities to create unique art.
With Fragemented, you are processing images with GDShaders. This brings almost endless opportunities to create unique art.
If you want to learn GDShader, take a look at the [Godot docs](https://docs.godotengine.org/en/stable/tutorials/shaders/).
The repo also includes examples. You can use them as a starting-point to write your own filters.
**The builtin editor got removed** from Fragmented with version **v9.0**. I advise you to write your shaders directly in the Godot Editor.
Besides the regular GDShader stuff, Fragmented also has so-called directives. Those allow to further control the behaviour of the application. The most important directive is `//!load` to load an image.
**To get started, use the project template (see the Releases section of this repo) and open it in Godot.**
The template includes many examples. You can use them as a starting-point to write your own stuff.
Besides the regular GDShader stuff, Fragmented has so-called directives. Those allow to further control the behaviour of the application. **The most important directive is `//!load` to load an image.**
### Load TEXTURE using the `//!load` directive
@ -58,20 +62,22 @@ 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
}
```
## Shaderlib
> Note: The shaderlib API is still unstable as I am figuring things out. It will be declared stable with version 10.
This repo comes with a (still small) shader library including pre-written functions and more.
Have a look at the `shaderlib` folder.
@ -80,7 +86,7 @@ Here is an example:
```glsl
shader_type canvas_item;
#include "res://shaderlib/oklab.gdshaderinc"
#include "./shaderlib/oklab.gdshaderinc"
//!load ./images/swamp.jpg
@ -101,20 +107,33 @@ You can run Fragmented from the commandline or scripts.
### Usage
```
./Fragmented cmd --shader PATH [--load-image PATH]
~ Fragmented CLI ~
-================-
--shader PATH The path to the shader
--output PATH Where to write the resulting image to.
In batch mode, this must be a folder.
--load-image PATH The path to the image. This will overwrite the
load directive of the shader file.
Passing a folder activates batch mode.
(optional)
Usage:
./Fragmented <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)
```
You can also run `./Fragmented cmd help` to show the help message.
### Batch Mode
Since version v8.0, you can pass a directory to `--load-image` and `--output`. This will process all images in the input directory and write the output to the output directory.
@ -124,15 +143,14 @@ Since version v8.0, you can pass a directory to `--load-image` and `--output`. T
#### Examples
```
./Fragmented cmd --shader ./examples/oklab.gdshader --output ./output.png
./Fragmented apply --shader ./examples/oklab.gdshader --output ./output.png
```
```
./Fragmented cmd --shader ./examples/oklab.gdshader --load-image ~/Pictures/test.png --output ./output.png
./Fragmented apply --shader ./examples/oklab.gdshader --load-image ~/Pictures/test.png --output ./output.png
```
## Known Issues
- screen scaling is unsupported; Using screen scaling could lead to an either blurry UI, or no scaling at all -> see #45
- the shaderlib API is still unstable, this will change with version 10
- commandline interface: `--headless` is not supported

View file

@ -7,13 +7,12 @@ 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

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

View file

@ -1,7 +1,7 @@
shader_type canvas_item;
//!load ./images/swamp.jpg
#include "res://shaderlib/blur.gdshaderinc"
#include "./shaderlib/blur.gdshaderinc"
void fragment() {
COLOR = gaussian_blur(TEXTURE, UV, 48, 24.0);

View file

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

View file

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

View file

@ -1,7 +1,7 @@
shader_type canvas_item;
#include "res://shaderlib/hsv.gdshaderinc"
#include "res://shaderlib/pixelate.gdshaderinc"
#include "./shaderlib/hsv.gdshaderinc"
#include "./shaderlib/pixelate.gdshaderinc"
//!load ./images/swamp.jpg

View file

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

View file

@ -2,7 +2,7 @@ shader_type canvas_item;
//!load ./images/noisy.png
#include "res://shaderlib/denoise.gdshaderinc"
#include "./shaderlib/denoise.gdshaderinc"
void fragment() {
COLOR = smart_denoise(TEXTURE, UV, 12.0, 1.0, .12);

View file

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

View file

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

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)

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

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

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

View file

@ -3,6 +3,8 @@ shader_type canvas_item;
//!steps 9
//!load ./images/swamp.jpg
uniform int STEP;
const float strength = 0.01;
void fragment() {

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

View file

@ -1,6 +1,6 @@
shader_type canvas_item;
#include "res://shaderlib/oklab.gdshaderinc"
#include "./shaderlib/oklab.gdshaderinc"
//!load ./images/swamp.jpg

View file

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

View file

@ -1,7 +1,7 @@
shader_type canvas_item;
#include "res://shaderlib/place_texture.gdshaderinc"
#include "res://shaderlib/common.gdshaderinc"
#include "./shaderlib/place_texture.gdshaderinc"
#include "./shaderlib/common.gdshaderinc"
//!load ./images/swamp.jpg
//!load+ img2 ./images/grass.png

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

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 630 KiB

After

Width:  |  Height:  |  Size: 502 KiB

Before After
Before After

View file

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

View file

@ -13,3 +13,15 @@ vec4 alpha_blend(vec4 b, vec4 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 @@
uid://b7ksfifyyfcip

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

@ -9,9 +9,11 @@
The fourth value is always alpha.
*/
#include "res://shaderlib/common.gdshaderinc"
#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;
@ -29,6 +31,8 @@ vec4 rgb2oklab(vec4 c) {
}
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;
@ -46,6 +50,7 @@ vec4 oklab2rgb(vec4 c) {
}
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)),
@ -55,6 +60,7 @@ vec4 oklab2oklch(vec4 c) {
}
vec4 oklch2oklab(vec4 c) {
// oklch.z (hue) ranges from -3.6 to 3.6
return vec4(
c.x,
c.y * cos(c.z),

View file

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

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

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

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,33 +65,13 @@ func save_result(path: String):
else:
self.last_image_savepath = path
func load_shader(path: String):
print("Load ", path)
var file = FileAccess.open(path, FileAccess.READ)
if file != null:
self.shader_code = file.get_as_text()
if "/" in path: # update current working directory
self.cwd = path.substr(0, path.rfind("/"))
self.last_shader_savepath = path
store_last_opened_file()
func save_shader(path: String):
print("Save ", path)
var file = FileAccess.open(path, FileAccess.WRITE)
file.store_string(self.shader_code)
file.flush()
if "/" in path: # update current working directory
self.cwd = path.substr(0, path.rfind("/"))
self.last_shader_savepath = path
store_last_opened_file()
func store_last_opened_file():
var f = FileAccess.open("user://last_opened", FileAccess.WRITE)
if f != null:
f.store_pascal_string(last_shader_savepath)
f.store_pascal_string(shader_path)
f.flush()
func remember_last_opened_file():
var f = FileAccess.open("user://last_opened", FileAccess.READ)
if f != null:
last_shader_savepath = f.get_pascal_string()
shader_path = f.get_pascal_string()

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

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

View file

@ -37,17 +37,26 @@ 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 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)
# inject STEP uniform & get number of steps
var shader: Shader = inject_step_uniform(Filesystem.shader_code)
var shader = Filesystem.shader # read from disk
if shader == null:
return ["No shader opened!"]
# get number of steps & check if code has STEP uniform
var steps: int = ShaderDirectiveParser.parse_steps_directive(shader.code)
var has_step_uniform: bool = shader_has_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!"]
@ -82,6 +91,9 @@ func update(overwrite_image_path: String = "") -> Array: # returns error message
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
@ -93,17 +105,19 @@ func update(overwrite_image_path: String = "") -> Array: # returns error message
# 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)
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
if fit_image:
camera.fit_image()
camera.update_vd_zoomlevel()
image_viewport_display.show()
# done
return errors

View file

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

View file

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

View file

@ -4,21 +4,24 @@ const BATCH_MODE_SUPPORTED_EXTS = [
".bmp", ".dds", ".exr", ".hdr", ".jpeg", ".jpg", ".ktx", ".png", ".svg", ".webp"
]
@onready var editor_window = %EditorWindow
@onready var ui_container = %UserInterfaceContainer
@onready var app_name = ProjectSettings.get_setting("application/config/name")
func show_help():
print(
"Usage:\n\n",
"./Fragmented cmd --shader PATH [--load-image PATH]\n\n",
" --shader PATH The path to the shader\n",
" --output PATH Where to write the resulting image to.\n",
" In batch mode, this must be a folder.\n",
" --load-image PATH The path to the image. This will overwrite the\n",
" load directive of the shader file.\n",
" Passing a folder activates batch mode.\n",
" (optional)\n")
"./Fragmented <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}
@ -41,83 +44,83 @@ func cli_handle_errors(errors: Array) -> int:
printerr(e)
return n_errors
func _ready():
var args = OS.get_cmdline_args()
if "cmd" in args: # commandline interface
if "help" in args:
show_help()
get_tree().quit(1)
return
var kwargs: Dictionary = parse_custom_cmdline(args)
if kwargs["--shader"] == null or kwargs["--output"] == null:
show_help()
get_tree().quit(1)
return
var batch_mode = false
var load_image_dir: DirAccess
if kwargs["--load-image"] != null:
load_image_dir = DirAccess.open(kwargs["--load-image"])
if load_image_dir != null:
# batch mode
if DirAccess.open(kwargs["--output"]) == null:
printerr("If --load-image is a directory, --output has to be one too.\n")
show_help()
func cli(args: PackedStringArray):
print(
"~ Fragmented CLI ~\n",
"-================-\n")
if "help" in args:
show_help()
get_tree().quit(1)
return
var kwargs: Dictionary = parse_custom_cmdline(args)
if kwargs["--shader"] == null or kwargs["--output"] == null:
show_help()
get_tree().quit(1)
return
var batch_mode = false
var load_image_dir: DirAccess
if kwargs["--load-image"] != null:
load_image_dir = DirAccess.open(kwargs["--load-image"])
if load_image_dir != null:
# batch mode
if DirAccess.open(kwargs["--output"]) == null:
printerr("If --load-image is a directory, --output has to be one too.\n")
show_help()
get_tree().quit(1)
return
else:
batch_mode = true
#
Filesystem.shader_path = kwargs["--shader"]
#
if batch_mode:
var in_dir_path = load_image_dir.get_current_dir()
var out_dir_path: String = kwargs["--output"].rstrip("/")
for f in load_image_dir.get_files():
var supported = false
for e in BATCH_MODE_SUPPORTED_EXTS:
if f.ends_with(e):
supported = true
break
if supported:
f = in_dir_path + "/" + f
print(f)
var errors = await $Compositor.update(f)
if cli_handle_errors(errors) == 0:
var filename = out_dir_path + "/" + f.substr(f.rfind("/"), -1)
Filesystem.save_result(filename)
else:
get_tree().quit(1)
return
else:
batch_mode = true
#
Filesystem.load_shader(kwargs["--shader"])
#
if batch_mode:
var in_dir_path = load_image_dir.get_current_dir()
var out_dir_path: String = kwargs["--output"].rstrip("/")
for f in load_image_dir.get_files():
var supported = false
for e in BATCH_MODE_SUPPORTED_EXTS:
if f.ends_with(e):
supported = true
break
if supported:
f = in_dir_path + "/" + f
print(f)
var errors = await $Compositor.update(f)
if cli_handle_errors(errors) == 0:
var filename = out_dir_path + "/" + f.substr(f.rfind("/"), -1)
Filesystem.save_result(filename)
else:
get_tree().quit(1)
return
get_tree().quit(0)
else:
var errors = []
if kwargs["--load-image"] == null:
errors = await $Compositor.update()
else:
errors = await $Compositor.update(kwargs["--load-image"])
if cli_handle_errors(errors) == 0:
Filesystem.save_result(kwargs["--output"])
get_tree().quit(0)
else:
var errors = []
if kwargs["--load-image"] == null:
errors = await $Compositor.update()
else:
errors = await $Compositor.update(kwargs["--load-image"])
if cli_handle_errors(errors) == 0:
Filesystem.save_result(kwargs["--output"])
get_tree().quit(0)
else:
get_tree().quit(1)
get_tree().quit(1)
func prepare_gui():
update_title()
# Load last opened file
Filesystem.remember_last_opened_file()
%MainUI._on_apply_shader_button_pressed()
func _ready():
var args = OS.get_cmdline_args()
if len(args) > 0 and args[0] in ["apply", "help"]:
# use the commandline interface
cli(args)
else:
update_title()
# position windows
get_window().position = Vector2i(
editor_window.position.x + editor_window.size.x + 50,
editor_window.position.y)
get_window().min_size = Vector2i(400, 400)
editor_window.min_size = Vector2i(560, 400)
editor_window.show()
# Load last opened file
Filesystem.remember_last_opened_file()
if Filesystem.last_shader_savepath != "":
ui_container.get_node("Editor")._on_open_shader_dialog_file_selected(Filesystem.last_shader_savepath)
prepare_gui()
func update_title(current_file: String = ""):
if current_file == "":
get_window().title = app_name + " - Viewer"
editor_window.title = app_name + " - Editor"
else:
get_window().title = current_file + " - " + app_name + " - Viewer"
editor_window.title = current_file + " - " + app_name + " - Editor"

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

7
tools/get_version.gd Normal file
View file

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

1
tools/get_version.gd.uid Normal file
View file

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