Skip to content

Commit

Permalink
Update HSL Adjustment Filter
Browse files Browse the repository at this point in the history
  • Loading branch information
bbazukun123 committed Dec 30, 2023
1 parent 194450d commit bc22ce1
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 91 deletions.
145 changes: 72 additions & 73 deletions filters/hsl-adjustment/src/HslAdjustmentFilter.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,36 @@
import { Filter, GlProgram } from 'pixi.js';
import { vertex } from '@tools/fragments';
import { Filter, GlProgram, GpuProgram, UniformGroup } from 'pixi.js';
import { vertex, wgslVertex } from '@tools/fragments';
import fragment from './hsladjustment.frag';
import source from './hsladjustment.wgsl';

// This WebGPU filter has been ported from the WebGL renderer that was originally created by Viktor Persson (@vikpe)

export interface HslAdjustmentFilterOptions
{
/**
* The amount of hue in degrees (-180 to 180)
* @default 0
*/
hue: number;
/**
* The amount of color saturation (-1 to 1)
* @default 0
*/
saturation: number;
/**
* The amount of lightness (-1 to 1)
* @default 0
*/
lightness: number;
/**
* Whether to colorize the image
* @default false
*/
colorize: boolean;
/**
* The amount of alpha (0 to 1)
* @default 1
*/
alpha: number;
}

