import { HDRImage } from "./HDRImage";
import Color from "colorjs.io";
import type { Coords } from "colorjs.io";
import type { HDRPredefinedColorSpace, HDRImageData, HDRImagePixelCallback } from "./types/HDRCanvas.d.ts";
/**
* Represents an image using a `Uint16Array` for its pixel data,
* providing support for high dynamic range (HDR) color spaces.
* **Don't use this anymore, it's just here for migrating to Float16Array!**
*/
export class Uint16Image extends HDRImage {
/** The raw pixel data stored as a `Uint16Array`. */
data: Uint16Array;
/** The color space of the image. */
colorSpace: HDRPredefinedColorSpace;
/**
* Creates a new `Uint16Image` instance.
*
* @param {number} width - The width of the image in pixels.
* @param {number} height - The height of the image in pixels.
* @param {string} [colorspace] - The color space to use for the image. Defaults to `DEFAULT_COLORSPACE`.
*/
constructor(width: number, height: number, colorspace?: string) {
super(width, height);
if (colorspace === undefined || colorspace === null) {
this.colorSpace = HDRImage.DEFAULT_COLORSPACE;
} else {
this.colorSpace = colorspace as HDRPredefinedColorSpace;
}
this.data = new Uint16Array(height * width * 4);
console.warn("Uint16Image isn't suported anymore, your browser will certainly drop support soon, use Float16Image instead.");
}
/**
* Fills the entire image with a single color.
*
* @param {number[]} color - An array of four numbers representing the R, G, B, and A channels (0-65535).
* @returns {Uint16Image | undefined} The `Uint16Image` instance for method chaining, or `undefined` if the color array is invalid.
*/
fill(color: number[]): this | undefined {
// Was Uint16Image
if (color.length != 4) {
return;
}
for (let i = 0; i < this.data.length; i += 4) {
this.data[i] = color[0];
this.data[i + 1] = color[1];
this.data[i + 2] = color[2];
this.data[i + 3] = color[3];
}
return this;
}
// Only use this for alpha, since it doesn't to color space conversions
/**
* Scales an 8-bit value to a 16-bit value. This is typically used for the alpha channel.
*
* @param {number} val - The 8-bit value to scale (0-255).
* @returns {number} The corresponding 16-bit value.
*/
static scaleUint8ToUint16(val: number): number {
return (val << 8) | val;
}
/**
* Creates a standard `ImageData` object from the `Uint16Image` data.
*
* @returns {ImageData | null} An `ImageData` object, or `null` if the data is undefined.
*/
getImageData(): ImageData | null {
if (this.data === undefined || this.data === null) {
return null;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return new ImageData(this.data as any, this.width, this.height, {
colorSpace: this.colorSpace as PredefinedColorSpace
});
}
/**
* Converts a single 8-bit pixel (from sRGB color space) to a 16-bit pixel
* in the `rec2100-hlg` color space.
*
* @param {Uint8ClampedArray} pixel - An array of four 8-bit numbers (R, G, B, A).
* @returns {Uint16Array} The converted 16-bit pixel in the `rec2100-hlg` color space.
*/
static convertPixelToRec2100_hlg(pixel: Uint8ClampedArray): Uint16Array {
const colorJScolorSpace = <string>Uint16Image.COLORSPACES["rec2100-hlg" as HDRPredefinedColorSpace];
const srgbColor = new Color(
"srgb",
Array.from(pixel.slice(0, 3)).map((band: number) => {
return band / 255;
}) as Coords,
pixel[3] / 255
);
const rec2100hlgColor = srgbColor.to(colorJScolorSpace);
const hlg: Array<number> = rec2100hlgColor.coords.map((band: number) => {
return Math.round(band * Uint16Image.SDR_MULTIPLIER);
});
// Readd alpha
hlg.push(rec2100hlgColor.alpha * Uint16Image.SDR_MULTIPLIER);
return Uint16Array.from(hlg);
}
/**
* Converts a `Uint8ClampedArray` of sRGB pixel data to a `Uint16Array`
* of pixels in the `rec2100-hlg` color space.
*
* @param {Uint8ClampedArray} data - The array of 8-bit pixel data.
* @returns {Uint16Array} The converted 16-bit pixel data.
*/
static convertArrayToRec2100_hlg(data: Uint8ClampedArray): Uint16Array {
const uint16Data = new Uint16Array(data.length);
for (let i = 0; i < data.length; i += 4) {
const rgbPixel: Uint8ClampedArray = data.slice(i, i + 4);
const pixel = Uint16Image.convertPixelToRec2100_hlg(rgbPixel);
uint16Data.set(pixel, i);
}
return uint16Data;
}
/**
* Iterates through each pixel of the image and applies a callback function to its data.
*
* @param {HDRImagePixelCallback} fn - The callback function to apply to each pixel.
*/
pixelCallback(fn: HDRImagePixelCallback) {
for (let i = 0; i < this.data.length; i += 4) {
this.data.set(fn(this.data[i], this.data[i + 1], this.data[i + 2], this.data[i + 3]), i);
}
}
/**
* Creates a `Uint16Image` instance from an `HDRImageData` object.
*
* @param {HDRImageData} imageData - The image data to use.
* @returns {Uint16Image} The new `Uint16Image` instance.
* @throws {Error} If the color space of the `HDRImageData` is not supported.
*/
static fromImageData(imageData: HDRImageData): Uint16Image {
const i = new Uint16Image(imageData.width, imageData.height);
if (imageData.colorSpace == "srgb") {
i.data = Uint16Image.convertArrayToRec2100_hlg(<Uint8ClampedArray>imageData.data);
} else if (imageData.colorSpace == HDRImage.DEFAULT_COLORSPACE) {
i.data = <Uint16Array>imageData.data;
} else {
throw new Error(`ColorSpace ${imageData.colorSpace} isn't supported!`);
}
return i;
}
/**
* Loads an image from a URL and creates a `Uint16Image` instance from it.
*
* @param {URL} url - The URL of the image to load.
* @returns {Promise<Uint16Image | undefined>} A promise that resolves with a `Uint16Image` instance, or `undefined` if the image could not be loaded.
*/
static async fromURL(url: URL): Promise<Uint16Image | undefined> {
return Uint16Image.loadSDRImageData(url).then((data: HDRImageData | undefined) => {
if (data !== undefined) {
return Uint16Image.fromImageData(data);
}
});
}
/**
* Sets the image data of the current `Uint16Image` instance.
*
* @param {HDRImageData} imageData - The image data to set.
* @throws {Error} If the color space of the `HDRImageData` is not supported.
*/
setImageData(imageData: HDRImageData): void {
this.width = imageData.width;
this.height = imageData.height;
if (imageData.colorSpace == "srgb") {
this.data = Uint16Image.convertArrayToRec2100_hlg(<Uint8ClampedArray>imageData.data);
} else if (imageData.colorSpace == HDRImage.DEFAULT_COLORSPACE) {
this.data = <Uint16Array>imageData.data;
} else {
throw new Error(`ColorSpace ${imageData.colorSpace} isn't supported!`);
}
this.colorSpace = HDRImage.DEFAULT_COLORSPACE;
}
}
Source