diff --git a/changelog/upgrade-notes.md b/changelog/upgrade-notes.md index b1ce5f6d12..4cd45e5919 100644 --- a/changelog/upgrade-notes.md +++ b/changelog/upgrade-notes.md @@ -1,5 +1,10 @@ ## Upgrade notes +#### `ol.style.Fill` with `CanvasGradient` or `CanvasPattern` + +The origin for gradients and patterns has changed from `[0, 0]` to the top-left +corner of the extent of the geometry being filled. + ### v3.19.0 #### `ol.style.Fill` with `CanvasGradient` or `CanvasPattern` diff --git a/examples/canvas-gradient-pattern.js b/examples/canvas-gradient-pattern.js index c3dde464b3..58ffb8ad75 100644 --- a/examples/canvas-gradient-pattern.js +++ b/examples/canvas-gradient-pattern.js @@ -1,5 +1,6 @@ goog.require('ol.Map'); goog.require('ol.View'); +goog.require('ol.extent'); goog.require('ol.format.GeoJSON'); goog.require('ol.has'); goog.require('ol.layer.Vector'); @@ -20,11 +21,11 @@ var pixelRatio = ol.has.DEVICE_PIXEL_RATIO; 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); + // 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. + var grad = context.createLinearGradient(0, 0, + ol.extent.getWidth(extent) / resolution * pixelRatio, 0); grad.addColorStop(0, 'red'); grad.addColorStop(1 / 6, 'orange'); grad.addColorStop(2 / 6, 'yellow'); diff --git a/src/ol/render/canvas/polygonreplay.js b/src/ol/render/canvas/polygonreplay.js index d12243f3d7..b845e5aa88 100644 --- a/src/ol/render/canvas/polygonreplay.js +++ b/src/ol/render/canvas/polygonreplay.js @@ -132,7 +132,7 @@ ol.render.canvas.PolygonReplay.prototype.drawCircle = function(circleGeometry, f ol.DEBUG && console.assert(state.lineWidth !== undefined, 'state.lineWidth should be defined'); } - this.setFillStrokeStyles_(); + this.setFillStrokeStyles_(circleGeometry); this.beginGeometry(circleGeometry, feature); // always fill the circle for hit detection this.hitDetectionInstructions.push( @@ -182,7 +182,7 @@ ol.render.canvas.PolygonReplay.prototype.drawPolygon = function(polygonGeometry, ol.DEBUG && console.assert(state.lineWidth !== undefined, 'state.lineWidth should be defined'); } - this.setFillStrokeStyles_(); + this.setFillStrokeStyles_(polygonGeometry); this.beginGeometry(polygonGeometry, feature); // always fill the polygon for hit detection this.hitDetectionInstructions.push( @@ -217,7 +217,7 @@ ol.render.canvas.PolygonReplay.prototype.drawMultiPolygon = function(multiPolygo ol.DEBUG && console.assert(state.lineWidth !== undefined, 'state.lineWidth should be defined'); } - this.setFillStrokeStyles_(); + this.setFillStrokeStyles_(multiPolygonGeometry); this.beginGeometry(multiPolygonGeometry, feature); // always fill the multi-polygon for hit detection this.hitDetectionInstructions.push( @@ -332,8 +332,9 @@ ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyle = function(fillStyle /** * @private + * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry. */ -ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyles_ = function() { +ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyles_ = function(geometry) { var state = this.state_; var fillStyle = state.fillStyle; var strokeStyle = state.strokeStyle; @@ -342,9 +343,13 @@ ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyles_ = function() { var lineJoin = state.lineJoin; var lineWidth = state.lineWidth; var miterLimit = state.miterLimit; - if (fillStyle !== undefined && state.currentFillStyle != fillStyle) { - this.instructions.push( - [ol.render.canvas.Instruction.SET_FILL_STYLE, fillStyle, typeof fillStyle != 'string']); + if (typeof fillStyle !== 'string' || fillStyle !== undefined && state.currentFillStyle != fillStyle) { + var fillInstruction = [ol.render.canvas.Instruction.SET_FILL_STYLE, fillStyle]; + if (typeof fillStyle !== 'string') { + var fillExtent = geometry.getExtent(); + fillInstruction.push([fillExtent[0], fillExtent[3]]); + } + this.instructions.push(fillInstruction); state.currentFillStyle = state.fillStyle; } if (strokeStyle !== undefined) { diff --git a/src/ol/render/canvas/replay.js b/src/ol/render/canvas/replay.js index f5993bb782..cb9fce4c17 100644 --- a/src/ol/render/canvas/replay.js +++ b/src/ol/render/canvas/replay.js @@ -60,9 +60,9 @@ ol.render.canvas.Replay = function(tolerance, maxExtent, resolution, overlaps) { /** * @private - * @type {boolean} + * @type {ol.Coordinate} */ - this.alignFill_ = false; + this.fillOrigin_; /** * @private @@ -194,18 +194,17 @@ ol.render.canvas.Replay.prototype.beginGeometry = function(geometry, feature) { /** * @private * @param {CanvasRenderingContext2D} context Context. - * @param {ol.Transform} transform Transform. * @param {number} rotation Rotation. */ -ol.render.canvas.Replay.prototype.fill_ = function(context, transform, rotation) { - if (this.alignFill_) { - context.translate(transform[4], transform[5]); +ol.render.canvas.Replay.prototype.fill_ = function(context, rotation) { + if (this.fillOrigin_) { + var origin = ol.transform.apply(this.renderedTransform_, this.fillOrigin_.slice()); + context.translate(origin[0], origin[1]); context.rotate(rotation); } context.fill(); - if (this.alignFill_) { - context.rotate(-rotation); - context.translate(-transform[4], -transform[5]); + if (this.fillOrigin_) { + context.setTransform.apply(context, this.resetTransform_); } }; @@ -275,7 +274,7 @@ ol.render.canvas.Replay.prototype.replay_ = function( break; case ol.render.canvas.Instruction.BEGIN_PATH: if (pendingFill > batchSize) { - this.fill_(context, transform, viewRotation); + this.fill_(context, viewRotation); pendingFill = 0; } if (pendingStroke > batchSize) { @@ -452,7 +451,7 @@ ol.render.canvas.Replay.prototype.replay_ = function( if (batchSize) { pendingFill++; } else { - this.fill_(context, transform, viewRotation); + this.fill_(context, viewRotation); } ++i; break; @@ -490,10 +489,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]; + this.fillOrigin_ = instruction[2]; if (pendingFill) { - this.fill_(context, transform, viewRotation); + this.fill_(context, viewRotation); pendingFill = 0; } @@ -559,7 +558,7 @@ ol.render.canvas.Replay.prototype.replay_ = function( } } if (pendingFill) { - this.fill_(context, transform, viewRotation); + this.fill_(context, viewRotation); } if (pendingStroke) { context.stroke(); diff --git a/src/ol/typedefs.js b/src/ol/typedefs.js index 74d3f29fef..7901c923f0 100644 --- a/src/ol/typedefs.js +++ b/src/ol/typedefs.js @@ -121,7 +121,9 @@ ol.Color; /** * A type accepted by CanvasRenderingContext2D.fillStyle * or CanvasRenderingContext2D.strokeStyle. - * Represents a color, pattern, or gradient. + * 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. * * @typedef {string|CanvasPattern|CanvasGradient} */ diff --git a/test_rendering/spec/ol/style/expected/polygon-pattern-gradient-canvas.png b/test_rendering/spec/ol/style/expected/polygon-pattern-gradient-canvas.png index bb4302a78f..4a2fb3412a 100644 Binary files a/test_rendering/spec/ol/style/expected/polygon-pattern-gradient-canvas.png and b/test_rendering/spec/ol/style/expected/polygon-pattern-gradient-canvas.png differ