diff --git a/src/effects/color.glsl b/src/effects/color.glsl index 142b89bf..5df65121 100644 --- a/src/effects/color.glsl +++ b/src/effects/color.glsl @@ -3,11 +3,109 @@ uniform float red; uniform float green; uniform float blue; uniform float blend; +uniform int mode; + +const int NORMAL = 0; +const int MULTIPLY = 1; +const int SCREEN = 2; +const int OVERLAY = 3; +const int DARKEN = 4; +const int LIGHTEN = 5; +const int PLUS_DARKER = 6; +const int PLUS_LIGHTER = 7; +const int COLOR_DODGE = 8; +const int COLOR_BURN = 9; +const int HARD_LIGHT = 10; +const int SOFT_LIGHT = 11; +const int DIFFERENCE = 12; +const int EXCLUSION = 13; +const int HUE = 14; +const int SATURATION = 15; +const int COLOR = 16; +const int LUMINOSITY = 17; + +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); +} + +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); +} + +float soft_light_channel(float base, float _blend) { + if (_blend < 0.5) { + return base - (1.0 - 2.0 * _blend) * base * (1.0 - base); + } else { + float d = (base < 0.25) + ? ((16.0 * base - 12.0) * base + 4.0) * base + : sqrt(base); + return base + (2.0 * _blend - 1.0) * (d - base); + } +} + +vec3 get_blend(vec3 base, vec3 _blend) { + if (mode == MULTIPLY) return base * _blend; + if (mode == SCREEN) return 1 - (1 - base) * (1 - _blend); + if (mode == OVERLAY) { + vec3 result; + result.r = base.r < 0.5 ? (2.0 * base.r * _blend.r) : (1.0 - 2.0 * (1.0 - base.r) * (1.0 - _blend.r)); + result.g = base.g < 0.5 ? (2.0 * base.g * _blend.g) : (1.0 - 2.0 * (1.0 - base.g) * (1.0 - _blend.g)); + result.b = base.b < 0.5 ? (2.0 * base.b * _blend.b) : (1.0 - 2.0 * (1.0 - base.b) * (1.0 - _blend.b)); + return result; + } + if (mode == DARKEN) return min(base, _blend); + if (mode == LIGHTEN) return max(base, _blend); + if (mode == PLUS_DARKER) return base + _blend - 1; + if (mode == PLUS_LIGHTER) return base + _blend; + if (mode == COLOR_DODGE) return base / (1 - _blend); + if (mode == COLOR_BURN) return 1 - (1 - base) / _blend; + if (mode == HARD_LIGHT) { + vec3 result; + result.r = _blend.r < 0.5 ? (2.0 * base.r * _blend.r) : (1.0 - 2.0 * (1.0 - base.r) * (1.0 - _blend.r)); + result.g = _blend.g < 0.5 ? (2.0 * base.g * _blend.g) : (1.0 - 2.0 * (1.0 - base.g) * (1.0 - _blend.g)); + result.b = _blend.b < 0.5 ? (2.0 * base.b * _blend.b) : (1.0 - 2.0 * (1.0 - base.b) * (1.0 - _blend.b)); + return result; + } + if (mode == SOFT_LIGHT) { + return vec3(soft_light_channel(base.r, _blend.r), soft_light_channel(base.g, _blend.g), soft_light_channel(base.b, _blend.b)); + } + if (mode == DIFFERENCE) return abs(base - _blend); + if (mode == EXCLUSION) return 0.5 - 2 * (base - 0.5) * (_blend - 0.5); + if (mode == HUE) { + vec3 base_hsl = rgb_to_hsl(base); + vec3 blend_hsl = rgb_to_hsl(_blend); + return hsl_to_rgb(vec3(blend_hsl.x, base_hsl.y, base_hsl.z)); + } + if (mode == SATURATION) { + vec3 base_hsl = rgb_to_hsl(base); + vec3 blend_hsl = rgb_to_hsl(_blend); + return hsl_to_rgb(vec3(base_hsl.x, blend_hsl.y, base_hsl.z)); + } + if (mode == COLOR) { + vec3 base_hsl = rgb_to_hsl(base); + vec3 blend_hsl = rgb_to_hsl(_blend); + return hsl_to_rgb(vec3(blend_hsl.x, blend_hsl.y, base_hsl.z)); + } + if (mode == LUMINOSITY) { + vec3 base_hsl = rgb_to_hsl(base); + vec3 blend_hsl = rgb_to_hsl(_blend); + return hsl_to_rgb(vec3(base_hsl.x, base_hsl.y, blend_hsl.z)); + } + return _blend; // For NORMAL +} void main() { vec4 c = texture2D(tex, cogl_tex_coord_in[0].st); vec3 pix_color = c.xyz; - vec3 color = vec3(red, green, blue); + vec3 color = get_blend(pix_color, vec3(red, green, blue)); cogl_color_out = vec4(mix(pix_color, color, blend), 1.); } \ No newline at end of file diff --git a/src/effects/color.js b/src/effects/color.js index 7de753d6..1f4bdf4a 100644 --- a/src/effects/color.js +++ b/src/effects/color.js @@ -1,12 +1,14 @@ 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 = 'color.glsl'; const DEFAULT_PARAMS = { - color: [0.0, 0.0, 0.0, 0.0] + color: [0.0, 0.0, 0.0, 0.0], + blend_mode: 0 }; @@ -47,7 +49,18 @@ export const ColorEffect = utils.IS_IN_PREFERENCES ? 0.0, 1.0, 0.0, ), + 'blend_mode': GObject.ParamSpec.int( + `blend_mode`, + `Blend mode`, + `Blend mode`, + GObject.ParamFlags.READWRITE, + 0, 17, + 0, + ) } + // Normal (0), Multiply (1), Screen (2), Overlay (3), Darken (4), Lighten (5), Plus darker (6), Plus lighter (7), Color dodge (8), + // Color burn (9), Hard light (10), Soft light (11), Difference (12), Exclusion (13), Hue (14), Saturation (15), Color (16), + // Luminosity (17) }, class ColorEffect extends Clutter.ShaderEffect { constructor(params) { // initialize without color as a parameter @@ -58,14 +71,16 @@ export const ColorEffect = utils.IS_IN_PREFERENCES ? this._green = null; this._blue = null; this._blend = null; + this._blend_mode = null; // set shader source this._source = utils.get_shader_source(Shell, SHADER_FILENAME, import.meta.url); if (this._source) this.set_shader_source(this._source); - // set shader color + // set params; utils.setup_params doesn't work here with color this.color = 'color' in params ? color : this.constructor.default_params.color; + this.blend_mode = 'blend_mode' in params ? params.blend_mode : this.constructor.default_params.blend_mode; } static get default_params() { @@ -121,6 +136,18 @@ export const ColorEffect = utils.IS_IN_PREFERENCES ? } } + get blend_mode() { + return this._blend_mode; + } + + set blend_mode(value) { + if (this._blend_mode !== value) { + this._blend_mode = value; + + this.set_uniform_value('mode', this._blend_mode); + } + } + set color(rgba) { let [r, g, b, a] = rgba; this.red = r; @@ -136,5 +163,6 @@ export const ColorEffect = utils.IS_IN_PREFERENCES ? /// False set function, only cares about the color. Too hard to change. set(params) { this.color = params.color; + this.blend_mode = params.blend_mode; } }); \ No newline at end of file diff --git a/src/effects/effects.js b/src/effects/effects.js index 931d3455..e35588d6 100644 --- a/src/effects/effects.js +++ b/src/effects/effects.js @@ -168,6 +168,31 @@ export function get_supported_effects(_ = () => "") { name: _("Color"), description: _("The color to blend in. The blending amount is controled by the opacity of the color."), type: "rgba" + }, + blend_mode: { + name: _("Blend mode"), + description: _("How the color is blended in."), + type: "dropdown", + options: [ + _("Normal"), + _("Multiply"), + _("Screen"), + _("Overlay"), + _("Darken"), + _("Lighten"), + _("Plus darker"), + _("Plus lighter"), + _("Color dodge"), + _("Color burn"), + _("Hard light"), + _("Soft light"), + _("Difference"), + _("Exclusion"), + _("Hue"), + _("Saturation"), + _("Color"), + _("Luminosity") + ] } } },