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] 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;