Expand All @@ -21,115 +44,91 @@ export interface HslAdjustmentFilterOptions
*/
export class HslAdjustmentFilter extends Filter
{
private _hue = 0;

/** Default values for options. */
static readonly defaults: HslAdjustmentFilterOptions = {
/** Hue */
public static readonly DEFAULT_OPTIONS: HslAdjustmentFilterOptions = {
hue: 0,
/** Saturation */
saturation: 0,
/** Lightness */
lightness: 0,
/** Colorize */
colorize: false,
/** Alpha */
alpha: 1,
};

/**
* @param options - The optional parameters of the filter.
* @param {number} [options.hue=0] - The amount of hue in degrees (-180 to 180)
* @param {number} [options.saturation=0] - The amount of color saturation (-1 to 1)
* @param {number} [options.lightness=0] - The amount of lightness (-1 to 1)
* @param {boolean} [options.colorize=false] - Whether to colorize the image
* @param {number} [options.alpha=1] - The amount of alpha (0 to 1)
*/
constructor(options?: Partial<HslAdjustmentFilterOptions>)
private _hue: number;

constructor(options?: HslAdjustmentFilterOptions)
{
options = { ...HslAdjustmentFilter.DEFAULT_OPTIONS, ...options };

const hslUniforms = new UniformGroup({
uHsl: { value: new Float32Array(3), type: 'vec3<f32>' },
uColorize: { value: options.colorize ? 1 : 0, type: 'f32' },
uAlpha: { value: options.alpha, type: 'f32' },
});

const gpuProgram = new GpuProgram({
vertex: {
source: wgslVertex,
entryPoint: 'mainVertex',
},
fragment: {
source,
entryPoint: 'mainFragment',
},
});

const glProgram = new GlProgram({
vertex,
fragment,
name: 'hsl-adjustment-filter',
});

super({
gpuProgram,
glProgram,
resources: {},
resources: {
hslUniforms,
},
});
const options_: HslAdjustmentFilterOptions = Object.assign({}, HslAdjustmentFilter.defaults, options);

Object.assign(this, options_);
this._hue = options.hue;
}

/**
* Hue (-180 to 180)
* The amount of hue in degrees (-180 to 180)
* @default 0
*/
get hue(): number
{
return this._hue;
}

get hue(): number { return this._hue; }
set hue(value: number)
{
this._hue = value;
// this.uniforms.uHue = this._hue * (Math.PI / 180); // convert degrees to radians
this.resources.hslUniforms.uniforms.uHsl[0] = value * (Math.PI / 180);
}

/**
* Alpha (0-1)
* @default 1
* The amount of lightness (-1 to 1)
* @default 0
*/
// get alpha(): number
// {
// return this.uniforms.uAlpha;
// }

// set alpha(value: number)
// {
// this.uniforms.uAlpha = value;
// }
get saturation(): number { return this.resources.hslUniforms.uniforms.uHsl[1]; }
set saturation(value: number) { this.resources.hslUniforms.uniforms.uHsl[1] = value; }

/**
* Colorize (render as a single color)
* @default false
* The amount of lightness (-1 to 1)
* @default 0
*/
// get colorize(): boolean
// {
// return this.uniforms.uColorize;
// }

// set colorize(value: boolean)
// {
// this.uniforms.uColorize = value;
// }
get lightness(): number { return this.resources.hslUniforms.uniforms.uHsl[2]; }
set lightness(value: number) { this.resources.hslUniforms.uniforms.uHsl[2] = value; }

/**
* Lightness (-1 to 1)
* @default 0
* Whether to colorize the image
* @default false
*/
// get lightness(): number
// {
// return this.uniforms.uLightness;
// }

// set lightness(value: number)
// {
// this.uniforms.uLightness = value;
// }
get colorize(): boolean { return this.resources.hslUniforms.uniforms.uColorize === 1; }
set colorize(value: boolean) { this.resources.hslUniforms.uniforms.uColorize = value ? 1 : 0; }

/**
* Saturation (-1 to 1)
* @default 0
* The amount of alpha (0 to 1)
* @default 1
*/
// get saturation(): number
// {
// return this.uniforms.uSaturation;
// }

// set saturation(value: number)
// {
// this.uniforms.uSaturation = value;
// }
get alpha(): number { return this.resources.hslUniforms.uniforms.uAlpha; }
set alpha(value: number) { this.resources.hslUniforms.uniforms.uAlpha = value; }
}
36 changes: 19 additions & 17 deletions filters/hsl-adjustment/src/hsladjustment.frag
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
precision mediump float;
in vec2 vTextureCoord;
out vec4 finalColor;

varying vec2 vTextureCoord;
uniform sampler2D uSampler;
uniform float uHue;
uniform vec3 uHsl;
uniform float uAlpha;
uniform bool uColorize;
uniform float uSaturation;
uniform float uLightness;
uniform float uColorize;

// https://en.wikipedia.org/wiki/Luma_(video)
const vec3 weight = vec3(0.299, 0.587, 0.114);
Expand All @@ -29,30 +27,34 @@ vec3 hueShift(vec3 color, float angle) {

void main()
{
vec4 color = texture2D(uSampler, vTextureCoord);
vec4 result = color;
vec4 color = texture(uSampler, vTextureCoord);
vec3 resultRGB = color.rgb;

float hue = uHsl[0];
float saturation = uHsl[1];
float lightness = uHsl[2];

// colorize
if (uColorize) {
result.rgb = vec3(getWeightedAverage(result.rgb), 0., 0.);
if (uColorize > 0.5) {
resultRGB = vec3(getWeightedAverage(resultRGB), 0., 0.);
}

// hue
result.rgb = hueShift(result.rgb, uHue);
resultRGB = hueShift(resultRGB, hue);

// saturation
// https://github.com/evanw/glfx.js/blob/master/src/filters/adjust/huesaturation.js
float average = (result.r + result.g + result.b) / 3.0;
float average = (resultRGB.r + resultRGB.g + resultRGB.b) / 3.0;

if (uSaturation > 0.) {
result.rgb += (average - result.rgb) * (1. - 1. / (1.001 - uSaturation));
if (saturation > 0.) {
resultRGB += (average - resultRGB) * (1. - 1. / (1.001 - saturation));
} else {
result.rgb -= (average - result.rgb) * uSaturation;
resultRGB -= (average - resultRGB) * saturation;
}

// lightness
result.rgb = mix(result.rgb, vec3(ceil(uLightness)) * color.a, abs(uLightness));
resultRGB = mix(resultRGB, vec3(ceil(lightness)) * color.a, abs(lightness));

// alpha
gl_FragColor = mix(color, result, uAlpha);
finalColor = mix(color, vec4(resultRGB, color.a), uAlpha);
}
58 changes: 58 additions & 0 deletions filters/hsl-adjustment/src/hsladjustment.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
struct HslUniforms {
uHsl:vec3<f32>,
uColorize:f32,
uAlpha:f32,
};

@group(0) @binding(1) var uSampler: texture_2d<f32>;
@group(1) @binding(0) var<uniform> hslUniforms : HslUniforms;

@fragment
fn mainFragment(
@location(0) uv: vec2<f32>,
@builtin(position) position: vec4<f32>
) -> @location(0) vec4<f32> {
let color: vec4<f32> = textureSample(uSampler, uSampler, uv);
var resultRGB: vec3<f32> = color.rgb;

let hue: f32 = hslUniforms.uHsl[0];
let saturation: f32 = hslUniforms.uHsl[1];
let lightness: f32 = hslUniforms.uHsl[2];

// colorize
if (hslUniforms.uColorize > 0.5) {
resultRGB = vec3<f32>(dot(color.rgb, vec3<f32>(0.299, 0.587, 0.114)), 0., 0.);
}

// hue
resultRGB = hueShift(resultRGB, hue);

// saturation
// https://github.com/evanw/glfx.js/blob/master/src/filters/adjust/huesaturation.js
let average: f32 = (resultRGB.r + resultRGB.g + resultRGB.b) / 3.0;

if (saturation > 0.) {
resultRGB += (average - resultRGB) * (1. - 1. / (1.001 - saturation));
} else {
resultRGB -= (average - resultRGB) * saturation;
}

// lightness
resultRGB = mix(resultRGB, vec3<f32>(ceil(lightness)) * color.a, abs(lightness));

// alpha
return mix(color, vec4<f32>(resultRGB, color.a), hslUniforms.uAlpha);
}

// https://gist.github.com/mairod/a75e7b44f68110e1576d77419d608786?permalink_comment_id=3195243#gistcomment-3195243
const k: vec3<f32> = vec3(0.57735, 0.57735, 0.57735);

fn hueShift(color: vec3<f32>, angle: f32) -> vec3<f32>
{
let cosAngle: f32 = cos(angle);
return vec3<f32>(
color * cosAngle +
cross(k, color) * sin(angle) +
k * dot(k, color) * (1.0 - cosAngle)
);
}
1 change: 0 additions & 1 deletion tools/demo/src/filters/hsl-adjustment.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ export default function ()
this.addFilter('HslAdjustmentFilter', {
enabled: false,
fishOnly: false,
args: [],
oncreate(folder)
{
folder.add(this, 'hue', -180, 180);
Expand Down
1 change: 1 addition & 0 deletions tools/demo/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const main = async () =>
filters.twist.call(app);
filters.pixelate.call(app);
filters.glow.call(app);
filters.hslAdjustment.call(app);
// filters.kawaseBlur.call(app);

// TODO: Re-enable this in place of the above once v8 conversion is complete
Expand Down

0 comments on commit bc22ce1

Please sign in to comment.