All files / Sources/Rendering/Core/ColorTransferFunction CssFilters.js

95.23% Statements 40/42
78.57% Branches 11/14
88.88% Functions 8/9
94.73% Lines 36/38

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111                                      1x     4002x       4002x                         4002x 4002x   4002x 4002x 16008x 16008x 411x 15597x 774x   14823x     4002x               3001x 3001x 9003x 9003x   3001x           1000x 1000x 1000x           1000x 1000x 3000x 3000x 3000x 3000x 9000x     1000x                 1001x 1001x 1001x           1000x 1000x 1000x    
/**
 * A helper file to transform RGBA points using CSS filters equivalent
 * The equivalents of CSS filters using SVG filters can be found here:
 * https://www.w3.org/TR/filter-effects-1/#ShorthandEquivalents
 * For each SVG filter, you can look for the maths behind it on the same page:
 * https://www.w3.org/TR/filter-effects-1/#FilterPrimitivesOverview
 *
 * For example, the saturate filter equivalent is here:
 * https://www.w3.org/TR/filter-effects-1/#saturateEquivalent
 * And the maths behind the feColorMatrix of type saturate is here:
 * https://www.w3.org/TR/filter-effects-1/#ref-for-attr-valuedef-type-saturate
 *
 * The transforms are done using matrices of size 5 by 5. They are row major
 * as in vtkMath. The vectors representing the RGBA points uses
 * [R, G, B, A, 1] vectors, with each channel between 0 and 1.
 */
 
import { identity, multiplyMatrix } from 'vtk.js/Sources/Common/Core/Math';
 
export const luminanceWeights = [0.213, 0.715, 0.072];
 
export function createCSSFilter() {
  return new Array(25);
}
 
export function createIdentityFilter(outFilter = createCSSFilter()) {
  return identity(5, outFilter);
}
 
export function combineFilters(
  baseFilter,
  newFilter,
  outFilter = createCSSFilter()
) {
  multiplyMatrix(newFilter, baseFilter, 5, 5, 5, 5, outFilter);
  return outFilter;
}
 
export function applyFilter(filter, r, g, b, a = 1) {
  const vec = [r, g, b, a, 1];
  multiplyMatrix(filter, vec, 5, 5, 5, 1, vec);
  // 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(
  slope,
  intercept,
  outFilter = createCSSFilter()
) {
  createIdentityFilter(outFilter);
  for (let row = 0; row < 3; ++row) {
    outFilter[row * 5 + row] = slope;
    outFilter[row * 5 + 4] = intercept;
  }
  return outFilter;
}
 
// https://www.w3.org/TR/filter-effects-1/#contrastEquivalent
// https://www.w3.org/TR/filter-effects-1/#attr-valuedef-type-linear
export function createContrastFilter(contrast, outFilter = createCSSFilter()) {
  const slope = contrast;
  const intercept = -(0.5 * contrast) + 0.5;
  return createLinearFilter(slope, intercept, outFilter);
}
 
// https://www.w3.org/TR/filter-effects-1/#saturateEquivalent
// https://www.w3.org/TR/filter-effects-1/#ref-for-attr-valuedef-type-saturate
export function createSaturateFilter(saturate, outFilter = createCSSFilter()) {
  createIdentityFilter(outFilter);
  for (let col = 0; col < 3; ++col) {
    const columnLuminance = luminanceWeights[col];
    const diagonalValue = columnLuminance + (1 - columnLuminance) * saturate;
    const nonDiagonalValue = columnLuminance - columnLuminance * saturate;
    for (let row = 0; row < 3; ++row) {
      outFilter[row * 5 + col] = row === col ? diagonalValue : nonDiagonalValue;
    }
  }
  return outFilter;
}
 
// https://www.w3.org/TR/filter-effects-1/#brightnessEquivalent
// https://www.w3.org/TR/filter-effects-1/#attr-valuedef-type-linear
export function createBrightnessFilter(
  brightness,
  outFilter = createCSSFilter()
) {
  const slope = brightness;
  const intercept = 0;
  return createLinearFilter(slope, intercept, outFilter);
}
 
// https://www.w3.org/TR/filter-effects-1/#invertEquivalent
// https://www.w3.org/TR/filter-effects-1/#attr-valuedef-type-table
export function createInvertFilter(invert, outFilter = createCSSFilter()) {
  const slope = 1 - 2 * invert;
  const intercept = invert;
  return createLinearFilter(slope, intercept, outFilter);
}