iwmlib/lib/pixi/deepzoom/tiles.js

336 lines
10 KiB
JavaScript

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
}
}
}