Release/v8.0 #46
13 changed files with 156 additions and 76 deletions
46
README.md
46
README.md
|
@ -3,7 +3,15 @@
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
<p align=center>Create image filters by writing shaders.</p>
|
<p align=center>An image editing/compositing software for graphics programmers.</p>
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Supported Platforms](#supported-platforms)
|
||||||
|
- [Usage](#usage)
|
||||||
|
- [Shaderlib](#shaderlib)
|
||||||
|
- [Commandline interface](#commandline-interface)
|
||||||
|
- [Known Issues](#known-issues)
|
||||||
|
|
||||||
## Supported Platforms
|
## Supported Platforms
|
||||||
|
|
||||||
|
@ -13,8 +21,12 @@ You can find the latest releases [here](https://github.com/ChaoticByte/Fragmente
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
The repo includes examples. You can use them as a starting-point to write your own filters.
|
With Fragemented, you are editing images by writing GDShaders. This brings almost endless opportunities to create unique art.
|
||||||
Just load an image using `//!load`, edit the shader code and hit `F5` to see the changes.
|
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
|
### Load TEXTURE using the `//!load` directive
|
||||||
|
|
||||||
|
@ -68,12 +80,15 @@ Here is an example:
|
||||||
```glsl
|
```glsl
|
||||||
shader_type canvas_item;
|
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() {
|
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));
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -89,14 +104,23 @@ You can run Fragmented from the commandline or scripts.
|
||||||
./Fragmented cmd --shader PATH [--load-image PATH]
|
./Fragmented cmd --shader PATH [--load-image PATH]
|
||||||
|
|
||||||
--shader PATH The path to the shader
|
--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-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.
|
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
|
#### Examples
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -106,3 +130,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
|
./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
|
||||||
|
|
|
@ -18,4 +18,4 @@ RUN git clone https://github.com/godotengine/godot.git -b 4.3-stable /godot-src
|
||||||
FROM clone-src
|
FROM clone-src
|
||||||
|
|
||||||
WORKDIR /godot-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
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
shader_type canvas_item;
|
shader_type canvas_item;
|
||||||
|
|
||||||
#include "res://shaderlib/colorspaces.gdshaderinc"
|
#include "res://shaderlib/hsv.gdshaderinc"
|
||||||
#include "res://shaderlib/effects.gdshaderinc"
|
#include "res://shaderlib/pixelate.gdshaderinc"
|
||||||
|
|
||||||
//!load ./images/swamp.jpg
|
//!load ./images/swamp.jpg
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
shader_type canvas_item;
|
shader_type canvas_item;
|
||||||
|
|
||||||
#include "res://shaderlib/colorspaces.gdshaderinc"
|
#include "res://shaderlib/oklab.gdshaderinc"
|
||||||
|
|
||||||
//!load ./images/swamp.jpg
|
//!load ./images/swamp.jpg
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
shader_type canvas_item;
|
shader_type canvas_item;
|
||||||
|
|
||||||
#include "res://shaderlib/transform.gdshaderinc"
|
#include "res://shaderlib/place_texture.gdshaderinc"
|
||||||
#include "res://shaderlib/transparency.gdshaderinc"
|
#include "res://shaderlib/common.gdshaderinc"
|
||||||
|
|
||||||
//!load ./images/swamp.jpg
|
//!load ./images/swamp.jpg
|
||||||
//!load+ img2 ./images/grass.png
|
//!load+ img2 ./images/grass.png
|
||||||
|
|
|
@ -11,7 +11,7 @@ config_version=5
|
||||||
[application]
|
[application]
|
||||||
|
|
||||||
config/name="Fragmented"
|
config/name="Fragmented"
|
||||||
config/version="v7.0"
|
config/version="v8.0"
|
||||||
run/main_scene="res://scenes/main.tscn"
|
run/main_scene="res://scenes/main.tscn"
|
||||||
config/features=PackedStringArray("4.3", "Mobile")
|
config/features=PackedStringArray("4.3", "Mobile")
|
||||||
run/low_processor_mode=true
|
run/low_processor_mode=true
|
||||||
|
|
|
@ -3,3 +3,13 @@
|
||||||
float cbrt(float x) {
|
float cbrt(float x) {
|
||||||
return pow(x, 1.0/3.0);
|
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);
|
||||||
|
}
|
||||||
|
|
27
shaderlib/hsv.gdshaderinc
Normal file
27
shaderlib/hsv.gdshaderinc
Normal file
|
@ -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);
|
||||||
|
}
|
|
@ -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
|
OkLab and OkLCh
|
||||||
For more details on oklab, see
|
For more details on oklab, see
|
||||||
- https://bottosson.github.io/posts/oklab/
|
- https://bottosson.github.io/posts/oklab/
|
||||||
- https://en.wikipedia.org/wiki/Oklab_color_space
|
- 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) {
|
vec4 rgb2oklab(vec4 c) {
|
||||||
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;
|
|
@ -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);
|
|
||||||
}
|
|
89
src/Main.gd
89
src/Main.gd
|
@ -1,5 +1,9 @@
|
||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
|
const BATCH_MODE_SUPPORTED_EXTS = [
|
||||||
|
".bmp", ".dds", ".exr", ".hdr", ".jpeg", ".jpg", ".ktx", ".png", ".svg", ".webp"
|
||||||
|
]
|
||||||
|
|
||||||
@onready var editor_window = %EditorWindow
|
@onready var editor_window = %EditorWindow
|
||||||
@onready var ui_container = %UserInterfaceContainer
|
@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")
|
||||||
|
@ -9,9 +13,12 @@ func show_help():
|
||||||
"Usage:\n\n",
|
"Usage:\n\n",
|
||||||
"./Fragmented cmd --shader PATH [--load-image PATH]\n\n",
|
"./Fragmented cmd --shader PATH [--load-image PATH]\n\n",
|
||||||
" --shader PATH The path to the shader\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-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):
|
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}
|
||||||
|
@ -25,32 +32,74 @@ func parse_custom_cmdline(args: PackedStringArray):
|
||||||
i += 1
|
i += 1
|
||||||
return kwargs
|
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():
|
func _ready():
|
||||||
var args = OS.get_cmdline_args()
|
var args = OS.get_cmdline_args()
|
||||||
if "cmd" in args: # commandline interface
|
if "cmd" in args: # commandline interface
|
||||||
if "help" in args:
|
if "help" in args:
|
||||||
show_help()
|
show_help()
|
||||||
get_tree().quit(1)
|
get_tree().quit(1)
|
||||||
else:
|
return
|
||||||
var kwargs: Dictionary = parse_custom_cmdline(args)
|
var kwargs: Dictionary = parse_custom_cmdline(args)
|
||||||
if kwargs["--shader"] == null or kwargs["--output"] == null:
|
if kwargs["--shader"] == null or kwargs["--output"] == null:
|
||||||
show_help()
|
show_help()
|
||||||
get_tree().quit(1)
|
get_tree().quit(1)
|
||||||
else:
|
return
|
||||||
Filesystem.load_shader(kwargs["--shader"])
|
var batch_mode = false
|
||||||
var errors = []
|
var load_image_dir: DirAccess
|
||||||
if kwargs["--load-image"] == null:
|
if kwargs["--load-image"] != null:
|
||||||
errors = await $Compositor.update()
|
load_image_dir = DirAccess.open(kwargs["--load-image"])
|
||||||
else:
|
if load_image_dir != null:
|
||||||
errors = await $Compositor.update(kwargs["--load-image"])
|
# batch mode
|
||||||
if errors.size() > 0:
|
if DirAccess.open(kwargs["--output"]) == null:
|
||||||
print("One or more errors occurred.")
|
printerr("If --load-image is a directory, --output has to be one too.\n")
|
||||||
for e in errors:
|
show_help()
|
||||||
printerr(e)
|
|
||||||
get_tree().quit(1)
|
get_tree().quit(1)
|
||||||
|
return
|
||||||
else:
|
else:
|
||||||
Filesystem.save_result(kwargs["--output"])
|
batch_mode = true
|
||||||
get_tree().quit(0)
|
#
|
||||||
|
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:
|
||||||
update_title()
|
update_title()
|
||||||
# position windows
|
# position windows
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue