This commit is contained in:
2024-07-08 22:46:35 +02:00
parent 02f44c49d2
commit 27254d817a
56249 changed files with 808097 additions and 1 deletions

View File

@ -0,0 +1,13 @@
uniform sampler2D tex;
uniform float red;
uniform float green;
uniform float blue;
uniform float blend;
void main() {
vec4 c = texture2D(tex, cogl_tex_coord_in[0].st);
vec3 pix_color = c.xyz;
vec3 color = vec3(red, green, blue);
cogl_color_out = vec4(mix(pix_color, color, blend), 1.);
}

View File

@ -0,0 +1,151 @@
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]
};
export const ColorEffect = utils.IS_IN_PREFERENCES ?
{ default_params: DEFAULT_PARAMS } :
new GObject.registerClass({
GTypeName: "ColorEffect",
Properties: {
'red': GObject.ParamSpec.double(
`red`,
`Red`,
`Red value in shader`,
GObject.ParamFlags.READWRITE,
0.0, 1.0,
0.0,
),
'green': GObject.ParamSpec.double(
`green`,
`Green`,
`Green value in shader`,
GObject.ParamFlags.READWRITE,
0.0, 1.0,
0.0,
),
'blue': GObject.ParamSpec.double(
`blue`,
`Blue`,
`Blue value in shader`,
GObject.ParamFlags.READWRITE,
0.0, 1.0,
0.0,
),
'blend': GObject.ParamSpec.double(
`blend`,
`Blend`,
`Amount of blending between the colors`,
GObject.ParamFlags.READWRITE,
0.0, 1.0,
0.0,
),
}
}, class ColorEffect extends Clutter.ShaderEffect {
constructor(params) {
// initialize without color as a parameter
const { color, ...parent_params } = params;
super(parent_params);
this._red = null;
this._green = null;
this._blue = null;
this._blend = 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
this.color = 'color' in params ? color : this.constructor.default_params.color;
}
static get default_params() {
return DEFAULT_PARAMS;
}
get red() {
return this._red;
}
set red(value) {
if (this._red !== value) {
this._red = value;
this.set_uniform_value('red', parseFloat(this._red - 1e-6));
}
}
get green() {
return this._green;
}
set green(value) {
if (this._green !== value) {
this._green = value;
this.set_uniform_value('green', parseFloat(this._green - 1e-6));
}
}
get blue() {
return this._blue;
}
set blue(value) {
if (this._blue !== value) {
this._blue = value;
this.set_uniform_value('blue', parseFloat(this._blue - 1e-6));
}
}
get blend() {
return this._blend;
}
set blend(value) {
if (this._blend !== value) {
this._blend = value;
this.set_uniform_value('blend', parseFloat(this._blend - 1e-6));
this.set_enabled(this.blend > 0);
}
}
set color(rgba) {
let [r, g, b, a] = rgba;
this.red = r;
this.green = g;
this.blue = b;
this.blend = a;
}
get color() {
return [this.red, this.green, this.blue, this.blend];
}
/// False set function, only cares about the color. Too hard to change.
set(params) {
this.color = params.color;
}
vfunc_paint_target(paint_node = null, paint_context = null) {
this.set_uniform_value("tex", 0);
if (paint_node && paint_context)
super.vfunc_paint_target(paint_node, paint_context);
else if (paint_node)
super.vfunc_paint_target(paint_node);
else
super.vfunc_paint_target();
}
});

View File

@ -0,0 +1,93 @@
// Heavily based on https://github.com/yilozt/rounded-window-corners
// which is itself based on upstream Mutter code
uniform sampler2D tex;
uniform float radius;
uniform float width;
uniform float height;
uniform bool corners_top;
uniform bool corners_bottom;
uniform float clip_x0;
uniform float clip_y0;
uniform float clip_width;
uniform float clip_height;
float circle_bounds(vec2 p, vec2 center, float clip_radius) {
vec2 delta = p - center;
float dist_squared = dot(delta, delta);
float outer_radius = clip_radius + 0.5;
if (dist_squared >= (outer_radius * outer_radius))
return 0.0;
float inner_radius = clip_radius - 0.5;
if (dist_squared <= (inner_radius * inner_radius))
return 1.0;
return outer_radius - sqrt(dist_squared);
}
vec4 getTexture(vec2 uv) {
if (uv.x < 2. / width)
uv.x = 2. / width;
if (uv.y < 2. / height)
uv.y = 2. / height;
if (uv.x > 1. - 3. / width)
uv.x = 1. - 3. / width;
if (uv.y > 1. - 3. / height)
uv.y = 1. - 3. / height;
return texture2D(tex, uv);
}
float rounded_rect_coverage(vec2 p, vec4 bounds, float clip_radius) {
// Outside the bounds
if (p.x < bounds.x || p.x > bounds.z || p.y < bounds.y || p.y > bounds.w) {
return 0.;
}
vec2 center;
float center_left = bounds.x + clip_radius;
float center_right = bounds.z - clip_radius;
if (p.x < center_left)
center.x = center_left + 2.;
else if (p.x > center_right)
center.x = center_right - 1.;
else
return 1.0;
float center_top = bounds.y + clip_radius;
float center_bottom = bounds.w - clip_radius;
if (corners_top && p.y < center_top)
center.y = center_top + 2.;
else if (corners_bottom && p.y > center_bottom)
center.y = center_bottom - 1.;
else
return 1.0;
return circle_bounds(p, center, clip_radius);
}
void main(void) {
vec2 uv = cogl_tex_coord_in[0].xy;
vec2 pos = uv * vec2(width, height);
vec4 c = getTexture(uv);
vec4 bounds;
if (clip_width < 0. || clip_height < 0.) {
bounds = vec4(clip_x0, clip_y0, clip_x0 + width, clip_y0 + height);
} else {
bounds = vec4(clip_x0, clip_y0, clip_x0 + clip_width, clip_y0 + clip_height);
}
float alpha = rounded_rect_coverage(pos, bounds, radius);
cogl_color_out = vec4(c.rgb * alpha, min(alpha, c.a));
}

View File

@ -0,0 +1,233 @@
import GObject from 'gi://GObject';
import * as utils from '../conveniences/utils.js';
const St = await utils.import_in_shell_only('gi://St');
const Shell = await utils.import_in_shell_only('gi://Shell');
const Clutter = await utils.import_in_shell_only('gi://Clutter');
const SHADER_FILENAME = 'corner.glsl';
const DEFAULT_PARAMS = {
radius: 12, width: 0, height: 0,
corners_top: true, corners_bottom: true,
clip: [0, 0, -1, -1]
};
export const CornerEffect = utils.IS_IN_PREFERENCES ?
{ default_params: DEFAULT_PARAMS } :
new GObject.registerClass({
GTypeName: "CornerEffect",
Properties: {
'radius': GObject.ParamSpec.double(
`radius`,
`Corner Radius`,
`Corner Radius`,
GObject.ParamFlags.READWRITE,
0, Number.MAX_SAFE_INTEGER,
12,
),
'width': GObject.ParamSpec.double(
`width`,
`Width`,
`Width`,
GObject.ParamFlags.READWRITE,
0.0, Number.MAX_SAFE_INTEGER,
0.0,
),
'height': GObject.ParamSpec.double(
`height`,
`Height`,
`Height`,
GObject.ParamFlags.READWRITE,
0.0, Number.MAX_SAFE_INTEGER,
0.0,
),
'corners_top': GObject.ParamSpec.boolean(
`corners_top`,
`Round top corners`,
`Round top corners`,
GObject.ParamFlags.READWRITE,
true,
),
'corners_bottom': GObject.ParamSpec.boolean(
`corners_bottom`,
`Round bottom corners`,
`Round bottom corners`,
GObject.ParamFlags.READWRITE,
true,
),
// FIXME this works but it logs an error, because I'm not a double...
// I don't want to fiddle with GVariants again
'clip': GObject.ParamSpec.double(
`clip`,
`Clip`,
`Clip`,
GObject.ParamFlags.READWRITE,
0.0, Number.MAX_SAFE_INTEGER,
0.0,
),
}
}, class CornerEffect extends Clutter.ShaderEffect {
constructor(params) {
super(params);
this._radius = null;
this._width = null;
this._height = null;
this._corners_top = null;
this._corners_bottom = null;
this._clip_x0 = null;
this._clip_y0 = null;
this._clip_width = null;
this._clip_height = null;
this.radius = 'radius' in params ? params.radius : this.constructor.default_params.radius;
this.width = 'width' in params ? params.width : this.constructor.default_params.width;
this.height = 'height' in params ? params.height : this.constructor.default_params.height;
this.height = 'corners_top' in params ? params.corners_top : this.constructor.default_params.corners_top;
this.height = 'corners_bottom' in params ? params.corners_bottom : this.constructor.default_params.corners_bottom;
this.clip = 'clip' in params ? params.clip : this.constructor.default_params.clip;
// set shader source
this._source = utils.get_shader_source(Shell, SHADER_FILENAME, import.meta.url);
if (this._source)
this.set_shader_source(this._source);
const theme_context = St.ThemeContext.get_for_stage(global.stage);
theme_context.connectObject('notify::scale-factor', _ => this.update_radius(), this);
}
static get default_params() {
return DEFAULT_PARAMS;
}
get radius() {
return this._radius;
}
set radius(value) {
if (this._radius !== value) {
this._radius = value;
this.update_radius();
}
}
update_radius() {
const theme_context = St.ThemeContext.get_for_stage(global.stage);
let radius = Math.min(
this.radius * theme_context.scale_factor,
this.width / 2, this.height / 2
);
if (this._clip_width >= 0 || this._clip_height >= 0)
radius = Math.min(radius, this._clip_width / 2, this._clip_height / 2);
this.set_uniform_value('radius', parseFloat(radius - 1e-6));
}
get width() {
return this._width;
}
set width(value) {
if (this._width !== value) {
this._width = value;
this.set_uniform_value('width', parseFloat(this._width + 3.0 - 1e-6));
this.update_radius();
}
}
get height() {
return this._height;
}
set height(value) {
if (this._height !== value) {
this._height = value;
this.set_uniform_value('height', parseFloat(this._height + 3.0 - 1e-6));
this.update_radius();
}
}
get corners_top() {
return this._corners_top;
}
set corners_top(value) {
if (this._corners_top !== value) {
this._corners_top = value;
this.set_uniform_value('corners_top', this._corners_top ? 1 : 0);
}
}
get corners_bottom() {
return this._corners_bottom;
}
set corners_bottom(value) {
if (this._corners_bottom !== value) {
this._corners_bottom = value;
this.set_uniform_value('corners_bottom', this._corners_bottom ? 1 : 0);
}
}
get clip() {
return [this._clip_x0, this._clip_y0, this._clip_width, this._clip_height];
}
set clip(value) {
[this._clip_x0, this._clip_y0, this._clip_width, this._clip_height] = value;
this.set_uniform_value('clip_x0', parseFloat(this._clip_x0 - 1e-6));
this.set_uniform_value('clip_y0', parseFloat(this._clip_y0 - 1e-6));
this.set_uniform_value('clip_width', parseFloat(this._clip_width + 3 - 1e-6));
this.set_uniform_value('clip_height', parseFloat(this._clip_height + 3 - 1e-6));
this.update_radius();
}
vfunc_set_actor(actor) {
if (this._actor_connection_size_id) {
let old_actor = this.get_actor();
old_actor?.disconnect(this._actor_connection_size_id);
}
if (this._actor_connection_clip_rect_id) {
let old_actor = this.get_actor();
old_actor?.disconnect(this._actor_connection_clip_rect_id);
}
if (actor) {
this.width = actor.width;
this.height = actor.height;
this._actor_connection_size_id = actor.connect('notify::size', _ => {
this.width = actor.width;
this.height = actor.height;
});
this.clip = actor.has_clip ? actor.get_clip() : [0, 0, -10, -10];
this._actor_connection_clip_rect_id = actor.connect('notify::clip-rect', _ => {
this.clip = actor.has_clip ? actor.get_clip() : [0, 0, -10, -10];
});
}
else {
this._actor_connection_size_id = null;
this._actor_connection_clip_rect_id = null;
}
super.vfunc_set_actor(actor);
}
vfunc_paint_target(paint_node = null, paint_context = null) {
//this.set_uniform_value("tex", 0);
if (paint_node && paint_context)
super.vfunc_paint_target(paint_node, paint_context);
else if (paint_node)
super.vfunc_paint_target(paint_node);
else
super.vfunc_paint_target();
}
});

View File

@ -0,0 +1,226 @@
import { NativeDynamicBlurEffect } from '../effects/native_dynamic_gaussian_blur.js';
import { NativeStaticBlurEffect } from '../effects/native_static_gaussian_blur.js';
import { GaussianBlurEffect } from '../effects/gaussian_blur.js';
import { MonteCarloBlurEffect } from '../effects/monte_carlo_blur.js';
import { ColorEffect } from '../effects/color.js';
import { PixelizeEffect } from './pixelize.js';
import { NoiseEffect } from '../effects/noise.js';
import { CornerEffect } from '../effects/corner.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.
export function get_effects_groups(_ = _ => "") {
return {
blur_effects: {
name: _("Blur effects"),
contains: [
"native_static_gaussian_blur",
"gaussian_blur",
"monte_carlo_blur"
]
},
texture_effects: {
name: _("Texture effects"),
contains: [
"pixelize",
"noise",
"color"
]
},
shape_effects: {
name: _("Shape effects"),
contains: [
"corner"
]
}
};
};
export function get_supported_effects(_ = () => "") {
return {
native_dynamic_gaussian_blur: {
class: NativeDynamicBlurEffect
},
native_static_gaussian_blur: {
class: NativeStaticBlurEffect,
name: _("Native gaussian blur"),
description: _("An optimized blur effect that smoothly blends pixels within a given radius."),
editable_params: {
unscaled_radius: {
name: _("Radius"),
description: _("The intensity of the blur effect."),
type: "float",
min: 0.,
max: 100.,
increment: 1.0,
big_increment: 10.,
digits: 0
},
brightness: {
name: _("Brightness"),
description: _("The brightness of the blur effect, a high value might make the text harder to read."),
type: "float",
min: 0.,
max: 1.,
increment: 0.01,
big_increment: 0.1,
digits: 2
},
}
},
gaussian_blur: {
class: GaussianBlurEffect,
name: _("Gaussian blur"),
description: _("A blur effect that smoothly blends pixels within a given radius. This effect is more precise, but way less optimized."),
editable_params: {
radius: {
name: _("Radius"),
description: _("The intensity of the blur effect. The bigger it is, the slower it will be."),
type: "float",
min: 0.,
max: 100.,
increment: .1,
big_increment: 10.,
digits: 1
},
brightness: {
name: _("Brightness"),
description: _("The brightness of the blur effect, a high value might make the text harder to read."),
type: "float",
min: 0.,
max: 1.,
increment: 0.01,
big_increment: 0.1,
digits: 2
},
}
},
monte_carlo_blur: {
class: MonteCarloBlurEffect,
name: _("Monte Carlo blur"),
description: _("A blur effect that mimics a random walk, by picking pixels further and further away from its origin and mixing them all together."),
editable_params: {
radius: {
name: _("Radius"),
description: _("The maximum travel distance for each step in the random walk. A higher value will make the blur more randomized."),
type: "float",
min: 0.,
max: 10.,
increment: 0.01,
big_increment: 0.1,
digits: 2
},
iterations: {
name: _("Iterations"),
description: _("The number of iterations. The more there are, the smoother the blur is."),
type: "integer",
min: 0,
max: 50,
increment: 1
},
brightness: {
name: _("Brightness"),
description: _("The brightness of the blur effect, a high value might make the text harder to read."),
type: "float",
min: 0.,
max: 1.,
increment: 0.01,
big_increment: 0.1,
digits: 2
},
use_base_pixel: {
name: _("Use base pixel"),
description: _("Whether or not the original pixel is counted for the blur. If it is, the image will be more legible."),
type: "boolean"
}
}
},
color: {
class: ColorEffect,
name: _("Color"),
description: _("An effect that blends a color into the pipeline."),
// TODO make this RGB + blend
editable_params: {
color: {
name: _("Color"),
description: _("The color to blend in. The blending amount is controled by the opacity of the color."),
type: "rgba"
}
}
},
pixelize: {
class: PixelizeEffect,
name: _("Pixelize"),
description: _("An effect that pixelizes the image."),
editable_params: {
divider: {
name: _("Divider"),
description: _("How much to scale down the image."),
type: "integer",
min: 1,
max: 50,
increment: 1
}
}
},
noise: {
class: NoiseEffect,
name: _("Noise"),
description: _("An effect that adds a random noise. Prefer the Monte Carlo blur for a more organic effect if needed."),
editable_params: {
noise: {
name: _("Noise"),
description: _("The amount of noise to add."),
type: "float",
min: 0.,
max: 1.,
increment: 0.01,
big_increment: 0.1,
digits: 2
},
lightness: {
name: _("Lightness"),
description: _("The luminosity of the noise. A setting of '1.0' will make the effect transparent."),
type: "float",
min: 0.,
max: 2.,
increment: 0.01,
big_increment: 0.1,
digits: 2
}
}
},
corner: {
class: CornerEffect,
name: _("Corner"),
description: _("An effect that draws corners. Add it last not to have the other effects perturb the corners."),
editable_params: {
radius: {
name: _("Radius"),
description: _("The radius of the corner. GNOME apps use a radius of 12 px by default."),
type: "integer",
min: 0,
max: 50,
increment: 1,
},
corners_top: {
name: _("Top corners"),
description: _("Whether or not to round the top corners."),
type: "boolean"
},
corners_bottom: {
name: _("Bottom corners"),
description: _("Whether or not to round the bottom corners."),
type: "boolean"
}
}
}
};
};

View File

@ -0,0 +1,70 @@
uniform sampler2D tex;
uniform float sigma;
uniform int dir;
uniform float brightness;
uniform float width;
uniform float height;
vec4 getTexture(vec2 uv) {
if (uv.x < 3. / width)
uv.x = 3. / width;
if (uv.y < 3. / height)
uv.y = 3. / height;
if (uv.x > 1. - 3. / width)
uv.x = 1. - 3. / width;
if (uv.y > 1. - 3. / height)
uv.y = 1. - 3. / height;
return texture2D(tex, uv);
}
void main(void) {
vec2 uv = cogl_tex_coord_in[0].xy;
vec2 direction = vec2(dir, (1.0 - dir));
float pixel_step;
if (dir == 0)
pixel_step = 1.0 / height;
else
pixel_step = 1.0 / width;
vec3 gauss_coefficient;
gauss_coefficient.x = 1.0 / (sqrt(2.0 * 3.14159265) * sigma);
gauss_coefficient.y = exp(-0.5 / (sigma * sigma));
gauss_coefficient.z = gauss_coefficient.y * gauss_coefficient.y;
float gauss_coefficient_total = gauss_coefficient.x;
vec4 ret = getTexture(uv) * gauss_coefficient.x;
gauss_coefficient.xy *= gauss_coefficient.yz;
int n_steps = int(ceil(1.5 * sigma)) * 2;
for (int i = 1; i <= n_steps; i += 2) {
float coefficient_subtotal = gauss_coefficient.x;
gauss_coefficient.xy *= gauss_coefficient.yz;
coefficient_subtotal += gauss_coefficient.x;
float gauss_ratio = gauss_coefficient.x / coefficient_subtotal;
float foffset = float(i) + gauss_ratio;
vec2 offset = direction * foffset * pixel_step;
ret += getTexture(uv + offset) * coefficient_subtotal;
ret += getTexture(uv - offset) * coefficient_subtotal;
gauss_coefficient_total += 2.0 * coefficient_subtotal;
gauss_coefficient.xy *= gauss_coefficient.yz;
}
vec4 outColor = ret / gauss_coefficient_total;
// apply brightness on the second pass (dir==0 comes last)
if (dir == 0) {
outColor.rgb *= brightness;
}
cogl_color_out = outColor;
}

View File

@ -0,0 +1,227 @@
import GObject from 'gi://GObject';
import * as utils from '../conveniences/utils.js';
const St = await utils.import_in_shell_only('gi://St');
const Shell = await utils.import_in_shell_only('gi://Shell');
const Clutter = await utils.import_in_shell_only('gi://Clutter');
const SHADER_FILENAME = 'gaussian_blur.glsl';
const DEFAULT_PARAMS = {
radius: 30, brightness: .6,
width: 0, height: 0, direction: 0
};
export const GaussianBlurEffect = utils.IS_IN_PREFERENCES ?
{ default_params: DEFAULT_PARAMS } :
new GObject.registerClass({
GTypeName: "GaussianBlurEffect",
Properties: {
'radius': GObject.ParamSpec.double(
`radius`,
`Radius`,
`Blur radius`,
GObject.ParamFlags.READWRITE,
0.0, 2000.0,
30.0,
),
'brightness': GObject.ParamSpec.double(
`brightness`,
`Brightness`,
`Blur brightness`,
GObject.ParamFlags.READWRITE,
0.0, 1.0,
0.6,
),
'width': GObject.ParamSpec.double(
`width`,
`Width`,
`Width`,
GObject.ParamFlags.READWRITE,
0.0, Number.MAX_SAFE_INTEGER,
0.0,
),
'height': GObject.ParamSpec.double(
`height`,
`Height`,
`Height`,
GObject.ParamFlags.READWRITE,
0.0, Number.MAX_SAFE_INTEGER,
0.0,
),
'direction': GObject.ParamSpec.int(
`direction`,
`Direction`,
`Direction`,
GObject.ParamFlags.READWRITE,
0, 1,
0,
),
'chained_effect': GObject.ParamSpec.object(
`chained_effect`,
`Chained Effect`,
`Chained Effect`,
GObject.ParamFlags.READABLE,
GObject.Object,
),
}
}, class GaussianBlurEffect extends Clutter.ShaderEffect {
constructor(params) {
super(params);
this._radius = null;
this._brightness = null;
this._width = null;
this._height = null;
this._direction = null;
this._chained_effect = null;
this.radius = 'radius' in params ? params.radius : this.constructor.default_params.radius;
this.brightness = 'brightness' in params ? params.brightness : this.constructor.default_params.brightness;
this.width = 'width' in params ? params.width : this.constructor.default_params.width;
this.height = 'height' in params ? params.height : this.constructor.default_params.height;
this.direction = 'direction' in params ? params.direction : this.constructor.default_params.direction;
// set shader source
this._source = utils.get_shader_source(Shell, SHADER_FILENAME, import.meta.url);
if (this._source)
this.set_shader_source(this._source);
const theme_context = St.ThemeContext.get_for_stage(global.stage);
theme_context.connectObject(
'notify::scale-factor', _ =>
this.set_uniform_value('sigma',
parseFloat(this.radius * theme_context.scale_factor / 2 - 1e-6)
),
this
);
}
static get default_params() {
return DEFAULT_PARAMS;
}
get radius() {
return this._radius;
}
set radius(value) {
if (this._radius !== value) {
this._radius = value;
const scale_factor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
// like Clutter, we use the assumption radius = 2*sigma
this.set_uniform_value('sigma', parseFloat(this._radius * scale_factor / 2 - 1e-6));
this.set_enabled(this.radius > 0.);
if (this._chained_effect)
this._chained_effect.radius = value;
}
}
get brightness() {
return this._brightness;
}
set brightness(value) {
if (this._brightness !== value) {
this._brightness = value;
this.set_uniform_value('brightness', parseFloat(this._brightness - 1e-6));
if (this._chained_effect)
this._chained_effect.brightness = value;
}
}
get width() {
return this._width;
}
set width(value) {
if (this._width !== value) {
this._width = value;
this.set_uniform_value('width', parseFloat(this._width + 3.0 - 1e-6));
if (this._chained_effect)
this._chained_effect.width = value;
}
}
get height() {
return this._height;
}
set height(value) {
if (this._height !== value) {
this._height = value;
this.set_uniform_value('height', parseFloat(this._height + 3.0 - 1e-6));
if (this._chained_effect)
this._chained_effect.height = value;
}
}
get direction() {
return this._direction;
}
set direction(value) {
if (this._direction !== value)
this._direction = value;
}
get chained_effect() {
return this._chained_effect;
}
vfunc_set_actor(actor) {
if (this._actor_connection_size_id) {
let old_actor = this.get_actor();
old_actor?.disconnect(this._actor_connection_size_id);
}
if (actor) {
this.width = actor.width;
this.height = actor.height;
this._actor_connection_size_id = actor.connect('notify::size', _ => {
this.width = actor.width;
this.height = actor.height;
});
}
else
this._actor_connection_size_id = null;
super.vfunc_set_actor(actor);
if (this.direction == 0) {
if (this.chained_effect)
this._chained_effect.get_actor()?.remove_effect(this._chained_effect);
else
this._chained_effect = new GaussianBlurEffect({
radius: this.radius,
brightness: this.brightness,
width: this.width,
height: this.height,
direction: 1
});
if (actor !== null)
actor.add_effect(this._chained_effect);
}
}
vfunc_paint_target(paint_node = null, paint_context = null) {
//this.set_uniform_value("tex", 0);
this.set_uniform_value("dir", this._direction);
if (paint_node && paint_context)
super.vfunc_paint_target(paint_node, paint_context);
else if (paint_node)
super.vfunc_paint_target(paint_node);
else
super.vfunc_paint_target();
}
});

View File

@ -0,0 +1,44 @@
uniform sampler2D tex;
uniform float radius;
uniform int iterations;
uniform float brightness;
uniform float width;
uniform float height;
uniform bool use_base_pixel;
float srand(vec2 a) {
return sin(dot(a, vec2(1233.224, 1743.335)));
}
float rand(inout float r) {
r = fract(3712.65 * r + 0.61432);
return (r - 0.5) * 2.0;
}
void main() {
vec2 uv = cogl_tex_coord0_in.st;
vec2 p = 16 * radius / vec2(width, height);
float r = srand(uv);
vec2 rv;
int count = 0;
vec4 c = vec4(0.);
for (int i = 0; i < iterations; i++) {
rv.x = rand(r);
rv.y = rand(r);
vec2 new_uv = uv + rv * p;
if (new_uv.x > 2. / width && new_uv.y > 2. / height && new_uv.x < 1. - 3. / width && new_uv.y < 1. - 3. / height) {
c += texture2D(tex, new_uv);
count += 1;
}
}
if (count == 0 || use_base_pixel) {
c += texture2D(tex, uv);
count += 1;
}
c.xyz *= brightness;
cogl_color_out = c / count;
}

View File

@ -0,0 +1,211 @@
import GObject from 'gi://GObject';
import * as utils from '../conveniences/utils.js';
const St = await utils.import_in_shell_only('gi://St');
const Shell = await utils.import_in_shell_only('gi://Shell');
const Clutter = await utils.import_in_shell_only('gi://Clutter');
const SHADER_FILENAME = 'monte_carlo_blur.glsl';
const DEFAULT_PARAMS = {
radius: 2., iterations: 5, brightness: .6,
width: 0, height: 0, use_base_pixel: true
};
export const MonteCarloBlurEffect = utils.IS_IN_PREFERENCES ?
{ default_params: DEFAULT_PARAMS } :
new GObject.registerClass({
GTypeName: "MonteCarloBlurEffect",
Properties: {
'radius': GObject.ParamSpec.double(
`radius`,
`Radius`,
`Blur radius`,
GObject.ParamFlags.READWRITE,
0.0, 2000.0,
2.0,
),
'iterations': GObject.ParamSpec.int(
`iterations`,
`Iterations`,
`Blur iterations`,
GObject.ParamFlags.READWRITE,
0, 64,
5,
),
'brightness': GObject.ParamSpec.double(
`brightness`,
`Brightness`,
`Blur brightness`,
GObject.ParamFlags.READWRITE,
0.0, 1.0,
0.6,
),
'width': GObject.ParamSpec.double(
`width`,
`Width`,
`Width`,
GObject.ParamFlags.READWRITE,
0.0, Number.MAX_SAFE_INTEGER,
0.0,
),
'height': GObject.ParamSpec.double(
`height`,
`Height`,
`Height`,
GObject.ParamFlags.READWRITE,
0.0, Number.MAX_SAFE_INTEGER,
0.0,
),
'use_base_pixel': GObject.ParamSpec.boolean(
`use_base_pixel`,
`Use base pixel`,
`Use base pixel`,
GObject.ParamFlags.READWRITE,
true,
),
}
}, class MonteCarloBlurEffect extends Clutter.ShaderEffect {
constructor(params) {
super(params);
this._radius = null;
this._iterations = null;
this._brightness = null;
this._width = null;
this._height = null;
this._use_base_pixel = null;
this.radius = 'radius' in params ? params.radius : this.constructor.default_params.radius;
this.iterations = 'iterations' in params ? params.iterations : this.constructor.default_params.iterations;
this.brightness = 'brightness' in params ? params.brightness : this.constructor.default_params.brightness;
this.width = 'width' in params ? params.width : this.constructor.default_params.width;
this.height = 'height' in params ? params.height : this.constructor.default_params.height;
this.use_base_pixel = 'use_base_pixel' in params ? params.use_base_pixel : this.constructor.default_params.use_base_pixel;
// set shader source
this._source = utils.get_shader_source(Shell, SHADER_FILENAME, import.meta.url);
if (this._source)
this.set_shader_source(this._source);
const theme_context = St.ThemeContext.get_for_stage(global.stage);
theme_context.connectObject(
'notify::scale-factor',
_ => this.set_uniform_value('radius',
parseFloat(this._radius * theme_context.scale_factor - 1e-6)
),
this
);
}
static get default_params() {
return DEFAULT_PARAMS;
}
get radius() {
return this._radius;
}
set radius(value) {
if (this._radius !== value) {
this._radius = value;
const scale_factor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
this.set_uniform_value('radius', parseFloat(this._radius * scale_factor - 1e-6));
this.set_enabled(this.radius > 0. && this.iterations > 0);
}
}
get iterations() {
return this._iterations;
}
set iterations(value) {
if (this._iterations !== value) {
this._iterations = value;
this.set_uniform_value('iterations', this._iterations);
this.set_enabled(this.radius > 0. && this.iterations > 0);
}
}
get brightness() {
return this._brightness;
}
set brightness(value) {
if (this._brightness !== value) {
this._brightness = value;
this.set_uniform_value('brightness', parseFloat(this._brightness - 1e-6));
}
}
get width() {
return this._width;
}
set width(value) {
if (this._width !== value) {
this._width = value;
this.set_uniform_value('width', parseFloat(this._width + 3.0 - 1e-6));
}
}
get height() {
return this._height;
}
set height(value) {
if (this._height !== value) {
this._height = value;
this.set_uniform_value('height', parseFloat(this._height + 3.0 - 1e-6));
}
}
get use_base_pixel() {
return this._use_base_pixel;
}
set use_base_pixel(value) {
if (this._use_base_pixel !== value) {
this._use_base_pixel = value;
this.set_uniform_value('use_base_pixel', this._use_base_pixel ? 1 : 0);
}
}
vfunc_set_actor(actor) {
if (this._actor_connection_size_id) {
let old_actor = this.get_actor();
old_actor?.disconnect(this._actor_connection_size_id);
}
if (actor) {
this.width = actor.width;
this.height = actor.height;
this._actor_connection_size_id = actor.connect('notify::size', _ => {
this.width = actor.width;
this.height = actor.height;
});
}
else
this._actor_connection_size_id = null;
super.vfunc_set_actor(actor);
}
vfunc_paint_target(paint_node = null, paint_context = null) {
//this.set_uniform_value("tex", 0);
if (paint_node && paint_context)
super.vfunc_paint_target(paint_node, paint_context);
else if (paint_node)
super.vfunc_paint_target(paint_node);
else
super.vfunc_paint_target();
}
});

