@luma.gl/effects
Version:
Post-processing effects for luma.gl
122 lines (105 loc) • 3.68 kB
text/typescript
// luma.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
import type {ShaderPass} from '@luma.gl/shadertools';
const source = /* wgsl */ `\
struct hueSaturationUniforms {
hue: f32,
saturation: f32,
};
@group(0), @binding(1) var<uniform> hueSaturation: hueSaturationUniforms;
fn hueSaturation_filterColor(color: vec4<f32>) -> vec4<f32> {
let angle: f32 = hueSaturation.hue * 3.1415927;
let s: f32 = sin(angle);
let c: f32 = cos(angle);
let weights: vec3<f32> = (vec3<f32>(2. * c, -sqrt(3.) * s - c, sqrt(3.) * s - c) + 1.) / 3.;
let len: f32 = length(color.rgb);
var colorrgb = color.rgb;
colorrgb = vec3<f32>(dot(color.rgb, weights.xyz), dot(color.rgb, weights.zxy), dot(color.rgb, weights.yzx));
color.r = colorrgb.x;
color.g = colorrgb.y;
color.b = colorrgb.z;
let average: f32 = (color.r + color.g + color.b) / 3.;
if (hueSaturation.saturation > 0.) {
var colorrgb = color.rgb;
colorrgb = color.rgb + ((average - color.rgb) * (1. - 1. / (1.001 - hueSaturation.saturation)));
color.r = colorrgb.x;
color.g = colorrgb.y;
color.b = colorrgb.z;
} else {
var colorrgb = color.rgb;
colorrgb = color.rgb + ((average - color.rgb) * -hueSaturation.saturation);
color.r = colorrgb.x;
color.g = colorrgb.y;
color.b = colorrgb.z;
}
return color;
}
fn hueSaturation_filterColor_ext(color: vec4<f32>, texSize: vec2<f32>, texCoord: vec2<f32>) -> vec4<f32> {
return hueSaturation_filterColor(color);
}
`;
const fs = /* glsl */ `\
uniform hueSaturationUniforms {
float hue;
float saturation;
} hueSaturation;
vec4 hueSaturation_filterColor(vec4 color) {
// hue adjustment, wolfram alpha: RotationTransform[angle, {1, 1, 1}][{x, y, z}]
float angle = hueSaturation.hue * 3.14159265;
float s = sin(angle), c = cos(angle);
vec3 weights = (vec3(2.0 * c, -sqrt(3.0) * s - c, sqrt(3.0) * s - c) + 1.0) / 3.0;
float len = length(color.rgb);
color.rgb = vec3(
dot(color.rgb, weights.xyz),
dot(color.rgb, weights.zxy),
dot(color.rgb, weights.yzx)
);
// saturation adjustment
float average = (color.r + color.g + color.b) / 3.0;
if (hueSaturation.saturation > 0.0) {
color.rgb += (average - color.rgb) * (1.0 - 1.0 / (1.001 - hueSaturation.saturation));
} else {
color.rgb += (average - color.rgb) * (-hueSaturation.saturation);
}
return color;
}
vec4 hueSaturation_filterColor_ext(vec4 color, vec2 texSize, vec2 texCoord) {
return hueSaturation_filterColor(color);
}
`;
/**
* Hue / Saturation
*/
export type HueSaturationProps = {
/** -1 to 1 (-1 is 180 degree rotation in the negative direction, 0 is no change,
* and 1 is 180 degree rotation in the positive direction) */
hue?: number;
/** -1 to 1 (-1 is solid gray, 0 is no change, and 1 is maximum contrast) */
saturation?: number;
};
export type HueSaturationUniforms = HueSaturationProps;
/**
* Hue / Saturation
* Provides rotational hue and multiplicative saturation control. RGB color space
* can be imagined as a cube where the axes are the red, green, and blue color
* values. Hue changing works by rotating the color vector around the grayscale
* line, which is the straight line from black (0, 0, 0) to white (1, 1, 1).
* Saturation is implemented by scaling all color channel values either toward
* or away from the average color channel value.
*/
export const hueSaturation = {
props: {} as HueSaturationProps,
name: 'hueSaturation',
source,
fs,
uniformTypes: {
hue: 'f32',
saturation: 'f32'
},
propTypes: {
hue: {value: 0, min: -1, max: 1},
saturation: {value: 0, min: -1, max: 1}
},
passes: [{filter: true}]
} as const satisfies ShaderPass<HueSaturationProps>;