Skip to content

Commit

Permalink
Update Color Replace Filter
Browse files Browse the repository at this point in the history
  • Loading branch information
bbazukun123 committed Jan 2, 2024
1 parent a148418 commit 442d112
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 105 deletions.
208 changes: 116 additions & 92 deletions filters/color-replace/src/ColorReplaceFilter.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,33 @@
import { vertex } from '@tools/fragments';
import fragment from './colorReplace.frag';
import { Filter, GlProgram } from 'pixi.js';
import { vertex, wgslVertex } from '@tools/fragments';
import fragment from './color-replace.frag';
import source from './color-replace.wgsl';
import { Color, ColorSource, Filter, FilterOptions, GlProgram, GpuProgram } from 'pixi.js';

type Color = number | number[] | Float32Array;
/**
* This WebGPU filter has been ported from the WebGL renderer that was originally created by mishaa, updated by timetocode
* http://www.html5gamedevs.com/topic/10640-outline-a-sprite-change-certain-colors/?p=69966
*/

export interface ColorReplaceFilterOptions
{
/**
* The color that will be changed.
* @example [1.0, 1.0, 1.0] = 0xffffff
* @default 0xff0000
*/
originalColor?: ColorSource;
/**
* The resulting color.
* @example [1.0, 1.0, 1.0] = 0xffffff
* @default 0x000000
*/
newColor?: ColorSource;
/**
* Tolerance/sensitivity of the floating-point comparison between colors (lower = more exact, higher = more inclusive)
* @default 0.4
*/
tolerance?: number;
}

/**
* ColorReplaceFilter, originally by mishaa, updated by timetocode
Expand All @@ -16,118 +41,117 @@ type Color = number | number[] | Float32Array;
*
* @example
* // replaces true red with true blue
* someSprite.filters = [new ColorReplaceFilter(
* [1, 0, 0],
* [0, 0, 1],
* 0.001
* )];
* someSprite.filters = [new ColorReplaceFilter({
* originalColor: [1, 0, 0],
* newColor: [0, 0, 1],
* tolerance: 0.001
* })];
* // replaces the RGB color 220, 220, 220 with the RGB color 225, 200, 215
* someOtherSprite.filters = [new ColorReplaceFilter(
* [220/255.0, 220/255.0, 220/255.0],
* [225/255.0, 200/255.0, 215/255.0],
* 0.001
* )];
* someOtherSprite.filters = [new ColorReplaceFilter({
* originalColor: [220/255.0, 220/255.0, 220/255.0],
* newColor: [225/255.0, 200/255.0, 215/255.0],
* tolerance: 0.001
* })];
* // replaces the RGB color 220, 220, 220 with the RGB color 225, 200, 215
* someOtherSprite.filters = [new ColorReplaceFilter(0xdcdcdc, 0xe1c8d7, 0.001)];
* someOtherSprite.filters = [new ColorReplaceFilter({ originalColor: 0xdcdcdc, newColor: 0xe1c8d7, tolerance: 0.001 })];
*
*/
export class ColorReplaceFilter extends Filter
{
private _originalColor = 0xff0000;
private _newColor = 0x0;
/** Default values for options. */
public static readonly DEFAULT_OPTIONS: ColorReplaceFilterOptions & Partial<FilterOptions> = {
...Filter.defaultOptions,
originalColor: 0xff0000,
newColor: 0x000000,
tolerance: 0.4
};

/**
* @param {number|Array<number>|Float32Array} [originalColor=0xFF0000] - The color that will be changed,
* as a 3 component RGB e.g. `[1.0, 1.0, 1.0]`
* @param {number|Array<number>|Float32Array} [newColor=0x000000] - The resulting color, as a 3 component
* RGB e.g. `[1.0, 0.5, 1.0]`
* @param {number} [epsilon=0.4] - Tolerance/sensitivity of the floating-point comparison between colors
* (lower = more exact, higher = more inclusive)
*/
constructor(originalColor: Color = 0xFF0000, newColor: Color = 0x000000, epsilon = 0.4)
public uniforms: {
uOriginalColor: Float32Array,
uNewColor: Float32Array,
uTolerance: number,
};

private _originalColor: Color;
private _newColor: Color;

constructor(options: ColorReplaceFilterOptions = {})
{
options = { ...ColorReplaceFilter.DEFAULT_OPTIONS, ...options };

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

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

super({
gpuProgram,
glProgram,
resources: {},
resources: {
colorReplaceUniforms: {
uOriginalColor: { value: new Float32Array(3), type: 'vec3<f32>' },
uNewColor: { value: new Float32Array(3), type: 'vec3<f32>' },
uTolerance: { value: options.tolerance, type: 'f32' },
}
},
});

// this.uniforms.originalColor = new Float32Array(3);
// this.uniforms.newColor = new Float32Array(3);
// this.originalColor = originalColor;
// this.newColor = newColor;
// this.epsilon = epsilon;
this.uniforms = this.resources.colorReplaceUniforms.uniforms;

this._originalColor = new Color();
this._newColor = new Color();

Object.assign(this, options);
}

/**
* The color that will be changed, as a 3 component RGB e.g. [1.0, 1.0, 1.0]
* @member {number|Array<number>|Float32Array}
* @default 0xFF0000
* The color that will be changed.
* @example [1.0, 1.0, 1.0] = 0xffffff
* @default 0xff0000
*/
// set originalColor(value: Color)
// {
// const arr = this.uniforms.originalColor;

// if (typeof value === 'number')
// {
// utils.hex2rgb(value, arr);
// this._originalColor = value;
// }
// else
// {
// arr[0] = value[0];
// arr[1] = value[1];
// arr[2] = value[2];
// this._originalColor = utils.rgb2hex(arr);
// }
// }
// get originalColor(): Color
// {
// return this._originalColor;
// }
get originalColor(): ColorSource { return this._originalColor.value as ColorSource; }
set originalColor(value: ColorSource)
{
this._originalColor.setValue(value);
const [r, g, b] = this._originalColor.toArray();

this.uniforms.uOriginalColor[0] = r;
this.uniforms.uOriginalColor[1] = g;
this.uniforms.uOriginalColor[2] = b;
}

/**
* The resulting color, as a 3 component RGB e.g. [1.0, 0.5, 1.0]
* @member {number|Array<number>|Float32Array}
* @default 0x000000
*/
// set newColor(value: Color)
// {
// const arr = this.uniforms.newColor;

// if (typeof value === 'number')
// {
// utils.hex2rgb(value, arr);
// this._newColor = value;
// }
// else
// {
// arr[0] = value[0];
// arr[1] = value[1];
// arr[2] = value[2];
// this._newColor = utils.rgb2hex(arr);
// }
// }
// get newColor(): Color
// {
// return this._newColor;
// }
* The resulting color.
* @example [1.0, 1.0, 1.0] = 0xffffff
* @default 0x000000
*/
get newColor(): ColorSource { return this._newColor.value as ColorSource; }
set newColor(value: ColorSource)
{
this._newColor.setValue(value);
const [r, g, b] = this._newColor.toArray();

this.uniforms.uNewColor[0] = r;
this.uniforms.uNewColor[1] = g;
this.uniforms.uNewColor[2] = b;
}

/**
* Tolerance/sensitivity of the floating-point comparison between colors (lower = more exact, higher = more inclusive)
* @default 0.4
*/
// set epsilon(value: number)
// {
// this.uniforms.epsilon = value;
// }
// get epsilon(): number
// {
// return this.uniforms.epsilon;
// }
* Tolerance/sensitivity of the floating-point comparison between colors (lower = more exact, higher = more inclusive)
* @default 0.4
*/
get tolerance(): number { return this.uniforms.uTolerance; }
set tolerance(value: number) { this.uniforms.uTolerance = value; }
}
15 changes: 15 additions & 0 deletions filters/color-replace/src/color-replace.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
in vec2 vTextureCoord;
out vec4 finalColor;

