From acdb52c91ee94c8755e0a8a1770bbfa20b53ea5f Mon Sep 17 00:00:00 2001 From: ChaoticByte Date: Fri, 24 Jan 2025 21:44:41 +0100 Subject: [PATCH 1/5] Readme: add a toc, add a Known Issues section and add a note about screen scaling not being supported (issue #45) --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 7fe8374..906ca6a 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,14 @@

Create image filters by writing shaders.

+## Table of Contents + +- [Supported Platforms](#supported-platforms) +- [Usage](#usage) +- [Shaderlib](#shaderlib) +- [Commandline interface](#commandline-interface) +- [Known Issues](#known-issues) + ## Supported Platforms - Linux @@ -106,3 +114,9 @@ You can also run `./Fragmented cmd help` to show the help message. ``` ./Fragmented cmd --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 From c70eaed0c491f42490393fd73daa7d028cf26013 Mon Sep 17 00:00:00 2001 From: ChaoticByte Date: Fri, 24 Jan 2025 21:56:37 +0100 Subject: [PATCH 2/5] Readme: Improved the screenshot subtitle and Usage section, added a link to the Godot Shader documentation - fixes #44 --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 906ca6a..214f6b8 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ![screenshot](./screenshot.png) -

Create image filters by writing shaders.

+

An image editing/compositing software for graphics programmers.

## Table of Contents @@ -21,8 +21,12 @@ You can find the latest releases [here](https://github.com/ChaoticByte/Fragmente ## Usage -The repo includes examples. You can use them as a starting-point to write your own filters. -Just load an image using `//!load`, edit the shader code and hit `F5` to see the changes. +With Fragemented, you are 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/). + +The repo also includes examples. You can use them as a starting-point to write your own filters. + +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. ### Load TEXTURE using the `//!load` directive From 40374bd8498e0714bf42fc2a1be612fad828a2a4 Mon Sep 17 00:00:00 2001 From: ChaoticByte Date: Fri, 24 Jan 2025 22:16:45 +0100 Subject: [PATCH 3/5] Restructured shaderlib by moving functions and renaming files --- README.md | 9 +++-- examples/color_and_pixelate.gdshader | 4 +-- examples/oklab.gdshader | 2 +- examples/place_texture.gdshader | 4 +-- shaderlib/common.gdshaderinc | 10 ++++++ shaderlib/hsv.gdshaderinc | 27 ++++++++++++++ ...orspaces.gdshaderinc => oklab.gdshaderinc} | 36 +++---------------- ...fects.gdshaderinc => pixelate.gdshaderinc} | 0 ....gdshaderinc => place_texture.gdshaderinc} | 0 shaderlib/transparency.gdshaderinc | 10 ------ 10 files changed, 53 insertions(+), 49 deletions(-) create mode 100644 shaderlib/hsv.gdshaderinc rename shaderlib/{colorspaces.gdshaderinc => oklab.gdshaderinc} (64%) rename shaderlib/{effects.gdshaderinc => pixelate.gdshaderinc} (100%) rename shaderlib/{transform.gdshaderinc => place_texture.gdshaderinc} (100%) delete mode 100644 shaderlib/transparency.gdshaderinc diff --git a/README.md b/README.md index 214f6b8..b9a0ac6 100644 --- a/README.md +++ b/README.md @@ -80,12 +80,15 @@ Here is an example: ```glsl shader_type canvas_item; -#include "res://shaderlib/hsv.gdshaderinc" +#include "res://shaderlib/oklab.gdshaderinc" -//!load ./examples/images/swamp.jpg +//!load ./images/swamp.jpg void fragment() { - COLOR = hsv_offset(COLOR, 0.32, 0.2, 0.0); + vec4 oklab = rgb2oklab(COLOR); + vec4 oklch = oklab2oklch(oklab); + oklch.z -= 2.0; + COLOR = oklab2rgb(oklch2oklab(oklch)); } ``` diff --git a/examples/color_and_pixelate.gdshader b/examples/color_and_pixelate.gdshader index bf5f18a..dfb5e7b 100644 --- a/examples/color_and_pixelate.gdshader +++ b/examples/color_and_pixelate.gdshader @@ -1,7 +1,7 @@ shader_type canvas_item; -#include "res://shaderlib/colorspaces.gdshaderinc" -#include "res://shaderlib/effects.gdshaderinc" +#include "res://shaderlib/hsv.gdshaderinc" +#include "res://shaderlib/pixelate.gdshaderinc" //!load ./images/swamp.jpg diff --git a/examples/oklab.gdshader b/examples/oklab.gdshader index 0d8f093..8018695 100644 --- a/examples/oklab.gdshader +++ b/examples/oklab.gdshader @@ -1,6 +1,6 @@ shader_type canvas_item; -#include "res://shaderlib/colorspaces.gdshaderinc" +#include "res://shaderlib/oklab.gdshaderinc" //!load ./images/swamp.jpg diff --git a/examples/place_texture.gdshader b/examples/place_texture.gdshader index 183d080..76d01fb 100644 --- a/examples/place_texture.gdshader +++ b/examples/place_texture.gdshader @@ -1,7 +1,7 @@ shader_type canvas_item; -#include "res://shaderlib/transform.gdshaderinc" -#include "res://shaderlib/transparency.gdshaderinc" +#include "res://shaderlib/place_texture.gdshaderinc" +#include "res://shaderlib/common.gdshaderinc" //!load ./images/swamp.jpg //!load+ img2 ./images/grass.png diff --git a/shaderlib/common.gdshaderinc b/shaderlib/common.gdshaderinc index 7ee57f8..8af35dc 100644 --- a/shaderlib/common.gdshaderinc +++ b/shaderlib/common.gdshaderinc @@ -3,3 +3,13 @@ float cbrt(float x) { return pow(x, 1.0/3.0); } + +/* + Alpha Blending a over b after Bruce A. Wallace + source: https://en.wikipedia.org/wiki/Alpha_compositing +*/ +vec4 alpha_blend(vec4 b, vec4 a) { + float alpha = a.a + (b.a * (1.0 - a.a)); + vec3 col = ((a.rgb*a.a) + ((b.rgb*b.a) * (1.0 - a.a)) / alpha); + return vec4(col.r, col.g, col.b, alpha); +} diff --git a/shaderlib/hsv.gdshaderinc b/shaderlib/hsv.gdshaderinc new file mode 100644 index 0000000..2a7834d --- /dev/null +++ b/shaderlib/hsv.gdshaderinc @@ -0,0 +1,27 @@ + +/* + rgb2hsv and hsv2rgb functions adapted + from https://godotshaders.com/shader/hsv-adjustment/ + original code by https://godotshaders.com/author/al1-ce/ + + Color space conversion functions always work with vec4. + The fourth value is always alpha. +*/ + +// Convert RGB to HSV (hue, saturation, brightness) +vec4 rgb2hsv(vec4 c) { + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec4(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x, c.a); +} + +// Convert HSV back to RGB (red, green, blue) +vec4 hsv2rgb(vec4 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + vec3 rgb = c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); + return vec4(rgb.r, rgb.g, rgb.b, c.a); +} diff --git a/shaderlib/colorspaces.gdshaderinc b/shaderlib/oklab.gdshaderinc similarity index 64% rename from shaderlib/colorspaces.gdshaderinc rename to shaderlib/oklab.gdshaderinc index 6a8ae9b..96b6f34 100644 --- a/shaderlib/colorspaces.gdshaderinc +++ b/shaderlib/oklab.gdshaderinc @@ -1,42 +1,16 @@ -/* - Color space conversion functions always work with vec4. - The fourth value is always alpha. -*/ - -#include "res://shaderlib/common.gdshaderinc" - -/* - rgb2hsv and hsv2rgb functions adapted - from https://godotshaders.com/shader/hsv-adjustment/ - original code by https://godotshaders.com/author/al1-ce/ -*/ - -// Convert RGB to HSV (hue, saturation, brightness) -vec4 rgb2hsv(vec4 c) { - vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); - vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); - vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); - float d = q.x - min(q.w, q.y); - float e = 1.0e-10; - return vec4(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x, c.a); -} - -// Convert HSV back to RGB (red, green, blue) -vec4 hsv2rgb(vec4 c) { - vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); - vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); - vec3 rgb = c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); - return vec4(rgb.r, rgb.g, rgb.b, c.a); -} - /* OkLab and OkLCh For more details on oklab, see - https://bottosson.github.io/posts/oklab/ - https://en.wikipedia.org/wiki/Oklab_color_space + + Color space conversion functions always work with vec4. + The fourth value is always alpha. */ +#include "res://shaderlib/common.gdshaderinc" + vec4 rgb2oklab(vec4 c) { 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; diff --git a/shaderlib/effects.gdshaderinc b/shaderlib/pixelate.gdshaderinc similarity index 100% rename from shaderlib/effects.gdshaderinc rename to shaderlib/pixelate.gdshaderinc diff --git a/shaderlib/transform.gdshaderinc b/shaderlib/place_texture.gdshaderinc similarity index 100% rename from shaderlib/transform.gdshaderinc rename to shaderlib/place_texture.gdshaderinc diff --git a/shaderlib/transparency.gdshaderinc b/shaderlib/transparency.gdshaderinc deleted file mode 100644 index a97e4b7..0000000 --- a/shaderlib/transparency.gdshaderinc +++ /dev/null @@ -1,10 +0,0 @@ - -/* - Alpha Blending a over b after Bruce A. Wallace - source: https://en.wikipedia.org/wiki/Alpha_compositing -*/ -vec4 alpha_blend(vec4 b, vec4 a) { - float alpha = a.a + (b.a * (1.0 - a.a)); - vec3 col = ((a.rgb*a.a) + ((b.rgb*b.a) * (1.0 - a.a)) / alpha); - return vec4(col.r, col.g, col.b, alpha); -} From 7810a1fa83be1da2ecf6fea29ec648c1d0903fad Mon Sep 17 00:00:00 2001 From: ChaoticByte Date: Sun, 26 Jan 2025 21:38:16 +0100 Subject: [PATCH 4/5] cli: added support for processing whole directories (batch mode) - this implements #40; changed build template to support more image formats --- README.md | 13 +++++- build-template/Containerfile | 2 +- src/Main.gd | 89 ++++++++++++++++++++++++++++-------- 3 files changed, 81 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index b9a0ac6..ef9ed9b 100644 --- a/README.md +++ b/README.md @@ -104,14 +104,23 @@ You can run Fragmented from the commandline or scripts. ./Fragmented cmd --shader PATH [--load-image PATH] --shader PATH The path to the shader - --output PATH Where to write the resulting image to + --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 (optional) + 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. + +> Note: You *can* use this feature for video frames, but it will take a loooong time. + #### Examples ``` diff --git a/build-template/Containerfile b/build-template/Containerfile index 38d31a7..e47f847 100644 --- a/build-template/Containerfile +++ b/build-template/Containerfile @@ -18,4 +18,4 @@ RUN git clone https://github.com/godotengine/godot.git -b 4.3-stable /godot-src FROM clone-src WORKDIR /godot-src -ENTRYPOINT scons platform=linuxbsd target=template_release lto=full optimize=size disable_3d=yes module_text_server_adv_enabled=no module_text_server_fb_enabled=yes module_basis_universal_enabled=no module_csg_enabled=no module_dds_enabled=no module_enet_enabled=no module_gridmap_enabled=no module_hdr_enabled=no module_jsonrpc_enabled=no module_ktx_enabled=no module_mbedtls_enabled=no module_meshoptimizer_enabled=no module_minimp3_enabled=no module_mobile_vr_enabled=no module_msdfgen_enabled=no module_multiplayer_enabled=no module_navigation_enabled=no module_ogg_enabled=no module_openxr_enabled=no module_raycast_enabled=no module_squish_enabled=no module_svg_enabled=no module_tga_enabled=no module_theora_enabled=no module_tinyexr_enabled=no module_upnp_enabled=no module_vhacd_enabled=no module_vorbis_enabled=no module_webrtc_enabled=no module_websocket_enabled=no module_webxr_enabled=no module_zip_enabled=no arch=x86_64 && strip bin/godot.linuxbsd.template_release.x86_64 +ENTRYPOINT scons platform=linuxbsd target=template_release lto=full optimize=size disable_3d=yes module_text_server_adv_enabled=no module_text_server_fb_enabled=yes module_basis_universal_enabled=no module_csg_enabled=no module_enet_enabled=no module_gridmap_enabled=no module_jsonrpc_enabled=no module_mbedtls_enabled=no module_meshoptimizer_enabled=no module_minimp3_enabled=no module_mobile_vr_enabled=no module_msdfgen_enabled=no module_multiplayer_enabled=no module_navigation_enabled=no module_ogg_enabled=no module_openxr_enabled=no module_raycast_enabled=no module_squish_enabled=no module_theora_enabled=no module_upnp_enabled=no module_vhacd_enabled=no module_vorbis_enabled=no module_webrtc_enabled=no module_websocket_enabled=no module_webxr_enabled=no arch=x86_64 && strip bin/godot.linuxbsd.template_release.x86_64 diff --git a/src/Main.gd b/src/Main.gd index 8e54a86..71e6bd3 100644 --- a/src/Main.gd +++ b/src/Main.gd @@ -1,5 +1,9 @@ extends Node +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") @@ -9,9 +13,12 @@ func show_help(): "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", + " --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 (optional)\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} @@ -25,32 +32,74 @@ func parse_custom_cmdline(args: PackedStringArray): i += 1 return kwargs +func cli_handle_errors(errors: Array) -> int: + # returns number of errors + var n_errors = errors.size() + if n_errors > 0: + print("One or more errors occurred.") + for e in errors: + printerr(e) + return n_errors + func _ready(): var args = OS.get_cmdline_args() if "cmd" in args: # commandline interface if "help" in args: show_help() get_tree().quit(1) - else: - var kwargs: Dictionary = parse_custom_cmdline(args) - if kwargs["--shader"] == null or kwargs["--output"] == null: - show_help() - get_tree().quit(1) - else: - Filesystem.load_shader(kwargs["--shader"]) - var errors = [] - if kwargs["--load-image"] == null: - errors = await $Compositor.update() - else: - errors = await $Compositor.update(kwargs["--load-image"]) - if errors.size() > 0: - print("One or more errors occurred.") - for e in errors: - printerr(e) + 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: - Filesystem.save_result(kwargs["--output"]) - get_tree().quit(0) + 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: update_title() # position windows From c0d9e00fe94728e834dcb7cc9f3583dc287df934 Mon Sep 17 00:00:00 2001 From: ChaoticByte Date: Mon, 27 Jan 2025 18:17:01 +0100 Subject: [PATCH 5/5] Bump version to 8.0 --- project.godot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project.godot b/project.godot index a8dffd7..d882762 100644 --- a/project.godot +++ b/project.godot @@ -11,7 +11,7 @@ config_version=5 [application] config/name="Fragmented" -config/version="v7.0" +config/version="v8.0" run/main_scene="res://scenes/main.tscn" config/features=PackedStringArray("4.3", "Mobile") run/low_processor_mode=true