const AbstractFilter = require('../../core/renderers/webgl/filters/AbstractFilter'); /** * The ColorMatrixFilter class lets you apply a 5x4 matrix transformation on the RGBA * color and alpha values of every pixel on your displayObject to produce a result * with a new set of RGBA color and alpha values. It's pretty powerful! * * ```js * var colorMatrix = new ColorMatrixFilter(); * container.filters = [colorMatrix]; * colorMatrix.contrast(2); * ``` * @author Clément Chenebault <clement@goodboydigital.com> * @class * @extends AbstractFilter */ function ColorMatrixFilter() { AbstractFilter.call(this, // vertex shader null, // fragment shader require('./colorMatrix.frag'), // custom uniforms { m: { type: '1fv', value: [ 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, ], }, } ); } ColorMatrixFilter.prototype = Object.create(AbstractFilter.prototype); ColorMatrixFilter.prototype.constructor = ColorMatrixFilter; module.exports = ColorMatrixFilter; /** * Transforms current matrix and set the new one * * @param matrix {number[]} (mat 5x4) * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix */ ColorMatrixFilter.prototype._loadMatrix = function(matrix, multiply) { multiply = !!multiply; var newMatrix = matrix; if (multiply) { this._multiply(newMatrix, this.uniforms.m.value, matrix); newMatrix = this._colorMatrix(newMatrix); } // set the new matrix this.uniforms.m.value = newMatrix; }; /** * Multiplies two mat5's * * @param out {number[]} (mat 5x4) the receiving matrix * @param a {number[]} (mat 5x4) the first operand * @param b {number[]} (mat 5x4) the second operand * @returns out {number[]} (mat 5x4) */ ColorMatrixFilter.prototype._multiply = function(out, a, b) { // Red Channel out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); // Green Channel out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); // Blue Channel out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); // Alpha Channel out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); return out; }; /** * Create a Float32 Array and normalize the offset component to 0-1 * * @param matrix {number[]} (mat 5x4) * @return m {number[]} (mat 5x4) with all values between 0-1 */ ColorMatrixFilter.prototype._colorMatrix = function(matrix) { // Create a Float32 Array and normalize the offset component to 0-1 var m = new Float32Array(matrix); m[4] /= 255; m[9] /= 255; m[14] /= 255; m[19] /= 255; return m; }; /** * Adjusts brightness * * @param b {number} value of the brigthness (0 is black) * @param multiply {boolean} refer to ._loadMatrix() method */ ColorMatrixFilter.prototype.brightness = function(b, multiply) { var matrix = [ b, 0, 0, 0, 0, 0, b, 0, 0, 0, 0, 0, b, 0, 0, 0, 0, 0, 1, 0, ]; this._loadMatrix(matrix, multiply); }; /** * Set the matrices in grey scales * * @param scale {number} value of the grey (0 is black) * @param multiply {boolean} refer to ._loadMatrix() method */ ColorMatrixFilter.prototype.greyscale = function(scale, multiply) { var matrix = [ scale, scale, scale, 0, 0, scale, scale, scale, 0, 0, scale, scale, scale, 0, 0, 0, 0, 0, 1, 0, ]; this._loadMatrix(matrix, multiply); }; // Americanized alias ColorMatrixFilter.prototype.grayscale = ColorMatrixFilter.prototype.greyscale; /** * Set the black and white matrice * Multiply the current matrix * * @param multiply {boolean} refer to ._loadMatrix() method */ ColorMatrixFilter.prototype.blackAndWhite = function(multiply) { var matrix = [ 0.3, 0.6, 0.1, 0, 0, 0.3, 0.6, 0.1, 0, 0, 0.3, 0.6, 0.1, 0, 0, 0, 0, 0, 1, 0, ]; this._loadMatrix(matrix, multiply); }; /** * Set the hue property of the color * * @param rotation {number} in degrees * @param multiply {boolean} refer to ._loadMatrix() method */ ColorMatrixFilter.prototype.hue = function(rotation, multiply) { rotation = (rotation || 0) / 180 * Math.PI; var cos = Math.cos(rotation), sin = Math.sin(rotation); // luminanceRed, luminanceGreen, luminanceBlue var lumR = 0.213, // or 0.3086 lumG = 0.715, // or 0.6094 lumB = 0.072; // or 0.0820 var matrix = [ lumR + cos * (1 - lumR) + sin * (-lumR), lumG + cos * (-lumG) + sin * (-lumG), lumB + cos * (-lumB) + sin * (1 - lumB), 0, 0, lumR + cos * (-lumR) + sin * (0.143), lumG + cos * (1 - lumG) + sin * (0.140), lumB + cos * (-lumB) + sin * (-0.283), 0, 0, lumR + cos * (-lumR) + sin * (-(1 - lumR)), lumG + cos * (-lumG) + sin * (lumG), lumB + cos * (1 - lumB) + sin * (lumB), 0, 0, 0, 0, 0, 1, 0, ]; this._loadMatrix(matrix, multiply); }; /** * Set the contrast matrix, increase the separation between dark and bright * Increase contrast : shadows darker and highlights brighter * Decrease contrast : bring the shadows up and the highlights down * * @param amount {number} value of the contrast * @param multiply {boolean} refer to ._loadMatrix() method */ ColorMatrixFilter.prototype.contrast = function(amount, multiply) { var v = (amount || 0) + 1; var o = -128 * (v - 1); var matrix = [ v, 0, 0, 0, o, 0, v, 0, 0, o, 0, 0, v, 0, o, 0, 0, 0, 1, 0, ]; this._loadMatrix(matrix, multiply); }; /** * Set the saturation matrix, increase the separation between colors * Increase saturation : increase contrast, brightness, and sharpness * * @param amount {number} * @param multiply {boolean} refer to ._loadMatrix() method */ ColorMatrixFilter.prototype.saturate = function(amount, multiply) { var x = (amount || 0) * 2 / 3 + 1; var y = ((x - 1) * -0.5); var matrix = [ x, y, y, 0, 0, y, x, y, 0, 0, y, y, x, 0, 0, 0, 0, 0, 1, 0, ]; this._loadMatrix(matrix, multiply); }; /** * Desaturate image (remove color) * * Call the saturate function * * @param multiply {boolean} refer to ._loadMatrix() method */ ColorMatrixFilter.prototype.desaturate = function(multiply) { /* eslint no-unused-vars:0 */ this.saturate(-1); }; /** * Negative image (inverse of classic rgb matrix) * * @param multiply {boolean} refer to ._loadMatrix() method */ ColorMatrixFilter.prototype.negative = function(multiply) { var matrix = [ 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, ]; this._loadMatrix(matrix, multiply); }; /** * Sepia image * * @param multiply {boolean} refer to ._loadMatrix() method */ ColorMatrixFilter.prototype.sepia = function(multiply) { var matrix = [ 0.393, 0.7689999, 0.18899999, 0, 0, 0.349, 0.6859999, 0.16799999, 0, 0, 0.272, 0.5339999, 0.13099999, 0, 0, 0, 0, 0, 1, 0, ]; this._loadMatrix(matrix, multiply); }; /** * Color motion picture process invented in 1916 (thanks Dominic Szablewski) * * @param multiply {boolean} refer to ._loadMatrix() method */ ColorMatrixFilter.prototype.technicolor = function(multiply) { var matrix = [ 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, 0, 0, 0, 1, 0, ]; this._loadMatrix(matrix, multiply); }; /** * Polaroid filter * * @param multiply {boolean} refer to ._loadMatrix() method */ ColorMatrixFilter.prototype.polaroid = function(multiply) { var matrix = [ 1.438, -0.062, -0.062, 0, 0, -0.122, 1.378, -0.122, 0, 0, -0.016, -0.016, 1.483, 0, 0, 0, 0, 0, 1, 0, ]; this._loadMatrix(matrix, multiply); }; /** * Filter who transforms : Red -> Blue and Blue -> Red * * @param multiply {boolean} refer to ._loadMatrix() method */ ColorMatrixFilter.prototype.toBGR = function(multiply) { var matrix = [ 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, ]; this._loadMatrix(matrix, multiply); }; /** * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) * * @param multiply {boolean} refer to ._loadMatrix() method */ ColorMatrixFilter.prototype.kodachrome = function(multiply) { var matrix = [ 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, 0, 0, 0, 1, 0, ]; this._loadMatrix(matrix, multiply); }; /** * Brown delicious browni filter (thanks Dominic Szablewski) * * @param multiply {boolean} refer to ._loadMatrix() method */ ColorMatrixFilter.prototype.browni = function(multiply) { var matrix = [ 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, 0, 0, 0, 1, 0, ]; this._loadMatrix(matrix, multiply); }; /* * Vintage filter (thanks Dominic Szablewski) * * @param multiply {boolean} refer to ._loadMatrix() method */ ColorMatrixFilter.prototype.vintage = function(multiply) { var matrix = [ 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, 0, 0, 0, 1, 0, ]; this._loadMatrix(matrix, multiply); }; /* * We don't know exactly what it does, kind of gradient map, but funny to play with! * * @param desaturation {number} * @param toned {number} * @param lightColor {string} (example : "0xFFE580") * @param darkColor {string} (example : "0xFFE580") * * @param multiply {boolean} refer to ._loadMatrix() method */ ColorMatrixFilter.prototype.colorTone = function(desaturation, toned, lightColor, darkColor, multiply) { desaturation = desaturation || 0.2; toned = toned || 0.15; lightColor = lightColor || 0xFFE580; darkColor = darkColor || 0x338000; var lR = ((lightColor >> 16) & 0xFF) / 255; var lG = ((lightColor >> 8) & 0xFF) / 255; var lB = (lightColor & 0xFF) / 255; var dR = ((darkColor >> 16) & 0xFF) / 255; var dG = ((darkColor >> 8) & 0xFF) / 255; var dB = (darkColor & 0xFF) / 255; var matrix = [ 0.3, 0.59, 0.11, 0, 0, lR, lG, lB, desaturation, 0, dR, dG, dB, toned, 0, lR - dR, lG - dG, lB - dB, 0, 0, ]; this._loadMatrix(matrix, multiply); }; /* * Night effect * * @param intensity {number} * @param multiply {boolean} refer to ._loadMatrix() method */ ColorMatrixFilter.prototype.night = function(intensity, multiply) { intensity = intensity || 0.1; var matrix = [ intensity * (-2.0), -intensity, 0, 0, 0, -intensity, 0, intensity, 0, 0, 0, intensity, intensity * 2.0, 0, 0, 0, 0, 0, 1, 0, ]; this._loadMatrix(matrix, multiply); }; /* * Predator effect * * Erase the current matrix by setting a new indepent one * * @param amount {number} how much the predator feels his future victim * @param multiply {boolean} refer to ._loadMatrix() method */ ColorMatrixFilter.prototype.predator = function(amount, multiply) { var matrix = [ 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, 0, 0, 0, 1, 0, ]; this._loadMatrix(matrix, multiply); }; /* * LSD effect * * Multiply the current matrix * * @param amount {number} How crazy is your effect * @param multiply {boolean} refer to ._loadMatrix() method */ ColorMatrixFilter.prototype.lsd = function(multiply) { var matrix = [ 2, -0.4, 0.5, 0, 0, -0.5, 2, -0.4, 0, 0, -0.4, -0.5, 3, 0, 0, 0, 0, 0, 1, 0, ]; this._loadMatrix(matrix, multiply); }; /* * Erase the current matrix by setting the default one * */ ColorMatrixFilter.prototype.reset = function() { var matrix = [ 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, ]; this._loadMatrix(matrix, false); }; Object.defineProperties(ColorMatrixFilter.prototype, { /** * Sets the matrix of the color matrix filter * * @member {number[]} * @memberof filters.ColorMatrixFilter# * @default [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0] */ matrix: { get: function() { return this.uniforms.m.value; }, set: function(value) { this.uniforms.m.value = value; }, }, });