uniform sampler2D uSampler;
uniform vec3 uOriginalColor;
uniform vec3 uNewColor;
uniform float uTolerance;

void main(void) {
vec4 c = texture(uSampler, vTextureCoord);
vec3 colorDiff = uOriginalColor - (c.rgb / max(c.a, 0.0000000001));
float colorDistance = length(colorDiff);
float doReplace = step(colorDistance, uTolerance);
finalColor = vec4(mix(c.rgb, (uNewColor + colorDiff) * c.a, doReplace), c.a);
}
22 changes: 22 additions & 0 deletions filters/color-replace/src/color-replace.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
struct ColorReplaceUniforms {
uOriginalColor: vec3<f32>,
uNewColor: vec3<f32>,
uTolerance: f32,
};

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

@fragment
fn mainFragment(
@builtin(position) position: vec4<f32>,
@location(0) uv : vec2<f32>
) -> @location(0) vec4<f32> {
let sample: vec4<f32> = textureSample(uSampler, uSampler, uv);

let colorDiff: vec3<f32> = colorReplaceUniforms.uOriginalColor - (sample.rgb / max(sample.a, 0.0000000001));
let colorDistance: f32 = length(colorDiff);
let doReplace: f32 = step(colorDistance, colorReplaceUniforms.uTolerance);

return vec4<f32>(mix(sample.rgb, (colorReplaceUniforms.uNewColor + colorDiff) * sample.a, doReplace), sample.a);
}
12 changes: 0 additions & 12 deletions filters/color-replace/src/colorReplace.frag

This file was deleted.

2 changes: 1 addition & 1 deletion tools/demo/src/filters/color-replace.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ export default function ()
{
folder.addColor(this, 'originalColor');
folder.addColor(this, 'newColor');
folder.add(this, 'epsilon', 0, 1);
folder.add(this, 'tolerance', 0, 1);
});
}
1 change: 1 addition & 0 deletions tools/demo/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const main = async () =>
filters.shockwave.call(app);
filters.zoomBlur.call(app);
filters.colorOverlay.call(app);
filters.colorReplace.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 442d112

Please sign in to comment.