import { BoundingBox } from "./BoundingBox";
import { addSignals } from "./Signals";
import { Tile } from "./Tile";
import { CoordinateSystem } from "./CoordinateSystem";
/**
* @typedef {Object} TileObj
* @property {number} level - Zoom level in the image pyramid
* @property {number} x - Horizontal position in tile grid
* @property {number} y - Vertical position in tile grid
* @property {number} index - Unique tile identifier
* @property {number} [start] - Starting byte position in dataset (for tar formats)
* @property {number} [end] - Ending byte position in dataset (for tar formats)
* @property {number} missing - Number of pending channel data requests
* @property {WebGLTexture[]} tex - Array of textures (one per channel)
* @property {number} time - Tile creation timestamp for cache management
* @property {number} priority - Loading priority for cache management
* @property {number} size - Total tile size in bytes
*/
/**
* @typedef {'image'|'deepzoom'|'deepzoom1px'|'google'|'zoomify'|'iiif'|'tarzoom'|'itarzoom'} LayoutType
* @description Supported image format types:
* - image: Single-resolution web images (jpg, png, etc.)
* - deepzoom: Microsoft Deep Zoom with root tile > 1px
* - deepzoom1px: Microsoft Deep Zoom with 1px root tile
* - google: Google Maps tiling scheme
* - zoomify: Zoomify format
* - iiif: International Image Interoperability Framework
* - tarzoom: OpenLIME tar-based tiling
* - itarzoom: OpenLIME indexed tar-based tiling
*/
/**
* @typedef {Object} LayoutOptions
* @property {number} [width] - Image width (required for google layout)
* @property {number} [height] - Image height (required for google layout)
* @property {string} [suffix='jpg'] - Tile file extension
* @property {string} [subdomains='abc'] - Available subdomains for URL templates
*/
/**
* @event Layout#ready
* @description FIXME Fired when a layout is ready to be drawn (the single-resolution image is downloaded or the multi-resolution structure has been initialized)
*/
/**
* @event Layout#updateSize
* @description Fired when layout dimensions change
*/
/**
* Layout manages image formats and tiling schemes in OpenLIME.
*
* This class is responsible for:
* - Managing different image formats
* - Handling tiling schemes
* - Coordinating tile loading
* - Converting between coordinate systems
* - Managing tile priorities
*
* Format Support:
* 1. Single-resolution images:
* - Direct URL to image file
* - Supports all standard web formats (jpg, png, etc)
*
* 2. Tiled formats:
* - DeepZoom (Microsoft): Uses .dzi config file
* - Google Maps: Direct directory structure
* - Zoomify: Uses ImageProperties.xml
* - IIIF: Standard server interface
* - TarZoom: OpenLIME's optimized format
*
* @fires Layout#ready
* @fires Layout#updateSize
*
* @example
* ```javascript
* // Single image
* const imageLayout = new Layout('image.jpg', 'image');
*
* // Deep Zoom
* const dzLayout = new Layout('tiles.dzi', 'deepzoom');
*
* // Google Maps format
* const googleLayout = new Layout('tiles/', 'google', {
* width: 2000,
* height: 1500
* });
* ```
*/
class Layout {
/**
* Creates a new Layout instance
* @param {string} url - URL to image or configuration file
* @param {LayoutType} type - Type of image layout
* @param {LayoutOptions} [options] - Additional configuration
* @throws {Error} If layout type is unknown or module not loaded
*/
constructor(url, type, options) {
if (type == 'image') {
this.setDefaults(type);
this.init(url, type, options);
} else if (type in this.types)
return this.types[type](url, type, options);
else if (type == null)
return;
else
throw "Layout type: " + type + " unknown, or module not loaded";
}
/**
* Gets tile dimensions
* @returns {number[]} [width, height] of tiles
*/
getTileSize() {
return [this.width, this.height];
}
/**
* Sets default layout properties
* @param {LayoutType} type - Layout type
* @private
*/
setDefaults(type) {
Object.assign(this, {
type: type,
width: 0,
height: 0,
suffix: 'jpg',
urls: [],
status: null,
subdomains: 'abc'
});
}
/**
* Initializes layout configuration
* @param {string} url - Resource URL
* @param {LayoutType} type - Layout type
* @param {LayoutOptions} options - Configuration options
* @private
*/
init(url, type, options) {
if (options)
Object.assign(this, options);
if (typeof (url) == 'string')
this.setUrls([url]);
}
/**
* Sets URLs for layout resources
* @param {string[]} urls - Array of resource URLs
* @fires Layout#ready
* @private
*/
setUrls(urls) {
this.urls = urls;
this.getTileURL = (rasterid, tile) => { return this.urls[rasterid]; }
this.status = 'ready';
this.emit('ready');
}
/**
* Constructs URL for specific image plane
* @param {string} url - Base URL
* @param {string} plane - Plane identifier
* @returns {string} Complete URL
*/
imageUrl(url, plane) {
let path = url.substring(0, url.lastIndexOf('/') + 1);
return path + plane + '.jpg';
}
/**
* Gets URL for specific tile
* @param {number} id - Channel identifier
* @param {TileObj} tile - Tile object
* @returns {string} Tile URL
* @abstract
*/
getTileURL(id, tile) {
throw Error("Layout not defined or ready.");
}
/**
* Gets layout bounds
* @returns {BoundingBox} Layout boundaries
*/
boundingBox() {
//if(!this.width) throw "Layout not initialized still";
return new BoundingBox({ xLow: -this.width / 2, yLow: -this.height / 2, xHigh: this.width / 2, yHigh: this.height / 2 });
}
/**
* Calculates tile coordinates
* @param {TileObj} tile - Tile to calculate coordinates for
* @returns {{coords: Float32Array, tcoords: Float32Array}} Image and texture coordinates
*/
tileCoords(tile) {
let w = this.width;
let h = this.height;
//careful: here y is inverted due to textures not being flipped on load (Firefox fault!).
var tcoords = new Float32Array([0, 1, 0, 0, 1, 0, 1, 1]);
return {
coords: new Float32Array([-w / 2, -h / 2, 0, -w / 2, h / 2, 0, w / 2, h / 2, 0, w / 2, -h / 2, 0]),
tcoords: tcoords
};
}
/**
* Creates new tile instance
* @param {number} index - Tile identifier
* @returns {TileObj} New tile object
* @private
*/
newTile(index) {
let tile = new Tile();
tile.index = index;
return tile;
}
/**
* Determines required tiles for rendering
* @param {Object} viewport - Current viewport
* @param {Object} transform - Current transform
* @param {Object} layerTransform - Layer transform
* @param {number} border - Border size
* @param {number} bias - Mipmap bias
* @param {Map<number, TileObj>} tiles - Existing tiles
* @param {number} [maxtiles=8] - Maximum tiles to return
* @returns {TileObj[]} Array of needed tiles
*/
needed(viewport, transform, layerTransform, border, bias, tiles, maxtiles = 8) {
//FIXME should check if image is withing the viewport (+ border)
let tile = tiles.get(0) || this.newTile(0); //{ index, x, y, missing, tex: [], level };
tile.time = performance.now();
tile.priority = 10;
if (tile.missing === null) // || tile.missing != 0 && !this.requested[index])
return [tile];
return [];
}
/**
* Gets tiles available for rendering
* @param {Object} viewport - Current viewport
* @param {Object} transform - Current transform
* @param {Object} layerTransform - Layer transform
* @param {number} border - Border size
* @param {number} bias - Mipmap bias
* @param {Map<number, TileObj>} tiles - Existing tiles
* @returns {Object.<number, TileObj>} Map of available tiles
*/
available(viewport, transform, layerTransform, border, bias, tiles) {
//FIXME should check if image is withing the viewport (+ border)
let torender = {};
if (tiles.has(0) && tiles.get(0).missing == 0)
torender[0] = tiles.get(0); //{ index: index, level: level, x: x >> d, y: y >> d, complete: true };
return torender;
}
/**
* Calculates viewport bounding box
* @param {Object} viewport - Viewport parameters
* @param {Object} transform - Current transform
* @param {Object} layerT - Layer transform
* @returns {BoundingBox} Viewport bounds in image space
*/
getViewportBox(viewport, transform, layerT) {
const boxViewport = new BoundingBox({ xLow: viewport.x, yLow: viewport.y, xHigh: viewport.x + viewport.dx, yHigh: viewport.y + viewport.dy });
return CoordinateSystem.fromViewportBoxToImageBox(boxViewport, transform, viewport, layerT, { w: this.width, h: this.height });
}
}
/**
* Collection of layout type factories
* @type {Object}
*/
Layout.prototype.types = {}
addSignals(Layout, 'ready', 'updateSize');
export { Layout }