hdr-canvas 0.0.13 → 0.1.1
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.
- package/README.md +126 -29
- package/dist/@types/hdr-canvas.d.ts +63 -15
- package/dist/hdr-canvas.js +252 -102
- package/dist/hdr-canvas.js.map +1 -1
- package/dist/hdr-canvas.min.js +1 -1
- package/dist/hdr-canvas.min.js.map +1 -1
- package/dist/hdr-canvas.umd.js +6124 -5972
- package/dist/hdr-canvas.umd.js.map +1 -1
- package/dist/index.d.ts +63 -15
- package/docs/release-notes-0.1.0.md +47 -0
- package/docs/release-notes-0.1.1.md +19 -0
- package/docs/release.md +13 -0
- package/package.json +51 -16
- package/patches/@typescript+dom-lib-generator+0.0.1.patch +46 -0
- package/scripts/check.sh +12 -0
- package/scripts/git-submodules.js +175 -0
- package/src/Float16Image.ts +238 -0
- package/src/HDRImage.ts +130 -0
- package/src/Uint16Image.ts +86 -68
- package/src/browser-util.ts +9 -0
- package/src/hdr-canvas.ts +52 -8
- package/src/hdr-check.ts +31 -13
- package/src/index.ts +2 -0
- package/src/types/HDRCanvas.d.ts +34 -3
- package/src/types/ImageData.d.ts +17 -0
- package/three/HDRWebGPUBackend.js +45 -61
- package/three/HDRWebGPURenderer.js +19 -0
- package/tsconfig.json +12 -6
- package/dist/hdr-canvas.d.ts +0 -4
|
@@ -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
|
+
}
|
package/src/HDRImage.ts
ADDED
|
@@ -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
|
+
}
|
package/src/Uint16Image.ts
CHANGED
|
@@ -1,43 +1,49 @@
|
|
|
1
|
-
import
|
|
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
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
19
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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 ==
|
|
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 ==
|
|
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 =
|
|
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
|
+
}
|