View File

@ -0,0 +1,44 @@
import GObject from 'gi://GObject';
import * as utils from '../conveniences/utils.js';
const St = await utils.import_in_shell_only('gi://St');
const Shell = await utils.import_in_shell_only('gi://Shell');
const DEFAULT_PARAMS = {
unscaled_radius: 30, brightness: 0.6
};
export const NativeDynamicBlurEffect = utils.IS_IN_PREFERENCES ?
{ default_params: DEFAULT_PARAMS } :
new GObject.registerClass({
GTypeName: "NativeDynamicBlurEffect"
}, class NativeDynamicBlurEffect extends Shell.BlurEffect {
constructor(params) {
const { unscaled_radius, ...parent_params } = params;
super({ ...parent_params, mode: Shell.BlurMode.BACKGROUND });
this._theme_context = St.ThemeContext.get_for_stage(global.stage);
this.unscaled_radius = 'unscaled_radius' in params ? unscaled_radius : this.constructor.default_params.unscaled_radius;
this._theme_context.connectObject(
'notify::scale-factor',
_ => this.radius = this.unscaled_radius * this._theme_context.scale_factor,
this
);
}
static get default_params() {
return DEFAULT_PARAMS;
}
get unscaled_radius() {
return this._unscaled_radius;
}
set unscaled_radius(value) {
this._unscaled_radius = value;
this.radius = value * this._theme_context.scale_factor;
}
});

View File

@ -0,0 +1,44 @@
import GObject from 'gi://GObject';
import * as utils from '../conveniences/utils.js';
const St = await utils.import_in_shell_only('gi://St');
const Shell = await utils.import_in_shell_only('gi://Shell');
const DEFAULT_PARAMS = {
unscaled_radius: 30, brightness: 0.6
};
export const NativeStaticBlurEffect = utils.IS_IN_PREFERENCES ?
{ default_params: DEFAULT_PARAMS } :
new GObject.registerClass({
GTypeName: "NativeStaticBlurEffect"
}, class NativeStaticBlurEffect extends Shell.BlurEffect {
constructor(params) {
const { unscaled_radius, ...parent_params } = params;
super({ ...parent_params, mode: Shell.BlurMode.ACTOR });
this._theme_context = St.ThemeContext.get_for_stage(global.stage);
this.unscaled_radius = 'unscaled_radius' in params ? unscaled_radius : this.constructor.default_params.unscaled_radius;
this._theme_context.connectObject(
'notify::scale-factor',
_ => this.radius = this.unscaled_radius * this._theme_context.scale_factor,
this
);
}
static get default_params() {
return DEFAULT_PARAMS;
}
get unscaled_radius() {
return this._unscaled_radius;
}
set unscaled_radius(value) {
this._unscaled_radius = value;
this.radius = value * this._theme_context.scale_factor;
}
});

View File

@ -0,0 +1,20 @@
uniform sampler2D tex;
uniform float noise;
uniform float lightness;
float PHI = 1.61803398874989484820459;
float SEED = 24;
float noise_gen(in vec2 xy) {
float r = fract(tan(distance(xy * PHI, xy) * SEED) * xy.x);
r = r != r ? 0.0 : r;
return r;
}
void main() {
vec4 c = texture2D(tex, cogl_tex_coord_in[0].st);
vec3 pix_color = c.xyz;
float blend = noise * (1. - noise_gen(gl_FragCoord.xy));
cogl_color_out = vec4(mix(pix_color, lightness * pix_color, blend), 1.);
}

View File

