diff --git a/changelog/upgrade-notes.md b/changelog/upgrade-notes.md index ce789ed4f2..bf6aa0a575 100644 --- a/changelog/upgrade-notes.md +++ b/changelog/upgrade-notes.md @@ -1,5 +1,10 @@ ## Upgrade notes +#### `ol.style.Fill` with `CanvasGradient` or `CanvasPattern` + +Previously, gradients and patterns were aligned with the canvas, so they did not +move and rotate with the map. This was changed to a more expected behavior by anchoring the fill to the map origin (usually at map coordinate `[0, 0]`). + #### `goog.DEBUG` define was renamed to `ol.DEBUG` As last step in the removal of the dependency on Google Closure Library, the `goog.DEBUG` compiler define was renamed to `ol.DEBUG`. Please change accordingly in your custom build configuration json files. diff --git a/examples/canvas-gradient-pattern.js b/examples/canvas-gradient-pattern.js index 3182867a04..c3dde464b3 100644 --- a/examples/canvas-gradient-pattern.js +++ b/examples/canvas-gradient-pattern.js @@ -1,6 +1,7 @@ goog.require('ol.Map'); goog.require('ol.View'); goog.require('ol.format.GeoJSON'); +goog.require('ol.has'); goog.require('ol.layer.Vector'); goog.require('ol.proj'); goog.require('ol.source.Vector'); @@ -8,14 +9,22 @@ goog.require('ol.style.Fill'); goog.require('ol.style.Stroke'); goog.require('ol.style.Style'); -// Will contain ol.Style instances key by country. -var styleLookup = {}; +var canvas = document.createElement('canvas'); +var context = canvas.getContext('2d'); + +// Gradient and pattern are in canvas pixel space, so we adjust for the +// renderer's pixel ratio +var pixelRatio = ol.has.DEVICE_PIXEL_RATIO; // Generate a rainbow gradient -var gradient = (function() { - var canvas = document.createElement('canvas'); - var context = canvas.getContext('2d'); - var grad = context.createLinearGradient(0,0,1000,0); +function gradient(feature, resolution) { + var extent = feature.getGeometry().getExtent(); + // Gradient starts on the left edge of each feature, and ends on the right. + // Coordinate origin is [0, 0], so we just divide by resolution and multiply + // with pixelRatio to match the renderer's pixel coordinate system. + var grad = context.createLinearGradient( + extent[0] / resolution * pixelRatio, 0, + extent[2] / resolution * pixelRatio, 0); grad.addColorStop(0, 'red'); grad.addColorStop(1 / 6, 'orange'); grad.addColorStop(2 / 6, 'yellow'); @@ -24,33 +33,35 @@ var gradient = (function() { grad.addColorStop(5 / 6, 'blue'); grad.addColorStop(1, 'purple'); return grad; -}()); +} -// Generate a canvasPattern with two circles +// Generate a canvasPattern with two circles on white background var pattern = (function() { - var canvas = document.createElement('canvas'); - var context = canvas.getContext('2d'); - canvas.width = 11; - canvas.height = 11; + canvas.width = 11 * pixelRatio; + canvas.height = 11 * pixelRatio; + // white background + context.fillStyle = 'white'; + context.fillRect(0, 0, canvas.width, canvas.height); + // outer circle context.fillStyle = 'rgba(102, 0, 102, 0.5)'; context.beginPath(); - context.arc(5, 5, 4, 0, 2 * Math.PI); + context.arc(5 * pixelRatio, 5 * pixelRatio, 4 * pixelRatio, 0, 2 * Math.PI); context.fill(); + // inner circle context.fillStyle = 'rgb(55, 0, 170)'; context.beginPath(); - context.arc(5, 5, 2, 0, 2 * Math.PI); + context.arc(5 * pixelRatio, 5 * pixelRatio, 2 * pixelRatio, 0, 2 * Math.PI); context.fill(); return context.createPattern(canvas, 'repeat'); }()); -// Generate a background style that all features will reuse -var backgroundStyle = new ol.style.Style({ +// Generate style for gradient or pattern fill +var fill = new ol.style.Fill(); +var style = new ol.style.Style({ + fill: fill, stroke: new ol.style.Stroke({ color: '#333', width: 2 - }), - fill: new ol.style.Fill({ - color: '#fff' }) }); @@ -58,33 +69,14 @@ var backgroundStyle = new ol.style.Style({ * The styling function for the vector layer, will return an array of styles * which either contains the aboove gradient or pattern. * - * @param {ol.Feature} feature the feature to style. - * @return {Array} the styles to use for the feature. + * @param {ol.Feature} feature The feature to style. + * @param {number} resolution Resolution. + * @return {ol.style.Style} The style to use for the feature. */ -var getStackedStyle = function(feature) { +var getStackedStyle = function(feature, resolution) { var id = feature.getId(); - if (!styleLookup[id]) { - var patternOrGradient; - if (id > 'J') { // some shall get the gradient, others the pattern. - patternOrGradient = gradient; - } else { - patternOrGradient = pattern; - } - // Store the style in the lookup, next call will just return the stored - // style for the feature. - styleLookup[id] = [ - // 1. Use the common background style - // (white fill and blackish stroke) - backgroundStyle, - // 2. On top of that, draw the pattern or gradient - new ol.style.Style({ - fill: new ol.style.Fill({ - color: patternOrGradient - }) - }) - ]; - } - return styleLookup[id]; + fill.setColor(id > 'J' ? gradient(feature, resolution) : pattern); + return style; }; // Create a vector layer that makes use of the style function above… diff --git a/src/ol/render/canvas/polygonreplay.js b/src/ol/render/canvas/polygonreplay.js index ab36710994..d12243f3d7 100644 --- a/src/ol/render/canvas/polygonreplay.js +++ b/src/ol/render/canvas/polygonreplay.js @@ -344,7 +344,7 @@ ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyles_ = function() { var miterLimit = state.miterLimit; if (fillStyle !== undefined && state.currentFillStyle != fillStyle) { this.instructions.push( - [ol.render.canvas.Instruction.SET_FILL_STYLE, fillStyle]); + [ol.render.canvas.Instruction.SET_FILL_STYLE, fillStyle, typeof fillStyle != 'string']); state.currentFillStyle = state.fillStyle; } if (strokeStyle !== undefined) { diff --git a/src/ol/render/canvas/replay.js b/src/ol/render/canvas/replay.js index a71efeedc9..2403595238 100644 --- a/src/ol/render/canvas/replay.js +++ b/src/ol/render/canvas/replay.js @@ -58,6 +58,12 @@ ol.render.canvas.Replay = function(tolerance, maxExtent, resolution, overlaps) { */ this.resolution = resolution; + /** + * @private + * @type {boolean} + */ + this.alignFill_ = false; + /** * @private * @type {Array.<*>} @@ -185,6 +191,19 @@ ol.render.canvas.Replay.prototype.beginGeometry = function(geometry, feature) { }; +ol.render.canvas.Replay.prototype.fill_ = function(context, transform, rotation) { + if (this.alignFill_) { + context.translate(transform[4], transform[5]); + context.rotate(rotation); + } + context.fill(); + if (this.alignFill_) { + context.rotate(-rotation); + context.translate(-transform[4], -transform[5]); + } +}; + + /** * @private * @param {CanvasRenderingContext2D} context Context. @@ -250,7 +269,7 @@ ol.render.canvas.Replay.prototype.replay_ = function( break; case ol.render.canvas.Instruction.BEGIN_PATH: if (pendingFill > batchSize) { - context.fill(); + this.fill_(context, transform, viewRotation); pendingFill = 0; } if (pendingStroke > batchSize) { @@ -429,7 +448,7 @@ ol.render.canvas.Replay.prototype.replay_ = function( if (batchSize) { pendingFill++; } else { - context.fill(); + this.fill_(context, transform, viewRotation); } ++i; break; @@ -467,8 +486,10 @@ ol.render.canvas.Replay.prototype.replay_ = function( ol.colorlike.isColorLike(instruction[1]), '2nd instruction should be a string, ' + 'CanvasPattern, or CanvasGradient'); + this.alignFill_ = instruction[2]; + if (pendingFill) { - context.fill(); + this.fill_(context, transform, viewRotation); pendingFill = 0; } @@ -534,7 +555,7 @@ ol.render.canvas.Replay.prototype.replay_ = function( } } if (pendingFill) { - context.fill(); + this.fill_(context, transform, viewRotation); } if (pendingStroke) { context.stroke();