import { Colors } from '../../utils.js' import { WorkerTileLoader, PIXITileLoader } from './loader.js' /** * A layer of tiles that represents a zoom level of a DeepZoomImage as a grid * of sprites. * @constructor * @param {number} level - the zoom level of the tile layer * @param {DeepZoomImage} view - the zoomable image the layer belongs to * @param {number} scale - the scale of the tile layer * @param {number} cols - the number of columns of the layer * @param {number} rows - the number of rows of the layer * @param {number} width - the width of the layer in pixel * @param {number} height - the height of the layer in pixel * @param {number} tileSize - the size of a single tile in pixel * @param {number} overlap - the overlap of the tiles in pixel * @param {number} fadeInTime - time needed to fade in tiles if TweenLite is set **/ export class Tiles extends PIXI.Container { constructor(level, view, scale, cols, rows, width, height, tileSize, overlap, fadeInTime = 0.33) { super() this.debug = false this.showGrid = false this.view = view this.level = level this.cols = cols this.rows = rows this.pixelWidth = width this.pixelHeight = height this.tileSize = tileSize this.overlap = overlap this.needed = new Map() // url as key, [col, row] as value this.requested = new Set() this.available = new Map() this.scale.set(scale, scale) this.tileScale = scale this.fadeInTime = fadeInTime this.keep = false if (this.view.useWorker && view.info.compression && view.info.compression.length > 0) { this.loader = new WorkerTileLoader(this, this.view.useWorker) } else { this.loader = new PIXITileLoader(this, view.info.compression) } this.interactive = false this._highlight = null this._info = null this._centerPoint = null this._boundsRect = null this.infoColor = Colors.random() this.pprint() this.destroyed = false } /** Tests whether all tiles are loaded. **/ isComplete() { return this.cols * this.rows === this.children.length } /** Returns the highligh graphics layer for debugging purposes. **/ get highlight() { if (this._highlight == null) { let graphics = new PIXI.Graphics() graphics.beginFill(0xffff00, 0.1) graphics.lineStyle(2, 0xffff00) graphics.drawRect(1, 1, this.tileSize - 2, this.tileSize - 2) graphics.endFill() graphics.interactive = false this._highlight = graphics } return this._highlight } /** Returns the highligh graphics layer for debugging purposes. **/ get info() { if (this._info == null) { let graphics = new PIXI.Graphics() graphics.lineStyle(4, 0xff0000) graphics.interactive = false this._info = graphics this.addChild(this._info) } return this._info } /** Helper method pretty printing debug information. **/ pprint() { if (this.debug) console.log( 'Tiles level: ' + this.level + ' scale: ' + this.scale.x + ' cols: ' + this.cols + ' rows: ' + this.rows + ' w: ' + this.pixelWidth + ' h: ' + this.pixelHeight + ' tsize:' + this.tileSize ) } /** Computes the tile position and obeys the overlap. * @param {number} col - The column of the tile * @param {number} row - The row of the tile * @returns {PIXI.Point} obj **/ tilePosition(col, row) { let x = col * this.tileSize let y = row * this.tileSize let overlap = this.overlap if (col != 0) { x -= overlap } if (row != 0) { y -= overlap } return new PIXI.Point(x, y) } /** Computes the tile size without overlap * @param {number} col - The column of the tile * @param {number} row - The row of the tile * @returns {PIXI.Point} obj **/ tileDimensions(col, row) { let w = this.tileSize let h = this.tileSize let pos = this.tilePosition(col, row) if (col == this.cols - 1) { w = this.pixelWidth - pos.x } if (row == this.rows - 1) { h = this.pixelHeight - pos.y } return new PIXI.Point(w, h) } /** Method to support debugging. Highlights the specified tile at col, row **/ highlightTile(col, row) { if (col > -1 && row > -1 && col < this.cols && row < this.rows) { let graphics = this.highlight let dim = this.tileDimensions(col, row) graphics.position = this.tilePosition(col, row) graphics.clear() graphics.beginFill(0xff00ff, 0.1) graphics.lineStyle(2, 0xffff00) graphics.drawRect(1, 1, dim.x - 2, dim.y - 2) graphics.endFill() this.addChild(this.highlight) } else { this.removeChild(this.highlight) } } /** Loads the tiles for the given urls and adds the tiles as sprites. * @param {array} urlpos - An array of URL, pos pairs * @param {boolean} onlyone - Loads only on tile at a time if true **/ loadTiles(urlpos, onlyone, refCol, refRow) { if (this.showGrid) { this.highlightTile(refCol, refRow) } urlpos.forEach(d => { let [url, col, row] = d if (this.loader.schedule(url, col, row)) { if (onlyone) { return this.loader.loadOneTile() } } }) this.loader.loadAll() } /** Private method: add a red border to a tile for debugging purposes. **/ _addTileBorder(tile, col, row) { let dim = this.tileDimensions(col, row) let graphics = new PIXI.Graphics() graphics.beginFill(0, 0) graphics.lineStyle(2, 0xff0000) graphics.drawRect(1, 1, dim.x - 2, dim.y - 2) graphics.endFill() tile.addChild(graphics) } /** Adds a tile. **/ addTile(tile, col, row, url) { if (this.available.has(url)) { console.warn('Trying to add available tile') return } this.addChildAt(tile, 0) this.available.set(url, tile) if (this.destroyed) { console.warn('Adding to destroyed tiles layer') } // this._addTileBorder(tile, col, row) } /** Called by the loader after each successfull loading of a single tile. * Adds the sprite to the tile layer. * @param {Object} tile - the loaded tile sprite * @param {Number} col - the col position * @param {Number} row - the rowposition **/ tileAvailable(tile, col, row, url) { let pos = this.tilePosition(col, row) if (this.showGrid) { this._addTileBorder(tile, col, row) } tile.position = pos tile.interactive = false if (TweenLite) { tile.alpha = 0 TweenLite.to(tile, this.fadeInTime, { alpha: this.alpha }) } this.addTile(tile, col, row, url) } /** Destroys the tiles layer and destroys the loader. Async load calls are * cancelled. **/ destroy() { this.destroyed = true this.loader.destroy() super.destroy({ children: true }) // Calls destroyChildren this.available.clear() this.requested.clear() this.needed.clear() } destroyTile(url, tile) { this.loader.unschedule(url) this.removeChild(tile) tile.destroy() this.available.delete(url) } destroyTileByUrl(url) { if (this.available.has(url)) { let tile = this.available.get(url) this.destroyTile(url, tile) } } /* Destroys the tiles which are not with the bounds of the app to free * memory. **/ destroyTiles(quadTrees) { let count = 0 for (let [url, tile] of this.available.entries()) { if (!quadTrees.has(url)) { this.destroyTile(url, tile) count += 1 } } if (count && this.debug) console.log('destroyTiles', this.level, count) } destroyUnneededTiles() { let count = 0 for (let [url, tile] of this.available.entries()) { if (!this.needed.has(url)) { this.destroyTile(url, tile) count += 1 } } if (count && this.debug) console.log('destroyUnneededTiles', this.level, count) } highlightInfos() { let graphics = this.info let color = this.infoColor graphics.clear() graphics.lineStyle(2, color) for (let [col, row] of this.needed.values()) { let dim = this.tileDimensions(col, row) let pos = this.tilePosition(col, row) graphics.beginFill(color, 0.2) graphics.drawRect(pos.x + 1, pos.y + 1, dim.x - 2, dim.y - 2) graphics.endFill() } let r = this._boundsRect if (r != null) { graphics.lineStyle(20, color) graphics.drawRect(r.x, r.y, r.width, r.height) graphics.moveTo(r.x, r.y) graphics.lineTo(r.x + r.width, r.y + r.height) graphics.moveTo(r.x, r.y + r.height) graphics.lineTo(r.x + r.width, r.y) } let p = this._centerPoint if (p != null) { graphics.drawCircle(p.x, p.y, 20) } } tintTiles(quadTrees) { for (let [url, tile] of this.available.entries()) { if (!quadTrees.has(url)) tile.tint = 0xff0000 } } untintTiles() { for (let [url, tile] of this.available.entries()) { tile.tint = 0xffffff } } }