Source: Raster.js

/*
* @fileoverview 
* Raster module provides functionality for loading and managing image data in various formats.
* Supports multiple color formats and handles both local and remote image loading with CORS support.
*/

/**
* @typedef {('vec3'|'vec4'|'float')} Raster#Format
* Defines the color format for image data storage in textures and renderbuffers.
* @property {'vec3'} vec3 - RGB format (3 components without alpha)
* @property {'vec4'} vec4 - RGBA format (4 components with alpha)
* @property {'float'} float - Single-channel format for coefficient data
*/

/**
* Raster class handles image loading and texture creation for OpenLIME.
* Provides functionality for:
* - Loading images from URLs or blobs
* - Converting images to WebGL textures
* - Handling different color formats
* - Supporting partial content requests
* - Managing CORS requests
* - Creating mipmaps for large textures
*/
class Raster {
	/**
	 * Creates a new Raster instance.
	 * @param {Object} [options] - Configuration options
	 * @param {Raster#Format} [options.format='vec3'] - Color format for image data:
	 *   - 'vec3' for RGB images
	 *   - 'vec4' for RGBA images
	 *   - 'float' for coefficient data
	 */
	constructor(options) {

		Object.assign(this, {
			format: 'vec3',
		});

		Object.assign(this, options);
	}

	/**
	 * Loads an image tile and converts it to a WebGL texture.
	 * Supports both full and partial content requests.
	 * @async
	 * @param {Object} tile - The tile to load
	 * @param {string} tile.url - URL of the image
	 * @param {number} [tile.start] - Start byte for partial requests
	 * @param {number} [tile.end] - End byte for partial requests
	 * @param {WebGLRenderingContext} gl - The WebGL rendering context
	 * @returns {Promise<Array>} Promise resolving to [texture, size]:
	 *   - texture: WebGLTexture object
	 *   - size: Size of the image in bytes (width * height * components)
	 * @throws {Error} If server doesn't support partial content requests when required
	 */
	async loadImage(tile, gl) {
		let img;
		let cors = (new URL(tile.url, window.location.href)).origin !== window.location.origin;
		if (tile.end || typeof createImageBitmap == 'undefined') {
			let options = {};
			options.headers = { range: `bytes=${tile.start}-${tile.end}`, 'Accept-Encoding': 'indentity', mode: cors ? 'cors' : 'same-origin' };
			let response = await fetch(tile.url, options);
			if (!response.ok) {
				callback("Failed loading " + tile.url + ": " + response.statusText);
				return;
			}

			if (response.status != 206)
				throw "The server doesn't support partial content requests (206).";

			let blob = await response.blob();
			img = await this.blobToImage(blob, gl);
		} else {
			img = document.createElement('img');
			if (cors) img.crossOrigin = "";
			img.onerror = function (e) { console.log("Texture loading error!"); };
			img.src = tile.url;
			await new Promise((resolve, reject) => {
				img.onload = () => { resolve(); }
			});
		}
		let tex = this.loadTexture(gl, img);
		//TODO 3 is not accurate for type of image, when changing from rgb to grayscale, fix this value.
		let size = img.width * img.height * 3;
		return [tex, size];
	}

	/**
	 * Converts a Blob to an Image or ImageBitmap.
	 * Handles browser-specific differences in image orientation.
	 * @private
	 * @async
	 * @param {Blob} blob - Image data as Blob
	 * @param {WebGLRenderingContext} gl - The WebGL rendering context
	 * @returns {Promise<HTMLImageElement|ImageBitmap>} Promise resolving to the image
	 */
	async blobToImage(blob, gl) {
		let tex, img;
		if (typeof createImageBitmap != 'undefined') {
			var isFirefox = typeof InstallTrigger !== 'undefined';
			//firefox does not support options for this call, BUT the image is automatically flipped.
			if (isFirefox)
				img = await createImageBitmap(blob);
			else
				img = await createImageBitmap(blob, { imageOrientation1: 'flipY' });

		} else { //fallback for IOS
			let urlCreator = window.URL || window.webkitURL;
			img = document.createElement('img');
			img.onerror = function (e) { console.log("Texture loading error!"); };
			img.src = urlCreator.createObjectURL(blob);

			await new Promise((resolve, reject) => { img.onload = () => resolve() });
			urlCreator.revokeObjectURL(img.src);

		}
		return img;
	}

	/**
	 * Creates a WebGL texture from an image.
	 * Handles different color formats and automatically creates mipmaps for large textures.
	 * @private
	 * @param {WebGLRenderingContext} gl - The WebGL rendering context
	 * @param {HTMLImageElement|ImageBitmap} img - The source image
	 * @returns {WebGLTexture} The created texture
	 * 
	 * @property {number} width - Width of the loaded image (set after loading)
	 * @property {number} height - Height of the loaded image (set after loading)
	 */
	loadTexture(gl, img) {
		this.width = img.width;  //this will be useful for layout image.
		this.height = img.height;

		var tex = gl.createTexture();
		gl.bindTexture(gl.TEXTURE_2D, tex);

		let glFormat = gl.RGBA;
		switch (this.format) {
			case 'vec3':
				glFormat = gl.RGB;
				break;
			case 'vec4':
				glFormat = gl.RGBA;
				break;
			case 'float':
				glFormat = gl.LUMINANCE;
				break;
			default:
				break;
		}

		gl.texImage2D(gl.TEXTURE_2D, 0, glFormat, glFormat, gl.UNSIGNED_BYTE, img);
		gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);

		//build mipmap for large images.
		if (this.width > 1024 || this.height > 1024) {
			gl.generateMipmap(gl.TEXTURE_2D);
			gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
		} else {
			gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
		}

		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
		return tex;
	}
}
/**
 * Example usage of Raster:
 * ```javascript
 * // Create a Raster for RGBA images
 * const raster = new Raster({ format: 'vec4' });
 * 
 * // Load an image tile
 * const tile = {
 *     url: 'https://example.com/image.jpg',
 *     start: 0,
 *     end: 1024  // Optional: for partial loading
 * };
 * 
 * // Get WebGL context and load the image
 * const gl = canvas.getContext('webgl');
 * const [texture, size] = await raster.loadImage(tile, gl);
 * 
 * // Texture is now ready for use in WebGL
 * gl.bindTexture(gl.TEXTURE_2D, texture);
 * ```
 */
export { Raster }