Skip to content

Commit

Permalink
Update Color Map Filter
Browse files Browse the repository at this point in the history
  • Loading branch information
bbazukun123 committed Jan 2, 2024
1 parent cfc2c1e commit 345a0ce
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 128 deletions.
225 changes: 119 additions & 106 deletions filters/color-map/src/ColorMapFilter.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
import { vertex } from '@tools/fragments';
import { vertex, wgslVertex } from '@tools/fragments';
import fragment from './color-map.frag';
import { Filter, Texture, TextureSource, GlProgram } from 'pixi.js';
import type { FilterSystem, RenderSurface } from 'pixi.js';
import source from './color-map.wgsl';
import { Filter, Texture, TextureSource, GlProgram, GpuProgram, SCALE_MODE } from 'pixi.js';

type ColorMapSource = TextureSource | Texture | null;
type ColorMapTexture = TextureSource | Texture;

export interface ColorMapFilterOptions
{
/** The colorMap texture of the filter. */
colorMap: ColorMapTexture;
/**
* The mix from 0 to 1, where 0 is the original image and 1 is the color mapped image.
* @default 1
*/
mix?: number;
/**
* Whether use NEAREST scale mode for `colorMap` texture.
* @default false
*/
nearest?: boolean;
}

/**
* The ColorMapFilter applies a color-map effect to an object.<br>
Expand All @@ -16,155 +32,152 @@ type ColorMapSource = TextureSource | Texture | null;
*/
export class ColorMapFilter extends Filter
{
/** The mix from 0 to 1, where 0 is the original image and 1 is the color mapped image. */
public mix = 1;
/** Default values for options. */
public static readonly DEFAULT_OPTIONS: ColorMapFilterOptions = {
colorMap: Texture.WHITE,
nearest: false,
mix: 1
};

public uniforms: {
uMix: number;
uSize: number;
uSliceSize: number;
uSlicePixelSize: number;
uSliceInnerSize: number;
};

private _size = 0;
private _sliceSize = 0;
private _slicePixelSize = 0;
private _sliceInnerSize = 0;
private _nearest = false;
// private _scaleMode: SCALE_MODES | null = null;
private _colorMap: Texture | null = null;
private _scaleMode: SCALE_MODE = 'linear';
private _colorMap!: ColorMapTexture;

/**
* @param {HTMLImageElement|HTMLCanvasElement|BaseTexture|Texture} [colorMap] - The
* colorMap texture of the filter.
* @param {boolean} [nearest=false] - Whether use NEAREST for colorMap texture.
* @param {number} [mix=1] - The mix from 0 to 1, where 0 is the original image and 1 is the color mapped image.
*/
constructor(colorMap: ColorMapSource, nearest = false, mix = 1)
constructor(options: ColorMapFilterOptions)
{
options = { ...ColorMapFilter.DEFAULT_OPTIONS, ...options };

if (!options.colorMap) throw Error('No color map texture source was provided to ColorMapFilter');

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

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

super({
gpuProgram,
glProgram,
resources: {},
resources: {
colorMapUniforms: {
uMix: { value: options.mix, type: 'f32' },
uSize: { value: 0, type: 'f32' },
uSliceSize: { value: 0, type: 'f32' },
uSlicePixelSize: { value: 0, type: 'f32' },
uSliceInnerSize: { value: 0, type: 'f32' },
},
uMapTexture: options.colorMap,
},
});

// this._scaleMode = null;
// this.nearest = nearest;
this.mix = mix;
this.colorMap = colorMap;
}
this.uniforms = this.resources.colorMapUniforms.uniforms;

/**
* Override existing apply method in Filter
* @private
*/
apply(filterManager: FilterSystem, input: Texture, output: RenderSurface, clear: boolean): void
{
// this.uniforms._mix = this.mix;

filterManager.applyFilter(this, input, output, clear);
Object.assign(this, options);
}

/** The mix from 0 to 1, where 0 is the original image and 1 is the color mapped image. */
get mix(): number { return this.uniforms.uMix; }
set mix(value: number) { this.uniforms.uMix = value; }

/**
* The size of one color slice
* The size of one color slice.
* @readonly
*/
get colorSize(): number
{
return this._size;
}
get colorSize(): number { return this._size; }

/**
* the colorMap texture
* @member {Texture}
*/
get colorMap(): ColorMapSource
{
return this._colorMap;
}
set colorMap(colorMap: ColorMapSource)
/** The colorMap texture. */
get colorMap(): ColorMapTexture { return this._colorMap; }
set colorMap(value: ColorMapTexture)
{
if (!colorMap)
{
return;
}
if (!(colorMap instanceof Texture))
{
colorMap = Texture.from(colorMap);
}
if ((colorMap as Texture)?.baseTexture)
{
// colorMap.baseTexture.scaleMode = this._scaleMode as SCALE_MODES;
// colorMap.baseTexture.mipmap = MIPMAP_MODES.OFF;
if (!value || value === this.colorMap) return;

this._size = colorMap.height;
this._sliceSize = 1 / this._size;
this._slicePixelSize = this._sliceSize / this._size;
this._sliceInnerSize = this._slicePixelSize * (this._size - 1);
const source = value instanceof Texture ? value.source : value;

// this.uniforms._size = this._size;
// this.uniforms._sliceSize = this._sliceSize;
// this.uniforms._slicePixelSize = this._slicePixelSize;
// this.uniforms._sliceInnerSize = this._sliceInnerSize;
source.style.scaleMode = this._scaleMode;
source.autoGenerateMipmaps = false;

// this.uniforms.colorMap = colorMap;
}
this._size = source.height;
this._sliceSize = 1 / this._size;
this._slicePixelSize = this._sliceSize / this._size;
this._sliceInnerSize = this._slicePixelSize * (this._size - 1);

this._colorMap = colorMap;
}

/**
* Whether use NEAREST for colorMap texture.
*/
// get nearest(): boolean
// {
// return this._nearest;
// }
// set nearest(nearest: boolean)
// {
// this._nearest = nearest;
// this._scaleMode = nearest ? SCALE_MODES.NEAREST : SCALE_MODES.LINEAR;
this.uniforms.uSize = this._size;
this.uniforms.uSliceSize = this._sliceSize;
this.uniforms.uSlicePixelSize = this._slicePixelSize;
this.uniforms.uSliceInnerSize = this._sliceInnerSize;

// const texture = this._colorMap;
this.resources.uMapTexture = source;
this._colorMap = value;
}

