hdrify-react 1.3.0 → 1.4.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.
- package/README.md +51 -19
- package/dist/HdrifyCanvas.d.ts +1 -1
- package/dist/HdrifyCanvas.d.ts.map +1 -1
- package/dist/HdrifyCanvas.js +5 -30
- package/dist/HdrifyCanvas.js.map +1 -1
- package/dist/ImageViewer.d.ts +75 -0
- package/dist/ImageViewer.d.ts.map +1 -0
- package/dist/ImageViewer.js +199 -0
- package/dist/ImageViewer.js.map +1 -0
- package/dist/capture.d.ts +21 -0
- package/dist/capture.d.ts.map +1 -0
- package/dist/capture.js +50 -0
- package/dist/capture.js.map +1 -0
- package/dist/decode.d.ts +7 -0
- package/dist/decode.d.ts.map +1 -0
- package/dist/decode.js +23 -0
- package/dist/decode.js.map +1 -0
- package/dist/element.d.ts +51 -0
- package/dist/element.d.ts.map +1 -0
- package/dist/element.js +212 -0
- package/dist/element.js.map +1 -0
- package/dist/image-viewer.js +159 -0
- package/dist/image-viewer.js.map +7 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/progress.d.ts +19 -0
- package/dist/progress.d.ts.map +1 -0
- package/dist/progress.js +47 -0
- package/dist/progress.js.map +1 -0
- package/dist/readiness.d.ts +15 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +46 -0
- package/dist/readiness.js.map +1 -0
- package/dist/styles.d.ts +6 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/styles.js +84 -0
- package/dist/styles.js.map +1 -0
- package/package.json +37 -13
package/README.md
CHANGED
|
@@ -1,12 +1,28 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://hdrify.ben3d.ca" target="_blank" rel="noopener noreferrer">
|
|
3
|
+
<img alt="hdrify-react logo" src="https://raw.githubusercontent.com/bhouston/hdrify/main/packages/hdrify-vscode-extension/images/icon.png" height="96">
|
|
4
|
+
</a>
|
|
5
|
+
</p>
|
|
6
|
+
<br/>
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://npmjs.com/package/hdrify-react"><img src="https://img.shields.io/npm/v/hdrify-react.svg" alt="npm package"></a>
|
|
9
|
+
<a href="https://www.npmtrends.com/hdrify-react"><img src="https://img.shields.io/npm/dw/hdrify-react.svg" alt="npm downloads"></a>
|
|
10
|
+
<a href="https://codecov.io/gh/bhouston/hdrify"><img src="https://codecov.io/gh/bhouston/hdrify/branch/main/graph/badge.svg" alt="coverage"></a>
|
|
11
|
+
<a href="https://discord.gg/5J5Ur3F6Z2"><img src="https://img.shields.io/badge/chat-discord-blue?style=flat&logo=discord" alt="discord chat"></a>
|
|
12
|
+
<a href="https://hdrify.ben3d.ca"><img src="https://img.shields.io/badge/docs-read-blue?style=flat" alt="documentation"></a>
|
|
13
|
+
</p>
|
|
14
|
+
<br/>
|
|
15
|
+
|
|
1
16
|
# hdrify-react
|
|
2
17
|
|
|
3
|
-
|
|
18
|
+
> Display HDR images in React with configurable tone mapping.
|
|
4
19
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
20
|
+
- 🖼️ `<image-viewer>` Web Component and `ImageViewer` React Component
|
|
21
|
+
- ⚛️ React Canvas Component
|
|
22
|
+
- 🎨 Four Tone Mappers
|
|
23
|
+
- ☀️ Adjustable Exposure
|
|
24
|
+
- 🖼️ Works With hdrify Readers
|
|
25
|
+
- 🔷 Typed Props
|
|
10
26
|
|
|
11
27
|
React component for displaying HDR images from [hdrify](https://www.npmjs.com/package/hdrify). Accepts `HdrifyImage` (returned by `readHdr`, `readExr`, `readJpegGainMap`) and renders with configurable tone mapping and exposure.
|
|
12
28
|
|
|
@@ -14,6 +30,8 @@ React component for displaying HDR images from [hdrify](https://www.npmjs.com/pa
|
|
|
14
30
|
|
|
15
31
|
An **online demo** is available at **[https://hdrify.ben3d.ca](https://hdrify.ben3d.ca)** — HDR, EXR, and Ultra HDR viewer and converter built with this component; try it in your browser.
|
|
16
32
|
|
|
33
|
+
[Read the Docs to Learn More](https://hdrify.ben3d.ca/).
|
|
34
|
+
|
|
17
35
|
## Installation
|
|
18
36
|
|
|
19
37
|
```sh
|
|
@@ -31,7 +49,28 @@ const hdrifyImage: HdrifyImage = readExr(buffer);
|
|
|
31
49
|
<HdrifyCanvas hdrifyImage={hdrifyImage} toneMapping="neutral" exposure={1.0} className="max-w-full" />;
|
|
32
50
|
```
|
|
33
51
|
|
|
34
|
-
###
|
|
52
|
+
### Image viewer
|
|
53
|
+
|
|
54
|
+
`ImageViewer` loads an EXR, HDR or gain map JPEG (detected from its content, gzip allowed) and adds an options panel and an optional vertical exposure slider:
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
import { ImageViewer } from 'hdrify-react';
|
|
58
|
+
|
|
59
|
+
<ImageViewer src="/images/memorial.exr" exposureControl toneMapping="agx" style={{ height: 600 }} />;
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The same viewer is available as a web component for plain HTML and other frameworks:
|
|
63
|
+
|
|
64
|
+
```html
|
|
65
|
+
<script type="module" src="https://cdn.jsdelivr.net/npm/hdrify-react/dist/image-viewer.js"></script>
|
|
66
|
+
<image-viewer src="/images/memorial.exr" exposure-control></image-viewer>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
While loading, a progress bar fills across the top (`onProgress` / the `progress` event report it). `whenReady()` and `toBlob()` (on the element, or on the component's `ref`) wait for the image and export the tone-mapped result.
|
|
70
|
+
|
|
71
|
+
With a bundler, `import 'hdrify-react/element'` instead. See the [`<image-viewer>` documentation](https://hdrify.ben3d.ca/docs/image-viewer/) for all attributes, events and theming.
|
|
72
|
+
|
|
73
|
+
### HdrifyCanvas props
|
|
35
74
|
|
|
36
75
|
| Prop | Type | Description |
|
|
37
76
|
| -------------- | -------------------------------------------- | ----------------------------------------------------- |
|
|
@@ -41,21 +80,14 @@ const hdrifyImage: HdrifyImage = readExr(buffer);
|
|
|
41
80
|
| `className` | `string` | Optional CSS class for the canvas |
|
|
42
81
|
| `forwardedRef` | `RefObject<HTMLCanvasElement \| null>` | Optional ref for canvas access (e.g. `toBlob`) |
|
|
43
82
|
|
|
83
|
+
## Contribution
|
|
84
|
+
|
|
85
|
+
See [Contributing Guide](https://github.com/bhouston/hdrify/blob/main/CONTRIBUTING.md).
|
|
86
|
+
|
|
44
87
|
## License
|
|
45
88
|
|
|
46
89
|
MIT
|
|
47
90
|
|
|
48
91
|
## Author
|
|
49
92
|
|
|
50
|
-
[Ben Houston](https://ben3d.ca), Sponsored by [Land of Assets](https://landofassets.com)
|
|
51
|
-
|
|
52
|
-
[npm]: https://img.shields.io/npm/v/hdrify-react
|
|
53
|
-
[npm-url]: https://www.npmjs.com/package/hdrify-react
|
|
54
|
-
[npm-downloads]: https://img.shields.io/npm/dw/hdrify-react
|
|
55
|
-
[npmtrends-url]: https://www.npmtrends.com/hdrify-react
|
|
56
|
-
[tests-badge]: https://github.com/bhouston/hdrify/workflows/Tests/badge.svg
|
|
57
|
-
[tests-url]: https://github.com/bhouston/hdrify/actions/workflows/test.yml
|
|
58
|
-
[coverage-badge]: https://codecov.io/gh/bhouston/hdrify/branch/main/graph/badge.svg
|
|
59
|
-
[coverage-url]: https://codecov.io/gh/bhouston/hdrify
|
|
60
|
-
[discord-badge]: https://img.shields.io/badge/Discord-Join-5865F2?logo=discord&logoColor=white
|
|
61
|
-
[discord-url]: https://discord.gg/5J5Ur3F6Z2
|
|
93
|
+
[Ben Houston](https://ben3d.ca), Sponsored by [Land of Assets](https://landofassets.com).
|
package/dist/HdrifyCanvas.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HdrifyCanvas.d.ts","
|
|
1
|
+
{"version":3,"file":"HdrifyCanvas.d.ts","sources":["../src/HdrifyCanvas.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAC3D,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AAIpC,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,WAAW,CAAC;IACzB,WAAW,EAAE,eAAe,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sEAAsE;IACtE,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;CAC1D;AAED,wBAAgB,YAAY,CAAC,EAC3B,WAAW,EACX,WAAW,EACX,QAAQ,EACR,SAAS,EACT,YAAY,GACb,EAAE,iBAAiB,GAAG,KAAK,CAAC,YAAY,CAmBxC"}
|
package/dist/HdrifyCanvas.js
CHANGED
|
@@ -1,44 +1,19 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { applyToneMapping } from 'hdrify';
|
|
3
2
|
import { useCallback, useEffect, useRef } from 'react';
|
|
3
|
+
import { drawToneMapped } from './capture.js';
|
|
4
4
|
export function HdrifyCanvas({ hdrifyImage, toneMapping, exposure, className, forwardedRef, }) {
|
|
5
5
|
const canvasRef = useRef(null);
|
|
6
6
|
const setRef = useCallback((el) => {
|
|
7
7
|
canvasRef.current = el;
|
|
8
8
|
if (forwardedRef) {
|
|
9
|
+
// oxlint-disable-next-line react/immutability -- forwarding the element into the parent-owned ref
|
|
9
10
|
forwardedRef.current = el;
|
|
10
11
|
}
|
|
11
12
|
}, [forwardedRef]);
|
|
12
|
-
const renderToCanvas = useCallback((data, exp, tone) => {
|
|
13
|
-
const canvas = canvasRef.current;
|
|
14
|
-
if (!canvas)
|
|
15
|
-
return;
|
|
16
|
-
const { width, height } = data;
|
|
17
|
-
canvas.width = width;
|
|
18
|
-
canvas.height = height;
|
|
19
|
-
// LDR from applyToneMapping is sRGB; use srgb canvas so it displays correctly
|
|
20
|
-
const ctx = canvas.getContext('2d', { colorSpace: 'srgb' }) ?? canvas.getContext('2d');
|
|
21
|
-
if (!ctx)
|
|
22
|
-
return;
|
|
23
|
-
const ldrRgb = applyToneMapping(data.data, width, height, {
|
|
24
|
-
toneMapping: tone,
|
|
25
|
-
exposure: exp,
|
|
26
|
-
sourceColorSpace: data.linearColorSpace,
|
|
27
|
-
});
|
|
28
|
-
const canvasImageData = ctx.createImageData(width, height);
|
|
29
|
-
const pixels = canvasImageData.data;
|
|
30
|
-
const sourceLength = width * height * 3;
|
|
31
|
-
for (let destIndex = 0, sourceIndex = 0; sourceIndex < sourceLength; destIndex += 4, sourceIndex += 3) {
|
|
32
|
-
pixels[destIndex] = ldrRgb[sourceIndex];
|
|
33
|
-
pixels[destIndex + 1] = ldrRgb[sourceIndex + 1];
|
|
34
|
-
pixels[destIndex + 2] = ldrRgb[sourceIndex + 2];
|
|
35
|
-
pixels[destIndex + 3] = 255;
|
|
36
|
-
}
|
|
37
|
-
ctx.putImageData(canvasImageData, 0, 0);
|
|
38
|
-
}, []);
|
|
39
13
|
useEffect(() => {
|
|
40
|
-
|
|
41
|
-
|
|
14
|
+
if (canvasRef.current)
|
|
15
|
+
drawToneMapped(canvasRef.current, hdrifyImage, toneMapping, exposure);
|
|
16
|
+
}, [hdrifyImage, exposure, toneMapping]);
|
|
42
17
|
return _jsx("canvas", { className: className, ref: setRef });
|
|
43
18
|
}
|
|
44
19
|
//# sourceMappingURL=HdrifyCanvas.js.map
|
package/dist/HdrifyCanvas.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HdrifyCanvas.js","
|
|
1
|
+
{"version":3,"file":"HdrifyCanvas.js","sources":["../src/HdrifyCanvas.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAW9C,MAAM,UAAU,YAAY,CAAC,EAC3B,WAAW,EACX,WAAW,EACX,QAAQ,EACR,SAAS,EACT,YAAY,GACM;IAClB,MAAM,SAAS,GAAG,MAAM,CAAoB,IAAI,CAAC,CAAC;IAElD,MAAM,MAAM,GAAG,WAAW,CACxB,CAAC,EAA4B,EAAE,EAAE;QAC/B,SAAS,CAAC,OAAO,GAAG,EAAE,CAAC;QACvB,IAAI,YAAY,EAAE,CAAC;YACjB,kGAAkG;YAClG,YAAY,CAAC,OAAO,GAAG,EAAE,CAAC;QAC5B,CAAC;IACH,CAAC,EACD,CAAC,YAAY,CAAC,CACf,CAAC;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,SAAS,CAAC,OAAO;YAAE,cAAc,CAAC,SAAS,CAAC,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC;IAC/F,CAAC,EAAE,CAAC,WAAW,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC;IAEzC,OAAO,iBAAQ,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAI,CAAC;AACvD,CAAC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { HdrifyImage, ToneMappingType } from 'hdrify';
|
|
2
|
+
import { type CSSProperties, type Ref } from 'react';
|
|
3
|
+
import { type ImageViewerCaptureOptions } from './capture.js';
|
|
4
|
+
import { type ImageViewerProgress } from './progress.js';
|
|
5
|
+
/** `open` shows the options panel, `closed` shows only its gear button, and `hidden` shows neither. */
|
|
6
|
+
export type SettingsPanelMode = 'open' | 'closed' | 'hidden';
|
|
7
|
+
/** `contain` scales the image to fit the viewer; `actual` shows one image pixel per CSS pixel, scrolling if needed. */
|
|
8
|
+
export type ImageFit = 'contain' | 'actual';
|
|
9
|
+
/** User-adjustable display settings. */
|
|
10
|
+
export interface ImageViewerSettings {
|
|
11
|
+
toneMapping: ToneMappingType;
|
|
12
|
+
/** Exposure in stops (EV): `0` is unchanged, `+1` doubles the brightness. */
|
|
13
|
+
exposure: number;
|
|
14
|
+
fit: ImageFit;
|
|
15
|
+
}
|
|
16
|
+
export declare const DEFAULT_IMAGE_VIEWER_SETTINGS: Readonly<ImageViewerSettings>;
|
|
17
|
+
export declare const TONE_MAPPING_OPTIONS: readonly {
|
|
18
|
+
value: ToneMappingType;
|
|
19
|
+
label: string;
|
|
20
|
+
}[];
|
|
21
|
+
/** Range of the exposure slider, in stops. The options panel accepts any value. */
|
|
22
|
+
export declare const EXPOSURE_SLIDER_RANGE: {
|
|
23
|
+
readonly min: -10;
|
|
24
|
+
readonly max: 10;
|
|
25
|
+
};
|
|
26
|
+
/** Imperative handle for {@link ImageViewer}, received through its `ref` (React 19). */
|
|
27
|
+
export interface ImageViewerHandle {
|
|
28
|
+
/**
|
|
29
|
+
* Resolve once the current source is loaded and drawn (immediately with no source). If the source changes first,
|
|
30
|
+
* it waits for the new source instead. Rejects with the load's error, or if the component unmounts.
|
|
31
|
+
*/
|
|
32
|
+
whenReady(): Promise<void>;
|
|
33
|
+
/** Wait for {@link whenReady}, then encode the tone-mapped image with the current settings; no viewer controls. */
|
|
34
|
+
toBlob(options?: ImageViewerCaptureOptions): Promise<Blob>;
|
|
35
|
+
}
|
|
36
|
+
/** Props for {@link ImageViewer}. Settings props override the user's choice whenever they change. */
|
|
37
|
+
export interface ImageViewerProps extends Partial<ImageViewerSettings> {
|
|
38
|
+
/** URL of an EXR, HDR or gain map JPEG (optionally gzipped). Fetching is cancelled when it changes. */
|
|
39
|
+
src?: string;
|
|
40
|
+
/** Encoded file bytes; takes precedence over `src`. */
|
|
41
|
+
data?: ArrayBuffer | Uint8Array;
|
|
42
|
+
/** An already decoded image; takes precedence over `data` and `src`. */
|
|
43
|
+
image?: HdrifyImage;
|
|
44
|
+
/** Initial state of the options panel. Defaults to `closed`. */
|
|
45
|
+
settingsPanel?: SettingsPanelMode;
|
|
46
|
+
/** Show the vertical exposure slider over the left edge. */
|
|
47
|
+
exposureControl?: boolean;
|
|
48
|
+
/** Wrapper class name. */
|
|
49
|
+
className?: string;
|
|
50
|
+
/** Wrapper style; the default height is 480 pixels. */
|
|
51
|
+
style?: CSSProperties;
|
|
52
|
+
/** Called after each image loads and is drawn. */
|
|
53
|
+
onLoad?: (image: HdrifyImage) => void;
|
|
54
|
+
/** Called with fetch and decode errors; they are also shown in the viewer. */
|
|
55
|
+
onError?: (error: Error) => void;
|
|
56
|
+
/** Called as the image downloads, decodes and is drawn. */
|
|
57
|
+
onProgress?: (progress: ImageViewerProgress) => void;
|
|
58
|
+
/** Called when the user changes a setting with the slider or the options panel. */
|
|
59
|
+
onSettingsChange?: (settings: ImageViewerSettings) => void;
|
|
60
|
+
/** Called when the user opens or closes the options panel. */
|
|
61
|
+
onSettingsPanelChange?: (mode: SettingsPanelMode) => void;
|
|
62
|
+
/** Receives an {@link ImageViewerHandle} for readiness and image capture. */
|
|
63
|
+
ref?: Ref<ImageViewerHandle>;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Loads and displays an HDR image with tone mapping, a loading bar, an optional options panel and an optional
|
|
67
|
+
* exposure slider. Accepts every format hdrify reads; see {@link decodeHdrifyImage}.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```tsx
|
|
71
|
+
* <ImageViewer src="/images/memorial.exr" exposureControl toneMapping="agx" style={{ height: 600 }} />
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export declare function ImageViewer({ src, data, image, toneMapping, exposure, fit, settingsPanel, exposureControl, className, style, onLoad, onError, onProgress, onSettingsChange, onSettingsPanelChange, ref, }: ImageViewerProps): import("react").JSX.Element;
|
|
75
|
+
//# sourceMappingURL=ImageViewer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImageViewer.d.ts","sources":["../src/ImageViewer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAC3D,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,GAAG,EAA2D,MAAM,OAAO,CAAC;AAC9G,OAAO,EAAgB,KAAK,yBAAyB,EAAE,MAAM,cAAc,CAAC;AAG5E,OAAO,EAAyB,KAAK,mBAAmB,EAAgB,MAAM,eAAe,CAAC;AAI9F,uGAAuG;AACvG,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAC7D,uHAAuH;AACvH,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE5C,wCAAwC;AACxC,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,eAAe,CAAC;IAC7B,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,QAAQ,CAAC;CACf;AAED,eAAO,MAAM,6BAA6B,EAAE,QAAQ,CAAC,mBAAmB,CAIvE,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,SAAS;IAAE,KAAK,EAAE,eAAe,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAKpF,CAAC;AAEF,mFAAmF;AACnF,eAAO,MAAM,qBAAqB;aAAK,GAAG,EAAE,CAAC,EAAE;aAAE,GAAG,EAAE,EAAE;CAAW,CAAC;AAEpE,wFAAwF;AACxF,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,mHAAmH;IACnH,MAAM,CAAC,OAAO,CAAC,EAAE,yBAAyB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5D;AAED,qGAAqG;AACrG,MAAM,WAAW,gBAAiB,SAAQ,OAAO,CAAC,mBAAmB,CAAC;IACpE,uGAAuG;IACvG,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,IAAI,CAAC,EAAE,WAAW,GAAG,UAAU,CAAC;IAChC,wEAAwE;IACxE,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,gEAAgE;IAChE,aAAa,CAAC,EAAE,iBAAiB,CAAC;IAClC,4DAA4D;IAC5D,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,0BAA0B;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,kDAAkD;IAClD,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IACtC,8EAA8E;IAC9E,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,2DAA2D;IAC3D,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACrD,mFAAmF;IACnF,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAC3D,8DAA8D;IAC9D,qBAAqB,CAAC,EAAE,CAAC,IAAI,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAC1D,6EAA6E;IAC7E,GAAG,CAAC,EAAE,GAAG,CAAC,iBAAiB,CAAC,CAAC;CAC9B;AAwBD;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,EAC1B,GAAG,EACH,IAAI,EACJ,KAAK,EACL,WAAW,EACX,QAAQ,EACR,GAAG,EACH,aAAwB,EACxB,eAAuB,EACvB,SAAS,EACT,KAAK,EACL,MAAM,EACN,OAAO,EACP,UAAU,EACV,gBAAgB,EAChB,qBAAqB,EACrB,GAAG,GACJ,EAAE,gBAAgB,+BA8LlB"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useId, useImperativeHandle, useRef, useState } from 'react';
|
|
3
|
+
import { captureImage } from './capture.js';
|
|
4
|
+
import { decodeHdrifyImage } from './decode.js';
|
|
5
|
+
import { HdrifyCanvas } from './HdrifyCanvas.js';
|
|
6
|
+
import { readResponse } from './progress.js';
|
|
7
|
+
import { ViewerReadiness } from './readiness.js';
|
|
8
|
+
import { IMAGE_VIEWER_CSS } from './styles.js';
|
|
9
|
+
export const DEFAULT_IMAGE_VIEWER_SETTINGS = {
|
|
10
|
+
toneMapping: 'neutral',
|
|
11
|
+
exposure: 0,
|
|
12
|
+
fit: 'contain',
|
|
13
|
+
};
|
|
14
|
+
export const TONE_MAPPING_OPTIONS = [
|
|
15
|
+
{ value: 'neutral', label: 'Neutral' },
|
|
16
|
+
{ value: 'agx', label: 'AgX' },
|
|
17
|
+
{ value: 'aces', label: 'ACES' },
|
|
18
|
+
{ value: 'reinhard', label: 'Reinhard' },
|
|
19
|
+
];
|
|
20
|
+
/** Range of the exposure slider, in stops. The options panel accepts any value. */
|
|
21
|
+
export const EXPOSURE_SLIDER_RANGE = { min: -10, max: 10 };
|
|
22
|
+
function definedSettings(settings) {
|
|
23
|
+
return Object.fromEntries(Object.entries(settings).filter(([, value]) => value !== undefined));
|
|
24
|
+
}
|
|
25
|
+
function formatStops(stops) {
|
|
26
|
+
return `${stops > 0 ? '+' : ''}${Number(stops.toFixed(1))} EV`;
|
|
27
|
+
}
|
|
28
|
+
/** Wait until the browser has painted, so the progress bar shows before decoding blocks the main thread. */
|
|
29
|
+
const afterPaint = () => new Promise((resolve) => requestAnimationFrame(() => setTimeout(resolve, 0)));
|
|
30
|
+
/** Share of the progress bar for the download; decoding and drawing take the rest. */
|
|
31
|
+
const DOWNLOAD_SHARE = 0.8;
|
|
32
|
+
const NO_DOWNLOAD = { loaded: 0, total: null, progress: null };
|
|
33
|
+
let loadCount = 0;
|
|
34
|
+
/**
|
|
35
|
+
* Loads and displays an HDR image with tone mapping, a loading bar, an optional options panel and an optional
|
|
36
|
+
* exposure slider. Accepts every format hdrify reads; see {@link decodeHdrifyImage}.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```tsx
|
|
40
|
+
* <ImageViewer src="/images/memorial.exr" exposureControl toneMapping="agx" style={{ height: 600 }} />
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export function ImageViewer({ src, data, image, toneMapping, exposure, fit, settingsPanel = 'closed', exposureControl = false, className, style, onLoad, onError, onProgress, onSettingsChange, onSettingsPanelChange, ref, }) {
|
|
44
|
+
const callbacks = useRef({ onLoad, onError, onProgress, onSettingsChange, onSettingsPanelChange });
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
callbacks.current = { onLoad, onError, onProgress, onSettingsChange, onSettingsPanelChange };
|
|
47
|
+
});
|
|
48
|
+
// Props seed the state and override it whenever they change; the user can change it in between.
|
|
49
|
+
const [settings, setSettings] = useState(() => ({
|
|
50
|
+
...DEFAULT_IMAGE_VIEWER_SETTINGS,
|
|
51
|
+
...definedSettings({ toneMapping, exposure, fit }),
|
|
52
|
+
}));
|
|
53
|
+
const [provided, setProvided] = useState({ toneMapping, exposure, fit, settingsPanel });
|
|
54
|
+
const [panelMode, setPanelMode] = useState(settingsPanel);
|
|
55
|
+
if (provided.toneMapping !== toneMapping ||
|
|
56
|
+
provided.exposure !== exposure ||
|
|
57
|
+
provided.fit !== fit ||
|
|
58
|
+
provided.settingsPanel !== settingsPanel) {
|
|
59
|
+
setProvided({ toneMapping, exposure, fit, settingsPanel });
|
|
60
|
+
setSettings((current) => ({ ...current, ...definedSettings({ toneMapping, exposure, fit }) }));
|
|
61
|
+
if (provided.settingsPanel !== settingsPanel)
|
|
62
|
+
setPanelMode(settingsPanel);
|
|
63
|
+
}
|
|
64
|
+
const change = (patch) => {
|
|
65
|
+
const next = { ...settings, ...patch };
|
|
66
|
+
setSettings(next);
|
|
67
|
+
callbacks.current.onSettingsChange?.(next);
|
|
68
|
+
};
|
|
69
|
+
const changePanel = (mode) => {
|
|
70
|
+
setPanelMode(mode);
|
|
71
|
+
callbacks.current.onSettingsPanelChange?.(mode);
|
|
72
|
+
};
|
|
73
|
+
const [readiness] = useState(() => new ViewerReadiness());
|
|
74
|
+
useEffect(() => () => readiness.disconnect(), [readiness]);
|
|
75
|
+
// The latest fetch/decode result, tagged with the source it came from; the rest is derived during render.
|
|
76
|
+
const source = image ? undefined : (data ?? src);
|
|
77
|
+
const [loaded, setLoaded] = useState({});
|
|
78
|
+
// Progress of the current load; `id` remounts the bar so it never animates backwards into a new load.
|
|
79
|
+
const [bar, setBar] = useState(null);
|
|
80
|
+
const download = useRef(NO_DOWNLOAD);
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
readiness.pending();
|
|
83
|
+
download.current = NO_DOWNLOAD;
|
|
84
|
+
if (image)
|
|
85
|
+
return; // Drawn below.
|
|
86
|
+
if (!source) {
|
|
87
|
+
readiness.finish();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const controller = new AbortController();
|
|
91
|
+
const { signal } = controller;
|
|
92
|
+
const id = ++loadCount;
|
|
93
|
+
let overall = 0;
|
|
94
|
+
const report = (stage, totalProgress) => {
|
|
95
|
+
if (signal.aborted)
|
|
96
|
+
return;
|
|
97
|
+
overall = Math.max(overall, totalProgress);
|
|
98
|
+
setBar({ id, value: overall });
|
|
99
|
+
callbacks.current.onProgress?.({ ...download.current, totalProgress: overall, stage });
|
|
100
|
+
};
|
|
101
|
+
const fetchWithProgress = async (url) => {
|
|
102
|
+
report('download', 0);
|
|
103
|
+
const response = await fetch(url, { signal });
|
|
104
|
+
if (!response.ok)
|
|
105
|
+
throw new Error(`Failed to load ${url}: HTTP ${response.status}`);
|
|
106
|
+
return readResponse(response, signal, (progress) => {
|
|
107
|
+
download.current = progress;
|
|
108
|
+
report('download', DOWNLOAD_SHARE * (progress.progress ?? 0));
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
(async () => {
|
|
112
|
+
const bytes = typeof source === 'string' ? await fetchWithProgress(source) : source;
|
|
113
|
+
report('decode', typeof source === 'string' ? DOWNLOAD_SHARE : 0);
|
|
114
|
+
await afterPaint();
|
|
115
|
+
signal.throwIfAborted();
|
|
116
|
+
const decoded = await decodeHdrifyImage(bytes);
|
|
117
|
+
signal.throwIfAborted();
|
|
118
|
+
setLoaded({ source, image: decoded });
|
|
119
|
+
})().catch((reason) => {
|
|
120
|
+
if (signal.aborted)
|
|
121
|
+
return;
|
|
122
|
+
const error = reason instanceof Error ? reason : new Error(String(reason));
|
|
123
|
+
readiness.finish(error);
|
|
124
|
+
setBar(null);
|
|
125
|
+
setLoaded({ source, error: error.message });
|
|
126
|
+
callbacks.current.onError?.(error);
|
|
127
|
+
});
|
|
128
|
+
return () => controller.abort();
|
|
129
|
+
}, [image, source, readiness]);
|
|
130
|
+
const loading = source !== undefined && loaded.source !== source;
|
|
131
|
+
// While a new source loads, keep showing the previous image.
|
|
132
|
+
const shownImage = image ?? (source === undefined ? undefined : loaded.image);
|
|
133
|
+
const error = !loading && source !== undefined ? loaded.error : undefined;
|
|
134
|
+
const barValue = bar && !loading ? 1 : bar?.value;
|
|
135
|
+
// HdrifyCanvas (a child) draws in its effect, which runs before this one: the new image is on screen here.
|
|
136
|
+
const drawn = loading ? undefined : shownImage;
|
|
137
|
+
const latest = useRef({ image: drawn, settings });
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
latest.current = { image: drawn, settings };
|
|
140
|
+
});
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
if (!drawn)
|
|
143
|
+
return;
|
|
144
|
+
readiness.finish();
|
|
145
|
+
callbacks.current.onProgress?.({ ...download.current, totalProgress: 1, stage: 'render' });
|
|
146
|
+
callbacks.current.onLoad?.(drawn);
|
|
147
|
+
}, [drawn, readiness]);
|
|
148
|
+
useImperativeHandle(ref, () => ({
|
|
149
|
+
whenReady: () => readiness.whenReady(),
|
|
150
|
+
toBlob: async (options) => {
|
|
151
|
+
await readiness.whenReady();
|
|
152
|
+
const { image: current, settings: currentSettings } = latest.current;
|
|
153
|
+
if (!current)
|
|
154
|
+
throw new Error('No image is loaded.');
|
|
155
|
+
return captureImage(current, currentSettings.toneMapping, 2 ** currentSettings.exposure, options);
|
|
156
|
+
},
|
|
157
|
+
}), [readiness]);
|
|
158
|
+
return (_jsxs("div", { className: className ? `hdrify-viewer ${className}` : 'hdrify-viewer', style: { height: 480, ...style }, "data-fit": settings.fit, "aria-busy": loading, children: [_jsx("style", { children: IMAGE_VIEWER_CSS }), _jsx("div", { className: "hdrify-viewer-viewport", children: shownImage && (_jsx(HdrifyCanvas, { hdrifyImage: shownImage, toneMapping: settings.toneMapping, exposure: 2 ** settings.exposure, className: "hdrify-viewer-canvas" })) }), _jsx("div", { className: "hdrify-viewer-progress", hidden: barValue === undefined, children: _jsx("slot", { name: "progress-bar", children: bar && barValue !== undefined && (_jsx("div", { className: barValue === 1 ? 'hdrify-viewer-progress-fill complete' : 'hdrify-viewer-progress-fill', part: "default-progress-bar",
|
|
159
|
+
// oxlint-disable-next-line jsx-a11y/prefer-tag-over-role -- a scaled fill, styleable via ::part like <material-viewer>'s
|
|
160
|
+
role: "progressbar", "aria-label": "Loading image", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": Math.round(barValue * 100), "aria-hidden": barValue === 1, style: { transform: `scaleX(${Math.max(0.02, barValue)})` } }, bar.id)) }) }), error && (_jsx("p", { role: "alert", className: "hdrify-viewer-error", children: error })), exposureControl && (_jsxs("div", { className: "hdrify-viewer-exposure hdrify-viewer-surface", title: "Exposure (double-click to reset)", children: [_jsx("output", { children: formatStops(settings.exposure) }), _jsx("input", { type: "range", "aria-label": "Exposure in stops", min: EXPOSURE_SLIDER_RANGE.min, max: EXPOSURE_SLIDER_RANGE.max, step: 0.1, value: settings.exposure, onChange: (event) => change({ exposure: event.currentTarget.valueAsNumber }), onDoubleClick: () => change({ exposure: 0 }) })] })), panelMode !== 'hidden' && (_jsx(OptionsPanel, { settings: settings, image: shownImage, open: panelMode === 'open', onChange: change, onOpenChange: (open) => changePanel(open ? 'open' : 'closed') }))] }));
|
|
161
|
+
}
|
|
162
|
+
function OptionsPanel({ settings, image, open, onChange, onOpenChange }) {
|
|
163
|
+
const id = useId();
|
|
164
|
+
const button = useRef(null);
|
|
165
|
+
const panel = useRef(null);
|
|
166
|
+
const close = useRef(() => onOpenChange(false));
|
|
167
|
+
useEffect(() => {
|
|
168
|
+
close.current = () => onOpenChange(false);
|
|
169
|
+
});
|
|
170
|
+
// Close on Escape or a pointer press outside; composedPath sees through the element's shadow root.
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
const document = button.current?.ownerDocument;
|
|
173
|
+
if (!open || !document)
|
|
174
|
+
return;
|
|
175
|
+
const outside = (event) => {
|
|
176
|
+
const path = event.composedPath();
|
|
177
|
+
if (!path.includes(panel.current) && !path.includes(button.current))
|
|
178
|
+
close.current();
|
|
179
|
+
};
|
|
180
|
+
const escape = (event) => {
|
|
181
|
+
if (event.key !== 'Escape')
|
|
182
|
+
return;
|
|
183
|
+
close.current();
|
|
184
|
+
button.current?.focus();
|
|
185
|
+
};
|
|
186
|
+
document.addEventListener('pointerdown', outside);
|
|
187
|
+
document.addEventListener('keydown', escape);
|
|
188
|
+
return () => {
|
|
189
|
+
document.removeEventListener('pointerdown', outside);
|
|
190
|
+
document.removeEventListener('keydown', escape);
|
|
191
|
+
};
|
|
192
|
+
}, [open]);
|
|
193
|
+
return (_jsxs(_Fragment, { children: [_jsx("button", { ref: button, type: "button", className: "hdrify-viewer-toggle hdrify-viewer-surface", "aria-label": "Viewer options", title: "Viewer options", "aria-controls": id, "aria-expanded": open, onClick: () => onOpenChange(!open), children: _jsxs("svg", { viewBox: "0 0 24 24", width: "20", height: "20", fill: "none", stroke: "currentColor", strokeWidth: "1.6", "aria-hidden": "true", children: [_jsx("path", { d: "m9 3-.8 2.2-2 .9L4 5.5 2.5 8l1.6 1.7-.2 2.3L2 13.5 3.5 16l2.3-.4 1.8 1.4.4 2.4h3l.8-2.2 2-.9 2.2.6 1.5-2.5-1.6-1.7.2-2.3L18 8.5 16.5 6l-2.3.4-1.8-1.4L12 3Z", transform: "translate(2 1)" }), _jsx("circle", { cx: "12", cy: "12", r: "3" })] }) }), _jsxs("section", { ref: panel, id: id, className: "hdrify-viewer-panel hdrify-viewer-surface", "aria-label": "Viewer options", hidden: !open, children: [_jsxs("header", { children: [_jsx("strong", { children: "Viewer options" }), _jsx("button", { type: "button", "aria-label": "Close viewer options", onClick: () => onOpenChange(false), children: "\u00D7" })] }), _jsxs("label", { children: ["Tone mapping", _jsx("select", { value: settings.toneMapping, onChange: (event) => onChange({ toneMapping: event.currentTarget.value }), children: TONE_MAPPING_OPTIONS.map((option) => (_jsx("option", { value: option.value, children: option.label }, option.value))) })] }), _jsxs("label", { children: ["Exposure (EV)", _jsx("input", { type: "number", step: 0.1, value: Number(settings.exposure.toFixed(2)), onChange: (event) => {
|
|
194
|
+
const value = event.currentTarget.valueAsNumber;
|
|
195
|
+
if (Number.isFinite(value))
|
|
196
|
+
onChange({ exposure: value });
|
|
197
|
+
} })] }), _jsxs("label", { children: ["Size", _jsxs("select", { value: settings.fit, onChange: (event) => onChange({ fit: event.currentTarget.value }), children: [_jsx("option", { value: "contain", children: "Fit to view" }), _jsx("option", { value: "actual", children: "Actual size" })] })] }), image && (_jsxs("dl", { children: [_jsx("dt", { children: "Dimensions" }), _jsxs("dd", { children: [image.width, " \u00D7 ", image.height] }), _jsx("dt", { children: "Color space" }), _jsx("dd", { children: image.linearColorSpace }), typeof image.metadata?.format === 'string' && (_jsxs(_Fragment, { children: [_jsx("dt", { children: "Format" }), _jsx("dd", { children: image.metadata.format })] }))] }))] })] }));
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=ImageViewer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImageViewer.js","sources":["../src/ImageViewer.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type HdrifyImage, type ToneMappingType } from 'hdrify';
|
|
2
|
+
/** Options for `toBlob()` on `<image-viewer>` and the {@link ImageViewer} handle. */
|
|
3
|
+
export interface ImageViewerCaptureOptions {
|
|
4
|
+
/** `image/png` (default), `image/jpeg` or `image/webp`. Browsers that cannot encode a type return a PNG. */
|
|
5
|
+
type?: 'image/png' | 'image/jpeg' | 'image/webp';
|
|
6
|
+
/** Encoding quality from 0 to 1 for JPEG and WebP. */
|
|
7
|
+
quality?: number;
|
|
8
|
+
/** Output size in pixels. Without either, the image's own size; with one, the other keeps the aspect ratio. */
|
|
9
|
+
width?: number;
|
|
10
|
+
height?: number;
|
|
11
|
+
}
|
|
12
|
+
/** Tone map `image` into `canvas`, resizing the canvas to the image. The result is opaque sRGB. */
|
|
13
|
+
export declare function drawToneMapped(canvas: HTMLCanvasElement, image: HdrifyImage, toneMapping: ToneMappingType, exposure: number): void;
|
|
14
|
+
/** Output size for a capture; throws on sizes that are not positive integers up to 16384 pixels. */
|
|
15
|
+
export declare function captureSize(options: ImageViewerCaptureOptions, imageWidth: number, imageHeight: number): {
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
};
|
|
19
|
+
/** Encode `image` tone mapped with the given settings, independently of what is on screen. */
|
|
20
|
+
export declare function captureImage(image: HdrifyImage, toneMapping: ToneMappingType, exposure: number, options?: ImageViewerCaptureOptions): Promise<Blob>;
|
|
21
|
+
//# sourceMappingURL=capture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capture.d.ts","sources":["../src/capture.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,QAAQ,CAAC;AAElF,qFAAqF;AACrF,MAAM,WAAW,yBAAyB;IACxC,4GAA4G;IAC5G,IAAI,CAAC,EAAE,WAAW,GAAG,YAAY,GAAG,YAAY,CAAC;IACjD,sDAAsD;IACtD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+GAA+G;IAC/G,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,mGAAmG;AACnG,wBAAgB,cAAc,CAC5B,MAAM,EAAE,iBAAiB,EACzB,KAAK,EAAE,WAAW,EAClB,WAAW,EAAE,eAAe,EAC5B,QAAQ,EAAE,MAAM,GACf,IAAI,CAqBN;AAED,oGAAoG;AACpG,wBAAgB,WAAW,CAAC,OAAO,EAAE,yBAAyB,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM;;;EAMtG;AAED,8FAA8F;AAC9F,wBAAsB,YAAY,CAChC,KAAK,EAAE,WAAW,EAClB,WAAW,EAAE,eAAe,EAC5B,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,IAAI,CAAC,CAoBf"}
|
package/dist/capture.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { applyToneMapping } from 'hdrify';
|
|
2
|
+
/** Tone map `image` into `canvas`, resizing the canvas to the image. The result is opaque sRGB. */
|
|
3
|
+
export function drawToneMapped(canvas, image, toneMapping, exposure) {
|
|
4
|
+
const { width, height } = image;
|
|
5
|
+
canvas.width = width;
|
|
6
|
+
canvas.height = height;
|
|
7
|
+
// LDR from applyToneMapping is sRGB; use an srgb canvas so it displays correctly.
|
|
8
|
+
const ctx = canvas.getContext('2d', { colorSpace: 'srgb' }) ?? canvas.getContext('2d');
|
|
9
|
+
if (!ctx)
|
|
10
|
+
return;
|
|
11
|
+
const ldrRgb = applyToneMapping(image.data, width, height, {
|
|
12
|
+
toneMapping,
|
|
13
|
+
exposure,
|
|
14
|
+
sourceColorSpace: image.linearColorSpace,
|
|
15
|
+
});
|
|
16
|
+
const imageData = ctx.createImageData(width, height);
|
|
17
|
+
const pixels = imageData.data;
|
|
18
|
+
for (let destIndex = 0, sourceIndex = 0; sourceIndex < ldrRgb.length; destIndex += 4, sourceIndex += 3) {
|
|
19
|
+
pixels[destIndex] = ldrRgb[sourceIndex];
|
|
20
|
+
pixels[destIndex + 1] = ldrRgb[sourceIndex + 1];
|
|
21
|
+
pixels[destIndex + 2] = ldrRgb[sourceIndex + 2];
|
|
22
|
+
pixels[destIndex + 3] = 255;
|
|
23
|
+
}
|
|
24
|
+
ctx.putImageData(imageData, 0, 0);
|
|
25
|
+
}
|
|
26
|
+
/** Output size for a capture; throws on sizes that are not positive integers up to 16384 pixels. */
|
|
27
|
+
export function captureSize(options, imageWidth, imageHeight) {
|
|
28
|
+
const width = options.width ?? Math.round(((options.height ?? imageHeight) * imageWidth) / imageHeight);
|
|
29
|
+
const height = options.height ?? Math.round((width * imageHeight) / imageWidth);
|
|
30
|
+
if (![width, height].every((value) => Number.isInteger(value) && value > 0 && value <= 16384))
|
|
31
|
+
throw new RangeError('Capture width and height must be positive integers up to 16384.');
|
|
32
|
+
return { width, height };
|
|
33
|
+
}
|
|
34
|
+
/** Encode `image` tone mapped with the given settings, independently of what is on screen. */
|
|
35
|
+
export async function captureImage(image, toneMapping, exposure, options = {}) {
|
|
36
|
+
const { width, height } = captureSize(options, image.width, image.height);
|
|
37
|
+
let canvas = document.createElement('canvas');
|
|
38
|
+
drawToneMapped(canvas, image, toneMapping, exposure);
|
|
39
|
+
if (width !== image.width || height !== image.height) {
|
|
40
|
+
const scaled = document.createElement('canvas');
|
|
41
|
+
scaled.width = width;
|
|
42
|
+
scaled.height = height;
|
|
43
|
+
const ctx = scaled.getContext('2d');
|
|
44
|
+
ctx.imageSmoothingQuality = 'high';
|
|
45
|
+
ctx.drawImage(canvas, 0, 0, width, height);
|
|
46
|
+
canvas = scaled;
|
|
47
|
+
}
|
|
48
|
+
return new Promise((resolve, reject) => canvas.toBlob((blob) => (blob ? resolve(blob) : reject(new Error('The browser could not encode the image.'))), options.type ?? 'image/png', options.quality));
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=capture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capture.js","sources":["../src/capture.ts"],"names":[],"mappings":"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"}
|
package/dist/decode.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type HdrifyImage } from 'hdrify';
|
|
2
|
+
/**
|
|
3
|
+
* Decode an OpenEXR, Radiance HDR or JPEG gain map (Ultra HDR / Adobe) file, detected from its content, not its
|
|
4
|
+
* name. Gzip-compressed files (such as `.hdr.gz`) are decompressed first.
|
|
5
|
+
*/
|
|
6
|
+
export declare function decodeHdrifyImage(input: ArrayBuffer | Uint8Array): Promise<HdrifyImage>;
|
|
7
|
+
//# sourceMappingURL=decode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decode.d.ts","sources":["../src/decode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAqC,MAAM,QAAQ,CAAC;AAS7E;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,WAAW,GAAG,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAO7F"}
|
package/dist/decode.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { readExr, readHdr, readJpegGainMap } from 'hdrify';
|
|
2
|
+
const startsWith = (bytes, magic) => magic.every((byte, i) => bytes[i] === byte);
|
|
3
|
+
async function gunzip(bytes) {
|
|
4
|
+
const stream = new Blob([bytes]).stream().pipeThrough(new DecompressionStream('gzip'));
|
|
5
|
+
return new Uint8Array(await new Response(stream).arrayBuffer());
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Decode an OpenEXR, Radiance HDR or JPEG gain map (Ultra HDR / Adobe) file, detected from its content, not its
|
|
9
|
+
* name. Gzip-compressed files (such as `.hdr.gz`) are decompressed first.
|
|
10
|
+
*/
|
|
11
|
+
export async function decodeHdrifyImage(input) {
|
|
12
|
+
let bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
|
|
13
|
+
if (startsWith(bytes, [0x1f, 0x8b]))
|
|
14
|
+
bytes = await gunzip(bytes);
|
|
15
|
+
if (startsWith(bytes, [0x76, 0x2f, 0x31, 0x01]))
|
|
16
|
+
return readExr(bytes);
|
|
17
|
+
if (startsWith(bytes, [0x23, 0x3f]))
|
|
18
|
+
return readHdr(bytes); // "#?RADIANCE" / "#?RGBE"
|
|
19
|
+
if (startsWith(bytes, [0xff, 0xd8]))
|
|
20
|
+
return readJpegGainMap(bytes);
|
|
21
|
+
throw new Error('Unsupported image: expected OpenEXR, Radiance HDR or a JPEG gain map');
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=decode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decode.js","sources":["../src/decode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAE7E,MAAM,UAAU,GAAG,CAAC,KAAiB,EAAE,KAAe,EAAW,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;AAEhH,KAAK,UAAU,MAAM,CAAC,KAAiB;IACrC,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,CAAC,KAAgC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,WAAW,CAAC,IAAI,mBAAmB,CAAC,MAAM,CAAC,CAAC,CAAC;IAClH,OAAO,IAAI,UAAU,CAAC,MAAM,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;AAClE,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,KAA+B;IACrE,IAAI,KAAK,GAAG,KAAK,YAAY,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC;IACxE,IAAI,UAAU,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAAE,KAAK,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,CAAC;IACjE,IAAI,UAAU,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;QAAE,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC;IACvE,IAAI,UAAU,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAAE,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,0BAA0B;IACtF,IAAI,UAAU,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAAE,OAAO,eAAe,CAAC,KAAK,CAAC,CAAC;IACnE,MAAM,IAAI,KAAK,CAAC,sEAAsE,CAAC,CAAC;AAC1F,CAAC"}
|