fabric
Version:
Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.
121 lines (103 loc) • 3.1 kB
text/typescript
import { BaseFilter } from './BaseFilter';
import { classRegistry } from '../ClassRegistry';
import { fragmentSource } from './shaders/gamma';
import type { T2DPipelineState, TWebGLUniformLocationMap } from './typedefs';
const GAMMA = 'Gamma' as const;
export type GammaInput = [number, number, number];
export type GammaOwnProps = {
gamma: GammaInput;
};
export const gammaDefaultValues: GammaOwnProps = {
gamma: [1, 1, 1],
};
/**
* Gamma filter class
* @example
* const filter = new Gamma({
* gamma: [1, 0.5, 2.1]
* });
* object.filters.push(filter);
* object.applyFilters();
*/
export class Gamma extends BaseFilter<typeof GAMMA, GammaOwnProps> {
/**
* Gamma array value, from 0.01 to 2.2.
* @param {Array} gamma
* @default
*/
declare gamma: GammaOwnProps['gamma'];
declare rgbValues?: {
r: Uint8Array;
g: Uint8Array;
b: Uint8Array;
};
static type = GAMMA;
static defaults = gammaDefaultValues;
static uniformLocations = ['uGamma'];
getFragmentSource() {
return fragmentSource;
}
constructor(options: { gamma?: GammaInput } = {}) {
super(options);
this.gamma =
options.gamma ||
((
this.constructor as typeof Gamma
).defaults.gamma.concat() as GammaInput);
}
/**
* Apply the Gamma operation to a Uint8Array representing the pixels of an image.
*
* @param {Object} options
* @param {ImageData} options.imageData The Uint8Array to be filtered.
*/
applyTo2d({ imageData: { data } }: T2DPipelineState) {
const gamma = this.gamma,
rInv = 1 / gamma[0],
gInv = 1 / gamma[1],
bInv = 1 / gamma[2];
if (!this.rgbValues) {
this.rgbValues = {
r: new Uint8Array(256),
g: new Uint8Array(256),
b: new Uint8Array(256),
};
}
// This is an optimization - pre-compute a look-up table for each color channel
// instead of performing these pow calls for each pixel in the image.
const rgb = this.rgbValues;
for (let i = 0; i < 256; i++) {
rgb.r[i] = Math.pow(i / 255, rInv) * 255;
rgb.g[i] = Math.pow(i / 255, gInv) * 255;
rgb.b[i] = Math.pow(i / 255, bInv) * 255;
}
for (let i = 0; i < data.length; i += 4) {
data[i] = rgb.r[data[i]];
data[i + 1] = rgb.g[data[i + 1]];
data[i + 2] = rgb.b[data[i + 2]];
}
}
/**
* Send data from this filter to its shader program's uniforms.
*
* @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader.
* @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects
*/
sendUniformData(
gl: WebGLRenderingContext,
uniformLocations: TWebGLUniformLocationMap,
) {
gl.uniform3fv(uniformLocations.uGamma, this.gamma);
}
isNeutralState() {
const { gamma } = this;
return gamma[0] === 1 && gamma[1] === 1 && gamma[2] === 1;
}
toObject(): { type: typeof GAMMA; gamma: GammaInput } {
return {
type: GAMMA,
gamma: this.gamma.concat() as GammaInput,
};
}
}
classRegistry.setClass(Gamma);