From f3cd1d8dfde1ba538c20ce4eee39a675d9a42027 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Thu, 9 Aug 2018 17:22:49 +0200 Subject: [PATCH 1/4] Round center in viewState --- src/ol/PluggableMap.js | 11 ++--------- src/ol/View.js | 9 +++++++-- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/ol/PluggableMap.js b/src/ol/PluggableMap.js index ffddf09188..7f70ddf671 100644 --- a/src/ol/PluggableMap.js +++ b/src/ol/PluggableMap.js @@ -1179,19 +1179,12 @@ class PluggableMap extends BaseObject { for (let i = 0, ii = layerStatesArray.length; i < ii; ++i) { layerStates[getUid(layerStatesArray[i].layer)] = layerStatesArray[i]; } - viewState = view.getState(); - let focus = this.focus_; - if (!focus) { - focus = viewState.center; - const pixelResolution = viewState.resolution / this.pixelRatio_; - focus[0] = Math.round(focus[0] / pixelResolution) * pixelResolution; - focus[1] = Math.round(focus[1] / pixelResolution) * pixelResolution; - } + viewState = view.getState(this.pixelRatio_); frameState = /** @type {module:ol/PluggableMap~FrameState} */ ({ animate: false, coordinateToPixelTransform: this.coordinateToPixelTransform_, extent: extent, - focus: focus, + focus: this.focus_ ? this.focus_ : viewState.center, index: this.frameIndex_++, layerStates: layerStates, layerStatesArray: layerStatesArray, diff --git a/src/ol/View.js b/src/ol/View.js index 6583af494d..bfa03058a8 100644 --- a/src/ol/View.js +++ b/src/ol/View.js @@ -894,16 +894,21 @@ class View extends BaseObject { } /** + * @param {number} pixelRatio Pixel ratio for center rounding. * @return {module:ol/View~State} View state. */ - getState() { + getState(pixelRatio) { const center = /** @type {module:ol/coordinate~Coordinate} */ (this.getCenter()); const projection = this.getProjection(); const resolution = /** @type {number} */ (this.getResolution()); + const pixelResolution = resolution / pixelRatio; const rotation = this.getRotation(); return ( /** @type {module:ol/View~State} */ ({ - center: center.slice(), + center: [ + Math.round(center[0] / pixelResolution) * pixelResolution, + Math.round(center[1] / pixelResolution) * pixelResolution + ], projection: projection !== undefined ? projection : null, resolution: resolution, rotation: rotation, From dbdaa73cf26a6b05f8076b9652e80f69522871e0 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Thu, 9 Aug 2018 17:26:51 +0200 Subject: [PATCH 2/4] Only change interacting flag when dragging --- src/ol/interaction/DragPan.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/ol/interaction/DragPan.js b/src/ol/interaction/DragPan.js index e5bd071d7a..3a5cb79cf5 100644 --- a/src/ol/interaction/DragPan.js +++ b/src/ol/interaction/DragPan.js @@ -54,6 +54,11 @@ class DragPan extends PointerInteraction { */ this.lastPointersCount_; + /** + * @type {boolean} + */ + this.panning_ = false; + /** * @private * @type {module:ol/events/condition~Condition} @@ -76,6 +81,10 @@ class DragPan extends PointerInteraction { * @this {module:ol/interaction/DragPan} */ function handleDragEvent(mapBrowserEvent) { + if (!this.panning_) { + this.panning_ = true; + this.getMap().getView().setHint(ViewHint.INTERACTING, 1); + } const targetPointers = this.targetPointers; const centroid = centroidFromPointers(targetPointers); if (targetPointers.length == this.lastPointersCount_) { @@ -128,7 +137,10 @@ function handleUpEvent(mapBrowserEvent) { easing: easeOut }); } - view.setHint(ViewHint.INTERACTING, -1); + if (this.panning_) { + this.panning_ = false; + view.setHint(ViewHint.INTERACTING, -1); + } return false; } else { if (this.kinetic_) { @@ -152,9 +164,6 @@ function handleDownEvent(mapBrowserEvent) { const map = mapBrowserEvent.map; const view = map.getView(); this.lastCentroid = null; - if (!this.handlingDownUpSequence) { - view.setHint(ViewHint.INTERACTING, 1); - } // stop any current animation if (view.getAnimating()) { view.setCenter(mapBrowserEvent.frameState.viewState.center); From f382ddf230fd4e78ec66ed4891b3b2802894d91e Mon Sep 17 00:00:00 2001 From: ahocevar Date: Thu, 9 Aug 2018 18:12:59 +0200 Subject: [PATCH 3/4] Remove snapToPixel option and deprecate getters/setters --- changelog/upgrade-notes.md | 10 ++ examples/dynamic-data.js | 3 - examples/feature-animation.js | 1 - examples/feature-move-animation.js | 1 - examples/street-labels.html | 2 +- src/ol/style/Circle.js | 6 - src/ol/style/Icon.js | 12 -- src/ol/style/Image.js | 219 ++++++++++++------------ src/ol/style/RegularShape.js | 14 +- test/spec/ol/style/circle.test.js | 4 +- test/spec/ol/style/icon.test.js | 2 - test/spec/ol/style/regularshape.test.js | 2 - 12 files changed, 118 insertions(+), 158 deletions(-) diff --git a/changelog/upgrade-notes.md b/changelog/upgrade-notes.md index 8598805720..4e5fc349ce 100644 --- a/changelog/upgrade-notes.md +++ b/changelog/upgrade-notes.md @@ -1,5 +1,15 @@ ## Upgrade notes +### Next version + +#### Removal of the `snapToPixel` option for `ol/style/Image` subclasses + +The `snapToPixel` option has been removed, and the `getSnapToPixel` and `setSnapToPixel` methods are deprecated. + +The renderer now snaps to integer pixels when no interaction or animation is running to get crisp rendering. During interaction or animation, it does not snap to integer pixels to avoid jitter. + +When rendering with the Immediate API, symbols will no longer be snapped to integer pixels. To get crisp images, set `context.imageSmoothingEnabled = false` before rendering with the Immediate API, and `context.imageSmoothingEnabled = true` afterwards. + ### v5.1.0 #### Geometry constructor and `setCoordinates` no longer accept `null` coordinates diff --git a/examples/dynamic-data.js b/examples/dynamic-data.js index 4896250490..885d90202d 100644 --- a/examples/dynamic-data.js +++ b/examples/dynamic-data.js @@ -22,7 +22,6 @@ const map = new Map({ const imageStyle = new Style({ image: new CircleStyle({ radius: 5, - snapToPixel: false, fill: new Fill({color: 'yellow'}), stroke: new Stroke({color: 'red', width: 1}) }) @@ -31,7 +30,6 @@ const imageStyle = new Style({ const headInnerImageStyle = new Style({ image: new CircleStyle({ radius: 2, - snapToPixel: false, fill: new Fill({color: 'blue'}) }) }); @@ -39,7 +37,6 @@ const headInnerImageStyle = new Style({ const headOuterImageStyle = new Style({ image: new CircleStyle({ radius: 5, - snapToPixel: false, fill: new Fill({color: 'black'}) }) }); diff --git a/examples/feature-animation.js b/examples/feature-animation.js index 51998e5196..d1c7ca4be4 100644 --- a/examples/feature-animation.js +++ b/examples/feature-animation.js @@ -65,7 +65,6 @@ function flash(feature) { const style = new Style({ image: new CircleStyle({ radius: radius, - snapToPixel: false, stroke: new Stroke({ color: 'rgba(255, 0, 0, ' + opacity + ')', width: 0.25 + opacity diff --git a/examples/feature-move-animation.js b/examples/feature-move-animation.js index 9443a84d5e..45b31ad280 100644 --- a/examples/feature-move-animation.js +++ b/examples/feature-move-animation.js @@ -94,7 +94,6 @@ const styles = { 'geoMarker': new Style({ image: new CircleStyle({ radius: 7, - snapToPixel: false, fill: new Fill({color: 'black'}), stroke: new Stroke({ color: 'white', width: 2 diff --git a/examples/street-labels.html b/examples/street-labels.html index 40da211001..e62a2b9236 100644 --- a/examples/street-labels.html +++ b/examples/street-labels.html @@ -1,7 +1,7 @@ --- layout: example.html title: Street Labels -shortdesc: Render street names with a custom render. +shortdesc: Render street names. docs: > Example showing the use of a text style with `placement: 'line'` to render text along a path. tags: "vector, label, streets" diff --git a/src/ol/style/Circle.js b/src/ol/style/Circle.js index 4da67132de..3dd4e703d1 100644 --- a/src/ol/style/Circle.js +++ b/src/ol/style/Circle.js @@ -9,10 +9,6 @@ import RegularShape from '../style/RegularShape.js'; * @typedef {Object} Options * @property {module:ol/style/Fill} [fill] Fill style. * @property {number} radius Circle radius. - * @property {boolean} [snapToPixel=true] If `true` integral numbers of pixels are used as the X and Y pixel coordinate - * when drawing the circle in the output canvas. If `false` fractional numbers may be used. Using `true` allows for - * "sharp" rendering (no blur), while using `false` allows for "accurate" rendering. Note that accuracy is important if - * the circle's position is animated. Without it, the circle may jitter noticeably. * @property {module:ol/style/Stroke} [stroke] Stroke style. * @property {module:ol/style/AtlasManager} [atlasManager] The atlas manager to use for this circle. * When using WebGL it is recommended to use an atlas manager to avoid texture switching. If an atlas manager is given, @@ -37,7 +33,6 @@ class CircleStyle extends RegularShape { points: Infinity, fill: options.fill, radius: options.radius, - snapToPixel: options.snapToPixel, stroke: options.stroke, atlasManager: options.atlasManager }); @@ -55,7 +50,6 @@ class CircleStyle extends RegularShape { fill: this.getFill() ? this.getFill().clone() : undefined, stroke: this.getStroke() ? this.getStroke().clone() : undefined, radius: this.getRadius(), - snapToPixel: this.getSnapToPixel(), atlasManager: this.atlasManager_ }); style.setOpacity(this.getOpacity()); diff --git a/src/ol/style/Icon.js b/src/ol/style/Icon.js index 3afc1050c1..525feab4e0 100644 --- a/src/ol/style/Icon.js +++ b/src/ol/style/Icon.js @@ -38,10 +38,6 @@ import ImageStyle from '../style/Image.js'; * `top-left` or `top-right`. Default is `top-left`. * @property {number} [opacity=1] Opacity of the icon. * @property {number} [scale=1] Scale. - * @property {boolean} [snapToPixel=true] If `true` integral numbers of pixels are used as the X and Y pixel coordinate - * when drawing the icon in the output canvas. If `false` fractional numbers may be used. Using `true` allows for - * "sharp" rendering (no blur), while using `false` allows for "accurate" rendering. Note that accuracy is important if - * the icon's position is animated. Without it, the icon may jitter noticeably. * @property {boolean} [rotateWithView=false] Whether to rotate the icon with the view. * @property {number} [rotation=0] Rotation in radians (positive rotation clockwise). * @property {module:ol/size~Size} [size] Icon size in pixel. Can be used together with `offset` to define the @@ -85,17 +81,10 @@ class Icon extends ImageStyle { const rotateWithView = options.rotateWithView !== undefined ? options.rotateWithView : false; - /** - * @type {boolean} - */ - const snapToPixel = options.snapToPixel !== undefined ? - options.snapToPixel : true; - super({ opacity: opacity, rotation: rotation, scale: scale, - snapToPixel: snapToPixel, rotateWithView: rotateWithView }); @@ -230,7 +219,6 @@ class Icon extends ImageStyle { size: this.size_ !== null ? this.size_.slice() : undefined, opacity: this.getOpacity(), scale: this.getScale(), - snapToPixel: this.getSnapToPixel(), rotation: this.getRotation(), rotateWithView: this.getRotateWithView() }); diff --git a/src/ol/style/Image.js b/src/ol/style/Image.js index edfdba8cfb..03747b2d6a 100644 --- a/src/ol/style/Image.js +++ b/src/ol/style/Image.js @@ -9,7 +9,6 @@ * @property {boolean} rotateWithView * @property {number} rotation * @property {number} scale - * @property {boolean} snapToPixel */ @@ -27,208 +26,200 @@ class ImageStyle { constructor(options) { /** - * @private - * @type {number} - */ + * @private + * @type {number} + */ this.opacity_ = options.opacity; /** - * @private - * @type {boolean} - */ + * @private + * @type {boolean} + */ this.rotateWithView_ = options.rotateWithView; /** - * @private - * @type {number} - */ + * @private + * @type {number} + */ this.rotation_ = options.rotation; /** - * @private - * @type {number} - */ + * @private + * @type {number} + */ this.scale_ = options.scale; - /** - * @private - * @type {boolean} - */ - this.snapToPixel_ = options.snapToPixel; - } /** - * Get the symbolizer opacity. - * @return {number} Opacity. - * @api - */ + * Get the symbolizer opacity. + * @return {number} Opacity. + * @api + */ getOpacity() { return this.opacity_; } /** - * Determine whether the symbolizer rotates with the map. - * @return {boolean} Rotate with map. - * @api - */ + * Determine whether the symbolizer rotates with the map. + * @return {boolean} Rotate with map. + * @api + */ getRotateWithView() { return this.rotateWithView_; } /** - * Get the symoblizer rotation. - * @return {number} Rotation. - * @api - */ + * Get the symoblizer rotation. + * @return {number} Rotation. + * @api + */ getRotation() { return this.rotation_; } /** - * Get the symbolizer scale. - * @return {number} Scale. - * @api - */ + * Get the symbolizer scale. + * @return {number} Scale. + * @api + */ getScale() { return this.scale_; } /** - * Determine whether the symbolizer should be snapped to a pixel. - * @return {boolean} The symbolizer should snap to a pixel. - * @api - */ + * This method is deprecated and always returns false. + * @return {boolean} false. + * @deprecated + * @api + */ getSnapToPixel() { - return this.snapToPixel_; + return false; } /** - * Get the anchor point in pixels. The anchor determines the center point for the - * symbolizer. - * @abstract - * @return {Array} Anchor. - */ + * Get the anchor point in pixels. The anchor determines the center point for the + * symbolizer. + * @abstract + * @return {Array} Anchor. + */ getAnchor() {} /** - * Get the image element for the symbolizer. - * @abstract - * @param {number} pixelRatio Pixel ratio. - * @return {HTMLCanvasElement|HTMLVideoElement|HTMLImageElement} Image element. - */ + * Get the image element for the symbolizer. + * @abstract + * @param {number} pixelRatio Pixel ratio. + * @return {HTMLCanvasElement|HTMLVideoElement|HTMLImageElement} Image element. + */ getImage(pixelRatio) {} /** - * @abstract - * @param {number} pixelRatio Pixel ratio. - * @return {HTMLCanvasElement|HTMLVideoElement|HTMLImageElement} Image element. - */ + * @abstract + * @param {number} pixelRatio Pixel ratio. + * @return {HTMLCanvasElement|HTMLVideoElement|HTMLImageElement} Image element. + */ getHitDetectionImage(pixelRatio) {} /** - * @abstract - * @return {module:ol/ImageState} Image state. - */ + * @abstract + * @return {module:ol/ImageState} Image state. + */ getImageState() {} /** - * @abstract - * @return {module:ol/size~Size} Image size. - */ + * @abstract + * @return {module:ol/size~Size} Image size. + */ getImageSize() {} /** - * @abstract - * @return {module:ol/size~Size} Size of the hit-detection image. - */ + * @abstract + * @return {module:ol/size~Size} Size of the hit-detection image. + */ getHitDetectionImageSize() {} /** - * Get the origin of the symbolizer. - * @abstract - * @return {Array} Origin. - */ + * Get the origin of the symbolizer. + * @abstract + * @return {Array} Origin. + */ getOrigin() {} /** - * Get the size of the symbolizer (in pixels). - * @abstract - * @return {module:ol/size~Size} Size. - */ + * Get the size of the symbolizer (in pixels). + * @abstract + * @return {module:ol/size~Size} Size. + */ getSize() {} /** - * Set the opacity. - * - * @param {number} opacity Opacity. - * @api - */ + * Set the opacity. + * + * @param {number} opacity Opacity. + * @api + */ setOpacity(opacity) { this.opacity_ = opacity; } /** - * Set whether to rotate the style with the view. - * - * @param {boolean} rotateWithView Rotate with map. - * @api - */ + * Set whether to rotate the style with the view. + * + * @param {boolean} rotateWithView Rotate with map. + * @api + */ setRotateWithView(rotateWithView) { this.rotateWithView_ = rotateWithView; } /** - * Set the rotation. - * - * @param {number} rotation Rotation. - * @api - */ + * Set the rotation. + * + * @param {number} rotation Rotation. + * @api + */ setRotation(rotation) { this.rotation_ = rotation; } - /** - * Set the scale. - * - * @param {number} scale Scale. - * @api - */ + * Set the scale. + * + * @param {number} scale Scale. + * @api + */ setScale(scale) { this.scale_ = scale; } /** - * Set whether to snap the image to the closest pixel. - * - * @param {boolean} snapToPixel Snap to pixel? - * @api - */ - setSnapToPixel(snapToPixel) { - this.snapToPixel_ = snapToPixel; - } + * This method is deprecated and does nothing. + * @param {boolean} snapToPixel Snap to pixel? + * @deprecated + * @api + */ + setSnapToPixel(snapToPixel) {} /** - * @abstract - * @param {function(this: T, module:ol/events/Event)} listener Listener function. - * @param {T} thisArg Value to use as `this` when executing `listener`. - * @return {module:ol/events~EventsKey|undefined} Listener key. - * @template T - */ + * @abstract + * @param {function(this: T, module:ol/events/Event)} listener Listener function. + * @param {T} thisArg Value to use as `this` when executing `listener`. + * @return {module:ol/events~EventsKey|undefined} Listener key. + * @template T + */ listenImageChange(listener, thisArg) {} /** - * Load not yet loaded URI. - * @abstract - */ + * Load not yet loaded URI. + * @abstract + */ load() {} /** - * @abstract - * @param {function(this: T, module:ol/events/Event)} listener Listener function. - * @param {T} thisArg Value to use as `this` when executing `listener`. - * @template T - */ + * @abstract + * @param {function(this: T, module:ol/events/Event)} listener Listener function. + * @param {T} thisArg Value to use as `this` when executing `listener`. + * @template T + */ unlistenImageChange(listener, thisArg) {} } diff --git a/src/ol/style/RegularShape.js b/src/ol/style/RegularShape.js index 5264ce51d9..c5b659e3ff 100644 --- a/src/ol/style/RegularShape.js +++ b/src/ol/style/RegularShape.js @@ -20,10 +20,6 @@ import ImageStyle from '../style/Image.js'; * @property {number} [radius1] Outer radius of a star. * @property {number} [radius2] Inner radius of a star. * @property {number} [angle=0] Shape's angle in radians. A value of 0 will have one of the shape's point facing up. - * @property {boolean} [snapToPixel=true] If `true` integral numbers of pixels are used as the X and Y pixel coordinate - * when drawing the shape in the output canvas. If `false` fractional numbers may be used. Using `true` allows for - * "sharp" rendering (no blur), while using `false` allows for "accurate" rendering. Note that accuracy is important if - * the shape's position is animated. Without it, the shape may jitter noticeably. * @property {module:ol/style/Stroke} [stroke] Stroke style. * @property {number} [rotation=0] Rotation in radians (positive rotation clockwise). * @property {boolean} [rotateWithView=false] Whether to rotate the shape with the view. @@ -58,12 +54,6 @@ class RegularShape extends ImageStyle { * @param {module:ol/style/RegularShape~Options} options Options. */ constructor(options) { - /** - * @type {boolean} - */ - const snapToPixel = options.snapToPixel !== undefined ? - options.snapToPixel : true; - /** * @type {boolean} */ @@ -74,8 +64,7 @@ class RegularShape extends ImageStyle { opacity: 1, rotateWithView: rotateWithView, rotation: options.rotation !== undefined ? options.rotation : 0, - scale: 1, - snapToPixel: snapToPixel + scale: 1 }); /** @@ -185,7 +174,6 @@ class RegularShape extends ImageStyle { radius: this.getRadius(), radius2: this.getRadius2(), angle: this.getAngle(), - snapToPixel: this.getSnapToPixel(), stroke: this.getStroke() ? this.getStroke().clone() : undefined, rotation: this.getRotation(), rotateWithView: this.getRotateWithView(), diff --git a/test/spec/ol/style/circle.test.js b/test/spec/ol/style/circle.test.js index 421bb193d8..5ad64e2fad 100644 --- a/test/spec/ol/style/circle.test.js +++ b/test/spec/ol/style/circle.test.js @@ -91,8 +91,7 @@ describe('ol.style.Circle', function() { stroke: new Stroke({ color: '#319FD3' }), - radius: 5, - snapToPixel: false + radius: 5 }); original.setOpacity(0.5); original.setScale(1.5); @@ -101,7 +100,6 @@ describe('ol.style.Circle', function() { expect(original.getOpacity()).to.eql(clone.getOpacity()); expect(original.getRadius()).to.eql(clone.getRadius()); expect(original.getScale()).to.eql(clone.getScale()); - expect(original.getSnapToPixel()).to.eql(clone.getSnapToPixel()); expect(original.getStroke().getColor()).to.eql(clone.getStroke().getColor()); }); diff --git a/test/spec/ol/style/icon.test.js b/test/spec/ol/style/icon.test.js index 57a4eea14f..6c790e32a1 100644 --- a/test/spec/ol/style/icon.test.js +++ b/test/spec/ol/style/icon.test.js @@ -61,7 +61,6 @@ describe('ol.style.Icon', function() { offsetOrigin: 'bottom-left', opacity: 0.5, scale: 2, - snapToPixel: false, rotation: 4, size: [10, 12] }); @@ -82,7 +81,6 @@ describe('ol.style.Icon', function() { expect(original.getOpacity()).to.eql(clone.getOpacity()); expect(original.getRotation()).to.eql(clone.getRotation()); expect(original.getRotateWithView()).to.eql(clone.getRotateWithView()); - expect(original.getSnapToPixel()).to.eql(clone.getSnapToPixel()); const original2 = new Icon({ src: src diff --git a/test/spec/ol/style/regularshape.test.js b/test/spec/ol/style/regularshape.test.js index 067ac1c219..883c361ca7 100644 --- a/test/spec/ol/style/regularshape.test.js +++ b/test/spec/ol/style/regularshape.test.js @@ -121,7 +121,6 @@ describe('ol.style.RegularShape', function() { radius: 4, radius2: 6, angle: 1, - snapToPixel: false, stroke: new Stroke({ color: '#319FD3' }), @@ -140,7 +139,6 @@ describe('ol.style.RegularShape', function() { expect(original.getRotation()).to.eql(clone.getRotation()); expect(original.getRotateWithView()).to.eql(clone.getRotateWithView()); expect(original.getScale()).to.eql(clone.getScale()); - expect(original.getSnapToPixel()).to.eql(clone.getSnapToPixel()); expect(original.getStroke().getColor()).to.eql(clone.getStroke().getColor()); }); From b9aceb23ac030fd83bf69e412e0e30577d994b4f Mon Sep 17 00:00:00 2001 From: ahocevar Date: Thu, 9 Aug 2018 18:16:25 +0200 Subject: [PATCH 4/4] Let renderer decide whether to snapToPixel or not, also for text --- src/ol/render/canvas/ImageReplay.js | 16 ++++------------ src/ol/render/canvas/Immediate.js | 15 ++------------- src/ol/render/canvas/Replay.js | 18 ++++++++++-------- src/ol/render/canvas/ReplayGroup.js | 9 ++++++--- src/ol/render/canvas/TextReplay.js | 4 ++-- src/ol/renderer/canvas/VectorLayer.js | 8 +++++--- src/ol/renderer/canvas/VectorTileLayer.js | 9 ++++++--- test/rendering/ol/layer/tile.test.js | 1 - 8 files changed, 35 insertions(+), 45 deletions(-) diff --git a/src/ol/render/canvas/ImageReplay.js b/src/ol/render/canvas/ImageReplay.js index 4442109e2b..6fc9e6b310 100644 --- a/src/ol/render/canvas/ImageReplay.js +++ b/src/ol/render/canvas/ImageReplay.js @@ -88,12 +88,6 @@ class CanvasImageReplay extends CanvasReplay { */ this.scale_ = undefined; - /** - * @private - * @type {boolean|undefined} - */ - this.snapToPixel_ = undefined; - /** * @private * @type {number|undefined} @@ -131,14 +125,14 @@ class CanvasImageReplay extends CanvasReplay { // Remaining arguments to DRAW_IMAGE are in alphabetical order this.anchorX_, this.anchorY_, this.declutterGroup_, this.height_, this.opacity_, this.originX_, this.originY_, this.rotateWithView_, this.rotation_, - this.scale_ * this.pixelRatio, this.snapToPixel_, this.width_ + this.scale_ * this.pixelRatio, this.width_ ]); this.hitDetectionInstructions.push([ CanvasInstruction.DRAW_IMAGE, myBegin, myEnd, this.hitDetectionImage_, // Remaining arguments to DRAW_IMAGE are in alphabetical order this.anchorX_, this.anchorY_, this.declutterGroup_, this.height_, this.opacity_, this.originX_, this.originY_, this.rotateWithView_, this.rotation_, - this.scale_, this.snapToPixel_, this.width_ + this.scale_, this.width_ ]); this.endGeometry(pointGeometry, feature); } @@ -161,14 +155,14 @@ class CanvasImageReplay extends CanvasReplay { // Remaining arguments to DRAW_IMAGE are in alphabetical order this.anchorX_, this.anchorY_, this.declutterGroup_, this.height_, this.opacity_, this.originX_, this.originY_, this.rotateWithView_, this.rotation_, - this.scale_ * this.pixelRatio, this.snapToPixel_, this.width_ + this.scale_ * this.pixelRatio, this.width_ ]); this.hitDetectionInstructions.push([ CanvasInstruction.DRAW_IMAGE, myBegin, myEnd, this.hitDetectionImage_, // Remaining arguments to DRAW_IMAGE are in alphabetical order this.anchorX_, this.anchorY_, this.declutterGroup_, this.height_, this.opacity_, this.originX_, this.originY_, this.rotateWithView_, this.rotation_, - this.scale_, this.snapToPixel_, this.width_ + this.scale_, this.width_ ]); this.endGeometry(multiPointGeometry, feature); } @@ -190,7 +184,6 @@ class CanvasImageReplay extends CanvasReplay { this.originY_ = undefined; this.rotateWithView_ = undefined; this.rotation_ = undefined; - this.snapToPixel_ = undefined; this.width_ = undefined; } @@ -215,7 +208,6 @@ class CanvasImageReplay extends CanvasReplay { this.rotateWithView_ = imageStyle.getRotateWithView(); this.rotation_ = imageStyle.getRotation(); this.scale_ = imageStyle.getScale(); - this.snapToPixel_ = imageStyle.getSnapToPixel(); this.width_ = size[0]; } } diff --git a/src/ol/render/canvas/Immediate.js b/src/ol/render/canvas/Immediate.js index 8896e9320a..3592e7f96f 100644 --- a/src/ol/render/canvas/Immediate.js +++ b/src/ol/render/canvas/Immediate.js @@ -156,12 +156,6 @@ class CanvasImmediateRenderer extends VectorContext { */ this.imageScale_ = 0; - /** - * @private - * @type {boolean} - */ - this.imageSnapToPixel_ = false; - /** * @private * @type {number} @@ -261,12 +255,8 @@ class CanvasImmediateRenderer extends VectorContext { rotation += this.viewRotation_; } for (let i = 0, ii = pixelCoordinates.length; i < ii; i += 2) { - let x = pixelCoordinates[i] - this.imageAnchorX_; - let y = pixelCoordinates[i + 1] - this.imageAnchorY_; - if (this.imageSnapToPixel_) { - x = Math.round(x); - y = Math.round(y); - } + const x = pixelCoordinates[i] - this.imageAnchorX_; + const y = pixelCoordinates[i + 1] - this.imageAnchorY_; if (rotation !== 0 || this.imageScale_ != 1) { const centerX = x + this.imageAnchorX_; const centerY = y + this.imageAnchorY_; @@ -856,7 +846,6 @@ class CanvasImmediateRenderer extends VectorContext { this.imageRotateWithView_ = imageStyle.getRotateWithView(); this.imageRotation_ = imageStyle.getRotation(); this.imageScale_ = imageStyle.getScale() * this.pixelRatio_; - this.imageSnapToPixel_ = imageStyle.getSnapToPixel(); this.imageWidth_ = imageSize[0]; } } diff --git a/src/ol/render/canvas/Replay.js b/src/ol/render/canvas/Replay.js index 5d40a154f4..3fea0bdd79 100644 --- a/src/ol/render/canvas/Replay.js +++ b/src/ol/render/canvas/Replay.js @@ -533,6 +533,7 @@ class CanvasReplay extends VectorContext { * @param {Object} skippedFeaturesHash Ids of features * to skip. * @param {Array<*>} instructions Instructions array. + * @param {boolean} snapToPixel Snap point symbols and text to integer pixels. * @param {function((module:ol/Feature|module:ol/render/Feature)): T|undefined} featureCallback Feature callback. * @param {module:ol/extent~Extent=} opt_hitExtent Only check features that intersect this * extent. @@ -544,6 +545,7 @@ class CanvasReplay extends VectorContext { transform, skippedFeaturesHash, instructions, + snapToPixel, featureCallback, opt_hitExtent ) { @@ -672,14 +674,13 @@ class CanvasReplay extends VectorContext { const rotateWithView = /** @type {boolean} */ (instruction[11]); let rotation = /** @type {number} */ (instruction[12]); const scale = /** @type {number} */ (instruction[13]); - const snapToPixel = /** @type {boolean} */ (instruction[14]); - const width = /** @type {number} */ (instruction[15]); + const width = /** @type {number} */ (instruction[14]); let padding, backgroundFill, backgroundStroke; if (instruction.length > 16) { - padding = /** @type {Array} */ (instruction[16]); - backgroundFill = /** @type {boolean} */ (instruction[17]); - backgroundStroke = /** @type {boolean} */ (instruction[18]); + padding = /** @type {Array} */ (instruction[15]); + backgroundFill = /** @type {boolean} */ (instruction[16]); + backgroundStroke = /** @type {boolean} */ (instruction[17]); } else { padding = defaultPadding; backgroundFill = backgroundStroke = false; @@ -853,11 +854,12 @@ class CanvasReplay extends VectorContext { * @param {number} viewRotation View rotation. * @param {Object} skippedFeaturesHash Ids of features * to skip. + * @param {boolean} snapToPixel Snap point symbols and text to integer pixels. */ - replay(context, transform, viewRotation, skippedFeaturesHash) { + replay(context, transform, viewRotation, skippedFeaturesHash, snapToPixel) { this.viewRotation_ = viewRotation; this.replay_(context, transform, - skippedFeaturesHash, this.instructions, undefined, undefined); + skippedFeaturesHash, this.instructions, snapToPixel, undefined, undefined); } /** @@ -883,7 +885,7 @@ class CanvasReplay extends VectorContext { ) { this.viewRotation_ = viewRotation; return this.replay_(context, transform, skippedFeaturesHash, - this.hitDetectionInstructions, opt_featureCallback, opt_hitExtent); + this.hitDetectionInstructions, true, opt_featureCallback, opt_hitExtent); } /** diff --git a/src/ol/render/canvas/ReplayGroup.js b/src/ol/render/canvas/ReplayGroup.js index 0e431c6288..5062984ab4 100644 --- a/src/ol/render/canvas/ReplayGroup.js +++ b/src/ol/render/canvas/ReplayGroup.js @@ -353,6 +353,7 @@ class CanvasReplayGroup extends ReplayGroup { * @param {module:ol/transform~Transform} transform Transform. * @param {number} viewRotation View rotation. * @param {Object} skippedFeaturesHash Ids of features to skip. + * @param {boolean} snapToPixel Snap point symbols and test to integer pixel. * @param {Array=} opt_replayTypes Ordered replay types to replay. * Default is {@link module:ol/render/replay~ORDER} * @param {Object=} opt_declutterReplays Declutter replays. @@ -362,6 +363,7 @@ class CanvasReplayGroup extends ReplayGroup { transform, viewRotation, skippedFeaturesHash, + snapToPixel, opt_replayTypes, opt_declutterReplays ) { @@ -393,7 +395,7 @@ class CanvasReplayGroup extends ReplayGroup { declutter.push(replay, transform.slice(0)); } } else { - replay.replay(context, transform, viewRotation, skippedFeaturesHash); + replay.replay(context, transform, viewRotation, skippedFeaturesHash, snapToPixel); } } } @@ -486,8 +488,9 @@ export function getCircleArray(radius) { * @param {!Object>} declutterReplays Declutter replays. * @param {CanvasRenderingContext2D} context Context. * @param {number} rotation Rotation. + * @param {boolean} snapToPixel Snap point symbols and text to integer pixels. */ -export function replayDeclutter(declutterReplays, context, rotation) { +export function replayDeclutter(declutterReplays, context, rotation, snapToPixel) { const zs = Object.keys(declutterReplays).map(Number).sort(numberSafeCompareFunction); const skippedFeatureUids = {}; for (let z = 0, zz = zs.length; z < zz; ++z) { @@ -495,7 +498,7 @@ export function replayDeclutter(declutterReplays, context, rotation) { for (let i = 0, ii = replayData.length; i < ii;) { const replay = replayData[i++]; const transform = replayData[i++]; - replay.replay(context, transform, rotation, skippedFeatureUids); + replay.replay(context, transform, rotation, skippedFeatureUids, snapToPixel); } } } diff --git a/src/ol/render/canvas/TextReplay.js b/src/ol/render/canvas/TextReplay.js index c7bec1ec27..3edd9f3156 100644 --- a/src/ol/render/canvas/TextReplay.js +++ b/src/ol/render/canvas/TextReplay.js @@ -337,7 +337,7 @@ class CanvasTextReplay extends CanvasReplay { this.instructions.push([CanvasInstruction.DRAW_IMAGE, begin, end, label, (anchorX - this.textOffsetX_) * pixelRatio, (anchorY - this.textOffsetY_) * pixelRatio, this.declutterGroup_, label.height, 1, 0, 0, this.textRotateWithView_, this.textRotation_, - 1, true, label.width, + 1, label.width, textState.padding == defaultPadding ? defaultPadding : textState.padding.map(function(p) { return p * pixelRatio; @@ -347,7 +347,7 @@ class CanvasTextReplay extends CanvasReplay { this.hitDetectionInstructions.push([CanvasInstruction.DRAW_IMAGE, begin, end, label, (anchorX - this.textOffsetX_) * pixelRatio, (anchorY - this.textOffsetY_) * pixelRatio, this.declutterGroup_, label.height, 1, 0, 0, this.textRotateWithView_, this.textRotation_, - 1 / pixelRatio, true, label.width, textState.padding, + 1 / pixelRatio, label.width, textState.padding, !!textState.backgroundFill, !!textState.backgroundStroke ]); } diff --git a/src/ol/renderer/canvas/VectorLayer.js b/src/ol/renderer/canvas/VectorLayer.js index 136aba67d1..7888dcbe00 100644 --- a/src/ol/renderer/canvas/VectorLayer.js +++ b/src/ol/renderer/canvas/VectorLayer.js @@ -158,11 +158,13 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { replayContext.translate(drawOffsetX, drawOffsetY); } + const viewHints = frameState.viewHints; + const snapToPixel = !(viewHints[ViewHint.ANIMATING] || viewHints[ViewHint.INTERACTING]); const width = frameState.size[0] * pixelRatio; const height = frameState.size[1] * pixelRatio; rotateAtOffset(replayContext, -rotation, width / 2, height / 2); - replayGroup.replay(replayContext, transform, rotation, skippedFeatureUids); + replayGroup.replay(replayContext, transform, rotation, skippedFeatureUids, snapToPixel); if (vectorSource.getWrapX() && projection.canWrapX() && !containsExtent(projectionExtent, extent)) { let startX = extent[0]; @@ -173,7 +175,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { --world; offsetX = worldWidth * world; transform = this.getTransform(frameState, offsetX); - replayGroup.replay(replayContext, transform, rotation, skippedFeatureUids); + replayGroup.replay(replayContext, transform, rotation, skippedFeatureUids, snapToPixel); startX += worldWidth; } world = 0; @@ -182,7 +184,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { ++world; offsetX = worldWidth * world; transform = this.getTransform(frameState, offsetX); - replayGroup.replay(replayContext, transform, rotation, skippedFeatureUids); + replayGroup.replay(replayContext, transform, rotation, skippedFeatureUids, snapToPixel); startX -= worldWidth; } } diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index 9e69471731..b696b772ca 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -4,6 +4,7 @@ import {getUid} from '../../util.js'; import LayerType from '../../LayerType.js'; import TileState from '../../TileState.js'; +import ViewHint from '../../ViewHint.js'; import {createCanvasContext2D} from '../../dom.js'; import {listen, unlisten} from '../../events.js'; import EventType from '../../events/EventType.js'; @@ -341,6 +342,8 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { if (declutterReplays) { this.declutterTree_.clear(); } + const viewHints = frameState.viewHints; + const snapToPixel = !(viewHints[ViewHint.ANIMATING] || viewHints[ViewHint.INTERACTING]); const tiles = this.renderedTiles; const tileGrid = source.getTileGridForProjection(frameState.viewState.projection); const clips = []; @@ -390,14 +393,14 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { context.clip(); } } - replayGroup.replay(context, transform, rotation, {}, replayTypes, declutterReplays); + replayGroup.replay(context, transform, rotation, {}, snapToPixel, replayTypes, declutterReplays); context.restore(); clips.push(currentClip); zs.push(currentZ); } } if (declutterReplays) { - replayDeclutter(declutterReplays, context, rotation); + replayDeclutter(declutterReplays, context, rotation, snapToPixel); } if (rotation) { rotateAtOffset(context, rotation, @@ -466,7 +469,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { scaleTransform(transform, pixelScale, -pixelScale); translateTransform(transform, -tileExtent[0], -tileExtent[3]); const replayGroup = sourceTile.getReplayGroup(layer, tile.tileCoord.toString()); - replayGroup.replay(context, transform, 0, {}, replays); + replayGroup.replay(context, transform, 0, {}, true, replays); } } } diff --git a/test/rendering/ol/layer/tile.test.js b/test/rendering/ol/layer/tile.test.js index 9f93c67092..fcb01b3694 100644 --- a/test/rendering/ol/layer/tile.test.js +++ b/test/rendering/ol/layer/tile.test.js @@ -280,7 +280,6 @@ describe('ol.rendering.layer.Tile', function() { evt.element.on('render', function(e) { e.vectorContext.setImageStyle(new CircleStyle({ radius: 5, - snapToPixel: false, fill: new Fill({color: 'yellow'}), stroke: new Stroke({color: 'red', width: 1}) }));