// if (texture && texture.baseTexture)
// {
// texture.baseTexture._glTextures = {};
/** Whether use NEAREST for colorMap texture. */
get nearest(): boolean { return this._nearest; }
set nearest(nearest: boolean)
{
this._nearest = nearest;
this._scaleMode = nearest ? 'nearest' : 'linear';

// texture.baseTexture.scaleMode = this._scaleMode;
// texture.baseTexture.mipmap = MIPMAP_MODES.OFF;
const texture = this._colorMap;

// texture._updateID++;
// texture.baseTexture.emit('update', texture.baseTexture);
// }
// }
if (texture && texture.source)
{
texture.source.style.scaleMode = this._scaleMode;
texture.source.autoGenerateMipmaps = false;
texture.source.style.update();
texture.source.update();
}
}

/**
* If the colorMap is based on canvas , and the content of canvas has changed,
* then call `updateColorMap` for update texture.
* If the colorMap is based on canvas,
* and the content of canvas has changed, then call `updateColorMap` for update texture.
*/
updateColorMap(): void
{
const texture = this._colorMap;

if (texture && texture.baseTexture)
if (texture?.source)
{
// texture._updateID++;
texture.baseTexture.emit('update', texture.baseTexture);

texture.source.update();
this.colorMap = texture;
}
}

/**
* Destroys this filter
*
* @param {boolean} [destroyBase=false] - Whether to destroy the base texture of colorMap as well
* @param destroyBase Whether to destroy the base texture of colorMap as well
* @default false
*/
destroy(destroyBase = false): void
{
if (this._colorMap)
{
this._colorMap.destroy(destroyBase);
}
super.destroy();
}
// TODO: Implement destroy functionality
// destroy(): void
// {
// if (this._colorMap)
// {
// this._colorMap.destroy(destroyBase);
// }
// super.destroy();
// }
}
38 changes: 21 additions & 17 deletions filters/color-map/src/color-map.frag
Original file line number Diff line number Diff line change
@@ -1,31 +1,35 @@
varying vec2 vTextureCoord;
in vec2 vTextureCoord;
out vec4 finalColor;

