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 1/6] 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]); }); From fd1effa992f34a7b27e0b93a4664ee31569b7acc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Brunner?= Date: Wed, 15 Jul 2020 10:16:46 +0200 Subject: [PATCH 2/6] High resolution icon --- examples/data/bigdot.png | Bin 0 -> 2520 bytes examples/data/dot.svg | 66 +++++++++++++++++++++++++++++++++ examples/data/square.svg | 67 ++++++++++++++++++++++++++++++++-- examples/icon-color.js | 38 +++++++++++++++++-- src/ol/style/Icon.js | 10 +++++ src/ol/style/IconImage.js | 75 ++++++++++++++++++++++---------------- 6 files changed, 216 insertions(+), 40 deletions(-) create mode 100644 examples/data/bigdot.png create mode 100644 examples/data/dot.svg diff --git a/examples/data/bigdot.png b/examples/data/bigdot.png new file mode 100644 index 0000000000000000000000000000000000000000..b7833b4495d3959feb392ac6c4f96d3b3a012534 GIT binary patch literal 2520 zcmV;}2`Bc6P)yZzUB#x`pm)+;Wl-Y{ zwlUGvbc^X#6YW+L6ueSuKjKPlWv!;!7}TtHO`2#z$eP{7UR2h$7siVfbi>DhfW%=y zV7m%4oL(G=XvJaXJnx(t@RvMcVw~qa^Z(EL&Uw#!&Uw;EewG32ff8Wd^y_?J$#moZ z*?O00X~_gW1g5YrR-$4G-BHOB&`*?n;6KyuC6Zb-MdF? zYb!Tz-sH}mJ47Op1hpK{X8Z^kbR&5d2^uPQ97k1DRH!RguBhSRVI@T<6jGNjUsis< zUpbDGsLR)FT)I#B$nC~^y348M|@u4&3YY zs*@*As!%B8x)CokG&H2@>grT>cD9>_OaVWZGNS+&$EGSPD-&`?M5C{-PgPY_xoJuW zJ?Lsg8qnfK1M~Cq)r}iBZ2RzkG&eUZpU>x(DW91d^1d4l^ZWg(zrWwM_H!~gIH-2+ z-07AnKaJOnrRWDTq7AC4sZo(g#J1*935Ub#$dMy%7}5uL;xRdculwly`TzP?`i zEctl8W)uL=qz+tLTWecOF{-PpB@KB7teTG@e~~(<-|tu9aM-qvVniYlwSD_`NmCl4 znei6zUx|a|=jW?Oj~?09P@Mk$e#!fkZ-_E&^#dvQlkuicist5K$+P5FuQNnsGJH zrWX7Cepatu&xUxmX=DSTJIpxyQ-+D5NJzk zBc#E42YrWb#OBSL1=^C@l$Di9q>(v8N=l5cdMS^P2J0R4eBHRo%*ydjY|z_>{QfoA~=hSAYc(-dP4V5I09L!Kj` zg}EWa!^5VHSTG1^F=l+p>hA8gz1lS?n3$L#5D1v28FSFfcjIPqa+1!@PSb{@JRKb! zgu`LeG=mO$FIywp+uKc3q&z|z+!ryVwY62CEve0|Tel?Aw2K(h($XT(mefW_gZpBS za34Q@yr`xC%|`kW?hw#zk|h#}aQ*sq)5fGM*REZYFryO~o*6p1X`1co)vKl{QWl}l z{=zk{iZ>JG;zDqu+|S_oysR%w?VXyM;=+XsrfE`+^XJb?T&@8A@$x^kA+Qu-XlTgx zmo{MpgF&e!317{aFnjp@@1_=yjg4{c+&R-UDMLd;1LNaHBTqBDQhTulJH5TVw!gft z)6>%nO(I!-pl!c*;x5*j~p)!2_%M`g+@18guH@DM>RF@Yng80eb5ou@Xh8zCF{$ zix;K0XPQ74F^Gw?f(=k;M{urwnpbKD$S3an&u1<*``fxa` z4jnoqZG-}PF_)&n$OAL;Go*EV}~1NC}2;#X3X%J8x8aMd`Wrp z*woY{d7tred}?Y2=x%V(r3R|X%1YJU-EG^4L~W#8C}9Zb%4@D|HJ+WFtxlXcVP)t( z7!0c8$B(NeOP084#uQK~WyTDmy{=b<$Kz48wY4e`2)J%UAP`VBH8pPU`92eI^{S$g zh0thlv~V0pm6w;ROP4N*o;5>5L#nZ{QI(gMC$d~cE4rlMyoj}+iw|AUC8j-RO>i8C z($dlw3c9RWvxcm!*O%m&n3$lWql31#HdTD@yRMn(ojMMW%Kx|H19+!sn74G#}9GBQFS5RjN_Yr+89jIKl+>>YHkAyKD& zs>Ks5l+Sw)T_Vo*hflV!QZ{df9{>-dG;lG+02T{N&QJ_Iz`hu^qXS)1Fe&h%_n);b zimhk`Rv4=&Oa|~9AcB2?5*0~XzI!&iSR4iJgSapp;ueLtG)kN#@hN)HHRad^1h6ll zRP>>@)Ja7x7ZhWzVQtB1YMJB!QulxM9 + + + + + image/svg+xml + + + + + + + + + + + diff --git a/examples/data/square.svg b/examples/data/square.svg index e7a5e249f3..f04cfc1521 100644 --- a/examples/data/square.svg +++ b/examples/data/square.svg @@ -1,7 +1,66 @@ - - - - + + + + + image/svg+xml + + + + + + + + + diff --git a/examples/icon-color.js b/examples/icon-color.js index 098319a119..ee8c86d92f 100644 --- a/examples/icon-color.js +++ b/examples/icon-color.js @@ -19,12 +19,19 @@ const london = new Feature({ const madrid = new Feature({ geometry: new Point(fromLonLat([-3.683333, 40.4])), }); +const paris = new Feature({ + geometry: new Point(fromLonLat([2.353, 48.8566])), +}); +const berlin = new Feature({ + geometry: new Point(fromLonLat([13.3884, 52.5169])), +}); rome.setStyle( new Style({ image: new Icon({ color: '#8959A8', crossOrigin: 'anonymous', + // For Internet Explorer 11 imgSize: [20, 20], src: 'data/square.svg', }), @@ -36,7 +43,8 @@ london.setStyle( image: new Icon({ color: '#4271AE', crossOrigin: 'anonymous', - src: 'data/dot.png', + src: 'data/bigdot.png', + scale: 0.2, }), }) ); @@ -44,15 +52,37 @@ london.setStyle( madrid.setStyle( new Style({ image: new Icon({ - color: [113, 140, 0], crossOrigin: 'anonymous', - src: 'data/dot.png', + src: 'data/bigdot.png', + scale: 0.2, }), }) ); +paris.setStyle( + new Style({ + image: new Icon({ + color: '#8959A8', + crossOrigin: 'anonymous', + // For Internet Explorer 11 + imgSize: [20, 20], + src: 'data/dot.svg', + }), + }) +); + +berlin.setStyle( + new Style({ + image: new Icon({ + crossOrigin: 'anonymous', + // For Internet Explorer 11 + imgSize: [20, 20], + src: 'data/dot.svg', + }), + }) +); const vectorSource = new VectorSource({ - features: [rome, london, madrid], + features: [rome, london, madrid, paris, berlin], }); const vectorLayer = new VectorLayer({ diff --git a/src/ol/style/Icon.js b/src/ol/style/Icon.js index a299a3e96b..df51f539ea 100644 --- a/src/ol/style/Icon.js +++ b/src/ol/style/Icon.js @@ -320,6 +320,16 @@ class Icon extends ImageStyle { return this.iconImage_.getImage(pixelRatio); } + /** + * Get the pixel ratio. + * @param {number} pixelRatio Pixel ratio. + * @return {number} The pixel ration of the image. + * @api + */ + getPixelRatio(pixelRatio) { + return this.iconImage_.getPixelRatio(pixelRatio); + } + /** * @return {import("../size.js").Size} Image size. */ diff --git a/src/ol/style/IconImage.js b/src/ol/style/IconImage.js index a65d7be0fc..80c46684a3 100644 --- a/src/ol/style/IconImage.js +++ b/src/ol/style/IconImage.js @@ -23,9 +23,9 @@ class IconImage extends EventTarget { /** * @private - * @type {HTMLImageElement|HTMLCanvasElement} + * @type {Object} */ - this.hitDetectionImage_ = null; + this.hitDetectionImage_ = {}; /** * @private @@ -39,9 +39,9 @@ class IconImage extends EventTarget { /** * @private - * @type {HTMLCanvasElement} + * @type {Object} */ - this.canvas_ = color ? document.createElement('canvas') : null; + this.canvas_ = {}; /** * @private @@ -75,22 +75,18 @@ class IconImage extends EventTarget { /** * @private - * @type {boolean|undefined} */ this.tainted_; } /** * @private - * @param {CanvasRenderingContext2D=} context A context with the image already drawn into. * @return {boolean} The image canvas is tainted. */ - isTainted_(context) { + isTainted_() { if (this.tainted_ === undefined && this.imageState_ === ImageState.LOADED) { - if (!context) { - context = createCanvasContext2D(1, 1); - context.drawImage(this.image_, 0, 0); - } + const context = createCanvasContext2D(1, 1); + context.drawImage(this.image_, 0, 0); try { context.getImageData(0, 0, 1, 1); this.tainted_ = false; @@ -125,10 +121,10 @@ class IconImage extends EventTarget { if (this.size_) { this.image_.width = this.size_[0]; this.image_.height = this.size_[1]; + } else { + this.size_ = [this.image_.width, this.image_.height]; } - this.size_ = [this.image_.width, this.image_.height]; this.unlistenImage_(); - this.replaceColor_(); this.dispatchChangeEvent_(); } @@ -137,7 +133,17 @@ class IconImage extends EventTarget { * @return {HTMLImageElement|HTMLCanvasElement} Image or Canvas element. */ getImage(pixelRatio) { - return this.canvas_ ? this.canvas_ : this.image_; + this.replaceColor_(pixelRatio); + return this.canvas_[pixelRatio] ? this.canvas_[pixelRatio] : this.image_; + } + + /** + * @param {number} pixelRatio Pixel ratio. + * @return {number} Image or Canvas element. + */ + getPixelRatio(pixelRatio) { + this.replaceColor_(pixelRatio); + return this.canvas_[pixelRatio] ? pixelRatio : 1; } /** @@ -152,18 +158,23 @@ class IconImage extends EventTarget { * @return {HTMLImageElement|HTMLCanvasElement} Image element. */ getHitDetectionImage(pixelRatio) { - if (!this.hitDetectionImage_) { + if (!this.hitDetectionImage_[pixelRatio]) { if (this.isTainted_()) { + const usedPixelRatio = this.color_ ? pixelRatio : 1; const width = this.size_[0]; const height = this.size_[1]; - const context = createCanvasContext2D(width, height); + const context = createCanvasContext2D( + Math.ceil(width * usedPixelRatio), + Math.ceil(height * usedPixelRatio) + ); + context.scale(usedPixelRatio, usedPixelRatio); context.fillRect(0, 0, width, height); - this.hitDetectionImage_ = context.canvas; + this.hitDetectionImage_[pixelRatio] = context.canvas; } else { - this.hitDetectionImage_ = this.image_; + this.hitDetectionImage_[pixelRatio] = this.image_; } } - return this.hitDetectionImage_; + return this.hitDetectionImage_[pixelRatio]; } /** @@ -201,20 +212,25 @@ class IconImage extends EventTarget { } /** + * @param {number} pixelRatio Pixel ratio. * @private */ - replaceColor_() { - if (!this.color_) { + replaceColor_(pixelRatio) { + if (!this.color_ || this.canvas_[pixelRatio]) { return; } - this.canvas_.width = this.image_.width; - this.canvas_.height = this.image_.height; + const canvas = document.createElement('canvas'); + this.canvas_[pixelRatio] = canvas; - const ctx = this.canvas_.getContext('2d'); + canvas.width = Math.ceil(this.image_.width * pixelRatio); + canvas.height = Math.ceil(this.image_.height * pixelRatio); + + const ctx = canvas.getContext('2d'); + ctx.scale(pixelRatio, pixelRatio); ctx.drawImage(this.image_, 0, 0); - if (this.isTainted_(ctx)) { + if (this.isTainted_()) { // If reading from the canvas throws a SecurityError the same effect can be // achieved with globalCompositeOperation. // This could be used as the default, but it is not fully supported by all @@ -226,19 +242,14 @@ class IconImage extends EventTarget { const c = this.color_; ctx.globalCompositeOperation = 'multiply'; ctx.fillStyle = 'rgb(' + c[0] + ',' + c[1] + ',' + c[2] + ')'; - ctx.fillRect(0, 0, this.image_.width, this.image_.height); + ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.globalCompositeOperation = 'destination-in'; ctx.drawImage(this.image_, 0, 0); return; } - const imgData = ctx.getImageData( - 0, - 0, - this.image_.width, - this.image_.height - ); + const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imgData.data; const r = this.color_[0] / 255.0; const g = this.color_[1] / 255.0; From bf24288f0955612695f3b9a51b6bd1cd2181149c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Brunner?= Date: Wed, 15 Jul 2020 16:37:40 +0200 Subject: [PATCH 3/6] Fix for not integer pixel ratio --- src/ol/render/canvas/ImageBuilder.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ol/render/canvas/ImageBuilder.js b/src/ol/render/canvas/ImageBuilder.js index 767deb5144..ec83a01110 100644 --- a/src/ol/render/canvas/ImageBuilder.js +++ b/src/ol/render/canvas/ImageBuilder.js @@ -145,7 +145,7 @@ class CanvasImageBuilder extends CanvasBuilder { this.anchorX_ * this.imagePixelRatio_, this.anchorY_ * this.imagePixelRatio_, this.declutterGroups_, - this.height_ * this.imagePixelRatio_, + Math.ceil(this.height_ * this.imagePixelRatio_), this.opacity_, this.originX_, this.originY_, @@ -155,7 +155,7 @@ class CanvasImageBuilder extends CanvasBuilder { (this.scale_[0] * this.pixelRatio) / this.imagePixelRatio_, (this.scale_[1] * this.pixelRatio) / this.imagePixelRatio_, ], - this.width_ * this.imagePixelRatio_, + Math.ceil(this.width_ * this.imagePixelRatio_), ]); this.hitDetectionInstructions.push([ CanvasInstruction.DRAW_IMAGE, @@ -166,7 +166,7 @@ class CanvasImageBuilder extends CanvasBuilder { this.anchorX_ * this.imagePixelRatio_, this.anchorY_ * this.imagePixelRatio_, this.declutterGroups_, - this.height_ * this.imagePixelRatio_, + Math.ceil(this.height_ * this.imagePixelRatio_), this.opacity_, this.originX_, this.originY_, @@ -176,7 +176,7 @@ class CanvasImageBuilder extends CanvasBuilder { (this.scale_[0] * this.pixelRatio) / this.imagePixelRatio_, (this.scale_[1] * this.pixelRatio) / this.imagePixelRatio_, ], - this.width_ * this.imagePixelRatio_, + Math.ceil(this.width_ * this.imagePixelRatio_), ]); this.endGeometry(feature); } @@ -208,7 +208,7 @@ class CanvasImageBuilder extends CanvasBuilder { this.anchorX_ * this.imagePixelRatio_, this.anchorY_ * this.imagePixelRatio_, this.declutterGroups_, - this.height_ * this.imagePixelRatio_, + Math.ceil(this.height_ * this.imagePixelRatio_), this.opacity_, this.originX_, this.originY_, @@ -218,7 +218,7 @@ class CanvasImageBuilder extends CanvasBuilder { (this.scale_[0] * this.pixelRatio) / this.imagePixelRatio_, (this.scale_[1] * this.pixelRatio) / this.imagePixelRatio_, ], - this.width_ * this.imagePixelRatio_, + Math.ceil(this.width_ * this.imagePixelRatio_), ]); this.hitDetectionInstructions.push([ CanvasInstruction.DRAW_IMAGE, @@ -229,7 +229,7 @@ class CanvasImageBuilder extends CanvasBuilder { this.anchorX_ * this.imagePixelRatio_, this.anchorY_ * this.imagePixelRatio_, this.declutterGroups_, - this.height_ * this.imagePixelRatio_, + Math.ceil(this.height_ * this.imagePixelRatio_), this.opacity_, this.originX_, this.originY_, @@ -239,7 +239,7 @@ class CanvasImageBuilder extends CanvasBuilder { (this.scale_[0] * this.pixelRatio) / this.imagePixelRatio_, (this.scale_[1] * this.pixelRatio) / this.imagePixelRatio_, ], - this.width_ * this.imagePixelRatio_, + Math.ceil(this.width_ * this.imagePixelRatio_), ]); this.endGeometry(feature); } From f6061f0589b8123105e4a88d7c478b5e0e2b5f84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Brunner?= Date: Fri, 17 Jul 2020 11:22:26 +0200 Subject: [PATCH 4/6] Don't creates too many canvas --- src/ol/style/IconImage.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/ol/style/IconImage.js b/src/ol/style/IconImage.js index 80c46684a3..a5f7793e0a 100644 --- a/src/ol/style/IconImage.js +++ b/src/ol/style/IconImage.js @@ -77,6 +77,11 @@ class IconImage extends EventTarget { * @private */ this.tainted_; + + /** + * @private + */ + this.taintedTestContext_; } /** @@ -85,12 +90,15 @@ class IconImage extends EventTarget { */ isTainted_() { if (this.tainted_ === undefined && this.imageState_ === ImageState.LOADED) { - const context = createCanvasContext2D(1, 1); - context.drawImage(this.image_, 0, 0); + if (!this.taintedTestContext_) { + this.taintedTestContext_ = createCanvasContext2D(1, 1); + } + this.taintedTestContext_.drawImage(this.image_, 0, 0); try { - context.getImageData(0, 0, 1, 1); + this.taintedTestContext_.getImageData(0, 0, 1, 1); this.tainted_ = false; } catch (e) { + this.taintedTestContext_ = undefined; this.tainted_ = true; } } From 2e781afd56cccf7308d3a6cf7d81568d72a77a58 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Sun, 26 Jul 2020 12:12:57 +0200 Subject: [PATCH 5/6] Use static test context for tainted canvas --- src/ol/style/IconImage.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/ol/style/IconImage.js b/src/ol/style/IconImage.js index a5f7793e0a..b97d979cb9 100644 --- a/src/ol/style/IconImage.js +++ b/src/ol/style/IconImage.js @@ -9,6 +9,11 @@ import {createCanvasContext2D} from '../dom.js'; import {shared as iconImageCache} from './IconImageCache.js'; import {listenImage} from '../Image.js'; +/** + * @type {CanvasRenderingContext2D} + */ +let taintedTestContext = null; + class IconImage extends EventTarget { /** * @param {HTMLImageElement|HTMLCanvasElement} image Image. @@ -77,11 +82,6 @@ class IconImage extends EventTarget { * @private */ this.tainted_; - - /** - * @private - */ - this.taintedTestContext_; } /** @@ -90,15 +90,15 @@ class IconImage extends EventTarget { */ isTainted_() { if (this.tainted_ === undefined && this.imageState_ === ImageState.LOADED) { - if (!this.taintedTestContext_) { - this.taintedTestContext_ = createCanvasContext2D(1, 1); + if (!taintedTestContext) { + taintedTestContext = createCanvasContext2D(1, 1); } - this.taintedTestContext_.drawImage(this.image_, 0, 0); + taintedTestContext.drawImage(this.image_, 0, 0); try { - this.taintedTestContext_.getImageData(0, 0, 1, 1); + taintedTestContext.getImageData(0, 0, 1, 1); this.tainted_ = false; } catch (e) { - this.taintedTestContext_ = undefined; + taintedTestContext = null; this.tainted_ = true; } } From c144c7265ed14909f4322e02a2bbd483abb960fb Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Sun, 26 Jul 2020 12:13:16 +0200 Subject: [PATCH 6/6] Example doc and color changes --- examples/icon-color.html | 2 +- examples/icon-color.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/icon-color.html b/examples/icon-color.html index f102fb16a2..add545fe73 100644 --- a/examples/icon-color.html +++ b/examples/icon-color.html @@ -3,7 +3,7 @@ layout: example.html title: Icon Colors shortdesc: Example assigning a custom color to an icon docs: > - Example assigning a custom color to an icon. The features in this examples are all using the same image with the different colors coming from the javascript file. + Example assigning a custom color to an icon. The icon styles in this example use images with a white fill. For some features, custom colors set using the `color` property. Note that icon files need to obey the same origin policy or send proper CORS headers for this to work. When relying on CORS headers, the `ol/style/Icon` must be configured with `crossOrigin: 'anonymous'`. tags: "vector, style, icon, marker" resources: diff --git a/examples/icon-color.js b/examples/icon-color.js index ee8c86d92f..cdb0412515 100644 --- a/examples/icon-color.js +++ b/examples/icon-color.js @@ -29,7 +29,7 @@ const berlin = new Feature({ rome.setStyle( new Style({ image: new Icon({ - color: '#8959A8', + color: '#BADA55', crossOrigin: 'anonymous', // For Internet Explorer 11 imgSize: [20, 20],