From 4d97b583c678ce4874c610de4fe75d0f2cdccf5e Mon Sep 17 00:00:00 2001 From: tschaub Date: Thu, 25 Oct 2012 16:28:17 -0600 Subject: [PATCH 1/3] Match the current Filter Effects spec This commit is a cherry-pick of 19f7778. The current draft of the [filter spec](https://dvcs.w3.org/hg/FXTF/raw-file/tip/filters/index.html) describes brightness, contrast, hue-rotate, and saturate functions that roughly match our layer's setBrightness, setContrast, setHue, and setSaturation methods. These changes make the range of our methods match the corresponding functions in the spec. The one exception is the brightness function. The spec says it has a range of 0 to positive infinity. The WebKit implementation accepts a range of -1 to 1 (as of https://github.com/WebKit/webkit/commit/8f4765e569). There's an open (ticket)[https://www.w3.org/Bugs/Public/show_bug.cgi?id=15647] recommending that the spec be changed to match. I'm not stuck on having our methods match those of the filter spec, but the parity would be nice. These changes leave the WebGL map renderer "broken" (whacky colors). It would be straightforward to update the current fragment shader to handle the new range of hue, but the brightness, contrast, and saturation handling will need to be reworked. For inspiration, here are the color transformation matrix calculations the WebKit filters: https://github.com/WebKit/webkit/blob/8f4765e5698c9171c2b3f984d0d9d65188185de3/Source/WebCore/platform/graphics/chromium/cc/CCRenderSurfaceFilters.cpp#L64-80 Conflicts: src/ol/renderer/dom/domlayerrenderer.js --- examples/side-by-side.js | 14 +- src/ol/layer/layer.js | 36 +++- test/ol.html | 1 + test/spec/ol/layer/layer.test.js | 275 +++++++++++++++++++++++++++++++ 4 files changed, 316 insertions(+), 10 deletions(-) create mode 100644 test/spec/ol/layer/layer.test.js diff --git a/examples/side-by-side.js b/examples/side-by-side.js index 5d7f531d31..e0ee1b8827 100644 --- a/examples/side-by-side.js +++ b/examples/side-by-side.js @@ -56,9 +56,9 @@ webglMap.getControls().push(new ol.control.MousePosition({ var keyboardInteraction = new ol.interaction.Keyboard(); keyboardInteraction.addCallback('0', function() { layer.setBrightness(0); - layer.setContrast(0); + layer.setContrast(1); layer.setHue(0); - layer.setSaturation(0); + layer.setSaturation(1); layer.setOpacity(1); layer.setVisible(true); }); @@ -72,13 +72,14 @@ keyboardInteraction.addCallback('c', function() { layer.setContrast(layer.getContrast() - 0.1); }); keyboardInteraction.addCallback('C', function() { - layer.setContrast(layer.getContrast() + 0.1); + // contrast is unbounded, but for this example we clamp to 3 + layer.setContrast(Math.min(3, layer.getContrast() + 0.1)); }); keyboardInteraction.addCallback('h', function() { - layer.setHue(layer.getHue() - 0.1); + layer.setHue(layer.getHue() - 10); }); keyboardInteraction.addCallback('H', function() { - layer.setHue(layer.getHue() + 0.1); + layer.setHue(layer.getHue() + 10); }); keyboardInteraction.addCallback('o', function() { layer.setOpacity(layer.getOpacity() - 0.1); @@ -93,7 +94,8 @@ keyboardInteraction.addCallback('s', function() { layer.setSaturation(layer.getSaturation() - 0.1); }); keyboardInteraction.addCallback('S', function() { - layer.setSaturation(layer.getSaturation() + 0.1); + // saturation is unbounded, but for this example we clamp to 3 + layer.setSaturation(Math.min(3, layer.getSaturation() + 0.1)); }); keyboardInteraction.addCallback('vV', function() { layer.setVisible(!layer.getVisible()); diff --git a/src/ol/layer/layer.js b/src/ol/layer/layer.js index 0334880fad..c91471b747 100644 --- a/src/ol/layer/layer.js +++ b/src/ol/layer/layer.js @@ -40,13 +40,13 @@ ol.layer.Layer = function(layerOptions) { this.setBrightness( goog.isDef(layerOptions.brightness) ? layerOptions.brightness : 0); this.setContrast( - goog.isDef(layerOptions.contrast) ? layerOptions.contrast : 0); + goog.isDef(layerOptions.contrast) ? layerOptions.contrast : 1); this.setHue( goog.isDef(layerOptions.hue) ? layerOptions.hue : 0); this.setOpacity( goog.isDef(layerOptions.opacity) ? layerOptions.opacity : 1); this.setSaturation( - goog.isDef(layerOptions.saturation) ? layerOptions.saturation : 0); + goog.isDef(layerOptions.saturation) ? layerOptions.saturation : 1); this.setVisible( goog.isDef(layerOptions.visible) ? layerOptions.visible : true); @@ -164,6 +164,23 @@ ol.layer.Layer.prototype.isReady = function() { /** + * Adjust the layer brightness. A value of -1 will render the layer completely + * black. A value of 0 will leave the brightness unchanged. A value of 1 will + * render the layer completely white. Other values are linear multipliers on + * the effect (values are clamped between -1 and 1). + * + * The filter effects draft [1] says the brightness function is supposed to + * render 0 black, 1 unchanged, and all other values as a linear multiplier. + * + * The current WebKit implementation clamps values between -1 (black) and 1 + * (white) [2]. There is a bug open to change the filter effect spec [3]. + * + * TODO: revisit this if the spec is still unmodified before we release + * + * [1] https://dvcs.w3.org/hg/FXTF/raw-file/tip/filters/index.html + * [2] https://github.com/WebKit/webkit/commit/8f4765e569 + * [3] https://www.w3.org/Bugs/Public/show_bug.cgi?id=15647 + * * @param {number} brightness Brightness. */ ol.layer.Layer.prototype.setBrightness = function(brightness) { @@ -179,10 +196,14 @@ goog.exportProperty( /** + * Adjust the layer contrast. A value of 0 will render the layer completely + * grey. A value of 1 will leave the contrast unchanged. Other values are + * linear multipliers on the effect (and values over 1 are permitted). + * * @param {number} contrast Contrast. */ ol.layer.Layer.prototype.setContrast = function(contrast) { - contrast = goog.math.clamp(contrast, -1, 1); + contrast = Math.max(0, contrast); if (contrast != this.getContrast()) { this.set(ol.layer.LayerProperty.CONTRAST, contrast); } @@ -194,6 +215,8 @@ goog.exportProperty( /** + * Apply a hue-rotation to the layer. A value of 0 will leave the hue + * unchanged. Other values are degrees around the color circle. * @param {number} hue Hue. */ ol.layer.Layer.prototype.setHue = function(hue) { @@ -223,10 +246,15 @@ goog.exportProperty( /** + * Adjust layer saturation. A value of 0 will render the layer completely + * unsaturated. A value of 1 will leave the saturation unchanged. Other + * values are linear multipliers of the effect (and values over 1 are + * permitted). + * * @param {number} saturation Saturation. */ ol.layer.Layer.prototype.setSaturation = function(saturation) { - saturation = goog.math.clamp(saturation, -1, 1); + saturation = Math.max(0, saturation); if (saturation != this.getSaturation()) { this.set(ol.layer.LayerProperty.SATURATION, saturation); } diff --git a/test/ol.html b/test/ol.html index 1d438b53cd..08793ee015 100644 --- a/test/ol.html +++ b/test/ol.html @@ -78,6 +78,7 @@ + diff --git a/test/spec/ol/layer/layer.test.js b/test/spec/ol/layer/layer.test.js new file mode 100644 index 0000000000..00988c7a09 --- /dev/null +++ b/test/spec/ol/layer/layer.test.js @@ -0,0 +1,275 @@ +describe('ol.layer.Layer', function() { + + describe('constructor (defaults)', function() { + + var layer; + + beforeEach(function() { + layer = new ol.layer.Layer({ + source: new ol.source.Source({ + projection: ol.Projection.getFromCode('EPSG:4326') + }) + }); + }); + + afterEach(function() { + layer.dispose(); + }); + + it('creates an instance', function() { + expect(layer).toBeA(ol.layer.Layer); + }); + + it('provides default brightness', function() { + expect(layer.getBrightness()).toBe(0); + }); + + it('provides default contrast', function() { + expect(layer.getContrast()).toBe(1); + }); + + it('provides default hue', function() { + expect(layer.getHue()).toBe(0); + }); + + it('provides default opacity', function() { + expect(layer.getOpacity()).toBe(1); + }); + + it('provides default saturation', function() { + expect(layer.getSaturation()).toBe(1); + }); + + it('provides default visibility', function() { + expect(layer.getVisible()).toBe(true); + }); + + }); + + describe('constructor (options)', function() { + + it('accepts options', function() { + var layer = new ol.layer.Layer({ + source: new ol.source.Source({ + projection: ol.Projection.getFromCode('EPSG:4326') + }), + brightness: 0.5, + contrast: 10, + hue: 180, + opacity: 0.5, + saturation: 5, + visible: false + }); + + expect(layer.getBrightness()).toBe(0.5); + expect(layer.getContrast()).toBe(10); + expect(layer.getHue()).toBe(180); + expect(layer.getOpacity()).toBe(0.5); + expect(layer.getSaturation()).toBe(5); + expect(layer.getVisible()).toBe(false); + + layer.dispose(); + }); + + }); + + describe('#setBrightness', function() { + + var layer; + + beforeEach(function() { + layer = new ol.layer.Layer({ + source: new ol.source.Source({ + projection: ol.Projection.getFromCode('EPSG:4326') + }) + }); + }); + + afterEach(function() { + layer.dispose(); + }); + + it('accepts a positive number', function() { + layer.setBrightness(0.3); + expect(layer.getBrightness()).toBe(0.3); + }); + + it('accepts a negative number', function() { + layer.setBrightness(-0.7); + expect(layer.getBrightness()).toBe(-0.7); + }); + + it('clamps to 1', function() { + layer.setBrightness(1.5); + expect(layer.getBrightness()).toBe(1); + }); + + it('clamps to -1', function() { + layer.setBrightness(-3); + expect(layer.getBrightness()).toBe(-1); + }); + + }); + + describe('#setContrast', function() { + + var layer; + + beforeEach(function() { + layer = new ol.layer.Layer({ + source: new ol.source.Source({ + projection: ol.Projection.getFromCode('EPSG:4326') + }) + }); + }); + + afterEach(function() { + layer.dispose(); + }); + + it('accepts a small positive number', function() { + layer.setContrast(0.3); + expect(layer.getContrast()).toBe(0.3); + }); + + it('clamps to 0', function() { + layer.setContrast(-0.7); + expect(layer.getContrast()).toBe(0); + }); + + it('accepts a big positive number', function() { + layer.setContrast(42); + expect(layer.getContrast()).toBe(42); + }); + + }); + + + describe('#setHue', function() { + + var layer; + + beforeEach(function() { + layer = new ol.layer.Layer({ + source: new ol.source.Source({ + projection: ol.Projection.getFromCode('EPSG:4326') + }) + }); + }); + + afterEach(function() { + layer.dispose(); + }); + + it('accepts a small positive number', function() { + layer.setHue(0.3); + expect(layer.getHue()).toBe(0.3); + }); + + it('accepts a small negative number', function() { + layer.setHue(-0.7); + expect(layer.getHue()).toBe(-0.7); + }); + + it('accepts a big positive number', function() { + layer.setHue(42); + expect(layer.getHue()).toBe(42); + }); + + it('accepts a big negative number', function() { + layer.setHue(-100); + expect(layer.getHue()).toBe(-100); + }); + + }); + + + describe('#setOpacity', function() { + + var layer; + + beforeEach(function() { + layer = new ol.layer.Layer({ + source: new ol.source.Source({ + projection: ol.Projection.getFromCode('EPSG:4326') + }) + }); + }); + + afterEach(function() { + layer.dispose(); + }); + + it('accepts a positive number', function() { + layer.setOpacity(0.3); + expect(layer.getOpacity()).toBe(0.3); + }); + + it('clamps to 0', function() { + layer.setOpacity(-1.5); + expect(layer.getOpacity()).toBe(0); + }); + + it('clamps to 1', function() { + layer.setOpacity(3); + expect(layer.getOpacity()).toBe(1); + }); + + }); + + + describe('#setSaturation', function() { + + var layer; + + beforeEach(function() { + layer = new ol.layer.Layer({ + source: new ol.source.Source({ + projection: ol.Projection.getFromCode('EPSG:4326') + }) + }); + }); + + afterEach(function() { + layer.dispose(); + }); + + it('accepts a small positive number', function() { + layer.setSaturation(0.3); + expect(layer.getSaturation()).toBe(0.3); + }); + + it('clamps to 0', function() { + layer.setSaturation(-0.7); + expect(layer.getSaturation()).toBe(0); + }); + + it('accepts a big positive number', function() { + layer.setSaturation(42); + expect(layer.getSaturation()).toBe(42); + }); + + }); + + + describe('#setVisible', function() { + + it('sets visible property', function() { + var layer = new ol.layer.Layer({ + source: new ol.source.Source({ + projection: ol.Projection.getFromCode('EPSG:4326') + }) + }); + + layer.setVisible(false); + expect(layer.getVisible()).toBe(false); + + layer.setVisible(true); + expect(layer.getVisible()).toBe(true); + + layer.dispose(); + }); + + }); + +}); From 9622395c862d52e50a532b6afadaede764c1c5e0 Mon Sep 17 00:00:00 2001 From: tschaub Date: Thu, 25 Oct 2012 18:36:35 -0600 Subject: [PATCH 2/3] Use radians for hue rotation This commit is a cherry-pick from 7f578f0. Conflicts: src/ol/renderer/dom/domlayerrenderer.js --- examples/side-by-side.js | 4 ++-- src/ol/layer/layer.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/side-by-side.js b/examples/side-by-side.js index e0ee1b8827..d2e9817662 100644 --- a/examples/side-by-side.js +++ b/examples/side-by-side.js @@ -76,10 +76,10 @@ keyboardInteraction.addCallback('C', function() { layer.setContrast(Math.min(3, layer.getContrast() + 0.1)); }); keyboardInteraction.addCallback('h', function() { - layer.setHue(layer.getHue() - 10); + layer.setHue(layer.getHue() - (Math.PI / 5)); }); keyboardInteraction.addCallback('H', function() { - layer.setHue(layer.getHue() + 10); + layer.setHue(layer.getHue() + (Math.PI / 5)); }); keyboardInteraction.addCallback('o', function() { layer.setOpacity(layer.getOpacity() - 0.1); diff --git a/src/ol/layer/layer.js b/src/ol/layer/layer.js index c91471b747..277127b7bc 100644 --- a/src/ol/layer/layer.js +++ b/src/ol/layer/layer.js @@ -216,7 +216,7 @@ goog.exportProperty( /** * Apply a hue-rotation to the layer. A value of 0 will leave the hue - * unchanged. Other values are degrees around the color circle. + * unchanged. Other values are radians around the color circle. * @param {number} hue Hue. */ ol.layer.Layer.prototype.setHue = function(hue) { From 21f3f90ca69fe57da522d08616140c6bca35f204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Thu, 29 Nov 2012 22:48:40 +0100 Subject: [PATCH 3/3] Adjust hue, sat, and constrast in webgl renderer --- src/ol/renderer/webgl/webglmaprenderer.js | 161 +++++++++++++++------- 1 file changed, 108 insertions(+), 53 deletions(-) diff --git a/src/ol/renderer/webgl/webglmaprenderer.js b/src/ol/renderer/webgl/webglmaprenderer.js index 494a6198ca..13ea482b40 100644 --- a/src/ol/renderer/webgl/webglmaprenderer.js +++ b/src/ol/renderer/webgl/webglmaprenderer.js @@ -16,6 +16,7 @@ goog.require('goog.events.Event'); goog.require('goog.events.EventType'); goog.require('goog.functions'); goog.require('goog.style'); +goog.require('goog.vec.Mat4'); goog.require('goog.webgl'); goog.require('ol.layer.Layer'); goog.require('ol.layer.TileLayer'); @@ -36,19 +37,15 @@ ol.renderer.webgl.TextureCacheEntry; /** * @constructor * @extends {ol.renderer.webgl.FragmentShader} - * @see https://github.com/evanw/glfx.js/blob/master/src/filters/adjust/brightnesscontrast.js - * @see https://github.com/evanw/glfx.js/blob/master/src/filters/adjust/huesaturation.js + * @see https://svn.webkit.org/repository/webkit/trunk/Source/WebCore/platform/graphics/filters/skia/SkiaImageFilterBuilder.cpp */ ol.renderer.webgl.map.shader.Fragment = function() { goog.base(this, [ 'precision mediump float;', '', - 'uniform float uBrightness;', - 'uniform float uContrast;', - 'uniform float uHue;', + 'uniform mat4 uColorMatrix;', 'uniform float uOpacity;', 'uniform mat4 uMatrix;', - 'uniform float uSaturation;', 'uniform sampler2D uTexture;', '', 'varying vec2 vTexCoord;', @@ -56,41 +53,9 @@ ol.renderer.webgl.map.shader.Fragment = function() { 'void main(void) {', '', ' vec4 texCoord = uMatrix * vec4(vTexCoord, 0., 1.);', - ' vec4 color = texture2D(uTexture, texCoord.st);', - '', - ' if (uHue != 0.) {', - ' float angle = uHue * 3.14159265;', - ' float s = sin(angle), c = cos(angle);', - ' vec3 weights = (vec3(2. * c, -sqrt(3.) * s - c, sqrt(3.) * s - c)', - ' + 1.) / 3.;', - ' color.rgb = vec3(', - ' dot(color.rgb, weights.xyz),', - ' dot(color.rgb, weights.zxy),', - ' dot(color.rgb, weights.yzx)', - ' );', - ' }', - '', - ' if (uSaturation != 0.) {', - ' float average = (color.r + color.g + color.b) / 3.;', - ' if (uSaturation > 0.) {', - ' color.rgb += (average - color.rgb)', - ' * (1. - 1. / (1. - uSaturation));', - ' } else {', - ' color.rgb += (average - color.rgb) * -uSaturation;', - ' }', - ' }', - '', - ' color.rgb += uBrightness;', - '', - ' if (uContrast != 0.) {', - ' if (uContrast > 0.) {', - ' color.rgb = (color.rgb - 0.5) / (1. - uContrast) + 0.5;', - ' } else {', - ' color.rgb = (color.rgb - 0.5) * (1. + uContrast) + 0.5;', - ' }', - ' }', - '', - ' color.a = color.a * uOpacity;', + ' vec4 texColor = texture2D(uTexture, texCoord.st);', + ' vec4 color = uColorMatrix * vec4(texColor.rgb, 1.);', + ' color.a = texColor.a * uOpacity;', '', ' gl_FragColor = color;', '', @@ -188,12 +153,9 @@ ol.renderer.webgl.Map = function(container, map) { * @private * @type {{aPosition: number, * aTexCoord: number, - * uBrightness: WebGLUniformLocation, - * uContrast: WebGLUniformLocation, - * uHue: WebGLUniformLocation, + * uColorMatrix: WebGLUniformLocation, * uMatrix: WebGLUniformLocation, * uOpacity: WebGLUniformLocation, - * uSaturation: WebGLUniformLocation, * uTexture: WebGLUniformLocation}|null} */ this.locations_ = null; @@ -307,6 +269,64 @@ ol.renderer.webgl.Map.prototype.bindImageTexture = ol.renderer.webgl.Map.prototype.canRotate = goog.functions.TRUE; +/** + * @param {number} value Hue value. + * @return {!goog.vec.Mat4.Float32} Matrix. + */ +ol.renderer.webgl.Map.prototype.createHueRotateMatrix = function(value) { + var cosHue = Math.cos(value); + var sinHue = Math.sin(value); + var v00 = 0.213 + cosHue * 0.787 - sinHue * 0.213; + var v01 = 0.715 - cosHue * 0.715 - sinHue * 0.715; + var v02 = 0.072 - cosHue * 0.072 + sinHue * 0.928; + var v03 = 0; + var v10 = 0.213 - cosHue * 0.213 + sinHue * 0.143; + var v11 = 0.715 + cosHue * 0.285 + sinHue * 0.140; + var v12 = 0.072 - cosHue * 0.072 - sinHue * 0.283; + var v13 = 0; + var v20 = 0.213 - cosHue * 0.213 - sinHue * 0.787; + var v21 = 0.715 - cosHue * 0.715 + sinHue * 0.715; + var v22 = 0.072 + cosHue * 0.928 + sinHue * 0.072; + var v23 = 0; + var v30 = 0; + var v31 = 0; + var v32 = 0; + var v33 = 1; + var matrix = goog.vec.Mat4.createFloat32(); + goog.vec.Mat4.setFromValues(matrix, + v00, v10, v20, v30, + v01, v11, v21, v31, + v02, v12, v22, v32, + v03, v13, v23, v33); + return matrix; +}; + + +/** + * @param {number} value Brightness value. + * @return {!goog.vec.Mat4.Float32} Matrix. + */ +ol.renderer.webgl.Map.prototype.createBrightnessMatrix = function(value) { + var matrix = goog.vec.Mat4.createFloat32Identity(); + goog.vec.Mat4.setColumnValues(matrix, 3, value, value, value, 1); + return matrix; +}; + + +/** + * @param {number} value Contrast value. + * @return {!goog.vec.Mat4.Float32} Matrix. + */ +ol.renderer.webgl.Map.prototype.createContrastMatrix = function(value) { + var matrix = goog.vec.Mat4.createFloat32(); + goog.vec.Mat4.setDiagonalValues(matrix, value, value, value, 1); + var translateValue = (-0.5 * value + 0.5); + goog.vec.Mat4.setColumnValues(matrix, 3, + translateValue, translateValue, translateValue, 1); + return matrix; +}; + + /** * @inheritDoc */ @@ -321,6 +341,38 @@ ol.renderer.webgl.Map.prototype.createLayerRenderer = function(layer) { }; +/** + * @param {number} value Saturation value. + * @return {!goog.vec.Mat4.Float32} Matrix. + */ +ol.renderer.webgl.Map.prototype.createSaturateMatrix = function(value) { + var v00 = 0.213 + 0.787 * value; + var v01 = 0.715 - 0.715 * value; + var v02 = 0.072 - 0.072 * value; + var v03 = 0; + var v10 = 0.213 - 0.213 * value; + var v11 = 0.715 + 0.285 * value; + var v12 = 0.072 - 0.072 * value; + var v13 = 0; + var v20 = 0.213 - 0.213 * value; + var v21 = 0.715 - 0.715 * value; + var v22 = 0.072 + 0.928 * value; + var v23 = 0; + var v30 = 0; + var v31 = 0; + var v32 = 0; + var v33 = 1; + var matrix = goog.vec.Mat4.createFloat32(); + goog.vec.Mat4.setFromValues(matrix, + v00, v10, v20, v30, + v01, v11, v21, v31, + v02, v12, v22, v32, + v03, v13, v23, v33); + return matrix; + +}; + + /** * @inheritDoc */ @@ -585,12 +637,9 @@ ol.renderer.webgl.Map.prototype.render = function() { this.locations_ = { aPosition: gl.getAttribLocation(program, 'aPosition'), aTexCoord: gl.getAttribLocation(program, 'aTexCoord'), - uBrightness: gl.getUniformLocation(program, 'uBrightness'), - uContrast: gl.getUniformLocation(program, 'uContrast'), - uHue: gl.getUniformLocation(program, 'uHue'), + uColorMatrix: gl.getUniformLocation(program, 'uColorMatrix'), uMatrix: gl.getUniformLocation(program, 'uMatrix'), uOpacity: gl.getUniformLocation(program, 'uOpacity'), - uSaturation: gl.getUniformLocation(program, 'uSaturation'), uTexture: gl.getUniformLocation(program, 'uTexture') }; } @@ -620,11 +669,17 @@ ol.renderer.webgl.Map.prototype.render = function() { this.forEachReadyVisibleLayer(function(layer, layerRenderer) { gl.uniformMatrix4fv( this.locations_.uMatrix, false, layerRenderer.getMatrix()); - gl.uniform1f(this.locations_.uBrightness, layer.getBrightness()); - gl.uniform1f(this.locations_.uContrast, layer.getContrast()); - gl.uniform1f(this.locations_.uHue, layer.getHue()); + var hueRotateMatrix = this.createHueRotateMatrix(layer.getHue()); + var saturateMatrix = this.createSaturateMatrix(layer.getSaturation()); + var brightnessMatrix = this.createBrightnessMatrix(layer.getBrightness()); + var contrastMatrix = this.createContrastMatrix(layer.getContrast()); + var colorMatrix = goog.vec.Mat4.createFloat32Identity(); + goog.vec.Mat4.multMat(colorMatrix, contrastMatrix, colorMatrix); + goog.vec.Mat4.multMat(colorMatrix, brightnessMatrix, colorMatrix); + goog.vec.Mat4.multMat(colorMatrix, saturateMatrix, colorMatrix); + goog.vec.Mat4.multMat(colorMatrix, hueRotateMatrix, colorMatrix); + gl.uniformMatrix4fv(this.locations_.uColorMatrix, false, colorMatrix); gl.uniform1f(this.locations_.uOpacity, layer.getOpacity()); - gl.uniform1f(this.locations_.uSaturation, layer.getSaturation()); gl.bindTexture(goog.webgl.TEXTURE_2D, layerRenderer.getTexture()); gl.drawArrays(goog.webgl.TRIANGLE_STRIP, 0, 4); }, this);