Compare commits

..

No commits in common. "main" and "v8.2" have entirely different histories.
main ... v8.2

81 changed files with 775 additions and 934 deletions

1
.gitignore vendored
View file

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

View file

@ -21,16 +21,12 @@ You can find the latest releases [here](https://github.com/ChaoticByte/Fragmente
## Usage ## Usage
With Fragemented, you are processing images with GDShaders. This brings almost endless opportunities to create unique art. With Fragemented, you are editing images by writing 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/). 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. The repo also includes examples. You can use them as a starting-point to write your own filters.
**To get started, use the project template (see the Releases section of this repo) and open it in Godot.** 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.
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 ### Load TEXTURE using the `//!load` directive
@ -62,22 +58,20 @@ Example:
//!load ... //!load ...
//!steps 5 //!steps 5
uniform int STEP;
uniform int STEPS;
void fragment() { void fragment() {
if (STEP == 0) { if (STEP == 0) {
... ...
} else if (STEP == 1) { } else if (STEP == 1) {
... ...
} else if (STEP == STEPS-1) {
...
} }
// ... and so on
} }
``` ```
## Shaderlib ## 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. This repo comes with a (still small) shader library including pre-written functions and more.
Have a look at the `shaderlib` folder. Have a look at the `shaderlib` folder.
@ -86,7 +80,7 @@ Here is an example:
```glsl ```glsl
shader_type canvas_item; shader_type canvas_item;
#include "./shaderlib/oklab.gdshaderinc" #include "res://shaderlib/oklab.gdshaderinc"
//!load ./images/swamp.jpg //!load ./images/swamp.jpg
@ -107,33 +101,20 @@ You can run Fragmented from the commandline or scripts.
### Usage ### Usage
``` ```
~ Fragmented CLI ~ ./Fragmented cmd --shader PATH [--load-image PATH]
-================-
Usage: --shader PATH The path to the shader
--output PATH Where to write the resulting image to.
./Fragmented <command> <args...> In batch mode, this must be a folder.
--load-image PATH The path to the image. This will overwrite the
Commands: load directive of the shader file.
Passing a folder activates batch mode.
help (optional)
| 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 ### 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. 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.
@ -143,14 +124,15 @@ Since version v8.0, you can pass a directory to `--load-image` and `--output`. T
#### Examples #### Examples
``` ```
./Fragmented apply --shader ./examples/oklab.gdshader --output ./output.png ./Fragmented cmd --shader ./examples/oklab.gdshader --output ./output.png
``` ```
``` ```
./Fragmented apply --shader ./examples/oklab.gdshader --load-image ~/Pictures/test.png --output ./output.png ./Fragmented cmd --shader ./examples/oklab.gdshader --load-image ~/Pictures/test.png --output ./output.png
``` ```
## Known Issues ## Known Issues
- screen scaling is unsupported; Using screen scaling could lead to an either blurry UI, or no scaling at all -> see #45 - 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 - commandline interface: `--headless` is not supported

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" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://04iv1gogpuhu" uid="uid://04iv1gogpuhu"
path="res://.godot/imported/error.svg-28fb29635cf59d39cabf7052619f602f.ctex" path="res://.godot/imported/error.svg-75fe5f417585e01e99de8885f1f45c3b.ctex"
metadata={ metadata={
"vram_texture": false "vram_texture": false
} }
[deps] [deps]
source_file="res://src/assets/error.svg" source_file="res://assets/error.svg"
dest_files=["res://.godot/imported/error.svg-28fb29635cf59d39cabf7052619f602f.ctex"] dest_files=["res://.godot/imported/error.svg-75fe5f417585e01e99de8885f1f45c3b.ctex"]
[params] [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" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://kqwc4avs2xdp" uid="uid://kqwc4avs2xdp"
path="res://.godot/imported/icon.png-d8298ab6eda392a806be6bb7eec65b9c.ctex" path="res://.godot/imported/icon.png-b6a7fb2db36edd3d95dc42f1dc8c1c5d.ctex"
metadata={ metadata={
"vram_texture": false "vram_texture": false
} }
[deps] [deps]
source_file="res://src/assets/icon.png" source_file="res://assets/icon.png"
dest_files=["res://.godot/imported/icon.png-d8298ab6eda392a806be6bb7eec65b9c.ctex"] dest_files=["res://.godot/imported/icon.png-b6a7fb2db36edd3d95dc42f1dc8c1c5d.ctex"]
[params] [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" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://m1omb6g45vst" uid="uid://m1omb6g45vst"
path="res://.godot/imported/okay.svg-de66a022ef37753b085371b7c60aefd1.ctex" path="res://.godot/imported/okay.svg-7f6df15523471a86f27b6817e9528a4e.ctex"
metadata={ metadata={
"vram_texture": false "vram_texture": false
} }
[deps] [deps]
source_file="res://src/assets/okay.svg" source_file="res://assets/okay.svg"
dest_files=["res://.godot/imported/okay.svg-de66a022ef37753b085371b7c60aefd1.ctex"] dest_files=["res://.godot/imported/okay.svg-7f6df15523471a86f27b6817e9528a4e.ctex"]
[params] [params]

View file

@ -7,12 +7,13 @@ FROM docker.io/ubuntu:focal AS os-base
# https://docs.godotengine.org/en/stable/contributing/development/compiling/compiling_for_linuxbsd.html # https://docs.godotengine.org/en/stable/contributing/development/compiling/compiling_for_linuxbsd.html
RUN apt-get update RUN apt-get update
RUN DEBIAN_FRONTEND=noninteractive apt-get install -yq python3-pip git build-essential pkg-config libx11-dev libxcursor-dev libxinerama-dev libgl1-mesa-dev libglu1-mesa-dev libasound2-dev libpulse-dev libudev-dev libxi-dev libxrandr-dev libwayland-dev RUN 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 pip3 install --system scons
RUN DEBIAN_FRONTEND=noninteractive apt-get install -yq git
FROM os-base AS clone-src FROM os-base AS clone-src
RUN git clone https://github.com/godotengine/godot.git -b 4.4-stable /godot-src RUN git clone https://github.com/godotengine/godot.git -b 4.3-stable /godot-src
FROM clone-src FROM clone-src

View file

@ -3,12 +3,12 @@
set -e set -e
function log { 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 "Fragmented - Godot Build Template Builder"
log " " log
cd $(dirname $0) cd $(dirname $0)
log Switched to $(pwd) log Switched to $(pwd)

30
dist.sh
View file

@ -1,30 +0,0 @@
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 Normal file
View file

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,8 +2,8 @@ shader_type canvas_item;
//!load ./images/mountain.jpg //!load ./images/mountain.jpg
#include "./shaderlib/kuwahara.gdshaderinc" #include "res://shaderlib/kuwahara.gdshaderinc"
#include "./shaderlib/hsv.gdshaderinc" #include "res://shaderlib/hsv.gdshaderinc"
void fragment() { void fragment() {
// Kuwahara // Kuwahara

View file

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

View file

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

View file

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

View file

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

View file

@ -1,20 +0,0 @@
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

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,15 +0,0 @@
; 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")

View file

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

View file

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

View file

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

View file

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

View file

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

46
scenes/main.tscn Normal file
View file

@ -0,0 +1,46 @@
[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"]

204
scenes/ui_container.tscn Normal file
View file

@ -0,0 +1,204 @@
[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: 502 KiB

After

Width:  |  Height:  |  Size: 630 KiB

Before After
Before After

View file

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

View file

@ -13,15 +13,3 @@ vec4 alpha_blend(vec4 b, vec4 a) {
vec3 col = ((a.rgb*a.a) + ((b.rgb*b.a) * (1.0 - a.a)) / alpha); vec3 col = ((a.rgb*a.a) + ((b.rgb*b.a) * (1.0 - a.a)) / alpha);
return vec4(col.r, col.g, col.b, 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

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

View file

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

View file

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

View file

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

View file

@ -9,11 +9,9 @@
The fourth value is always alpha. The fourth value is always alpha.
*/ */
#include "./common.gdshaderinc" #include "res://shaderlib/common.gdshaderinc"
vec4 rgb2oklab(vec4 c) { 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 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 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 s = 0.0883024619f * c.r + 0.2817188376f * c.g + 0.6299787005f * c.b;
@ -31,8 +29,6 @@ vec4 rgb2oklab(vec4 c) {
} }
vec4 oklab2rgb(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 l_ = c.x + 0.3963377774f * c.y + 0.2158037573f * c.z;
float m_ = c.x - 0.1055613458f * c.y - 0.0638541728f * 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 s_ = c.x - 0.0894841775f * c.y - 1.2914855480f * c.z;
@ -50,7 +46,6 @@ vec4 oklab2rgb(vec4 c) {
} }
vec4 oklab2oklch(vec4 c) { vec4 oklab2oklch(vec4 c) {
// oklch.z (hue) ranges from -3.6 to 3.6
return vec4( return vec4(
c.x, c.x,
sqrt((c.y * c.y) + (c.z * c.z)), sqrt((c.y * c.y) + (c.z * c.z)),
@ -60,7 +55,6 @@ vec4 oklab2oklch(vec4 c) {
} }
vec4 oklch2oklab(vec4 c) { vec4 oklch2oklab(vec4 c) {
// oklch.z (hue) ranges from -3.6 to 3.6
return vec4( return vec4(
c.x, c.x,
c.y * cos(c.z), c.y * cos(c.z),

View file

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

View file

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

View file

@ -1,126 +0,0 @@
/*
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

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

View file

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

View file

@ -1,50 +0,0 @@
/*
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

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

View file

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

View file

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

307
src/Editor.gd Normal file
View file

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

View file

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

View file

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

View file

@ -37,26 +37,17 @@ func validate_shader_compilation(shader: Shader) -> bool:
# test if uniform list is empty -> if it is empty, the shader compilation failed # test if uniform list is empty -> if it is empty, the shader compilation failed
return len(shader.get_shader_uniform_list()) > 0 return len(shader.get_shader_uniform_list()) > 0
func shader_has_uniform(shader: Shader, name: String, type: int) -> bool: func inject_step_uniform(shader_code: String) -> Shader:
for u in shader.get_shader_uniform_list(): var shader = Shader.new()
if u["name"] == name && u["type"] == type: # this should run after validate_shader_compilation()
return true var fragment_function_match = _fragment_function_regex.search(shader_code)
return false shader.code = shader_code.insert(fragment_function_match.get_start(), "\nuniform int STEP;")
return shader
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) func update(overwrite_image_path: String = "") -> Array: # returns error messages (strings)
var shader = Filesystem.shader # read from disk # inject STEP uniform & get number of steps
if shader == null: var shader: Shader = inject_step_uniform(Filesystem.shader_code)
return ["No shader opened!"]
# get number of steps & check if code has STEP uniform
var steps: int = ShaderDirectiveParser.parse_steps_directive(shader.code) 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 # validate shader
if not validate_shader_compilation(shader): if not validate_shader_compilation(shader):
return ["Shader compilation failed!"] return ["Shader compilation failed!"]
@ -91,9 +82,6 @@ func update(overwrite_image_path: String = "") -> Array: # returns error message
image_sprite.texture = Filesystem.original_image image_sprite.texture = Filesystem.original_image
image_sprite.offset = Filesystem.original_image.get_size() / 2 image_sprite.offset = Filesystem.original_image.get_size() / 2
self.size = Filesystem.original_image.get_size() 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 # create shader material
var mat = ShaderMaterial.new() var mat = ShaderMaterial.new()
mat.shader = shader mat.shader = shader
@ -105,19 +93,17 @@ func update(overwrite_image_path: String = "") -> Array: # returns error message
# assign material # assign material
image_sprite.material = mat image_sprite.material = mat
# iterate n times # 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): for i in range(steps):
if has_step_uniform: # set STEP param
# set STEP param mat.set_shader_parameter("STEP", i)
mat.set_shader_parameter("STEP", i)
# Get viewport texture # Get viewport texture
await RenderingServer.frame_post_draw # wait for next frame to get drawn await RenderingServer.frame_post_draw # wait for next frame to get drawn
Filesystem.result = get_texture().get_image() Filesystem.result = get_texture().get_image()
image_sprite.texture = ImageTexture.create_from_image(Filesystem.result) image_sprite.texture = ImageTexture.create_from_image(Filesystem.result)
set_vsync(true) # reenable vsync
image_sprite.material = null image_sprite.material = null
if fit_image:
camera.fit_image()
camera.update_vd_zoomlevel()
image_viewport_display.show()
# done # done
return errors return errors

View file

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

View file

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

View file

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

View file

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

View file

@ -1,100 +0,0 @@
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()

View file

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

View file

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

4
src/UIAppVersion.gd Normal file
View file

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

4
src/UIWindow.gd Normal file
View file

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

View file

@ -1,8 +0,0 @@
extends Label
func _ready():
text = ProjectSettings.get_setting("application/config/name") \
+ " " \
+ ProjectSettings.get_setting("application/config/version") \
+ " | Godot " \
+ Engine.get_version_info()["string"]

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 532 B

View file

@ -1,34 +0,0 @@
[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

@ -1,194 +0,0 @@
[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.x > 1.0-t.x ||
UV.y > 1.0-t.y UV.y > 1.0-t.y
) { ) {
COLOR = mix(COLOR, vec4(0.5), 0.5); COLOR = mix(COLOR, vec4(1.0), 0.5);
} }
} }

View file

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

View file

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

View file

@ -1,57 +0,0 @@
[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")

View file

@ -1,7 +0,0 @@
extends SceneTree
# godot --headless --no-header -s tools/get_version.gd
func _init() -> void:
print(ProjectSettings.get_setting("application/config/version"))
quit(0)

View file

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