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(); + }); + + }); + +});