From e5f965ac51faa41c8fa7850741210474754a3722 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Hamy?= Date: Sat, 16 Aug 2025 11:21:00 +0200 Subject: [PATCH] Add the luminosity effect, needs more testing --- src/effects/effects.js | 64 +++++++- src/effects/luminosity.glsl | 33 ++++ src/effects/luminosity.js | 141 ++++++++++++++++++ .../pipelines_management/effect_row.js | 12 +- 4 files changed, 246 insertions(+), 4 deletions(-) create mode 100644 src/effects/luminosity.glsl create mode 100644 src/effects/luminosity.js diff --git a/src/effects/effects.js b/src/effects/effects.js index 931d3455..85e65523 100644 --- a/src/effects/effects.js +++ b/src/effects/effects.js @@ -11,6 +11,7 @@ import { PixelizeEffect } from './pixelize.js'; import { DerivativeEffect } from './derivative.js'; import { RgbToHslEffect } from './rgb_to_hsl.js'; import { HslToRgbEffect } from './hsl_to_rgb.js'; +import { LuminosityEffect } from './luminosity.js'; // We do in this way because I've not found another way to store our preferences in a dictionnary // while calling `gettext` on it while in preferences. Not so pretty, but works. @@ -33,6 +34,7 @@ export function get_effects_groups(_ = _ => "") { "derivative", "noise", "color", + "luminosity", "rgb_to_hsl", "hsl_to_rgb" ] @@ -167,11 +169,71 @@ export function get_supported_effects(_ = () => "") { color: { name: _("Color"), description: _("The color to blend in. The blending amount is controled by the opacity of the color."), - type: "rgba" + type: "rgba", + use_alpha: true, } } }, + luminosity: { + class: LuminosityEffect, + name: _("Luminosity"), + description: _("An effect that affects the luminosity of the image."), + is_advanced: false, + editable_params: { + brightness_shift: { + name: _("Shift brightness"), + description: _("The brightness to add of remove to the image."), + type: "float", + min: -1., + max: 1., + increment: 0.01, + big_increment: 0.1, + digits: 2 + }, + brightness_multiplicator: { + name: _("Multiply brightness"), + description: _("The brightness multiplicator of the image, so that 0 means no brightness and 2 means infinite brightness."), + type: "float", + min: 0., + max: 2., + increment: 0.01, + big_increment: 0.1, + digits: 2 + }, + contrast: { + name: _("Contrast"), + description: _("The contrast of the image in regard to the center of the contrast."), + type: "float", + min: 0., + max: 2., + increment: 0.01, + big_increment: 0.1, + digits: 2 + }, + contrast_center: { + name: _("Contrast center"), + description: _("The center of the contrast to use."), + type: "float", + min: 0., + max: 1., + increment: 0.01, + big_increment: 0.1, + digits: 2 + }, + saturation_multiplicator: { + name: _("Saturation"), + description: _("The saturation of the image, so that 0 means no saturation and 2 means infinite saturation."), + type: "float", + min: 0., + max: 2., + increment: 0.01, + big_increment: 0.1, + digits: 2 + }, + } + }, + pixelize: { class: PixelizeEffect, name: _("Pixelize"), diff --git a/src/effects/luminosity.glsl b/src/effects/luminosity.glsl new file mode 100644 index 00000000..4c50ca49 --- /dev/null +++ b/src/effects/luminosity.glsl @@ -0,0 +1,33 @@ +uniform sampler2D tex; +uniform float brightness_shift; +uniform float brightness_multiplicator; +uniform float contrast; +uniform float contrast_center; +uniform float saturation_multiplicator; + +vec3 hsl_to_rgb(vec3 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); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +vec3 rgb_to_hsl(vec3 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 vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +void main() { + vec4 c = texture2D(tex, cogl_tex_coord_in[0].st); + + vec3 pix_hsl = rgb_to_hsl(c.xyz) / c.a; + pix_hsl.z = clamp(pix_hsl.z * brightness_multiplicator, 0., 1.); + pix_hsl.z = clamp((pix_hsl.z - contrast_center) * contrast + contrast_center + brightness_shift, 0., 1.); + pix_hsl.y = clamp(pix_hsl.y * saturation_multiplicator, 0., 1.); + + cogl_color_out = vec4(hsl_to_rgb(pix_hsl) * c.a, c.a); +} \ No newline at end of file diff --git a/src/effects/luminosity.js b/src/effects/luminosity.js new file mode 100644 index 00000000..c22636dc --- /dev/null +++ b/src/effects/luminosity.js @@ -0,0 +1,141 @@ +import GObject from 'gi://GObject'; + +import * as utils from '../conveniences/utils.js'; +const Shell = await utils.import_in_shell_only('gi://Shell'); +const Clutter = await utils.import_in_shell_only('gi://Clutter'); + +const SHADER_FILENAME = 'luminosity.glsl'; +const DEFAULT_PARAMS = { + brightness_shift: 0., brightness_multiplicator: 1., contrast: 1., contrast_center: 0.5, saturation_multiplicator: 1. +}; + + +export const LuminosityEffect = utils.IS_IN_PREFERENCES ? + { default_params: DEFAULT_PARAMS } : + new GObject.registerClass({ + GTypeName: "LuminosityEffect", + Properties: { + 'brightness_shift': GObject.ParamSpec.double( + `brightness_shift`, + `Brightness shift`, + `Brightness shift value in shader`, + GObject.ParamFlags.READWRITE, + -1.0, 1.0, + 0.0, + ), + 'brightness_multiplicator': GObject.ParamSpec.double( + `brightness_multiplicator`, + `Brightness multiplicator`, + `Brightness multiplicator value in shader`, + GObject.ParamFlags.READWRITE, + 0.0, 2.0, + 1.0, + ), + 'contrast': GObject.ParamSpec.double( + `contrast`, + `Contrast`, + `Contrast value in shader`, + GObject.ParamFlags.READWRITE, + 0.0, 2.0, + 1.0, + ), + 'contrast_center': GObject.ParamSpec.double( + `contrast_center`, + `Contrast center`, + `Contrast center value in shader`, + GObject.ParamFlags.READWRITE, + 0.0, 1.0, + 0.5, + ), + 'saturation_multiplicator': GObject.ParamSpec.double( + `saturation_multiplicator`, + `Saturation multiplicator`, + `Saturation multiplicator value in shader`, + GObject.ParamFlags.READWRITE, + 0.0, 2.0, + 1.0, + ), + } + }, class LuminosityEffect extends Clutter.ShaderEffect { + constructor(params) { + super(params); + + utils.setup_params(this, params); + + // set shader source + this._source = utils.get_shader_source(Shell, SHADER_FILENAME, import.meta.url); + if (this._source) + this.set_shader_source(this._source); + } + + static get default_params() { + return DEFAULT_PARAMS; + } + + get brightness_shift() { + return this._brightness; + } + + set brightness_shift(value) { + if (this._brightness_shift !== value) { + this._brightness_shift = value; + + this.set_uniform_value('brightness_shift', parseFloat(this._brightness_shift - 1e-6)); + } + } + + + get brightness_multiplicator() { + return this._brightness_multiplicator; + } + + set brightness_multiplicator(value) { + if (this._brightness_multiplicator !== value) { + this._brightness_multiplicator = value; + + let brightness_mul = 600.; + if (value < 1.995) + brightness_mul = 3. * (1. / (1. - (value / 2.) ** 2) - 1.); + this.set_uniform_value('brightness_multiplicator', parseFloat(brightness_mul - 1e-6)); + } + } + + get contrast() { + return this._contrast; + } + + set contrast(value) { + if (this._contrast !== value) { + this._contrast = value; + + this.set_uniform_value('contrast', parseFloat(this._contrast - 1e-6)); + } + } + + get contrast_center() { + return this._contrast_center; + } + + set contrast_center(value) { + if (this._contrast_center !== value) { + this._contrast_center = value; + + this.set_uniform_value('contrast_center', parseFloat(this._contrast_center - 1e-6)); + } + } + + get saturation_multiplicator() { + return this._saturation_multiplicator; + } + + set saturation_multiplicator(value) { + if (this._saturation_multiplicator !== value) { + this._saturation_multiplicator = value; + + let saturation_mul = 600.; + if (value < 1.995) + saturation_mul = 3. * (1. / (1. - (value / 2.) ** 2) - 1.); + this.set_uniform_value('saturation_multiplicator', parseFloat(saturation_mul - 1e-6)); + } + } + }); \ No newline at end of file diff --git a/src/preferences/pipelines_management/effect_row.js b/src/preferences/pipelines_management/effect_row.js index 130edfdb..3a95d49e 100644 --- a/src/preferences/pipelines_management/effect_row.js +++ b/src/preferences/pipelines_management/effect_row.js @@ -154,18 +154,24 @@ export const EffectRow = GObject.registerClass({ width_request: 75, height_request: 45, show_editor: true, - use_alpha: true + use_alpha: param.use_alpha }); row.add_suffix(color_button); // set original color let c = color_button.get_rgba().copy(); - [c.red, c.green, c.blue, c.alpha] = this.get_effect_param(param_key); + if (param.use_alpha) + [c.red, c.green, c.blue, c.alpha] = this.get_effect_param(param_key); + else + [c.red, c.green, c.blue] = this.get_effect_param(param_key); color_button.set_rgba(c); // update on on 'color-set' color_button.connect( 'color-set', () => { let c = color_button.get_rgba(); - this.set_effect_param(param_key, [c.red, c.green, c.blue, c.alpha]); + if (param.use_alpha) + this.set_effect_param(param_key, [c.red, c.green, c.blue, c.alpha]); + else + this.set_effect_param(param_key, [c.red, c.green, c.blue]); } ); break;