diff --git a/changelog/upgrade-notes.md b/changelog/upgrade-notes.md index 9647079c34..15d26e5db0 100644 --- a/changelog/upgrade-notes.md +++ b/changelog/upgrade-notes.md @@ -2,6 +2,10 @@ ### Next release +#### `ol/style/Fill` with `CanvasGradient` or `CanvasPattern` + +The origin for gradients and patterns has changed from the top-left corner of the extent of the geometry being filled to 512 css pixel increments from map coordinate `[0, 0]`. This allows repeat patterns to be aligned properly with vector tiles. For seamless repeat patterns, width and height of the pattern image must be a factor of two (2, 4, 8, ..., 512). + #### Removal of the renderer option for maps The `renderer` option has been removed from the `Map` constructor. The purpose of this change is to avoid bundling code in your application that you do not need. Previously, code for both the Canvas and WebGL renderers was included in all applications - even though most people only use one renderer. The `Map` constructor now gives you a Canvas (2D) based renderer. If you want to try the WebGL renderer, you can import the constructor from `ol/WebGLMap`. diff --git a/examples/canvas-gradient-pattern.html b/examples/canvas-gradient-pattern.html index fed8ded8d2..f1f1f8ee9e 100644 --- a/examples/canvas-gradient-pattern.html +++ b/examples/canvas-gradient-pattern.html @@ -3,10 +3,11 @@ layout: example.html title: Styling feature with CanvasGradient or CanvasPattern shortdesc: Example showing the countries vector layer styled with patterns and gradients. docs: > - First this example creates a reusable [`CanvasPattern`](https://developer.mozilla.org/en-US/docs/Web/API/CanvasPattern) + This example creates a [`CanvasPattern`](https://developer.mozilla.org/en-US/docs/Web/API/CanvasPattern) and a [`CanvasGradient`](https://developer.mozilla.org/en/docs/Web/API/CanvasGradient). The countries are loaded from - a GeoJSON file. A style function determines for each country whether to use a fill with the pregenerated - CanvasGradient (rainbow colors) or a CanvasPattern (repeating stacked circles). + a GeoJSON file. A style function determines for each country whether to use a fill with the + CanvasGradient (rainbow colors) or a CanvasPattern (repeating stacked circles). **Note**: For seamless repeat patterns, + image width and height of the pattern image must be a factor of two (2, 4, 8, ..., 512). tags: "canvas, gradient, pattern, style" ---
diff --git a/examples/canvas-gradient-pattern.js b/examples/canvas-gradient-pattern.js index 3c8348f0dc..6e2e1018ae 100644 --- a/examples/canvas-gradient-pattern.js +++ b/examples/canvas-gradient-pattern.js @@ -1,6 +1,5 @@ import Map from '../src/ol/Map.js'; import View from '../src/ol/View.js'; -import {getWidth} from '../src/ol/extent.js'; import GeoJSON from '../src/ol/format/GeoJSON.js'; import {DEVICE_PIXEL_RATIO} from '../src/ol/has.js'; import VectorLayer from '../src/ol/layer/Vector.js'; @@ -16,14 +15,8 @@ const context = canvas.getContext('2d'); const pixelRatio = DEVICE_PIXEL_RATIO; // Generate a rainbow gradient -function gradient(feature, resolution) { - const extent = feature.getGeometry().getExtent(); - // Gradient starts on the left edge of each feature, and ends on the right. - // Coordinate origin is the top-left corner of the extent of the geometry, so - // we just divide the geometry's extent width by resolution and multiply with - // pixelRatio to match the renderer's pixel coordinate system. - const grad = context.createLinearGradient(0, 0, - getWidth(extent) / resolution * pixelRatio, 0); +const gradient = (function() { + const grad = context.createLinearGradient(0, 0, 512 * pixelRatio, 0); grad.addColorStop(0, 'red'); grad.addColorStop(1 / 6, 'orange'); grad.addColorStop(2 / 6, 'yellow'); @@ -32,24 +25,24 @@ function gradient(feature, resolution) { grad.addColorStop(5 / 6, 'blue'); grad.addColorStop(1, 'purple'); return grad; -} +})(); // Generate a canvasPattern with two circles on white background const pattern = (function() { - canvas.width = 11 * pixelRatio; - canvas.height = 11 * pixelRatio; + canvas.width = 8 * pixelRatio; + canvas.height = 8 * 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 * pixelRatio, 5 * pixelRatio, 4 * pixelRatio, 0, 2 * Math.PI); + context.arc(4 * pixelRatio, 4 * pixelRatio, 3 * pixelRatio, 0, 2 * Math.PI); context.fill(); // inner circle context.fillStyle = 'rgb(55, 0, 170)'; context.beginPath(); - context.arc(5 * pixelRatio, 5 * pixelRatio, 2 * pixelRatio, 0, 2 * Math.PI); + context.arc(4 * pixelRatio, 4 * pixelRatio, 1.5 * pixelRatio, 0, 2 * Math.PI); context.fill(); return context.createPattern(canvas, 'repeat'); }()); @@ -69,12 +62,11 @@ const style = new Style({ * which either contains the aboove gradient or pattern. * * @param {module:ol/Feature~Feature} feature The feature to style. - * @param {number} resolution Resolution. * @return {module:ol/style/Style} The style to use for the feature. */ -const getStackedStyle = function(feature, resolution) { +const getStackedStyle = function(feature) { const id = feature.getId(); - fill.setColor(id > 'J' ? gradient(feature, resolution) : pattern); + fill.setColor(id > 'J' ? gradient : pattern); return style; }; @@ -94,7 +86,7 @@ const map = new Map({ ], target: 'map', view: new View({ - center: fromLonLat([7, 52]), + center: fromLonLat([16, 48]), zoom: 3 }) }); diff --git a/src/ol/colorlike.js b/src/ol/colorlike.js index eb0b2d37cf..9e0c26b99b 100644 --- a/src/ol/colorlike.js +++ b/src/ol/colorlike.js @@ -8,8 +8,9 @@ import {toString} from './color.js'; * A type accepted by CanvasRenderingContext2D.fillStyle * or CanvasRenderingContext2D.strokeStyle. * Represents a color, pattern, or gradient. The origin for patterns and - * gradients as fill style is the top-left corner of the extent of the geometry - * being filled. + * gradients as fill style is an increment of 512 css pixels from map coordinate + * `[0, 0]`. For seamless repeat patterns, width and height of the pattern image + * must be a factor of two (2, 4, 8, ..., 512). * * @typedef {string|CanvasPattern|CanvasGradient} ColorLike * @api diff --git a/src/ol/render/canvas/Replay.js b/src/ol/render/canvas/Replay.js index a65db3fea1..ec6dbc6523 100644 --- a/src/ol/render/canvas/Replay.js +++ b/src/ol/render/canvas/Replay.js @@ -87,9 +87,9 @@ const CanvasReplay = function(tolerance, maxExtent, resolution, pixelRatio, over /** * @private - * @type {module:ol/coordinate~Coordinate} + * @type {boolean} */ - this.fillOrigin_; + this.alignFill_; /** * @private @@ -191,7 +191,7 @@ CanvasReplay.prototype.replayTextBackground_ = function(context, p1, p2, p3, p4, context.lineTo.apply(context, p4); context.lineTo.apply(context, p1); if (fillInstruction) { - this.fillOrigin_ = /** @type {Array.} */ (fillInstruction[2]); + this.alignFill_ = /** @type {boolean} */ (fillInstruction[2]); this.fill_(context); } if (strokeInstruction) { @@ -455,13 +455,14 @@ CanvasReplay.prototype.beginGeometry = function(geometry, feature) { * @param {CanvasRenderingContext2D} context Context. */ CanvasReplay.prototype.fill_ = function(context) { - if (this.fillOrigin_) { - const origin = applyTransform(this.renderedTransform_, this.fillOrigin_.slice()); - context.translate(origin[0], origin[1]); + if (this.alignFill_) { + const origin = applyTransform(this.renderedTransform_, [0, 0]); + const repeatSize = 512 * this.pixelRatio; + context.translate(origin[0] % repeatSize, origin[1] % repeatSize); context.rotate(this.viewRotation_); } context.fill(); - if (this.fillOrigin_) { + if (this.alignFill_) { context.setTransform.apply(context, resetTransform); } }; @@ -794,7 +795,7 @@ CanvasReplay.prototype.replay_ = function( break; case CanvasInstruction.SET_FILL_STYLE: lastFillInstruction = instruction; - this.fillOrigin_ = instruction[2]; + this.alignFill_ = instruction[2]; if (pendingFill) { this.fill_(context); @@ -965,8 +966,8 @@ CanvasReplay.prototype.createFill = function(state, geometry) { const fillStyle = state.fillStyle; const fillInstruction = [CanvasInstruction.SET_FILL_STYLE, fillStyle]; if (typeof fillStyle !== 'string') { - const fillExtent = geometry.getExtent(); - fillInstruction.push([fillExtent[0], fillExtent[3]]); + // Fill is a pattern or gradient - align it! + fillInstruction.push(true); } return fillInstruction; }; diff --git a/test/rendering/ol/style/expected/polygon-pattern-gradient-canvas.png b/test/rendering/ol/style/expected/polygon-pattern-gradient-canvas.png index 4a2fb3412a..769c63fcce 100644 Binary files a/test/rendering/ol/style/expected/polygon-pattern-gradient-canvas.png and b/test/rendering/ol/style/expected/polygon-pattern-gradient-canvas.png differ