Release/v7.0 #43

Merged
ChaoticByte merged 8 commits from release/v7.0 into main 2025-01-19 12:00:29 +00:00
18 changed files with 332 additions and 130 deletions

41
LICENSE
View file

@ -1,21 +1,28 @@
MIT License
BSD 3-Clause License
Copyright (c) 2024 Julian Müller (ChaoticByte)
Copyright (c) 2025, Julian Müller (ChaoticByte)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -74,3 +74,33 @@ void fragment() {
COLOR = hsv_offset(COLOR, 0.32, 0.2, 0.0);
}
```
## Commandline interface
You can run Fragmented from the commandline or scripts.
> Note: Headless mode is not supported. Using the commandline interface still opens a window.
### Usage
```
./Fragmented cmd --shader PATH [--load-image PATH]
--shader PATH The path to the shader
--output PATH Where to write the resulting image to
--load-image PATH The path to the image. This will overwrite the
load directive of the shader file (optional)
```
You can also run `./Fragmented cmd help` to show the help message.
#### Examples
```
./Fragmented cmd --shader ./examples/oklab.gdshader --output ./output.png
```
```
./Fragmented cmd --shader ./examples/oklab.gdshader --load-image ~/Pictures/test.png --output ./output.png
```

8
examples/blur.gdshader Normal file
View file

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

View file

@ -1,6 +1,6 @@
shader_type canvas_item;
#include "res://shaderlib/hsv.gdshaderinc"
#include "res://shaderlib/colorspaces.gdshaderinc"
#include "res://shaderlib/effects.gdshaderinc"
//!load ./images/swamp.jpg
@ -8,6 +8,7 @@ shader_type canvas_item;
void fragment() {
COLOR = pixelate(TEXTURE, UV, 200.0);
vec4 hsv = rgb2hsv(COLOR);
COLOR = hsv_offset(COLOR, 0.65, .42-(hsv.y*.3), -.125);
COLOR = hsv_multiply(COLOR, 1.0, 1.0, 1.25);
hsv.xyz += vec3(0.65, .42-(hsv.y*.3), -.125);
hsv.xyz *= vec3(1.0, 1.0, 1.25);
COLOR = hsv2rgb(hsv);
}

12
examples/oklab.gdshader Normal file
View file

@ -0,0 +1,12 @@
shader_type canvas_item;
#include "res://shaderlib/colorspaces.gdshaderinc"
//!load ./images/swamp.jpg
void fragment() {
vec4 oklab = rgb2oklab(COLOR);
vec4 oklch = oklab2oklch(oklab);
oklch.z -= 2.0;
COLOR = oklab2rgb(oklch2oklab(oklch));
}

View file

@ -11,7 +11,7 @@ config_version=5
[application]
config/name="Fragmented"
config/version="v6.2"
config/version="v7.0"
run/main_scene="res://scenes/main.tscn"
config/features=PackedStringArray("4.3", "Mobile")
run/low_processor_mode=true

View file

@ -1,7 +1,7 @@
[gd_scene load_steps=10 format=3 uid="uid://bjah7k4bxo044"]
[ext_resource type="Script" path="res://src/Main.gd" id="1_2625y"]
[ext_resource type="Script" path="res://src/Compositor.gd" id="2_hvo65"]
[ext_resource type="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"]
@ -20,14 +20,7 @@ script = ExtResource("1_2625y")
[node name="Compositor" type="SubViewport" parent="."]
unique_name_in_owner = true
disable_3d = true
transparent_bg = true
canvas_item_default_texture_filter = 0
render_target_update_mode = 4
script = ExtResource("2_hvo65")
[node name="ImageSprite" type="Sprite2D" parent="Compositor"]
unique_name_in_owner = true
script = ExtResource("2_4thch")
[node name="ImageViewportDisplay" type="Sprite2D" parent="."]
unique_name_in_owner = true
@ -44,6 +37,7 @@ 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")]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 635 KiB

After

Width:  |  Height:  |  Size: 630 KiB

Before After
Before After

View file

@ -0,0 +1,32 @@
/*
gaussian_blur adapted from https://godotshaders.com/shader/customizable-gausian-blur/
original code by https://godotshaders.com/author/djbob-gaming-yt/
maximum radius is 64
*/
vec4 gaussian_blur(sampler2D texture, vec2 uv, int radius, float sigma) {
vec2 resolution = 1.0 / vec2(textureSize(texture, 0));
// calculate kernel
float kernel[64];
float sum = 0.0;
for (int i = 0; i <= radius; i++) {
kernel[i] = exp(-0.5 * float(i * i) / (sigma * sigma));
sum += i == 0 ? kernel[i] : 2.0 * kernel[i];
}
for (int i = 0; i <= radius; i++) {
kernel[i] /= sum;
}
//
vec4 final_color = vec4(0.0);
float total_weight = 0.0;
for (int x = -radius; x <= radius; x++) {
for (int y = -radius; y <= radius; y++) {
float weight = kernel[abs(x)] * kernel[abs(y)];
vec2 offset = vec2(float(x), float(y)) * resolution;
final_color += texture(texture, uv + offset) * weight;
total_weight += weight;
}
}
final_color /= total_weight;
return final_color;
}

View file

@ -0,0 +1,90 @@
/*
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
*/
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;
float s = 0.0883024619f * c.r + 0.2817188376f * c.g + 0.6299787005f * c.b;
float l_ = cbrt(l);
float m_ = cbrt(m);
float s_ = cbrt(s);
return vec4(
0.2104542553f*l_ + 0.7936177850f*m_ - 0.0040720468f*s_,
1.9779984951f*l_ - 2.4285922050f*m_ + 0.4505937099f*s_,
0.0259040371f*l_ + 0.7827717662f*m_ - 0.8086757660f*s_,
c.a
);
}
vec4 oklab2rgb(vec4 c) {
float l_ = c.x + 0.3963377774f * c.y + 0.2158037573f * c.z;
float m_ = c.x - 0.1055613458f * c.y - 0.0638541728f * c.z;
float s_ = c.x - 0.0894841775f * c.y - 1.2914855480f * c.z;
float l = l_*l_*l_;
float m = m_*m_*m_;
float s = s_*s_*s_;
return vec4(
+4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s,
-1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s,
-0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s,
c.a
);
}
vec4 oklab2oklch(vec4 c) {
return vec4(
c.x,
sqrt((c.y * c.y) + (c.z * c.z)),
atan(c.z, c.y),
c.a
);
}
vec4 oklch2oklab(vec4 c) {
return vec4(
c.x,
c.y * cos(c.z),
c.y * sin(c.z),
c.a
);
}

View file

@ -0,0 +1,5 @@
// inefficient cuberoot function
float cbrt(float x) {
return pow(x, 1.0/3.0);
}

View file

@ -1,7 +1,9 @@
/* glslSmartDenoise by Michele Morrone, adapted
/*
glslSmartDenoise by Michele Morrone, adapted
original code: https://github.com/BrutPitt/glslSmartDeNoise
license of the original code:
BSD 2-Clause License
Copyright (c) 2019-2020 Michele Morrone

View file

@ -1,50 +0,0 @@
// 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);
}
// Offset the hue, saturation and brightness of a RGB color
vec4 hsv_offset(
vec4 rgba,
float offset_hue,
float offset_saturation,
float offset_brightness
) {
vec4 c = rgb2hsv(rgba);
c.x += offset_hue;
c.y += offset_saturation;
c.z += offset_brightness;
return hsv2rgb(c);
}
// Multiply the hue, saturation and brightness of a RGB color
vec4 hsv_multiply(
vec4 rgba,
float mult_hue,
float mult_saturation,
float mult_brightness
) {
vec4 c = rgb2hsv(rgba);
c.x *= mult_hue;
c.y *= mult_saturation;
c.z *= mult_brightness;
return hsv2rgb(c);
}

View file

@ -1,6 +1,8 @@
// Load in a texture from a sampler2D with an offset and scale
// See examples/place_texture.gdshader
/*
Load in a texture from a sampler2D with an offset and scale
See examples/place_texture.gdshader
*/
vec4 place_texture(sampler2D sampler, vec2 uv, vec2 texture_pixel_size, vec2 offset, vec2 scale) {
vec2 texture_size = vec2(textureSize(sampler, 0));
// position of current pixel; sample color c

View file

@ -1,6 +1,8 @@
// Alpha Blending a over b after Bruce A. Wallace
// source: https://en.wikipedia.org/wiki/Alpha_compositing
/*
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);

View file

@ -129,18 +129,20 @@ const gdshader_preprocessor = [
]
# shaderlib
var shaderlib_regex = {
"hsv": RegEx.create_from_string(r'\s*\#include\s+\"res\:\/\/shaderlib\/hsv\.gdshaderinc\"'),
"colorspaces": RegEx.create_from_string(r'\s*\#include\s+\"res\:\/\/shaderlib\/colorspaces\.gdshaderinc\"'),
"transform": RegEx.create_from_string(r'\s*\#include\s+\"res\:\/\/shaderlib\/transform\.gdshaderinc\"'),
"transparency": RegEx.create_from_string(r'\s*\#include\s+\"res\:\/\/shaderlib\/transparency\.gdshaderinc\"'),
"effects": RegEx.create_from_string(r'\s*\#include\s+\"res\:\/\/shaderlib\/effects\.gdshaderinc\"'),
"denoise": RegEx.create_from_string(r'\s*\#include\s+\"res\:\/\/shaderlib\/denoise\.gdshaderinc\"')
"denoise": RegEx.create_from_string(r'\s*\#include\s+\"res\:\/\/shaderlib\/denoise\.gdshaderinc\"'),
"blur": RegEx.create_from_string(r'\s*\#include\s+\"res\:\/\/shaderlib\/blur\.gdshaderinc\"'),
}
const shaderlib_functions = {
"hsv": ["rgb2hsv", "hsv2rgb", "hsv_offset", "hsv_multiply"],
"colorspaces": ["rgb2hsv", "hsv2rgb", "oklab2rgb", "rgb2oklab", "oklab2oklch", "oklch2oklab"],
"transform": ["place_texture"],
"transparency": ["alpha_blend"],
"effects": ["pixelate"],
"denoise": ["smart_denoise"]
"denoise": ["smart_denoise"],
"blur": ["gaussian_blur"],
}
#
# configure Highlighter

View file

@ -1,9 +1,22 @@
extends SubViewport
class_name ImageCompositor extends SubViewport
var image_sprite: Sprite2D
func _init() -> void:
# Overwrite some variables
self.render_target_update_mode = SubViewport.UPDATE_ALWAYS
self.disable_3d = true
self.transparent_bg = true
self.canvas_item_default_texture_filter = Viewport.DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_NEAREST
self.image_sprite = Sprite2D.new()
@onready var camera = %Camera
@onready var image_sprite = %ImageSprite
@onready var image_viewport_display = %ImageViewportDisplay
func _ready() -> void:
# Add image sprite as child to be rendered
self.add_child(image_sprite)
var _fragment_function_regex: RegEx = RegEx.create_from_string(r'\s*void\s+fragment\s*\(\s*\)\s*{\s*')
func validate_shader_compilation(shader: Shader) -> bool:
@ -31,7 +44,7 @@ func inject_step_uniform(shader_code: String) -> Shader:
shader.code = shader_code.insert(fragment_function_match.get_start(), "\nuniform int STEP;")
return shader
func update() -> Array: # returns error messages (strings)
func update(overwrite_image_path: String = "") -> Array: # returns error messages (strings)
# inject STEP uniform & get number of steps
var shader: Shader = inject_step_uniform(Filesystem.shader_code)
var steps: int = ShaderDirectiveParser.parse_steps_directive(shader.code)
@ -40,11 +53,15 @@ func update() -> Array: # returns error messages (strings)
return ["Shader compilation failed!"]
var errors = []
# load texture(s) from //!load directive -> TEXTURE
var original_image_path = ""
if overwrite_image_path == "":
var m = ShaderDirectiveParser.parse_load_directive(shader.code)
if len(m) < 1:
errors.append("Didn't find a load directive!")
return errors
var original_image_path = Filesystem.get_absolute_path(m[1])
original_image_path = Filesystem.get_absolute_path(m[1])
else:
original_image_path = overwrite_image_path
var fit_image = false
if original_image_path != Filesystem.last_original_image_path:
fit_image = true

View file

@ -4,7 +4,54 @@ extends Node
@onready var ui_container = %UserInterfaceContainer
@onready var app_name = ProjectSettings.get_setting("application/config/name")
func show_help():
print(
"Usage:\n\n",
"./Fragmented cmd --shader PATH [--load-image PATH]\n\n",
" --shader PATH The path to the shader\n",
" --output PATH Where to write the resulting image to\n",
" --load-image PATH The path to the image. This will overwrite the\n",
" load directive of the shader file (optional)\n")
func parse_custom_cmdline(args: PackedStringArray):
var kwargs: Dictionary = {"--shader": null, "--output": null, "--load-image": null}
var args_len = args.size()
var i = 0
while i < args_len:
var a = args[i]
if a in kwargs && args_len > i+1:
i += 1
kwargs[a] = args[i]
i += 1
return kwargs
func _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)
get_tree().quit(1)
else:
Filesystem.save_result(kwargs["--output"])
get_tree().quit(0)
else:
update_title()
# position windows
get_window().position = Vector2i(
@ -12,6 +59,7 @@ func _ready():
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 != "":