import { type DataTextureLoaderTexData, type WebGPURenderer } from 'three/webgpu'; import { type GainMapMetadata } from '../../core/types'; import { GainMapNode } from '../../decode/webgpu/nodes/GainMapNode'; import { type WorkerInterfaceImplementation } from '../../worker-types'; import { type CompressedImage, type CompressOptions, type GainmapEncodingParameters } from '../types'; import { GainMapEncoderNode } from './nodes/GainMapEncoderNode'; import { type HDRInput, SDRRenditionNode, type SDRRenditionNodeOptions } from './nodes/SDRRenditionNode'; /** * Options of a {@link GainMapEncoder} * * @category Encoding Parameters * @group Encoding Parameters */ export type GainMapEncoderOptions = Partial & SDRRenditionNodeOptions; /** * Raw (uncompressed) result of a {@link GainMapEncoder} * * @category Encoding Parameters * @group Encoding Parameters */ export type GainMapEncoderRawResult = { /** SDR rendition, rows top to bottom */ sdr: ImageData; /** Gain map, rows top to bottom */ gainMap: ImageData; metadata: GainMapMetadata; }; /** * Compressed result of a {@link GainMapEncoder}, * can be passed as is to `encodeJPEGMetadata` when compressed as `image/jpeg` * * @category Encoding Parameters * @group Encoding Parameters */ export type GainMapEncoderCompressedResult = GainMapMetadata & { sdr: CompressedImage; gainMap: CompressedImage; rawSDR: Uint8ClampedArray; rawGainMap: Uint8ClampedArray; }; /** * Encodes an HDR image into an SDR rendition + a gain map, on the GPU, with TSL nodes. * * {@link GainMapEncoder.sdr} and {@link GainMapEncoder.gainMap} are live texture nodes: * every encoding parameter is a uniform, changing one re-renders the affected nodes once, when they're next used. * No renderer is needed until the result is read back with {@link GainMapEncoder.read} or {@link GainMapEncoder.compress}. * * Read back images are oriented top to bottom (like `ImageData`), no `flipY` is needed when compressing them. * * @category Encoding Functions * @group Encoding Functions * * @example * import { GainMapEncoder } from '@monogrid/gainmap-js/encode/webgpu' * import { encodeJPEGMetadata } from '@monogrid/gainmap-js/libultrahdr' * import { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader.js' * import { AgXToneMapping, MeshBasicNodeMaterial, WebGPURenderer } from 'three/webgpu' * * const renderer = new WebGPURenderer() * await renderer.init() * * const image = await new EXRLoader().loadAsync('image.exr') * const encoder = new GainMapEncoder(image, { toneMapping: AgXToneMapping }) * // encode the full HDR range * await encoder.fitMaxContentBoost(renderer) * * // live previews, they follow any parameter change * sdrMaterial.colorNode = encoder.sdr * gainMapMaterial.colorNode = encoder.gainMap * hdrMaterial.colorNode = encoder.createPreviewNode() // decodes the encoded result * * encoder.sdr.exposure.value = 0.9 * encoder.gainMap.gamma.value.setScalar(1.1) * * // compress and embed into a single JPEG file * const result = await encoder.compress(renderer, { mimeType: 'image/jpeg', quality: 0.9 }) * const jpeg = encodeJPEGMetadata(result) */ export declare class GainMapEncoder { /** * The SDR rendition node */ readonly sdr: SDRRenditionNode; /** * The gain map node */ readonly gainMap: GainMapEncoderNode; private _ownedTexture; /** * @param image the HDR image: a texture, raw texture data (e.g. from a worker) or a TSL node (which requires `width` and `height`) * @param options encoding parameters, all of them can be changed later through the node uniforms */ constructor(image: HDRInput | DataTextureLoaderTexData, options?: GainMapEncoderOptions); /** Width of the encoded images */ get width(): number; /** Height of the encoded images */ get height(): number; /** * The gain map metadata matching the current parameters */ get metadata(): GainMapMetadata; /** * Finds the max value of the HDR image and uses it as `maxContentBoost`, * so the full HDR range is encoded. * * @param renderer * @returns the new `maxContentBoost` */ fitMaxContentBoost(renderer: WebGPURenderer): Promise; /** * Finds the actual range of the per pixel gain between the HDR image and its current SDR rendition * and uses it as `minContentBoost` / `maxContentBoost`, so that the gain map can restore the HDR image exactly. * * Unlike {@link GainMapEncoder.fitMaxContentBoost} this takes the SDR rendition into account: * tone mapping operators like Cineon, AgX or ACES make some pixels of the SDR rendition brighter * (or less saturated) than the HDR image, which requires a `minContentBoost` below 1, * and they compress highlights more than a plain clamp, which may require a `maxContentBoost` above the HDR max. * Exposure, brightness, contrast and saturation have the same effect. * * Call it again whenever the SDR rendition or the offsets change. * * @param renderer * @returns the new `minContentBoost` and `maxContentBoost` */ fitContentBoost(renderer: WebGPURenderer): Promise<{ min: number; max: number; }>; /** * Creates a {@link GainMapNode} decoding the encoded result on the fly, * it follows any change to the encoding parameters. * Its `maxDisplayBoost` is initialized to restore the full HDR range, and is not changed afterwards. */ createPreviewNode(): GainMapNode; /** * Renders the SDR rendition and the gain map, only if something changed since the last render * * @param renderer */ render(renderer: WebGPURenderer): void; /** * Renders (if needed) and reads back the raw SDR rendition and gain map * * @param renderer */ read(renderer: WebGPURenderer): Promise; /** * Renders (if needed), reads back and compresses the SDR rendition and gain map * * @param renderer * @param options compression options, `flipY` is not needed * @throws {Error} if the browser does not support [createImageBitmap](https://caniuse.com/createimagebitmap) */ compress(renderer: WebGPURenderer, options: CompressOptions & { withWorker?: WorkerInterfaceImplementation; }): Promise; /** * Disposes the nodes, and the HDR texture if it was created by the encoder from raw data */ dispose(): void; }