Skip to content

Commit

Permalink
test(CssFilters): Add tests and fix clamping
Browse files Browse the repository at this point in the history
  • Loading branch information
bruyeret committed Feb 8, 2024
1 parent c4f856c commit 6874457
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 2 deletions.
6 changes: 5 additions & 1 deletion Sources/Rendering/Core/ColorTransferFunction/CssFilters.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ export function createIdentityFilter(outFilter?: FilterMatrix): FilterMatrix;

/**
* Combine two filters into a single filter
* The order matters
* Warning: it is NOT an operation inspired by CSS filters
* For this, apply filters one by one using applyFilter
* The clamping step is not applied between each filter when the filters are combined
* The order of the filters matters
* @param baseFilter The first filter that will be applied
* @param newFilter The second filter that will be applied
* @param outFilter An optional filter that will contain the combined filter
Expand All @@ -45,6 +48,7 @@ export function combineFilters(baseFilter: FilterMatrix, newFilter: FilterMatrix

/**
* Apply a filter to a rgb(a) point
* It is a multiplication by the matrix and a clamping
* @param filter The filter
* @param r The red channel (between 0 and 1)
* @param g The green channel (between 0 and 1)
Expand Down
14 changes: 13 additions & 1 deletion Sources/Rendering/Core/ColorTransferFunction/CssFilters.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,19 @@ export function combineFilters(
export function applyFilter(filter, r, g, b, a = 1) {
const vec = [r, g, b, a, 1];
multiplyMatrix(filter, vec, 5, 5, 5, 1, vec);
return vec.slice(0, 4);
// Clamp R, G, B, A
const output = new Array(4);
for (let i = 0; i < 4; ++i) {
const value = vec[i];
if (value < 0) {
output[i] = 0;
} else if (value > 1) {
output[i] = 1;
} else {
output[i] = value;
}
}
return output;
}

export function createLinearFilter(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import test from 'tape';

import * as CssFilters from 'vtk.js/Sources/Rendering/Core/ColorTransferFunction/CssFilters';

test('Test CssFilters identity', (t) => {
const color = [Math.random(), Math.random(), -1, 2];
const identity = CssFilters.createIdentityFilter();
const output = CssFilters.applyFilter(identity, ...color);
t.deepEqual(color, output, 'Apply identity filter');
t.end();
});

test('Test CssFilters brightness', (t) => {
const color = [0.1, 0.5, 0.9, 0.2];
const halfBright = CssFilters.createBrightnessFilter(0.5);
const output = CssFilters.applyFilter(halfBright, ...color);
t.deepEqual([0.05, 0.25, 0.45, 0.2], output, 'Apply brightness filter');
t.end();
});

test('Fuzzy test all CssFilters', (t) => {
const canvas = document.createElement('canvas');
canvas.width = 1;
canvas.height = 1;
const ctx = canvas.getContext('2d', { willReadFrequently: true });

const randomChannel = () => Math.floor(Math.random() * 256);

const numberOfRepetitions = 1000;
for (let repetition = 0; repetition < numberOfRepetitions; repetition++) {
const contrast = 2 * Math.random();
const saturate = 2 * Math.random();
const brightness = 2 * Math.random();
const invert = Math.random();

const color = [randomChannel(), randomChannel(), randomChannel(), 255];

// Apply filters one by one (don't combine as it is not equivalent)
let filtersOutput = color.map((channel) => channel / 255);
const contrastFilter = CssFilters.createContrastFilter(contrast);
filtersOutput = CssFilters.applyFilter(contrastFilter, ...filtersOutput);
const saturateFilter = CssFilters.createSaturateFilter(saturate);
filtersOutput = CssFilters.applyFilter(saturateFilter, ...filtersOutput);
const brightnessFilter = CssFilters.createBrightnessFilter(brightness);
filtersOutput = CssFilters.applyFilter(brightnessFilter, ...filtersOutput);
const invertFilter = CssFilters.createInvertFilter(invert);
filtersOutput = CssFilters.applyFilter(invertFilter, ...filtersOutput);

// Reference: canvas 2D context filters
ctx.fillStyle = `rgb(${color[0]} ${color[1]} ${color[2]})`;
ctx.filter = `contrast(${contrast}) saturate(${saturate}) brightness(${brightness}) invert(${invert})`;
ctx.beginPath();
ctx.fillRect(0, 0, canvas.width, canvas.height);
const canvasPixel = ctx.getImageData(0, 0, 1, 1).data;
const referenceOutput = [...canvasPixel].map((x) => x / 255);

for (let channel = 0; channel < 4; ++channel) {
const error = referenceOutput[channel] - filtersOutput[channel];
// The error depends on the values of each filter
// For example, using a color of [127.5, 127.5, 128.49] with a saturate of 1000 creates big errors
t.assert(Math.abs(error) <= 7 / 255);
}
}
t.end();
});

0 comments on commit 6874457

Please sign in to comment.