diff --git a/examples/data/bigdot.png b/examples/data/bigdot.png new file mode 100644 index 0000000000..b7833b4495 Binary files /dev/null and b/examples/data/bigdot.png differ diff --git a/examples/data/dot.svg b/examples/data/dot.svg new file mode 100644 index 0000000000..cc16a8e91b --- /dev/null +++ b/examples/data/dot.svg @@ -0,0 +1,66 @@ + + + + + + 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;