hdr-canvas 0.0.13 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,238 @@
1
+ import { HDRImage } from "./HDRImage";
2
+
3
+ import Color from "colorjs.io";
4
+ import type { Coords } from "colorjs.io";
5
+
6
+ import type { HDRPredefinedColorSpace, HDRImageData, HDRImagePixelCallback } from "./types/HDRCanvas.d.ts";
7
+
8
+ /**
9
+ * Represents an image using a `Float16Array` for its pixel data,
10
+ * providing support for high dynamic range (HDR) color spaces.
11
+ */
12
+ export class Float16Image extends HDRImage {
13
+ /** The raw pixel data stored as a `Float16Array`. */
14
+ data: Float16Array;
15
+
16
+ /** The default pixel format for new images, set to "rgba-float16". */
17
+ static DEFAULT_PIXELFORMAT: ImageDataPixelFormat = "rgba-float16";
18
+
19
+ /** The color space of the image. */
20
+ colorSpace: HDRPredefinedColorSpace;
21
+ /** The pixel format of the image - usualy 'rgba-float16'. */
22
+ pixelFormat: ImageDataPixelFormat;
23
+
24
+ /**
25
+ * Creates a new `Float16Image` instance.
26
+ *
27
+ * @param {number} width - The width of the image in pixels.
28
+ * @param {number} height - The height of the image in pixels.
29
+ * @param {string} [colorspace] - The color space to use for the image. Defaults to `HDRImage.DEFAULT_COLORSPACE`.
30
+ * @param {string} [pixelFormat] - The pixel format to use for the image. Defaults to `DEFAULT_PIXELFORMAT`.
31
+ */
32
+ constructor(width: number, height: number, colorspace?: string, pixelFormat?: string) {
33
+ super(width, height);
34
+ if (colorspace === undefined || colorspace === null) {
35
+ this.colorSpace = Float16Image.DEFAULT_COLORSPACE;
36
+ } else {
37
+ this.colorSpace = colorspace as HDRPredefinedColorSpace;
38
+ }
39
+
40
+ if (pixelFormat === undefined || pixelFormat === null || (pixelFormat !== "rgba-unorm8" && pixelFormat !== "rgba-float16")) {
41
+ pixelFormat = Float16Image.DEFAULT_PIXELFORMAT;
42
+ }
43
+ this.pixelFormat = pixelFormat as ImageDataPixelFormat;
44
+ this.data = new Float16Array(height * width * 4);
45
+ }
46
+
47
+ /**
48
+ * Fills the entire image with a single color.
49
+ *
50
+ * @param {number[]} color - An array of four numbers representing the R, G, B, and A channels (0-65535).
51
+ * @returns {Float16Image | undefined} The `Float16Image` instance for method chaining, or `undefined` if the color array is invalid.
52
+ */
53
+ fill(color: number[]): this | undefined {
54
+ if (color.length != 4) {
55
+ return;
56
+ }
57
+ for (let i = 0; i < this.data.length; i += 4) {
58
+ this.data[i] = color[0];
59
+ this.data[i + 1] = color[1];
60
+ this.data[i + 2] = color[2];
61
+ this.data[i + 3] = color[3];
62
+ }
63
+ return this;
64
+ }
65
+
66
+ // Only use this for alpha, since it doesn't to color space conversions
67
+ /**
68
+ * Scales an 8-bit value to a 16-bit value. This is typically used for the alpha channel.
69
+ *
70
+ * @param {number} val - The 8-bit value to scale (0-255).
71
+ * @returns {number} The corresponding 16-bit value.
72
+ */
73
+ static scaleUint8ToFloat16(val: number): number {
74
+ return (val << 8) | val;
75
+ }
76
+
77
+ // See https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/html/canvas/image_data.idl
78
+ /**
79
+ * Creates a standard `ImageData` object from the `Float16Image` data.
80
+ *
81
+ * @returns {ImageData | null} An `ImageData` object, or `null` if the data is undefined.
82
+ */
83
+ getImageData(): ImageData | null {
84
+ if (this.data === undefined || this.data === null) {
85
+ return null;
86
+ }
87
+
88
+ return new ImageData(this.data as unknown as ImageDataArray, this.width, this.height, {
89
+ colorSpace: this.colorSpace as PredefinedColorSpace,
90
+ pixelFormat: this.pixelFormat as ImageDataPixelFormat
91
+ } as ImageDataSettings) as ImageData;
92
+ }
93
+
94
+ /**
95
+ * Converts a single 8-bit pixel (from sRGB color space) to a 16-bit pixel
96
+ * in the `rec2100-hlg` color space.
97
+ *
98
+ * @param {Uint8ClampedArray} pixel - An array of four 8-bit numbers (R, G, B, A).
99
+ * @returns {Float16Array} The converted 16-bit pixel in the `rec2100-hlg` color space.
100
+ */
101
+ static convertPixelToRec2100_hlg(pixel: Uint8ClampedArray): Float16Array {
102
+ const colorJScolorSpace = <string>Float16Image.COLORSPACES["rec2100-hlg" as HDRPredefinedColorSpace];
103
+
104
+ const srgbColor = new Color(
105
+ "srgb",
106
+ Array.from(pixel.slice(0, 3)).map((band: number) => {
107
+ return band / 255;
108
+ }) as Coords,
109
+ pixel[3] / 255
110
+ );
111
+ const rec2100hlgColor = srgbColor.to(colorJScolorSpace);
112
+ const hlg: Array<number> = rec2100hlgColor.coords.map((band: number) => {
113
+ return Math.round(band * Float16Image.SDR_MULTIPLIER);
114
+ });
115
+ // Readd alpha
116
+ hlg.push(rec2100hlgColor.alpha * Float16Image.SDR_MULTIPLIER);
117
+
118
+ return Float16Array.from(hlg);
119
+ }
120
+
121
+ /**
122
+ * Converts a `Uint8ClampedArray` of sRGB pixel data to a `Float16Array`
123
+ * of pixels in the `rec2100-hlg` color space.
124
+ *
125
+ * @param {Uint8ClampedArray} data - The array of 8-bit pixel data.
126
+ * @returns {Float16Array} The converted 16-bit pixel data.
127
+ */
128
+ static convertArrayToRec2100_hlg(data: Uint8ClampedArray): Float16Array {
129
+ const uint16Data = new Float16Array(data.length);
130
+ for (let i = 0; i < data.length; i += 4) {
131
+ const rgbPixel: Uint8ClampedArray = data.slice(i, i + 4);
132
+ const pixel = Float16Image.convertPixelToRec2100_hlg(rgbPixel);
133
+ uint16Data.set(pixel, i);
134
+ }
135
+ return uint16Data;
136
+ }
137
+
138
+ /**
139
+ * Iterates through each pixel of the image and applies a callback function to its data.
140
+ *
141
+ * @param {HDRPixelCallback} fn - The callback function to apply to each pixel.
142
+ */
143
+ pixelCallback(fn: HDRImagePixelCallback) {
144
+ for (let i = 0; i < this.data.length; i += 4) {
145
+ this.data.set(fn(this.data[i], this.data[i + 1], this.data[i + 2], this.data[i + 3]), i);
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Creates a `Float16Image` instance from an `HDRImageData` object.
151
+ *
152
+ * @param {HDRImageData} imageData - The image data to use.
153
+ * @returns {Float16Image} The new `Float16Image` instance.
154
+ * @throws {Error} If the color space of the `HDRImageData` is not supported.
155
+ */
156
+ static fromImageData(imageData: HDRImageData): Float16Image {
157
+ const i = new Float16Image(imageData.width, imageData.height);
158
+ if (imageData.colorSpace == "srgb") {
159
+ i.data = Float16Image.convertArrayToRec2100_hlg(<Uint8ClampedArray>imageData.data);
160
+ } else if (imageData.colorSpace == HDRImage.DEFAULT_COLORSPACE) {
161
+ i.data = <Float16Array>imageData.data;
162
+ } else {
163
+ throw new Error(`ColorSpace ${imageData.colorSpace} isn't supported!`);
164
+ }
165
+ return i;
166
+ }
167
+
168
+ /**
169
+ * Creates a `Float16Image` instance from an `Uint8ClampedArray` object.
170
+ *
171
+ * @param {number} width - The width of the image.
172
+ * @param {number} height - The height of the image.
173
+ * @param {HDRImageData} imageData - The image data to use.
174
+ * @returns {Float16Image} The new `Float16Image` instance.
175
+ * @throws {Error} If the color space of the `HDRImageData` is not supported.
176
+ */
177
+ static fromImageDataArray(
178
+ width: number,
179
+ height: number,
180
+ imageDataArray: Uint8ClampedArray | Uint8ClampedArray<ArrayBufferLike>
181
+ ): Float16Image {
182
+ //const colorSpace == "srgb";
183
+ const i = new Float16Image(width, height);
184
+ //if (imageData.colorSpace == "srgb") {
185
+ i.data = Float16Image.convertArrayToRec2100_hlg(<Uint8ClampedArray>imageDataArray);
186
+ // } else if (imageData.colorSpace == HDRImage.DEFAULT_COLORSPACE) {
187
+ // i.data = <Float16Array>imageData.data;
188
+ // } else {
189
+ // throw new Error(`ColorSpace ${imageData.colorSpace} isn't supported!`);
190
+ // }
191
+ return i;
192
+ }
193
+
194
+ /**
195
+ * Loads an image from a URL and creates a `Float16Image` instance from it.
196
+ *
197
+ * @param {URL} url - The URL of the image to load.
198
+ * @returns {Promise<Float16Image | undefined>} A promise that resolves with a `Float16Image` instance, or `undefined` if the image could not be loaded.
199
+ */
200
+ static async fromURL(url: URL): Promise<Float16Image | undefined> {
201
+ return Float16Image.loadSDRImageData(url).then((data: HDRImageData | undefined) => {
202
+ if (data !== undefined) {
203
+ return Float16Image.fromImageData(data);
204
+ }
205
+ });
206
+ }
207
+
208
+ /**
209
+ * Sets the image data of the current `Float16Image` instance.
210
+ *
211
+ * @param {HDRImageData} imageData - The image data to set.
212
+ * @throws {Error} If the color space of the `HDRImageData` is not supported.
213
+ */
214
+ setImageData(imageData: HDRImageData): void {
215
+ this.width = imageData.width;
216
+ this.height = imageData.height;
217
+ if (imageData.colorSpace == "srgb") {
218
+ this.data = Float16Image.convertArrayToRec2100_hlg(<Uint8ClampedArray>imageData.data);
219
+ } else if (imageData.colorSpace == HDRImage.DEFAULT_COLORSPACE) {
220
+ this.data = <Float16Array>imageData.data;
221
+ } else {
222
+ throw new Error(`ColorSpace ${imageData.colorSpace} isn't supported!`);
223
+ }
224
+ this.colorSpace = HDRImage.DEFAULT_COLORSPACE;
225
+ }
226
+
227
+ /**
228
+ * Creates a deep clone of the current `Float16Image` instance.
229
+ *
230
+ * @returns {Float16Image} A new `Float16Image` instance with a copy of the data.
231
+ * @private
232
+ */
233
+ clone(): this {
234
+ const c = new Float16Image(this.width, this.height, this.colorSpace, this.pixelFormat);
235
+ c.data = this.data.slice();
236
+ return c as this;
237
+ }
238
+ }
@@ -0,0 +1,130 @@
1
+ //import type { ImageData } from "./types/ImageData.d.ts";
2
+ import type { HDRPredefinedColorSpace, HDRImageData, HDRImageDataArray, HDRImagePixelCallback } from "./types/HDRCanvas.d.ts";
3
+
4
+ import type { ColorTypes } from "colorjs.io";
5
+
6
+ export abstract class HDRImage {
7
+ /** The default color space for new images, set to "rec2100-hlg". */
8
+ static DEFAULT_COLORSPACE: HDRPredefinedColorSpace = "rec2100-hlg";
9
+
10
+ /** A multiplier used for scaling 8-bit SDR values to 16-bit. */
11
+ static SDR_MULTIPLIER = 2 ** 16 - 1; //(2**16 - 1)
12
+
13
+ /** A mapping of predefined HDR color space names to their corresponding `colorjs.io` string representations. */
14
+ static COLORSPACES: Record<HDRPredefinedColorSpace, ColorTypes> = {
15
+ "rec2100-hlg": "rec2100hlg",
16
+ "display-p3": "p3",
17
+ srgb: "sRGB",
18
+ "rec2100-pq": "rec2100pq"
19
+ };
20
+
21
+ /** The raw pixel data stored as a `Float16Array`. */
22
+ data: HDRImageDataArray;
23
+ /** The height of the image in pixels. */
24
+ height: number;
25
+ /** The width of the image in pixels. */
26
+ width: number;
27
+
28
+ constructor(width: number, height: number) {
29
+ this.height = height;
30
+ this.width = width;
31
+ }
32
+
33
+ /**
34
+ * Creates a `Float16Image` instance from an `HDRImageData` object.
35
+ *
36
+ * @param {HDRImageData} imageData - The image data to use.
37
+ * @returns {Float16Image} The new `Float16Image` instance.
38
+ * @throws {Error} If the color space of the `HDRImageData` is not supported.
39
+ */
40
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
41
+ static fromImageData(imageData: HDRImageData | ImageData): HDRImage {
42
+ throw new Error("Method not implemented!");
43
+ }
44
+
45
+ /**
46
+ * Creates a `Float16Image` instance from an `Uint8ClampedArray` object.
47
+ *
48
+ * @param {number} width - The width of the image.
49
+ * @param {number} height - The height of the image.
50
+ * @param {HDRImageData} imageData - The image data to use.
51
+ * @returns {Float16Image} The new `Float16Image` instance.
52
+ * @throws {Error} If the color space of the `HDRImageData` is not supported.
53
+ */
54
+ /* eslint-disable @typescript-eslint/no-unused-vars */
55
+ static fromImageDataArray(
56
+ width: number,
57
+ height: number,
58
+ imageDataArray: Uint8ClampedArray | Uint8ClampedArray<ArrayBufferLike>
59
+ ): HDRImage {
60
+ throw new Error("Method not implemented!");
61
+ }
62
+
63
+ /**
64
+ * Loads an SDR image from a URL and returns its image data.
65
+ *
66
+ * @param {URL} url - The URL of the image to load.
67
+ * @returns {Promise<ImageData | undefined>} A promise that resolves with the `HDRImageData` or `undefined` if loading fails.
68
+ */
69
+ static async loadSDRImageData(url: URL): Promise<ImageData | undefined> {
70
+ return fetch(url)
71
+ .then((response) => response.blob())
72
+ .then((blob) => {
73
+ return createImageBitmap(blob);
74
+ })
75
+ .then((bitmap) => {
76
+ const { width, height } = bitmap;
77
+ const offscreen = new OffscreenCanvas(width, height);
78
+ const ctx = offscreen.getContext("2d");
79
+ ctx!.drawImage(bitmap, 0, 0);
80
+ return ctx!.getImageData(0, 0, width, height);
81
+ });
82
+ }
83
+
84
+ /**
85
+ * Retrieves the pixel data at a specified coordinate.
86
+ *
87
+ * @param {number} w - The x-coordinate (width).
88
+ * @param {number} h - The y-coordinate (height).
89
+ * @returns {Float16Array} A new `Float16Array` containing the R, G, B, and A values of the pixel.
90
+ */
91
+ getPixel(w: number, h: number): HDRImageDataArray {
92
+ const pos = (h * this.width + w) * 4;
93
+
94
+ return this.data.slice(pos, pos + 4);
95
+ }
96
+
97
+ /**
98
+ * Sets the pixel data at a specified coordinate.
99
+ *
100
+ * @param {number} w - The x-coordinate (width).
101
+ * @param {number} h - The y-coordinate (height).
102
+ * @param {number[]} px - An array of four numbers representing the R, G, B, and A channels.
103
+ */
104
+ setPixel(w: number, h: number, px: number[]): void {
105
+ const pos = (h * this.width + w) * 4;
106
+ this.data[pos + 0] = px[0];
107
+ this.data[pos + 1] = px[1];
108
+ this.data[pos + 2] = px[2];
109
+ this.data[pos + 3] = px[3];
110
+ }
111
+
112
+ abstract setImageData(imageData: HDRImageData | ImageData): void;
113
+ abstract getImageData(): ImageData | null;
114
+
115
+ abstract fill(color: number[]): this | undefined;
116
+ abstract pixelCallback(fn: HDRImagePixelCallback): void;
117
+
118
+ /**
119
+ * Creates a deep clone of the current `Uint16Image` instance.
120
+ *
121
+ * @returns {Uint16Image} A new `Uint16Image` instance with a copy of the data.
122
+ * @private
123
+ */
124
+ clone(): this {
125
+ // Was Uint16Image
126
+ const copy = Object.create(Object.getPrototypeOf(this));
127
+ Object.assign(copy, this);
128
+ return copy;
129
+ }
130
+ }
@@ -1,43 +1,49 @@
1
- import Color from "colorjs.io";
2
- import type { Coords, ColorTypes } from "colorjs.io";
3
-
4
- import type { HDRPredefinedColorSpace, HDRImageData } from "./types/HDRCanvas.d.ts";
1
+ import { HDRImage } from "./HDRImage";
5
2
 
6
- type Uint16ImagePixelCallback = (red: number, green: number, blue: number, alpha: number) => Uint16Array;
3
+ import Color from "colorjs.io";
4
+ import type { Coords } from "colorjs.io";
7
5
 
8
- /*
9
- interface ColorSpaceMapping {
10
- [key: HDRPredefinedColorSpace]: string
11
- }
12
- */
6
+ import type { HDRPredefinedColorSpace, HDRImageData, HDRImagePixelCallback } from "./types/HDRCanvas.d.ts";
13
7
 
14
- export class Uint16Image {
15
- height: number;
16
- width: number;
8
+ /**
9
+ * Represents an image using a `Uint16Array` for its pixel data,
10
+ * providing support for high dynamic range (HDR) color spaces.
11
+ * **Don't use this anymore, it's just here for migrating to Float16Array!**
12
+ */
13
+ export class Uint16Image extends HDRImage {
14
+ /** The raw pixel data stored as a `Uint16Array`. */
17
15
  data: Uint16Array;
18
- static DEFAULT_COLORSPACE: HDRPredefinedColorSpace = "rec2100-hlg";
19
- static SDR_MULTIPLIER = 2 ** 16 - 1; //(2**16 - 1)
20
- static COLORSPACES: Record<HDRPredefinedColorSpace, ColorTypes> = {
21
- "rec2100-hlg": "rec2100hlg",
22
- "display-p3": "p3",
23
- srgb: "sRGB",
24
- "rec2100-pq": "rec2100pq"
25
- };
16
+
17
+ /** The color space of the image. */
26
18
  colorSpace: HDRPredefinedColorSpace;
27
19
 
20
+ /**
21
+ * Creates a new `Uint16Image` instance.
22
+ *
23
+ * @param {number} width - The width of the image in pixels.
24
+ * @param {number} height - The height of the image in pixels.
25
+ * @param {string} [colorspace] - The color space to use for the image. Defaults to `DEFAULT_COLORSPACE`.
26
+ */
28
27
  constructor(width: number, height: number, colorspace?: string) {
28
+ super(width, height);
29
29
  if (colorspace === undefined || colorspace === null) {
30
- this.colorSpace = Uint16Image.DEFAULT_COLORSPACE;
30
+ this.colorSpace = HDRImage.DEFAULT_COLORSPACE;
31
31
  } else {
32
32
  this.colorSpace = colorspace as HDRPredefinedColorSpace;
33
33
  }
34
34
 
35
- this.height = height;
36
- this.width = width;
37
35
  this.data = new Uint16Array(height * width * 4);
36
+ console.warn("Uint16Image isn't suported anymore, your browser will certainly drop support soon, use Float16Image instead.");
38
37
  }
39
38
 
40
- fill(color: number[]): Uint16Image | undefined {
39
+ /**
40
+ * Fills the entire image with a single color.
41
+ *
42
+ * @param {number[]} color - An array of four numbers representing the R, G, B, and A channels (0-65535).
43
+ * @returns {Uint16Image | undefined} The `Uint16Image` instance for method chaining, or `undefined` if the color array is invalid.
44
+ */
45
+ fill(color: number[]): this | undefined {
46
+ // Was Uint16Image
41
47
  if (color.length != 4) {
42
48
  return;
43
49
  }
@@ -50,34 +56,39 @@ export class Uint16Image {
50
56
  return this;
51
57
  }
52
58
 
53
- getPixel(w: number, h: number): Uint16Array {
54
- const pos = (h * this.width + w) * 4;
55
-
56
- return this.data.slice(pos, pos + 4);
57
- }
58
-
59
- setPixel(w: number, h: number, px: number[]): void {
60
- const pos = (h * this.width + w) * 4;
61
- this.data[pos + 0] = px[0];
62
- this.data[pos + 1] = px[1];
63
- this.data[pos + 2] = px[2];
64
- this.data[pos + 3] = px[3];
65
- }
66
-
67
59
  // Only use this for alpha, since it doesn't to color space conversions
60
+ /**
61
+ * Scales an 8-bit value to a 16-bit value. This is typically used for the alpha channel.
62
+ *
63
+ * @param {number} val - The 8-bit value to scale (0-255).
64
+ * @returns {number} The corresponding 16-bit value.
65
+ */
68
66
  static scaleUint8ToUint16(val: number): number {
69
67
  return (val << 8) | val;
70
68
  }
71
69
 
70
+ /**
71
+ * Creates a standard `ImageData` object from the `Uint16Image` data.
72
+ *
73
+ * @returns {ImageData | null} An `ImageData` object, or `null` if the data is undefined.
74
+ */
72
75
  getImageData(): ImageData | null {
73
76
  if (this.data === undefined || this.data === null) {
74
77
  return null;
75
78
  }
76
- return new ImageData(this.data as unknown as Uint8ClampedArray, this.width, this.height, {
79
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
80
+ return new ImageData(this.data as any, this.width, this.height, {
77
81
  colorSpace: this.colorSpace as PredefinedColorSpace
78
82
  });
79
83
  }
80
84
 
85
+ /**
86
+ * Converts a single 8-bit pixel (from sRGB color space) to a 16-bit pixel
87
+ * in the `rec2100-hlg` color space.
88
+ *
89
+ * @param {Uint8ClampedArray} pixel - An array of four 8-bit numbers (R, G, B, A).
90
+ * @returns {Uint16Array} The converted 16-bit pixel in the `rec2100-hlg` color space.
91
+ */
81
92
  static convertPixelToRec2100_hlg(pixel: Uint8ClampedArray): Uint16Array {
82
93
  const colorJScolorSpace = <string>Uint16Image.COLORSPACES["rec2100-hlg" as HDRPredefinedColorSpace];
83
94
 
@@ -98,6 +109,13 @@ export class Uint16Image {
98
109
  return Uint16Array.from(hlg);
99
110
  }
100
111
 
112
+ /**
113
+ * Converts a `Uint8ClampedArray` of sRGB pixel data to a `Uint16Array`
114
+ * of pixels in the `rec2100-hlg` color space.
115
+ *
116
+ * @param {Uint8ClampedArray} data - The array of 8-bit pixel data.
117
+ * @returns {Uint16Array} The converted 16-bit pixel data.
118
+ */
101
119
  static convertArrayToRec2100_hlg(data: Uint8ClampedArray): Uint16Array {
102
120
  const uint16Data = new Uint16Array(data.length);
103
121
  for (let i = 0; i < data.length; i += 4) {
@@ -108,35 +126,29 @@ export class Uint16Image {
108
126
  return uint16Data;
109
127
  }
110
128
 
111
- pixelCallback(fn: Uint16ImagePixelCallback) {
129
+ /**
130
+ * Iterates through each pixel of the image and applies a callback function to its data.
131
+ *
132
+ * @param {HDRImagePixelCallback} fn - The callback function to apply to each pixel.
133
+ */
134
+ pixelCallback(fn: HDRImagePixelCallback) {
112
135
  for (let i = 0; i < this.data.length; i += 4) {
113
136
  this.data.set(fn(this.data[i], this.data[i + 1], this.data[i + 2], this.data[i + 3]), i);
114
137
  }
115
138
  }
116
139
 
117
- static async loadSDRImageData(url: URL): Promise<HDRImageData | undefined> {
118
- return fetch(url)
119
- .then((response) => response.blob())
120
- .then((blob: Blob) => {
121
- return createImageBitmap(blob);
122
- })
123
- .then((bitmap: ImageBitmap) => {
124
- const { width, height } = bitmap;
125
- const offscreen = new OffscreenCanvas(width, height);
126
- const ctx = offscreen.getContext("2d");
127
- ctx?.drawImage(bitmap, 0, 0);
128
- return ctx;
129
- })
130
- .then((ctx: OffscreenCanvasRenderingContext2D | null) => {
131
- return ctx?.getImageData(0, 0, ctx?.canvas.width, ctx?.canvas.height);
132
- });
133
- }
134
-
140
+ /**
141
+ * Creates a `Uint16Image` instance from an `HDRImageData` object.
142
+ *
143
+ * @param {HDRImageData} imageData - The image data to use.
144
+ * @returns {Uint16Image} The new `Uint16Image` instance.
145
+ * @throws {Error} If the color space of the `HDRImageData` is not supported.
146
+ */
135
147
  static fromImageData(imageData: HDRImageData): Uint16Image {
136
148
  const i = new Uint16Image(imageData.width, imageData.height);
137
149
  if (imageData.colorSpace == "srgb") {
138
150
  i.data = Uint16Image.convertArrayToRec2100_hlg(<Uint8ClampedArray>imageData.data);
139
- } else if (imageData.colorSpace == Uint16Image.DEFAULT_COLORSPACE) {
151
+ } else if (imageData.colorSpace == HDRImage.DEFAULT_COLORSPACE) {
140
152
  i.data = <Uint16Array>imageData.data;
141
153
  } else {
142
154
  throw new Error(`ColorSpace ${imageData.colorSpace} isn't supported!`);
@@ -144,6 +156,12 @@ export class Uint16Image {
144
156
  return i;
145
157
  }
146
158
 
159
+ /**
160
+ * Loads an image from a URL and creates a `Uint16Image` instance from it.
161
+ *
162
+ * @param {URL} url - The URL of the image to load.
163
+ * @returns {Promise<Uint16Image | undefined>} A promise that resolves with a `Uint16Image` instance, or `undefined` if the image could not be loaded.
164
+ */
147
165
  static async fromURL(url: URL): Promise<Uint16Image | undefined> {
148
166
  return Uint16Image.loadSDRImageData(url).then((data: HDRImageData | undefined) => {
149
167
  if (data !== undefined) {
@@ -152,22 +170,22 @@ export class Uint16Image {
152
170
  });
153
171
  }
154
172
 
173
+ /**
174
+ * Sets the image data of the current `Uint16Image` instance.
175
+ *
176
+ * @param {HDRImageData} imageData - The image data to set.
177
+ * @throws {Error} If the color space of the `HDRImageData` is not supported.
178
+ */
155
179
  setImageData(imageData: HDRImageData): void {
156
180
  this.width = imageData.width;
157
181
  this.height = imageData.height;
158
182
  if (imageData.colorSpace == "srgb") {
159
183
  this.data = Uint16Image.convertArrayToRec2100_hlg(<Uint8ClampedArray>imageData.data);
160
- } else if (imageData.colorSpace == Uint16Image.DEFAULT_COLORSPACE) {
184
+ } else if (imageData.colorSpace == HDRImage.DEFAULT_COLORSPACE) {
161
185
  this.data = <Uint16Array>imageData.data;
162
186
  } else {
163
187
  throw new Error(`ColorSpace ${imageData.colorSpace} isn't supported!`);
164
188
  }
165
- this.colorSpace = Uint16Image.DEFAULT_COLORSPACE;
166
- }
167
-
168
- clone(): Uint16Image {
169
- const i = new Uint16Image(this.width, this.height, this.colorSpace);
170
- i.data = this.data.slice();
171
- return i;
189
+ this.colorSpace = HDRImage.DEFAULT_COLORSPACE;
172
190
  }
173
191
  }
@@ -0,0 +1,9 @@
1
+ export function getBrowserVersion(): number | null {
2
+ const majorVersionStr = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
3
+ if (majorVersionStr == null) {
4
+ console.warn(`Unsupported / untested browser (${navigator.userAgent}) detected - using more modern defaults`);
5
+ } else if (majorVersionStr.length >= 3) {
6
+ return Number(majorVersionStr[2]);
7
+ }
8
+ return null;
9
+ }