From f2fe6e5957be867ba47ae98f357929837eb3bd0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Brunner?= Date: Tue, 14 Jul 2020 14:50:46 +0200 Subject: [PATCH] Have high resolution regular shape --- src/ol/render/canvas/ImageBuilder.js | 64 ++++++++++------ src/ol/style/Image.js | 8 ++ src/ol/style/RegularShape.js | 97 ++++++++++++++++++------- test/spec/ol/style/circle.test.js | 18 ++--- test/spec/ol/style/regularshape.test.js | 23 +++--- 5 files changed, 142 insertions(+), 68 deletions(-) diff --git a/src/ol/render/canvas/ImageBuilder.js b/src/ol/render/canvas/ImageBuilder.js index df8320742d..767deb5144 100644 --- a/src/ol/render/canvas/ImageBuilder.js +++ b/src/ol/render/canvas/ImageBuilder.js @@ -32,6 +32,12 @@ class CanvasImageBuilder extends CanvasBuilder { */ this.image_ = null; + /** + * @private + * @type {number|undefined} + */ + this.imagePixelRatio_ = undefined; + /** * @private * @type {number|undefined} @@ -136,17 +142,20 @@ class CanvasImageBuilder extends CanvasBuilder { myEnd, this.image_, // Remaining arguments to DRAW_IMAGE are in alphabetical order - this.anchorX_, - this.anchorY_, + this.anchorX_ * this.imagePixelRatio_, + this.anchorY_ * this.imagePixelRatio_, this.declutterGroups_, - this.height_, + this.height_ * this.imagePixelRatio_, this.opacity_, this.originX_, this.originY_, this.rotateWithView_, this.rotation_, - [this.scale_[0] * this.pixelRatio, this.scale_[1] * this.pixelRatio], - this.width_, + [ + (this.scale_[0] * this.pixelRatio) / this.imagePixelRatio_, + (this.scale_[1] * this.pixelRatio) / this.imagePixelRatio_, + ], + this.width_ * this.imagePixelRatio_, ]); this.hitDetectionInstructions.push([ CanvasInstruction.DRAW_IMAGE, @@ -154,17 +163,20 @@ class CanvasImageBuilder extends CanvasBuilder { myEnd, this.hitDetectionImage_, // Remaining arguments to DRAW_IMAGE are in alphabetical order - this.anchorX_, - this.anchorY_, + this.anchorX_ * this.imagePixelRatio_, + this.anchorY_ * this.imagePixelRatio_, this.declutterGroups_, - this.height_, + this.height_ * this.imagePixelRatio_, this.opacity_, this.originX_, this.originY_, this.rotateWithView_, this.rotation_, - this.scale_, - this.width_, + [ + (this.scale_[0] * this.pixelRatio) / this.imagePixelRatio_, + (this.scale_[1] * this.pixelRatio) / this.imagePixelRatio_, + ], + this.width_ * this.imagePixelRatio_, ]); this.endGeometry(feature); } @@ -193,17 +205,20 @@ class CanvasImageBuilder extends CanvasBuilder { myEnd, this.image_, // Remaining arguments to DRAW_IMAGE are in alphabetical order - this.anchorX_, - this.anchorY_, + this.anchorX_ * this.imagePixelRatio_, + this.anchorY_ * this.imagePixelRatio_, this.declutterGroups_, - this.height_, + this.height_ * this.imagePixelRatio_, this.opacity_, this.originX_, this.originY_, this.rotateWithView_, this.rotation_, - [this.scale_[0] * this.pixelRatio, this.scale_[1] * this.pixelRatio], - this.width_, + [ + (this.scale_[0] * this.pixelRatio) / this.imagePixelRatio_, + (this.scale_[1] * this.pixelRatio) / this.imagePixelRatio_, + ], + this.width_ * this.imagePixelRatio_, ]); this.hitDetectionInstructions.push([ CanvasInstruction.DRAW_IMAGE, @@ -211,17 +226,20 @@ class CanvasImageBuilder extends CanvasBuilder { myEnd, this.hitDetectionImage_, // Remaining arguments to DRAW_IMAGE are in alphabetical order - this.anchorX_, - this.anchorY_, + this.anchorX_ * this.imagePixelRatio_, + this.anchorY_ * this.imagePixelRatio_, this.declutterGroups_, - this.height_, + this.height_ * this.imagePixelRatio_, this.opacity_, this.originX_, this.originY_, this.rotateWithView_, this.rotation_, - this.scale_, - this.width_, + [ + (this.scale_[0] * this.pixelRatio) / this.imagePixelRatio_, + (this.scale_[1] * this.pixelRatio) / this.imagePixelRatio_, + ], + this.width_ * this.imagePixelRatio_, ]); this.endGeometry(feature); } @@ -236,6 +254,7 @@ class CanvasImageBuilder extends CanvasBuilder { this.anchorY_ = undefined; this.hitDetectionImage_ = null; this.image_ = null; + this.imagePixelRatio_ = undefined; this.height_ = undefined; this.scale_ = undefined; this.opacity_ = undefined; @@ -254,9 +273,10 @@ class CanvasImageBuilder extends CanvasBuilder { setImageStyle(imageStyle, declutterGroups) { const anchor = imageStyle.getAnchor(); const size = imageStyle.getSize(); - const hitDetectionImage = imageStyle.getHitDetectionImage(1); - const image = imageStyle.getImage(1); + const hitDetectionImage = imageStyle.getHitDetectionImage(this.pixelRatio); + const image = imageStyle.getImage(this.pixelRatio); const origin = imageStyle.getOrigin(); + this.imagePixelRatio_ = imageStyle.getPixelRatio(this.pixelRatio); this.anchorX_ = anchor[0]; this.anchorY_ = anchor[1]; this.declutterGroups_ = declutterGroups; diff --git a/src/ol/style/Image.js b/src/ol/style/Image.js index 4d9e83259a..22a8f1f65d 100644 --- a/src/ol/style/Image.js +++ b/src/ol/style/Image.js @@ -161,6 +161,14 @@ class ImageStyle { return abstract(); } + /* + * Get the image pixel ratio. + * @param {number} pixelRatio Pixel ratio. + * */ + getPixelRatio(pixelRatio) { + return 1; + } + /** * @abstract * @return {import("../ImageState.js").default} Image state. diff --git a/src/ol/style/RegularShape.js b/src/ol/style/RegularShape.js index 6699922c47..a65ce2366e 100644 --- a/src/ol/style/RegularShape.js +++ b/src/ol/style/RegularShape.js @@ -73,15 +73,15 @@ class RegularShape extends ImageStyle { /** * @private - * @type {HTMLCanvasElement} + * @type {Object} */ - this.canvas_ = null; + this.canvas_ = {}; /** * @private - * @type {HTMLCanvasElement} + * @type {Object} */ - this.hitDetectionCanvas_ = null; + this.hitDetectionCanvas_ = {}; /** * @private @@ -205,20 +205,45 @@ class RegularShape extends ImageStyle { /** * @param {number} pixelRatio Pixel ratio. - * @return {HTMLImageElement|HTMLCanvasElement} Image element. + * @return {HTMLCanvasElement} Image element. */ getHitDetectionImage(pixelRatio) { - return this.hitDetectionCanvas_; + if (!this.hitDetectionCanvas_[pixelRatio || 1]) { + const renderOptions = this.createRenderOptions(); + + this.createHitDetectionCanvas_(renderOptions, pixelRatio || 1); + } + return this.hitDetectionCanvas_[pixelRatio || 1]; } /** * Get the image icon. * @param {number} pixelRatio Pixel ratio. - * @return {HTMLImageElement|HTMLCanvasElement} Image or Canvas element. + * @return {HTMLCanvasElement} Image or Canvas element. * @api */ getImage(pixelRatio) { - return this.canvas_; + if (!this.canvas_[pixelRatio || 1]) { + const renderOptions = this.createRenderOptions(); + + const context = createCanvasContext2D( + renderOptions.size * pixelRatio || 1, + renderOptions.size * pixelRatio || 1 + ); + + this.draw_(renderOptions, context, 0, 0, pixelRatio || 1); + + this.canvas_[pixelRatio || 1] = context.canvas; + } + return this.canvas_[pixelRatio || 1]; + } + + /* + * Get the image pixel ratio. + * @param {number} pixelRatio Pixel ratio. + * */ + getPixelRatio(pixelRatio) { + return pixelRatio; } /** @@ -312,9 +337,10 @@ class RegularShape extends ImageStyle { unlistenImageChange(listener) {} /** + * @returns {RenderOptions} The render options * @protected */ - render() { + createRenderOptions() { let lineCap = defaultLineCap; let lineJoin = defaultLineJoin; let miterLimit = 0; @@ -349,9 +375,9 @@ class RegularShape extends ImageStyle { } } - let size = 2 * (this.radius_ + strokeWidth) + 1; + const size = 2 * (this.radius_ + strokeWidth) + 1; - const renderOptions = { + return { strokeStyle: strokeStyle, strokeWidth: strokeWidth, size: size, @@ -361,18 +387,30 @@ class RegularShape extends ImageStyle { lineJoin: lineJoin, miterLimit: miterLimit, }; + } - const context = createCanvasContext2D(size, size); - this.canvas_ = context.canvas; + /** + * @protected + */ + render() { + const renderOptions = this.createRenderOptions(); + + const context = createCanvasContext2D( + renderOptions.size, + renderOptions.size + ); + + this.draw_(renderOptions, context, 0, 0, 1); + + this.canvas_[1] = context.canvas; // canvas.width and height are rounded to the closest integer - size = this.canvas_.width; + const size = context.canvas.width; const imageSize = size; const displacement = this.getDisplacement(); - this.draw_(renderOptions, context, 0, 0); - - this.createHitDetectionCanvas_(renderOptions); + this.hitDetectionImageSize_ = [renderOptions.size, renderOptions.size]; + this.createHitDetectionCanvas_(renderOptions, 1); this.anchor_ = [size / 2 - displacement[0], size / 2 + displacement[1]]; this.size_ = [size, size]; @@ -385,11 +423,13 @@ class RegularShape extends ImageStyle { * @param {CanvasRenderingContext2D} context The rendering context. * @param {number} x The origin for the symbol (x). * @param {number} y The origin for the symbol (y). + * @param {number} pixelRatio The pixel ratio. */ - draw_(renderOptions, context, x, y) { + draw_(renderOptions, context, x, y, pixelRatio) { let i, angle0, radiusC; + // reset transform - context.setTransform(1, 0, 0, 1, 0, 0); + context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0); // then move to (x, y) context.translate(x, y); @@ -448,10 +488,10 @@ class RegularShape extends ImageStyle { /** * @private * @param {RenderOptions} renderOptions Render options. + * @param {number} pixelRatio The pixel ratio. */ - createHitDetectionCanvas_(renderOptions) { - this.hitDetectionImageSize_ = [renderOptions.size, renderOptions.size]; - this.hitDetectionCanvas_ = this.canvas_; + createHitDetectionCanvas_(renderOptions, pixelRatio) { + this.hitDetectionCanvas_[pixelRatio] = this.getImage(pixelRatio); if (this.fill_) { let color = this.fill_.getColor(); @@ -469,12 +509,12 @@ class RegularShape extends ImageStyle { // if a transparent fill style is set, create an extra hit-detection image // with a default fill style const context = createCanvasContext2D( - renderOptions.size, - renderOptions.size + renderOptions.size * pixelRatio, + renderOptions.size * pixelRatio ); - this.hitDetectionCanvas_ = context.canvas; + this.hitDetectionCanvas_[pixelRatio] = context.canvas; - this.drawHitDetectionCanvas_(renderOptions, context, 0, 0); + this.drawHitDetectionCanvas_(renderOptions, context, 0, 0, pixelRatio); } } } @@ -485,10 +525,11 @@ class RegularShape extends ImageStyle { * @param {CanvasRenderingContext2D} context The context. * @param {number} x The origin for the symbol (x). * @param {number} y The origin for the symbol (y). + * @param {number} pixelRatio The pixel ratio. */ - drawHitDetectionCanvas_(renderOptions, context, x, y) { + drawHitDetectionCanvas_(renderOptions, context, x, y, pixelRatio) { // reset transform - context.setTransform(1, 0, 0, 1, 0, 0); + context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0); // then move to (x, y) context.translate(x, y); diff --git a/test/spec/ol/style/circle.test.js b/test/spec/ol/style/circle.test.js index a80eea543c..88eaae179a 100644 --- a/test/spec/ol/style/circle.test.js +++ b/test/spec/ol/style/circle.test.js @@ -6,14 +6,14 @@ describe('ol.style.Circle', function () { describe('#constructor', function () { it('creates a canvas (no fill-style)', function () { const style = new CircleStyle({radius: 10}); - expect(style.getImage()).to.be.an(HTMLCanvasElement); + expect(style.getImage(1)).to.be.an(HTMLCanvasElement); expect(style.getSize()).to.eql([21, 21]); expect(style.getImageSize()).to.eql([21, 21]); expect(style.getOrigin()).to.eql([0, 0]); expect(style.getAnchor()).to.eql([10.5, 10.5]); // no hit-detection image is created, because no fill style is set - expect(style.getImage()).to.be(style.getHitDetectionImage()); - expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement); + expect(style.getImage(1)).to.be(style.getHitDetectionImage(1)); + expect(style.getHitDetectionImage(1)).to.be.an(HTMLCanvasElement); expect(style.getHitDetectionImageSize()).to.eql([21, 21]); }); @@ -24,14 +24,14 @@ describe('ol.style.Circle', function () { color: 'transparent', }), }); - expect(style.getImage()).to.be.an(HTMLCanvasElement); + expect(style.getImage(1)).to.be.an(HTMLCanvasElement); expect(style.getSize()).to.eql([21, 21]); expect(style.getImageSize()).to.eql([21, 21]); expect(style.getOrigin()).to.eql([0, 0]); expect(style.getAnchor()).to.eql([10.5, 10.5]); // hit-detection image is created, because transparent fill style is set - expect(style.getImage()).to.not.be(style.getHitDetectionImage()); - expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement); + expect(style.getImage(1)).to.not.be(style.getHitDetectionImage(1)); + expect(style.getHitDetectionImage(1)).to.be.an(HTMLCanvasElement); expect(style.getHitDetectionImageSize()).to.eql([21, 21]); }); @@ -42,14 +42,14 @@ describe('ol.style.Circle', function () { color: '#FFFF00', }), }); - expect(style.getImage()).to.be.an(HTMLCanvasElement); + expect(style.getImage(1)).to.be.an(HTMLCanvasElement); expect(style.getSize()).to.eql([21, 21]); expect(style.getImageSize()).to.eql([21, 21]); expect(style.getOrigin()).to.eql([0, 0]); expect(style.getAnchor()).to.eql([10.5, 10.5]); // no hit-detection image is created, because non-transparent fill style is set - expect(style.getImage()).to.be(style.getHitDetectionImage()); - expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement); + expect(style.getImage(1)).to.be(style.getHitDetectionImage(1)); + expect(style.getHitDetectionImage(1)).to.be.an(HTMLCanvasElement); expect(style.getHitDetectionImageSize()).to.eql([21, 21]); }); }); diff --git a/test/spec/ol/style/regularshape.test.js b/test/spec/ol/style/regularshape.test.js index 51e901158b..a099ada418 100644 --- a/test/spec/ol/style/regularshape.test.js +++ b/test/spec/ol/style/regularshape.test.js @@ -32,14 +32,14 @@ describe('ol.style.RegularShape', function () { it('creates a canvas (no fill-style)', function () { const style = new RegularShape({radius: 10}); - expect(style.getImage()).to.be.an(HTMLCanvasElement); + expect(style.getImage(1)).to.be.an(HTMLCanvasElement); expect(style.getSize()).to.eql([21, 21]); expect(style.getImageSize()).to.eql([21, 21]); expect(style.getOrigin()).to.eql([0, 0]); expect(style.getAnchor()).to.eql([10.5, 10.5]); // no hit-detection image is created, because no fill style is set - expect(style.getImage()).to.be(style.getHitDetectionImage()); - expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement); + expect(style.getImage(1)).to.be(style.getHitDetectionImage(1)); + expect(style.getHitDetectionImage(1)).to.be.an(HTMLCanvasElement); expect(style.getHitDetectionImageSize()).to.eql([21, 21]); }); @@ -50,15 +50,20 @@ describe('ol.style.RegularShape', function () { color: 'transparent', }), }); - expect(style.getImage()).to.be.an(HTMLCanvasElement); + expect(style.getImage(1)).to.be.an(HTMLCanvasElement); + expect(style.getImage(1).width).to.be(21); + expect(style.getImage(2).width).to.be(42); + expect(style.getPixelRatio(2)).to.be(2); expect(style.getSize()).to.eql([21, 21]); expect(style.getImageSize()).to.eql([21, 21]); expect(style.getOrigin()).to.eql([0, 0]); expect(style.getAnchor()).to.eql([10.5, 10.5]); // hit-detection image is created, because transparent fill style is set - expect(style.getImage()).to.not.be(style.getHitDetectionImage()); - expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement); + expect(style.getImage(1)).to.not.be(style.getHitDetectionImage(1)); + expect(style.getHitDetectionImage(1)).to.be.an(HTMLCanvasElement); expect(style.getHitDetectionImageSize()).to.eql([21, 21]); + expect(style.getHitDetectionImage(1).width).to.be(21); + expect(style.getHitDetectionImage(2).width).to.be(42); }); it('creates a canvas (non-transparent fill-style)', function () { @@ -68,14 +73,14 @@ describe('ol.style.RegularShape', function () { color: '#FFFF00', }), }); - expect(style.getImage()).to.be.an(HTMLCanvasElement); + expect(style.getImage(1)).to.be.an(HTMLCanvasElement); expect(style.getSize()).to.eql([21, 21]); expect(style.getImageSize()).to.eql([21, 21]); expect(style.getOrigin()).to.eql([0, 0]); expect(style.getAnchor()).to.eql([10.5, 10.5]); // no hit-detection image is created, because non-transparent fill style is set - expect(style.getImage()).to.be(style.getHitDetectionImage()); - expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement); + expect(style.getImage(1)).to.be(style.getHitDetectionImage(1)); + expect(style.getHitDetectionImage(1)).to.be.an(HTMLCanvasElement); expect(style.getHitDetectionImageSize()).to.eql([21, 21]); });