/* Copyright (c) 2015 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). * * Photo style for vector features */ import ol_style_RegularShape from 'ol/style/RegularShape.js' import {asString as ol_color_asString} from 'ol/color.js' import ol_style_Stroke from 'ol/style/Stroke.js' import ol_style_Fill from 'ol/style/Fill.js' /** * @classdesc * Set Photo style for vector features. * * @constructor * @param {} options * @param { default | square | circle | anchored | folio } options.kind * @param {boolean} options.crop crop within square, default is false * @param {Number} options.radius symbol size * @param {Number} [options.shadow=0] drop a shadow (the shadow width in pixel) * @param {string} [options.declutterMode] Declutter mode "declutter" | "obstacle" | "none" | undefined * @param {ol_style_Stroke} options.stroke * @param {String} options.src image src * @param {String} options.crossOrigin The crossOrigin attribute for loaded images. Note that you must provide a crossOrigin value if you want to access pixel data with the Canvas renderer. * @param {Array} [options.displacement] to use with ol > 6 * @param {number} [options.offsetX=0] Horizontal offset in pixels, deprecated use displacement with ol>6 * @param {number} [options.offsetY=0] Vertical offset in pixels, deprecated use displacement with ol>6 * @param {function} [options.onload] callback when image is loaded (to redraw the layer) * @param {function} [options.onerror] callback when image is on error (not loaded) * @extends {ol_style_RegularShape} * @implements {ol.structs.IHasChecksum} * @api */ var ol_style_Photo = class olstylePhoto extends ol_style_RegularShape { constructor(options) { options = options || {} if (!options.displacement){ options.displacement = [options.offsetX || 0, -options.offsetY || 0] } var sanchor = (options.kind === "anchored" ? 8 : 0) var shadow = (Number(options.shadow) || 0) if (!options.stroke) { options.stroke = new ol_style_Stroke({ width: 0, color: "#000" }) } var strokeWidth = options.stroke.getWidth() if (strokeWidth < 0) strokeWidth = 0; if (options.kind == 'folio') strokeWidth += 6; options.stroke.setWidth(strokeWidth) super({ radius: options.radius + strokeWidth + sanchor / 2 + shadow / 2, points: 0, displacement: [options.displacement[0] || 0, (options.displacement[1] || 0) + sanchor], // No fill to create a hit detection Image (v5) or transparent (v6) fill: ol_style_RegularShape.prototype.render ? new ol_style_Fill({ color: [0, 0, 0, 0] }) : null, declutterMode: options.declutterMode, }) this.sanchor_ = sanchor; this._shadow = shadow; // Hack to get the hit detection Image (v4.6.5 ?) if (!this.getHitDetectionImage) { var img = super.getImage.call(this) if (!this.hitDetectionCanvas_) { for (var i in this) { if (this[i] && this[i].getContext && this[i] !== img) { this.hitDetectionCanvas_ = this[i] break } } } // Clone canvas for hit detection (old versions) this.hitDetectionCanvas_ = document.createElement('canvas') this.hitDetectionCanvas_.width = img.width this.hitDetectionCanvas_.height = img.height var hit = this.hitDetectionCanvas_ this.getHitDetectionImage = function () { return hit } } this._stroke = options.stroke this._fill = options.fill this._crop = options.crop this._crossOrigin = options.crossOrigin this._kind = options.kind || "default" this._radius = options.radius this._src = options.src this._offset = [options.offsetX ? options.offsetX : 0, options.offsetY ? options.offsetY : 0] this._onload = options.onload this._onerror = options.onerror if (typeof (options.opacity) == 'number'){ this.setOpacity(options.opacity) } if (typeof (options.rotation) == 'number'){ this.setRotation(options.rotation) } // Calculate image this.render(); this.getImage() } /** Set photo offset * @param {ol.pixel} offset */ setOffset(offset) { this._offset = [offset[0] || 0, offset[1] || 0] this.render() this.getImage() } /** * Clones the style. * @return {ol_style_Photo} */ clone() { var i = new ol_style_Photo({ stroke: this._stroke, fill: this._fill, shadow: this._shadow, crop: this._crop, crossOrigin: this._crossOrigin, kind: this._kind, radius: this._radius, src: this._src, offsetX: this._offset[0], offsetY: this._offset[1], opacity: this.getOpacity(), rotation: this.getRotation(), declutterMode: this.getDeclutterMode ? this.getDeclutterMode() : null, }) i.render() i.getImage() return i } /** * Draw the form without the image * @private */ drawBack_(context, color, strokeWidth, pixelratio) { var shadow = this._shadow var canvas = context.canvas context.beginPath() context.fillStyle = color context.clearRect(0, 0, canvas.width, canvas.height) var width = canvas.width / pixelratio var height = canvas.height / pixelratio switch (this._kind) { case 'square': { context.rect(0, 0, width - shadow, height - shadow) break } case 'circle': { context.arc(this._radius + strokeWidth, this._radius + strokeWidth, this._radius + strokeWidth, 0, 2 * Math.PI, false) break } case 'folio': { var offset = 6 strokeWidth -= offset context.strokeStyle = 'rgba(0,0,0,0.5)' context.lineWidth = 1 var w = width - shadow - 2 * offset var a = Math.atan(6 / w) context.save() context.rotate(-a) context.translate(-6, 2) context.beginPath() context.rect(offset, offset, w, w) context.stroke() context.fill() context.restore() context.save() context.translate(6, -1) context.rotate(a) context.beginPath() context.rect(offset, offset, w, w) context.stroke() context.fill() context.restore() context.beginPath() context.rect(offset, offset, w, w) context.stroke() break } case 'anchored': { context.roundRect(this.sanchor_ / 2, 0, width - this.sanchor_ - shadow, height - this.sanchor_ - shadow, strokeWidth) context.moveTo(width / 2 - this.sanchor_ - shadow / 2, height - this.sanchor_ - shadow) context.lineTo(width / 2 + this.sanchor_ - shadow / 2, height - this.sanchor_ - shadow) context.lineTo(width / 2 - shadow / 2, height - shadow); break } default: { // roundrect context.roundRect(0, 0, width - shadow, height - shadow, strokeWidth) break } } context.closePath() } /** * @return {RenderOptions} The render options */ createRenderOptions() { var opt = super.createRenderOptions() opt.photoOptions = [ 'photo', this._crossOrigin, this._crop, this._src, this._shadow, this._kind, ].join('-') return opt; } /** * Get the image icon. * @param {number} pixelRatio Pixel ratio. * @return {HTMLCanvasElement} Image or Canvas element. * @api */ getImage(pixelratio) { pixelratio = pixelratio || window.devicePixelRatio; var canvas = super.getImage(pixelratio) if ((this._gethit || this.img_) && this._currentRatio === pixelratio) return canvas; // Calculate image at pixel ratio this._currentRatio = pixelratio; var strokeStyle var strokeWidth = 0 if (this._stroke) { strokeStyle = ol_color_asString(this._stroke.getColor()) strokeWidth = this._stroke.getWidth() } // Draw hitdetection image this._gethit = true var context = this.getHitDetectionImage().getContext('2d') context.save() context.setTransform(1, 0, 0, 1, 0, 0) this.drawBack_(context, "#000", strokeWidth, 1) context.fill() context.restore() this._gethit = false // Draw the image context = canvas.getContext('2d') context.save() context.setTransform(pixelratio, 0, 0, pixelratio, 0, 0) this.drawBack_(context, strokeStyle, strokeWidth, pixelratio) // Draw a shadow if (this._shadow) { context.shadowColor = 'rgba(0,0,0,0.5)' context.shadowBlur = pixelratio * this._shadow / 2 context.shadowOffsetX = pixelratio * this._shadow / 2 context.shadowOffsetY = pixelratio * this._shadow / 2 } context.fill() context.restore() var self = this var img = this.img_ = new Image() if (this._crossOrigin) img.crossOrigin = this._crossOrigin img.src = this._src // Draw image if (img.width) { self.drawImage_(canvas, img, pixelratio) } else { img.onload = function () { self.drawImage_(canvas, img, pixelratio) // Force change (?!) // self.setScale(1); if (self._onload) self._onload() } if (self._onerror) { img.onerror = function () { self._onerror() } } } // Set anchor (ol < 6) if (!this.getDisplacement) { var a = this.getAnchor() a[0] = (canvas.width / pixelratio - this._shadow) / 2 - this._offset[0] if (this.sanchor_) { a[1] = canvas.height / pixelratio - this._shadow - this._offset[1] } else { a[1] = (canvas.height / pixelratio - this._shadow) / 2 - this._offset[1] } } return canvas } /** Returns the photo image * @returns {HTMLImageElement} */ getPhoto() { return this.img_ } /** * Draw an timage when loaded * @private */ drawImage_(canvas, img, pixelratio) { // Remove the circle on the canvas var context = (canvas.getContext('2d')) var strokeWidth = 0 if (this._stroke) strokeWidth = this._stroke.getWidth() var size = 2 * this._radius context.save() if (ol_style_RegularShape.prototype.render) context.setTransform(pixelratio, 0, 0, pixelratio, 0, 0) if (this._kind == 'circle') { context.beginPath() context.arc(this._radius + strokeWidth, this._radius + strokeWidth, this._radius, 0, 2 * Math.PI, false) context.clip() } var s, x, y, w, h, sx, sy, sw, sh // Crop the image to a square vignette if (this._crop) { s = Math.min(img.width / size, img.height / size) sw = sh = s * size sx = (img.width - sw) / 2 sy = (img.height - sh) / 2 x = y = 0 w = h = size + 1 } else { // Fit the image to the size s = Math.min(size / img.width, size / img.height) sx = sy = 0 sw = img.width sh = img.height w = s * sw h = s * sh x = (size - w) / 2 y = (size - h) / 2 } x += strokeWidth + this.sanchor_ / 2 y += strokeWidth context.drawImage(img, sx, sy, sw, sh, x, y, w, h) // Draw a circle to avoid aliasing on clip if (this._kind == 'circle' && strokeWidth) { context.beginPath() context.strokeStyle = ol_color_asString(this._stroke.getColor()) context.lineWidth = strokeWidth / 4 context.arc(this._radius + strokeWidth, this._radius + strokeWidth, this._radius, 0, 2 * Math.PI, false) context.stroke() } context.restore() } } /** * Draws a rounded rectangle using the current state of the canvas. * Draw a rectangle if the radius is null. * @param {Number} x The top left x coordinate * @param {Number} y The top left y coordinate * @param {Number} width The width of the rectangle * @param {Number} height The height of the rectangle * @param {Number} radius The corner radius. */ CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) { if (!r) { this.rect(x,y,w,h); } else { if (w < 2 * r) r = w / 2; if (h < 2 * r) r = h / 2; this.beginPath(); this.moveTo(x+r, y); this.arcTo(x+w, y, x+w, y+h, r); this.arcTo(x+w, y+h, x, y+h, r); this.arcTo(x, y+h, x, y, r); this.arcTo(x, y, x+w, y, r); this.closePath(); } return this; }; export default ol_style_Photo