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 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
- <img src="https://hdrify.ben3d.ca/logo192.png" alt="HDRify logo" width="192" height="192">
18
+ > Display HDR images in React with configurable tone mapping.
4
19
 
5
- [![NPM Package][npm]][npm-url]
6
- [![NPM Downloads][npm-downloads]][npmtrends-url]
7
- [![Tests][tests-badge]][tests-url]
8
- [![Coverage][coverage-badge]][coverage-url]
9
- [![Discord][discord-badge]][discord-url]
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
- ### Props
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).
@@ -1,4 +1,4 @@
1
- import { type HdrifyImage, type ToneMappingType } from 'hdrify';
1
+ import type { HdrifyImage, ToneMappingType } from 'hdrify';
2
2
  import type * as React from 'react';
3
3
  export interface HdrifyCanvasProps {
4
4
  hdrifyImage: HdrifyImage;
@@ -1 +1 @@
1
- {"version":3,"file":"HdrifyCanvas.d.ts","sourceRoot":"","sources":["../src/HdrifyCanvas.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,QAAQ,CAAC;AAClF,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AAGpC,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,CAiDxC"}
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"}
@@ -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
- renderToCanvas(hdrifyImage, exposure, toneMapping);
41
- }, [hdrifyImage, exposure, toneMapping, renderToCanvas]);
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
@@ -1 +1 @@
1
- {"version":3,"file":"HdrifyCanvas.js","sourceRoot":"","sources":["../src/HdrifyCanvas.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,gBAAgB,EAA0C,MAAM,QAAQ,CAAC;AAElF,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAWvD,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;QAC9B,SAA8D,CAAC,OAAO,GAAG,EAAE,CAAC;QAC7E,IAAI,YAAY,EAAE,CAAC;YAChB,YAAiE,CAAC,OAAO,GAAG,EAAE,CAAC;QAClF,CAAC;IACH,CAAC,EACD,CAAC,YAAY,CAAC,CACf,CAAC;IAEF,MAAM,cAAc,GAAG,WAAW,CAAC,CAAC,IAAiB,EAAE,GAAW,EAAE,IAAqB,EAAQ,EAAE;QACjG,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC;QACjC,IAAI,CAAC,MAAM;YAAE,OAAO;QAEpB,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;QAC/B,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;QACrB,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;QACvB,8EAA8E;QAC9E,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QAEvF,IAAI,CAAC,GAAG;YAAE,OAAO;QAEjB,MAAM,MAAM,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE;YACxD,WAAW,EAAE,IAAI;YACjB,QAAQ,EAAE,GAAG;YACb,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;SACxC,CAAC,CAAC;QAEH,MAAM,eAAe,GAAG,GAAG,CAAC,eAAe,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QAC3D,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,CAAC;QACpC,MAAM,YAAY,GAAG,KAAK,GAAG,MAAM,GAAG,CAAC,CAAC;QACxC,KAAK,IAAI,SAAS,GAAG,CAAC,EAAE,WAAW,GAAG,CAAC,EAAE,WAAW,GAAG,YAAY,EAAE,SAAS,IAAI,CAAC,EAAE,WAAW,IAAI,CAAC,EAAE,CAAC;YACtG,MAAM,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,WAAW,CAAE,CAAC;YACzC,MAAM,CAAC,SAAS,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,WAAW,GAAG,CAAC,CAAE,CAAC;YACjD,MAAM,CAAC,SAAS,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,WAAW,GAAG,CAAC,CAAE,CAAC;YACjD,MAAM,CAAC,SAAS,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC;QAC9B,CAAC;QAED,GAAG,CAAC,YAAY,CAAC,eAAe,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAC1C,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,SAAS,CAAC,GAAG,EAAE;QACb,cAAc,CAAC,WAAW,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC;IACrD,CAAC,EAAE,CAAC,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,cAAc,CAAC,CAAC,CAAC;IAEzD,OAAO,iBAAQ,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAI,CAAC;AACvD,CAAC"}
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":";AACA,OAAO,EAAgC,SAAS,EAAE,KAAK,EAAE,mBAAmB,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAC9G,OAAO,EAAE,YAAY,EAAkC,MAAM,cAAc,CAAC;AAC5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAmD,YAAY,EAAE,MAAM,eAAe,CAAC;AAC9F,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAe/C,MAAM,CAAC,MAAM,6BAA6B,GAAkC;IAC1E,WAAW,EAAE,SAAS;IACtB,QAAQ,EAAE,CAAC;IACX,GAAG,EAAE,SAAS;CACf,CAAC;AAEF,MAAM,CAAC,MAAM,oBAAoB,GAAyD;IACxF,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE;IACtC,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE;IAC9B,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE;IAChC,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE;CACzC,CAAC;AAEF,mFAAmF;AACnF,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE,EAAW,CAAC;AA2CpE,SAAS,eAAe,CAAC,QAAsC;IAC7D,OAAO,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC;AACjG,CAAC;AAED,SAAS,WAAW,CAAC,KAAa;IAChC,OAAO,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AACjE,CAAC;AAED,4GAA4G;AAC5G,MAAM,UAAU,GAAG,GAAG,EAAE,CAAC,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,qBAAqB,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;AAE7G,sFAAsF;AACtF,MAAM,cAAc,GAAG,GAAG,CAAC;AAC3B,MAAM,WAAW,GAAqB,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AACjF,IAAI,SAAS,GAAG,CAAC,CAAC;AAQlB;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,EAC1B,GAAG,EACH,IAAI,EACJ,KAAK,EACL,WAAW,EACX,QAAQ,EACR,GAAG,EACH,aAAa,GAAG,QAAQ,EACxB,eAAe,GAAG,KAAK,EACvB,SAAS,EACT,KAAK,EACL,MAAM,EACN,OAAO,EACP,UAAU,EACV,gBAAgB,EAChB,qBAAqB,EACrB,GAAG,GACc;IACjB,MAAM,SAAS,GAAG,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,CAAC,CAAC;IACnG,SAAS,CAAC,GAAG,EAAE;QACb,SAAS,CAAC,OAAO,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,CAAC;IAC/F,CAAC,CAAC,CAAC;IAEH,gGAAgG;IAChG,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAsB,GAAG,EAAE,CAAC,CAAC;QACnE,GAAG,6BAA6B;QAChC,GAAG,eAAe,CAAC,EAAE,WAAW,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC;KACnD,CAAC,CAAC,CAAC;IACJ,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,EAAE,WAAW,EAAE,QAAQ,EAAE,GAAG,EAAE,aAAa,EAAE,CAAC,CAAC;IACxF,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAoB,aAAa,CAAC,CAAC;IAC7E,IACE,QAAQ,CAAC,WAAW,KAAK,WAAW;QACpC,QAAQ,CAAC,QAAQ,KAAK,QAAQ;QAC9B,QAAQ,CAAC,GAAG,KAAK,GAAG;QACpB,QAAQ,CAAC,aAAa,KAAK,aAAa,EACxC,CAAC;QACD,WAAW,CAAC,EAAE,WAAW,EAAE,QAAQ,EAAE,GAAG,EAAE,aAAa,EAAE,CAAC,CAAC;QAC3D,WAAW,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,GAAG,eAAe,CAAC,EAAE,WAAW,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAC/F,IAAI,QAAQ,CAAC,aAAa,KAAK,aAAa;YAAE,YAAY,CAAC,aAAa,CAAC,CAAC;IAC5E,CAAC;IAED,MAAM,MAAM,GAAG,CAAC,KAAmC,EAAE,EAAE;QACrD,MAAM,IAAI,GAAG,EAAE,GAAG,QAAQ,EAAE,GAAG,KAAK,EAAE,CAAC;QACvC,WAAW,CAAC,IAAI,CAAC,CAAC;QAClB,SAAS,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC,IAAI,CAAC,CAAC;IAC7C,CAAC,CAAC;IACF,MAAM,WAAW,GAAG,CAAC,IAAuB,EAAE,EAAE;QAC9C,YAAY,CAAC,IAAI,CAAC,CAAC;QACnB,SAAS,CAAC,OAAO,CAAC,qBAAqB,EAAE,CAAC,IAAI,CAAC,CAAC;IAClD,CAAC,CAAC;IAEF,MAAM,CAAC,SAAS,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,eAAe,EAAE,CAAC,CAAC;IAC1D,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;IAE3D,0GAA0G;IAC1G,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC;IACjD,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAY,EAAE,CAAC,CAAC;IACpD,sGAAsG;IACtG,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAuC,IAAI,CAAC,CAAC;IAC3E,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC;IACrC,SAAS,CAAC,GAAG,EAAE;QACb,SAAS,CAAC,OAAO,EAAE,CAAC;QACpB,QAAQ,CAAC,OAAO,GAAG,WAAW,CAAC;QAC/B,IAAI,KAAK;YAAE,OAAO,CAAC,eAAe;QAClC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,SAAS,CAAC,MAAM,EAAE,CAAC;YACnB,OAAO;QACT,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC;QAC9B,MAAM,EAAE,GAAG,EAAE,SAAS,CAAC;QACvB,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,MAAM,MAAM,GAAG,CAAC,KAAmC,EAAE,aAAqB,EAAE,EAAE;YAC5E,IAAI,MAAM,CAAC,OAAO;gBAAE,OAAO;YAC3B,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;YAC3C,MAAM,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;YAC/B,SAAS,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,EAAE,GAAG,QAAQ,CAAC,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;QACzF,CAAC,CAAC;QACF,MAAM,iBAAiB,GAAG,KAAK,EAAE,GAAW,EAAE,EAAE;YAC9C,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;YACtB,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;YAC9C,IAAI,CAAC,QAAQ,CAAC,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,kBAAkB,GAAG,UAAU,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;YACpF,OAAO,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE;gBACjD,QAAQ,CAAC,OAAO,GAAG,QAAQ,CAAC;gBAC5B,MAAM,CAAC,UAAU,EAAE,cAAc,GAAG,CAAC,QAAQ,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC,CAAC;YAChE,CAAC,CAAC,CAAC;QACL,CAAC,CAAC;QACF,CAAC,KAAK,IAAI,EAAE;YACV,MAAM,KAAK,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;YACpF,MAAM,CAAC,QAAQ,EAAE,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAClE,MAAM,UAAU,EAAE,CAAC;YACnB,MAAM,CAAC,cAAc,EAAE,CAAC;YACxB,MAAM,OAAO,GAAG,MAAM,iBAAiB,CAAC,KAAK,CAAC,CAAC;YAC/C,MAAM,CAAC,cAAc,EAAE,CAAC;YACxB,SAAS,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;QACxC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,MAAe,EAAE,EAAE;YAC7B,IAAI,MAAM,CAAC,OAAO;gBAAE,OAAO;YAC3B,MAAM,KAAK,GAAG,MAAM,YAAY,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;YAC3E,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACxB,MAAM,CAAC,IAAI,CAAC,CAAC;YACb,SAAS,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YAC5C,SAAS,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;QACH,OAAO,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;IAClC,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;IAC/B,MAAM,OAAO,GAAG,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM,CAAC;IACjE,6DAA6D;IAC7D,MAAM,UAAU,GAAG,KAAK,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9E,MAAM,KAAK,GAAG,CAAC,OAAO,IAAI,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IAC1E,MAAM,QAAQ,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC;IAElD,2GAA2G;IAC3G,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC;IAC/C,MAAM,MAAM,GAAG,MAAM,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;IAClD,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,CAAC,OAAO,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;IAC9C,CAAC,CAAC,CAAC;IACH,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,KAAK;YAAE,OAAO;QACnB,SAAS,CAAC,MAAM,EAAE,CAAC;QACnB,SAAS,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,EAAE,GAAG,QAAQ,CAAC,OAAO,EAAE,aAAa,EAAE,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC3F,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC;IACpC,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC;IAEvB,mBAAmB,CACjB,GAAG,EACH,GAAG,EAAE,CAAC,CAAC;QACL,SAAS,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE;QACtC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YACxB,MAAM,SAAS,CAAC,SAAS,EAAE,CAAC;YAC5B,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC;YACrE,IAAI,CAAC,OAAO;gBAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;YACrD,OAAO,YAAY,CAAC,OAAO,EAAE,eAAe,CAAC,WAAW,EAAE,CAAC,IAAI,eAAe,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACpG,CAAC;KACF,CAAC,EACF,CAAC,SAAS,CAAC,CACZ,CAAC;IAEF,OAAO,CACL,eACE,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,iBAAiB,SAAS,EAAE,CAAC,CAAC,CAAC,eAAe,EACrE,KAAK,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,EAAE,cACtB,QAAQ,CAAC,GAAG,eACX,OAAO,aAElB,0BAAQ,gBAAgB,GAAS,EACjC,cAAK,SAAS,EAAC,wBAAwB,YACpC,UAAU,IAAI,CACb,KAAC,YAAY,IACX,WAAW,EAAE,UAAU,EACvB,WAAW,EAAE,QAAQ,CAAC,WAAW,EACjC,QAAQ,EAAE,CAAC,IAAI,QAAQ,CAAC,QAAQ,EAChC,SAAS,EAAC,sBAAsB,GAChC,CACH,GACG,EACN,cAAK,SAAS,EAAC,wBAAwB,EAAC,MAAM,EAAE,QAAQ,KAAK,SAAS,YAEpE,eAAM,IAAI,EAAC,cAAc,YACtB,GAAG,IAAI,QAAQ,KAAK,SAAS,IAAI,CAChC,cAEE,SAAS,EAAE,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,sCAAsC,CAAC,CAAC,CAAC,6BAA6B,EAClG,IAAI,EAAC,sBAAsB;wBAC3B,yHAAyH;wBACzH,IAAI,EAAC,aAAa,gBACP,eAAe,mBACX,CAAC,mBACD,GAAG,mBACH,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,GAAG,CAAC,iBAC5B,QAAQ,KAAK,CAAC,EAC3B,KAAK,EAAE,EAAE,SAAS,EAAE,UAAU,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,GAAG,EAAE,IAVtD,GAAG,CAAC,EAAE,CAWX,CACH,GACI,GACH,EACL,KAAK,IAAI,CACR,YAAG,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,qBAAqB,YAC5C,KAAK,GACJ,CACL,EACA,eAAe,IAAI,CAClB,eAAK,SAAS,EAAC,8CAA8C,EAAC,KAAK,EAAC,kCAAkC,aACpG,2BAAS,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAU,EACjD,gBACE,IAAI,EAAC,OAAO,gBACD,mBAAmB,EAC9B,GAAG,EAAE,qBAAqB,CAAC,GAAG,EAC9B,GAAG,EAAE,qBAAqB,CAAC,GAAG,EAC9B,IAAI,EAAE,GAAG,EACT,KAAK,EAAE,QAAQ,CAAC,QAAQ,EACxB,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,aAAa,CAAC,aAAa,EAAE,CAAC,EAC5E,aAAa,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,GAC5C,IACE,CACP,EACA,SAAS,KAAK,QAAQ,IAAI,CACzB,KAAC,YAAY,IACX,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,UAAU,EACjB,IAAI,EAAE,SAAS,KAAK,MAAM,EAC1B,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,GAC7D,CACH,IACG,CACP,CAAC;AACJ,CAAC;AAUD,SAAS,YAAY,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAqB;IACxF,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;IACnB,MAAM,MAAM,GAAG,MAAM,CAAoB,IAAI,CAAC,CAAC;IAC/C,MAAM,KAAK,GAAG,MAAM,CAAc,IAAI,CAAC,CAAC;IACxC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;IAChD,SAAS,CAAC,GAAG,EAAE;QACb,KAAK,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IACH,mGAAmG;IACnG,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,EAAE,aAAa,CAAC;QAC/C,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO;QAC/B,MAAM,OAAO,GAAG,CAAC,KAAY,EAAE,EAAE;YAC/B,MAAM,IAAI,GAAG,KAAK,CAAC,YAAY,EAAE,CAAC;YAClC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAQ,CAAC;gBAAE,KAAK,CAAC,OAAO,EAAE,CAAC;QACzF,CAAC,CAAC;QACF,MAAM,MAAM,GAAG,CAAC,KAAoB,EAAE,EAAE;YACtC,IAAI,KAAK,CAAC,GAAG,KAAK,QAAQ;gBAAE,OAAO;YACnC,KAAK,CAAC,OAAO,EAAE,CAAC;YAChB,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;QAC1B,CAAC,CAAC;QACF,QAAQ,CAAC,gBAAgB,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;QAClD,QAAQ,CAAC,gBAAgB,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QAC7C,OAAO,GAAG,EAAE;YACV,QAAQ,CAAC,mBAAmB,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;YACrD,QAAQ,CAAC,mBAAmB,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QAClD,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IACX,OAAO,CACL,8BACE,iBACE,GAAG,EAAE,MAAM,EACX,IAAI,EAAC,QAAQ,EACb,SAAS,EAAC,4CAA4C,gBAC3C,gBAAgB,EAC3B,KAAK,EAAC,gBAAgB,mBACP,EAAE,mBACF,IAAI,EACnB,OAAO,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,YAElC,eACE,OAAO,EAAC,WAAW,EACnB,KAAK,EAAC,IAAI,EACV,MAAM,EAAC,IAAI,EACX,IAAI,EAAC,MAAM,EACX,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,KAAK,iBACL,MAAM,aAElB,eACE,CAAC,EAAC,6JAA6J,EAC/J,SAAS,EAAC,gBAAgB,GAC1B,EACF,iBAAQ,EAAE,EAAC,IAAI,EAAC,EAAE,EAAC,IAAI,EAAC,CAAC,EAAC,GAAG,GAAG,IAC5B,GACC,EACT,mBACE,GAAG,EAAE,KAAK,EACV,EAAE,EAAE,EAAE,EACN,SAAS,EAAC,2CAA2C,gBAC1C,gBAAgB,EAC3B,MAAM,EAAE,CAAC,IAAI,aAEb,6BACE,8CAA+B,EAC/B,iBAAQ,IAAI,EAAC,QAAQ,gBAAY,sBAAsB,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,uBAEjF,IACF,EACT,4CAEE,iBACE,KAAK,EAAE,QAAQ,CAAC,WAAW,EAC3B,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,aAAa,CAAC,KAAwB,EAAE,CAAC,YAE3F,oBAAoB,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CACpC,iBAA2B,KAAK,EAAE,MAAM,CAAC,KAAK,YAC3C,MAAM,CAAC,KAAK,IADF,MAAM,CAAC,KAAK,CAEhB,CACV,CAAC,GACK,IACH,EACR,6CAEE,gBACE,IAAI,EAAC,QAAQ,EACb,IAAI,EAAE,GAAG,EACT,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAC3C,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE;oCAClB,MAAM,KAAK,GAAG,KAAK,CAAC,aAAa,CAAC,aAAa,CAAC;oCAChD,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;wCAAE,QAAQ,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;gCAC5D,CAAC,GACD,IACI,EACR,oCAEE,kBAAQ,KAAK,EAAE,QAAQ,CAAC,GAAG,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,aAAa,CAAC,KAAiB,EAAE,CAAC,aACxG,iBAAQ,KAAK,EAAC,SAAS,4BAAqB,EAC5C,iBAAQ,KAAK,EAAC,QAAQ,4BAAqB,IACpC,IACH,EACP,KAAK,IAAI,CACR,yBACE,sCAAmB,EACnB,yBACG,KAAK,CAAC,KAAK,cAAK,KAAK,CAAC,MAAM,IAC1B,EACL,uCAAoB,EACpB,uBAAK,KAAK,CAAC,gBAAgB,GAAM,EAChC,OAAO,KAAK,CAAC,QAAQ,EAAE,MAAM,KAAK,QAAQ,IAAI,CAC7C,8BACE,kCAAe,EACf,uBAAK,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAM,IAC/B,CACJ,IACE,CACN,IACO,IACT,CACJ,CAAC;AACJ,CAAC"}
@@ -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"}
@@ -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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"}
@@ -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"}