uniform sampler2D uSampler;
uniform sampler2D colorMap;
uniform float _mix;
uniform float _size;
uniform float _sliceSize;
uniform float _slicePixelSize;
uniform float _sliceInnerSize;
void main() {
vec4 color = texture2D(uSampler, vTextureCoord.xy);
uniform sampler2D uMapTexture;
uniform float uMix;
uniform float uSize;
uniform float uSliceSize;
uniform float uSlicePixelSize;
uniform float uSliceInnerSize;

void main() {
vec4 color = texture(uSampler, vTextureCoord.xy);
vec4 adjusted;

if (color.a > 0.0) {
color.rgb /= color.a;
float innerWidth = _size - 1.0;
float innerWidth = uSize - 1.0;
float zSlice0 = min(floor(color.b * innerWidth), innerWidth);
float zSlice1 = min(zSlice0 + 1.0, innerWidth);
float xOffset = _slicePixelSize * 0.5 + color.r * _sliceInnerSize;
float s0 = xOffset + (zSlice0 * _sliceSize);
float s1 = xOffset + (zSlice1 * _sliceSize);
float yOffset = _sliceSize * 0.5 + color.g * (1.0 - _sliceSize);
vec4 slice0Color = texture2D(colorMap, vec2(s0,yOffset));
vec4 slice1Color = texture2D(colorMap, vec2(s1,yOffset));
float xOffset = uSlicePixelSize * 0.5 + color.r * uSliceInnerSize;
float s0 = xOffset + (zSlice0 * uSliceSize);
float s1 = xOffset + (zSlice1 * uSliceSize);
float yOffset = uSliceSize * 0.5 + color.g * (1.0 - uSliceSize);
vec4 slice0Color = texture(uMapTexture, vec2(s0,yOffset));
vec4 slice1Color = texture(uMapTexture, vec2(s1,yOffset));
float zOffset = fract(color.b * innerWidth);
adjusted = mix(slice0Color, slice1Color, zOffset);

color.rgb *= color.a;
}
gl_FragColor = vec4(mix(color, adjusted, _mix).rgb, color.a);

finalColor = vec4(mix(color, adjusted, uMix).rgb, color.a);

}
39 changes: 39 additions & 0 deletions filters/color-map/src/color-map.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
struct ColorMapUniforms {
uMix: f32,
uSize: f32,
uSliceSize: f32,
uSlicePixelSize: f32,
uSliceInnerSize: f32,
};

@group(0) @binding(1) var uSampler: texture_2d<f32>;
@group(1) @binding(0) var<uniform> colorMapUniforms : ColorMapUniforms;
@group(1) @binding(1) var uMapTexture: texture_2d<f32>;

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

var adjusted: vec4<f32>;

var altColor: vec4<f32> = vec4<f32>(color.rgb / color.a, color.a);
let innerWidth: f32 = colorMapUniforms.uSize - 1.0;
let zSlice0: f32 = min(floor(color.b * innerWidth), innerWidth);
let zSlice1: f32 = min(zSlice0 + 1.0, innerWidth);
let xOffset: f32 = colorMapUniforms.uSlicePixelSize * 0.5 + color.r * colorMapUniforms.uSliceInnerSize;
let s0: f32 = xOffset + (zSlice0 * colorMapUniforms.uSliceSize);
let s1: f32 = xOffset + (zSlice1 * colorMapUniforms.uSliceSize);
let yOffset: f32 = colorMapUniforms.uSliceSize * 0.5 + color.g * (1.0 - colorMapUniforms.uSliceSize);
let slice0Color: vec4<f32> = textureSample(uMapTexture, iSampler, vec2(s0,yOffset));
let slice1Color: vec4<f32> = textureSample(uMapTexture, iSampler, vec2(s1,yOffset));
let zOffset: f32 = fract(color.b * innerWidth);
adjusted = mix(slice0Color, slice1Color, zOffset);
altColor = vec4<f32>(color.rgb * color.a, color.a);

let realColor: vec4<f32> = select(color, altColor, color.a > 0.0);

return vec4<f32>(mix(realColor, adjusted, colorMapUniforms.uMix).rgb, realColor.a);
}
7 changes: 2 additions & 5 deletions tools/demo/src/filters/color-map.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,13 @@ export default function ()

this.addFilter('ColorMapFilter', {
enabled: false,
args: [colorMap, false],
args: { colorMap },
oncreate(folder)
{
folder.add(this, 'mix', 0, 1);
folder.add(this, 'nearest');

this._noop = function ()
{
// noop
};
this._noop = () => {};
folder.add(this, '_noop').name('<img src="./images/colormap.png" width="220" height="13">');
},
});
Expand Down
Loading

0 comments on commit 345a0ce

Please sign in to comment.