@ -0,0 +1,91 @@
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 = 'noise.glsl';
const DEFAULT_PARAMS = {
noise: 0.4, lightness: 0.4
};
export const NoiseEffect = utils.IS_IN_PREFERENCES ?
{ default_params: DEFAULT_PARAMS } :
new GObject.registerClass({
GTypeName: "NoiseEffect",
Properties: {
'noise': GObject.ParamSpec.double(
`noise`,
`Noise`,
`Amount of noise integrated with the image`,
GObject.ParamFlags.READWRITE,
0.0, 1.0,
0.4,
),
'lightness': GObject.ParamSpec.double(
`lightness`,
`Lightness`,
`Lightness of the grey used for the noise`,
GObject.ParamFlags.READWRITE,
0.0, 2.0,
0.4,
),
}
}, class NoiseEffect extends Clutter.ShaderEffect {
constructor(params) {
super(params);
this._noise = null;
this._lightness = null;
this.noise = 'noise' in params ? params.noise : this.constructor.default_params.noise;
this.lightness = 'lightness' in params ? params.lightness : this.constructor.default_params.lightness;
// 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 noise() {
return this._noise;
}
set noise(value) {
if (this._noise !== value) {
this._noise = value;
this.set_uniform_value('noise', parseFloat(this._noise - 1e-6));
this.set_enabled(this.noise > 0. && this.lightness != 1);
}
}
get lightness() {
return this._lightness;
}
set lightness(value) {
if (this._lightness !== value) {
this._lightness = value;
this.set_uniform_value('lightness', parseFloat(this._lightness - 1e-6));
this.set_enabled(this.noise > 0. && this.lightness != 1);
}
}
vfunc_paint_target(paint_node = null, paint_context = null) {
this.set_uniform_value("tex", 0);
if (paint_node && paint_context)
super.vfunc_paint_target(paint_node, paint_context);
else if (paint_node)
super.vfunc_paint_target(paint_node);
else
super.vfunc_paint_target();
}
});

View File

@ -0,0 +1,27 @@
uniform sampler2D tex;
uniform int divider;
uniform float width;
uniform float height;
vec4 getTexture(vec2 uv) {
if (uv.x < 3. / width)
uv.x = 3. / width;
if (uv.y < 3. / height)
uv.y = 3. / height;
if (uv.x > 1. - 3. / width)
uv.x = 1. - 3. / width;
if (uv.y > 1. - 3. / height)
uv.y = 1. - 3. / height;
return texture2D(tex, uv);
}
void main() {
vec2 uv = cogl_tex_coord0_in.st;
vec2 scaled_multiplier = vec2(width, height) / divider;
vec2 new_uv = 0.5 + floor((uv - 0.5) * scaled_multiplier) / scaled_multiplier;
cogl_color_out = getTexture(new_uv);
}

View File

@ -0,0 +1,130 @@
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 = 'pixelize.glsl';
const DEFAULT_PARAMS = {
divider: 8, width: 0, height: 0
};
export const PixelizeEffect = utils.IS_IN_PREFERENCES ?
{ default_params: DEFAULT_PARAMS } :
new GObject.registerClass({
GTypeName: "PixelizeEffect",
Properties: {
'divider': GObject.ParamSpec.int(
`divider`,
`Divider`,
`Divider`,
GObject.ParamFlags.READWRITE,
0, 64,
5,
),
'width': GObject.ParamSpec.double(
`width`,
`Width`,
`Width`,
GObject.ParamFlags.READWRITE,
0.0, Number.MAX_SAFE_INTEGER,
0.0,
),
'height': GObject.ParamSpec.double(
`height`,
`Height`,
`Height`,
GObject.ParamFlags.READWRITE,
0.0, Number.MAX_SAFE_INTEGER,
0.0,
)
}
}, class PixelizeEffect extends Clutter.ShaderEffect {
constructor(params) {
super(params);
this._divider = null;
this._width = null;
this._height = null;
this.divider = 'divider' in params ? params.divider : this.constructor.default_params.divider;
this.width = 'width' in params ? params.width : this.constructor.default_params.width;
this.height = 'height' in params ? params.height : this.constructor.default_params.height;
// 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 divider() {
return this._width;
}
set divider(value) {
if (this._divider !== value) {
this._divider = value;
this.set_uniform_value('divider', this._divider);
}
}
get width() {
return this._width;
}
set width(value) {
if (this._width !== value) {
this._width = value;
this.set_uniform_value('width', parseFloat(this._width + 3.0 - 1e-6));
}
}
get height() {
return this._height;
}
set height(value) {
if (this._height !== value) {
this._height = value;
this.set_uniform_value('height', parseFloat(this._height + 3.0 - 1e-6));
}
}
vfunc_set_actor(actor) {
if (this._actor_connection_size_id) {
let old_actor = this.get_actor();
old_actor?.disconnect(this._actor_connection_size_id);
}
if (actor) {
this.width = actor.width;
this.height = actor.height;
this._actor_connection_size_id = actor.connect('notify::size', _ => {
this.width = actor.width;
this.height = actor.height;
});
}
else
this._actor_connection_size_id = null;
super.vfunc_set_actor(actor);
}
vfunc_paint_target(paint_node = null, paint_context = null) {
//this.set_uniform_value("tex", 0);
if (paint_node && paint_context)
super.vfunc_paint_target(paint_node, paint_context);
else if (paint_node)
super.vfunc_paint_target(paint_node);
else
super.vfunc_paint_target();
}
});