diff --git a/Documentation/content/docs/gallery/HDRReaderWithIcon.jpg b/Documentation/content/docs/gallery/HDRReaderWithIcon.jpg
new file mode 100644
index 00000000000..75003ce6039
Binary files /dev/null and b/Documentation/content/docs/gallery/HDRReaderWithIcon.jpg differ
diff --git a/Documentation/content/examples/index.md b/Documentation/content/examples/index.md
index 1207c49b9fa..6615015ceac 100644
--- a/Documentation/content/examples/index.md
+++ b/Documentation/content/examples/index.md
@@ -184,6 +184,7 @@ This will allow you to see the some live code running in your browser. Just pick
[![HttpDataSetSeriesReader Example][HttpDataSetSeriesReaderWithIcon]](./HttpDataSetSeriesReader.html "Import a VTK dataset with time support.")
[![HttpSceneLoader Example][HttpSceneLoaderWithIcon]](./HttpSceneLoader.html "Import a VTK scene (data + representation)")
[![OfflineLocalView Example][OfflineLocalViewWithIcon]](./OfflineLocalView.html "Load a serialized scene (VTKSZ)")
+[![HDRReader Example][HDRReaderWithIcon]](./HDRReader.html "Load an HDR image")
@@ -203,6 +204,7 @@ This will allow you to see the some live code running in your browser. Just pick
[HttpDataSetSeriesReaderWithIcon]: ../docs/gallery/HttpDataSetSeriesReaderWithIcon.gif
[HttpSceneLoaderWithIcon]: ../docs/gallery/HttpSceneLoaderWithIcon.jpg
[OfflineLocalViewWithIcon]: ../docs/gallery/OfflineLocalViewWithIcon.jpg
+[HDRReaderWithIcon]: ../docs/gallery/HDRReaderWithIcon.jpg
# Actors
diff --git a/Sources/Common/Core/Math/index.d.ts b/Sources/Common/Core/Math/index.d.ts
index d54f888a6a5..f597f2dffba 100755
--- a/Sources/Common/Core/Math/index.d.ts
+++ b/Sources/Common/Core/Math/index.d.ts
@@ -51,6 +51,11 @@ export function swapColumnsMatrix_nxn(
*/
export function Pi(): number;
+/**
+ * Calculates x times (2 to the power of exponent).
+ */
+export function ldexp(x: number, exponent: number): number;
+
/**
* Convert degrees to radians.
* @param {Number} deg The value in degrees.
diff --git a/Sources/Common/Core/Math/index.js b/Sources/Common/Core/Math/index.js
index f7f5997223a..3ba8a25c0ed 100644
--- a/Sources/Common/Core/Math/index.js
+++ b/Sources/Common/Core/Math/index.js
@@ -57,6 +57,16 @@ export function createArray(size = 3) {
export const Pi = () => Math.PI;
+export function ldexp(x, exponent) {
+ if (exponent > 1023) {
+ return x * 2 ** 1023 * 2 ** (exponent - 1023);
+ }
+ if (exponent < -1074) {
+ return x * 2 ** -1074 * 2 ** (exponent + 1074);
+ }
+ return x * 2 ** exponent;
+}
+
export function radiansFromDegrees(deg) {
return (deg / 180) * Math.PI;
}
@@ -2227,6 +2237,7 @@ export function float2CssRGBA(rgbArray) {
export default {
Pi,
+ ldexp,
radiansFromDegrees,
degreesFromRadians,
round,
diff --git a/Sources/IO/Image/HDRReader/Utils.js b/Sources/IO/Image/HDRReader/Utils.js
new file mode 100644
index 00000000000..259885030e5
--- /dev/null
+++ b/Sources/IO/Image/HDRReader/Utils.js
@@ -0,0 +1,43 @@
+import * as vtkMath from 'vtk.js/Sources/Common/Core/Math';
+
+/**
+ * Read a line from a Uint8Array
+ * @param {Uint8Array} uint8array
+ * @param {number} startIndex
+ * @returns string
+ */
+function readLine(uint8array, startIndex) {
+ let line = '';
+ let character = '';
+ for (let i = startIndex; i < uint8array.length - startIndex; i++) {
+ character = String.fromCharCode(uint8array[i]);
+ if (character === '\n') {
+ break;
+ }
+ line += character;
+ }
+ return line;
+}
+
+/**
+ * Convert rgbe to float
+ * @param {Array} rgbe The rgbe array
+ * @param {Array} floats The output array
+ * @param {number} exposure The exposure value
+ */
+function rgbe2float(rgbe, exposure, floats = []) {
+ if (rgbe[3] > 0) {
+ /* nonzero pixel */
+ const f = vtkMath.ldexp(1.0, rgbe[3] - (128 + 8)) / exposure;
+ floats[0] = rgbe[0] * f;
+ floats[1] = rgbe[1] * f;
+ floats[2] = rgbe[2] * f;
+ } else {
+ floats[0] = 0;
+ floats[1] = 0;
+ floats[2] = 0;
+ }
+ return floats;
+}
+
+export { readLine, rgbe2float };
diff --git a/Sources/IO/Image/HDRReader/example/index.js b/Sources/IO/Image/HDRReader/example/index.js
new file mode 100644
index 00000000000..8c802521a8b
--- /dev/null
+++ b/Sources/IO/Image/HDRReader/example/index.js
@@ -0,0 +1,122 @@
+import '@kitware/vtk.js/favicon';
+
+// Load the rendering pieces we want to use (for both WebGL and WebGPU)
+import '@kitware/vtk.js/Rendering/Profiles/Geometry';
+
+import vtkFullScreenRenderWindow from '@kitware/vtk.js/Rendering/Misc/FullScreenRenderWindow';
+import vtkActor from '@kitware/vtk.js/Rendering/Core/Actor';
+import vtkMapper from '@kitware/vtk.js/Rendering/Core/Mapper';
+import vtkPlaneSource from '@kitware/vtk.js/Filters/Sources/PlaneSource';
+import vtkHDRReader from '@kitware/vtk.js/IO/Image/HDRReader';
+import vtkTexture from '@kitware/vtk.js/Rendering/Core/Texture';
+import vtkURLExtract from '@kitware/vtk.js/Common/Core/URLExtract';
+
+// ----------------------------------------------------------------------------
+// Example code
+// ----------------------------------------------------------------------------
+const userParams = vtkURLExtract.extractURLParameters();
+
+const reader = vtkHDRReader.newInstance();
+const texture = vtkTexture.newInstance();
+const planeSource = vtkPlaneSource.newInstance();
+const mapper = vtkMapper.newInstance();
+const actor = vtkActor.newInstance();
+mapper.setInputConnection(planeSource.getOutputPort());
+actor.setMapper(mapper);
+
+// ----------------------------------------------------------------------------
+// Use a file reader to load a local file
+// ----------------------------------------------------------------------------
+
+const myContainer = document.querySelector('body');
+const fileContainer = document.createElement('div');
+fileContainer.innerHTML =
+ '
Select a hdr file.
';
+myContainer.appendChild(fileContainer);
+
+const fileInput = fileContainer.querySelector('input');
+
+function zoomCameraToFitPlane(camera, planeWidth, planeHeight) {
+ const fov = 60; // Field of view in degrees
+
+ // Calculate the distance needed to fit the plane in view
+ const distance =
+ Math.max(planeWidth, planeHeight) /
+ (2 * Math.tan((fov * Math.PI) / 180 / 2));
+
+ // Set camera position
+ camera.setPosition(planeWidth / 2, planeHeight / 2, distance);
+ camera.setFocalPoint(planeWidth / 2, planeHeight / 2, 0);
+ camera.setViewUp(0, 1, 0);
+
+ // Set parallel scale for orthographic projection
+ camera.setParallelScale(planeHeight / 2);
+}
+
+function update() {
+ // Get the vtkImageData from the reader
+ const imageData = reader.getOutputData();
+
+ // Set the vtkImageData as the texture input
+ texture.setInputData(imageData);
+
+ // Get the image's extent and spacing
+ const [xMin, xMax, yMin, yMax] = imageData.getExtent();
+ const [spacingX, spacingY] = imageData.getSpacing();
+
+ // Calculate the plane's width and height based on the image's dimensions
+ const planeWidth = (xMax - xMin + 1) * spacingX;
+ const planeHeight = (yMax - yMin + 1) * spacingY;
+
+ // Set the plane's origin and corners based on calculated width and height
+ planeSource.setOrigin(0, 0, 0);
+ planeSource.setPoint1(planeWidth, 0, 0); // Horizontal edge
+ planeSource.setPoint2(0, planeHeight, 0); // Vertical edge
+
+ actor.addTexture(texture);
+
+ const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance();
+ const renderer = fullScreenRenderer.getRenderer();
+ const renderWindow = fullScreenRenderer.getRenderWindow();
+ const camera = renderer.getActiveCamera();
+ const interactor = renderWindow.getInteractor();
+
+ // Disable default interactor style
+ interactor.setInteractorStyle(null);
+
+ renderer.addActor(actor);
+
+ // Adjust the camera to fit the plane in the view
+ zoomCameraToFitPlane(camera, planeWidth, planeHeight);
+ renderer.resetCameraClippingRange();
+
+ renderWindow.render();
+}
+
+function handleFile(event) {
+ event.preventDefault();
+ const dataTransfer = event.dataTransfer;
+ const files = event.target.files || dataTransfer.files;
+ if (files.length === 1) {
+ const file = files[0];
+ const fileReader = new FileReader();
+ fileReader.onload = () => {
+ reader.parse(fileReader.result);
+ update();
+ };
+ fileReader.readAsArrayBuffer(file);
+ }
+}
+
+fileInput.addEventListener('change', handleFile);
+
+// ----------------------------------------------------------------------------
+// Use the reader to download a file
+// ----------------------------------------------------------------------------
+if (userParams.fileURL) {
+ reader.setUrl(userParams.fileURL).then(() => {
+ reader.loadData().then(() => {
+ update();
+ });
+ });
+}
diff --git a/Sources/IO/Image/HDRReader/index.d.ts b/Sources/IO/Image/HDRReader/index.d.ts
new file mode 100644
index 00000000000..a6db1f92d09
--- /dev/null
+++ b/Sources/IO/Image/HDRReader/index.d.ts
@@ -0,0 +1,121 @@
+import { vtkAlgorithm, vtkObject } from '../../../interfaces';
+import HtmlDataAccessHelper from '../../Core/DataAccessHelper/HtmlDataAccessHelper';
+import HttpDataAccessHelper from '../../Core/DataAccessHelper/HttpDataAccessHelper';
+import JSZipDataAccessHelper from '../../Core/DataAccessHelper/JSZipDataAccessHelper';
+import LiteHttpDataAccessHelper from '../../Core/DataAccessHelper/LiteHttpDataAccessHelper';
+
+interface IHDRReaderOptions {
+ compression?: string;
+ progressCallback?: any;
+}
+
+/**
+ *
+ */
+export interface IHDRReaderInitialValues {}
+
+type vtkHDRReaderBase = vtkObject &
+ Omit<
+ vtkAlgorithm,
+ | 'getInputData'
+ | 'setInputData'
+ | 'setInputConnection'
+ | 'getInputConnection'
+ | 'addInputConnection'
+ | 'addInputData'
+ >;
+
+export interface vtkHDRReader extends vtkHDRReaderBase {
+ /**
+ * Get the base url.
+ */
+ getBaseURL(): string;
+
+ /**
+ * Get the dataAccess helper.
+ */
+ getDataAccessHelper():
+ | HtmlDataAccessHelper
+ | HttpDataAccessHelper
+ | JSZipDataAccessHelper
+ | LiteHttpDataAccessHelper;
+
+ /**
+ * Get the url of the object to load.
+ */
+ getUrl(): string;
+
+ /**
+ * Load the object data.
+ * @param {IHDRReaderOptions} [options]
+ */
+ loadData(options?: IHDRReaderOptions): Promise;
+
+ /**
+ * Parse data.
+ * @param {ArrayBuffer} content The content to parse.
+ */
+ parse(content: ArrayBuffer): void;
+
+ /**
+ * Parse data as ArrayBuffer.
+ * @param {ArrayBuffer} content The content to parse.
+ */
+ parseAsArrayBuffer(content: ArrayBuffer): void;
+
+ /**
+ *
+ * @param inData
+ * @param outData
+ */
+ requestData(inData: any, outData: any): void;
+
+ /**
+ *
+ * @param dataAccessHelper
+ */
+ setDataAccessHelper(
+ dataAccessHelper:
+ | HtmlDataAccessHelper
+ | HttpDataAccessHelper
+ | JSZipDataAccessHelper
+ | LiteHttpDataAccessHelper
+ ): boolean;
+
+ /**
+ * Set the url of the object to load.
+ * @param {String} url the url of the object to load.
+ * @param {IHDRReaderOptions} [option] The PLY reader options.
+ */
+ setUrl(url: string, option?: IHDRReaderOptions): Promise;
+}
+
+/**
+ * Method used to decorate a given object (publicAPI+model) with vtkHDRReader characteristics.
+ *
+ * @param publicAPI object on which methods will be bounds (public)
+ * @param model object on which data structure will be bounds (protected)
+ * @param {IHDRReaderInitialValues} [initialValues] (default: {})
+ */
+export function extend(
+ publicAPI: object,
+ model: object,
+ initialValues?: IHDRReaderInitialValues
+): void;
+
+/**
+ * Method used to create a new instance of vtkHDRReader
+ * @param {IHDRReaderInitialValues} [initialValues] for pre-setting some of its content
+ */
+export function newInstance(
+ initialValues?: IHDRReaderInitialValues
+): vtkHDRReader;
+
+/**
+ * vtkHDRReader is a source object that reads Radiance HDR files.
+ */
+export declare const vtkHDRReader: {
+ newInstance: typeof newInstance;
+ extend: typeof extend;
+};
+export default vtkHDRReader;
diff --git a/Sources/IO/Image/HDRReader/index.js b/Sources/IO/Image/HDRReader/index.js
new file mode 100644
index 00000000000..70817229da4
--- /dev/null
+++ b/Sources/IO/Image/HDRReader/index.js
@@ -0,0 +1,421 @@
+/* eslint-disable no-bitwise */
+
+// Enable data soure for DataAccessHelper
+import 'vtk.js/Sources/IO/Core/DataAccessHelper/LiteHttpDataAccessHelper'; // Just need HTTP
+// import 'vtk.js/Sources/IO/Core/DataAccessHelper/HttpDataAccessHelper'; // HTTP + zip
+// import 'vtk.js/Sources/IO/Core/DataAccessHelper/HtmlDataAccessHelper'; // html + base64 + zip
+// import 'vtk.js/Sources/IO/Core/DataAccessHelper/JSZipDataAccessHelper'; // zip
+
+import macro from 'vtk.js/Sources/macros';
+import DataAccessHelper from 'vtk.js/Sources/IO/Core/DataAccessHelper';
+import * as vtkMath from 'vtk.js/Sources/Common/Core/Math';
+import vtkImageData from 'vtk.js/Sources/Common/DataModel/ImageData';
+import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray';
+import { readLine, rgbe2float } from 'vtk.js/Sources/IO/Image/HDRReader/Utils';
+
+const { vtkErrorMacro } = macro;
+
+const FormatType = {
+ FORMAT_32BIT_RLE_RGBE: 0,
+ FORMAT_32BIT_RLE_XYZE: 1,
+};
+
+const Patterns = {
+ magicToken: /^#\?(\S+)/,
+ gamma: /^\s*GAMMA\s*=\s*(\d+(\.\d+)?)\s*$/,
+ exposure: /^\s*EXPOSURE\s*=\s*(\d+(\.\d+)?)\s*$/,
+ format: /^\s*FORMAT=(\S+)\s*$/,
+ dimensions: /^\s*-Y\s+(\d+)\s+\+X\s+(\d+)\s*$/,
+};
+
+// ----------------------------------------------------------------------------
+// vtkHDRReader methods
+// ----------------------------------------------------------------------------
+
+function vtkHDRReader(publicAPI, model) {
+ // Set our className
+ model.classHierarchy.push('vtkHDRReader');
+
+ /**
+ * Reads header information from an RGBE texture stored in a native array.
+ * More information on this format are available here:
+ * https://en.wikipedia.org/wiki/RGBE_image_format
+ *
+ * @param {Uint8Array} uint8array The binary file stored in native array.
+ * @returns The header information.
+ */
+ function readHeader(uint8array) {
+ // RGBE format header
+ const header = {
+ valid: 0,
+ string: '',
+ comments: '',
+ programtype: 'RGBE',
+ format: '',
+ gamma: 1.0,
+ exposure: 1.0,
+ pixelAspect: 1.0,
+ width: 0,
+ height: 0,
+ dataIndex: 0,
+ };
+
+ let match;
+ let line = readLine(uint8array, 0);
+
+ match = line.match(Patterns.magicToken);
+ if (!match) {
+ throw new Error('Bad HDR Format.');
+ }
+
+ header.programtype = match[1];
+ header.string += `${line}\n`;
+
+ let lineIndex = 0;
+
+ do {
+ lineIndex += line.length + 1;
+ line = readLine(uint8array, lineIndex);
+ header.string += `${line}\n`;
+
+ if (line.charAt(0) === '#') {
+ header.comments += `${line}\n`;
+ // eslint-disable-next-line no-continue
+ continue; // comment line
+ }
+
+ match = line.match(Patterns.gamma);
+ if (match) {
+ header.gamma = parseFloat(match[1]);
+ }
+
+ match = line.match(Patterns.exposure);
+ if (match) {
+ header.exposure = parseFloat(match[1]);
+ }
+
+ match = line.match(Patterns.format);
+ if (match) {
+ header.format = match[1];
+ }
+ } while (line.length !== 0);
+
+ lineIndex += line.length + 1;
+ line = readLine(uint8array, lineIndex);
+
+ match = line.match(Patterns.dimensions);
+ if (match) {
+ header.height = parseInt(match[1], 10);
+ header.width = parseInt(match[2], 10);
+ }
+
+ if (header.width < 8 || header.width > 0x7fff) {
+ vtkErrorMacro('HDR Bad header format, unsupported size');
+ }
+
+ lineIndex += line.length + 1;
+ header.dataIndex = lineIndex;
+ return header;
+ }
+
+ /**
+ *
+ * @param {Uint8Array} uint8array
+ * @param {*} header
+ * @returns Float32Array
+ */
+ function readRGBEPixelsNotRLE(uint8array, header) {
+ // this file is not run length encoded
+ // read values sequentially
+
+ let numScanlines = header.height;
+ const scanlineWidth = header.width;
+
+ let a;
+ let b;
+ let c;
+ let d;
+ let i;
+ let dataIndex = header.dataIndex;
+
+ // 3 channels of 4 bytes per pixel in float.
+ const resultBuffer = new ArrayBuffer(header.width * header.height * 4 * 3);
+ const resultArray = new Float32Array(resultBuffer);
+
+ // read in each successive scanline
+ while (numScanlines > 0) {
+ for (i = 0; i < header.width; i++) {
+ a = uint8array[dataIndex++];
+ b = uint8array[dataIndex++];
+ c = uint8array[dataIndex++];
+ d = uint8array[dataIndex++];
+
+ const offset = (numScanlines - 1) * scanlineWidth * 3 + i * 3;
+ let output = [];
+ const input = [a, b, c, d];
+ if (model.format === FormatType.FORMAT_32BIT_RLE_XYZE) {
+ // convert from XYZE to RGBE
+ vtkMath.xyz2rgb(input, output);
+ } else {
+ output = rgbe2float(input, model.exposure);
+ }
+ resultArray[offset] = output[0];
+ resultArray[offset + 1] = output[1];
+ resultArray[offset + 2] = output[2];
+ }
+
+ numScanlines--;
+ }
+
+ return resultArray;
+ }
+
+ /**
+ *
+ * @param {Uint8Array} uint8array
+ * @param {*} header
+ * @returns Float32Array
+ */
+ function readRGBEPixelsRLE(uint8array, header) {
+ let numScanlines = header.height;
+ const scanlineWidth = header.width;
+
+ let a;
+ let b;
+ let c;
+ let d;
+ let count;
+ let dataIndex = header.dataIndex;
+ let index = 0;
+ let endIndex = 0;
+ let i = 0;
+
+ const scanLineArrayBuffer = new ArrayBuffer(scanlineWidth * 4); // four channel R G B E
+ const scanLineArray = new Uint8Array(scanLineArrayBuffer);
+
+ // 3 channels of 4 bytes per pixel in float.
+ const resultBuffer = new ArrayBuffer(header.width * header.height * 4 * 3);
+ const resultArray = new Float32Array(resultBuffer);
+
+ // read in each successive scanline
+ while (numScanlines > 0) {
+ a = uint8array[dataIndex++];
+ b = uint8array[dataIndex++];
+ c = uint8array[dataIndex++];
+ d = uint8array[dataIndex++];
+
+ if (
+ a !== 2 ||
+ b !== 2 ||
+ c & 0x80 ||
+ header.width < 8 ||
+ header.width > 32767
+ ) {
+ return readRGBEPixelsNotRLE(uint8array, header);
+ }
+
+ if (((c << 8) | d) !== scanlineWidth) {
+ vtkErrorMacro('HDR Bad header format, wrong scan line width');
+ }
+
+ index = 0;
+
+ // read each of the four channels for the scanline into the buffer
+ for (i = 0; i < 4; i++) {
+ endIndex = (i + 1) * scanlineWidth;
+
+ while (index < endIndex) {
+ a = uint8array[dataIndex++];
+ b = uint8array[dataIndex++];
+
+ if (a > 128) {
+ // A run of the same value
+ count = a - 128;
+ if (count === 0 || count > endIndex - index) {
+ vtkErrorMacro('HDR Bad Format, bad scanline data (run)');
+ }
+
+ while (count-- > 0) {
+ scanLineArray[index++] = b;
+ }
+ } else {
+ // A non run
+ count = a;
+ if (count === 0 || count > endIndex - index) {
+ vtkErrorMacro('HDR Bad Format, bad scanline data (non-run)');
+ }
+
+ scanLineArray[index++] = b;
+ if (--count > 0) {
+ for (let j = 0; j < count; j++) {
+ scanLineArray[index++] = uint8array[dataIndex++];
+ }
+ }
+ }
+ }
+ }
+
+ // now convert data from buffer into floats
+ for (i = 0; i < scanlineWidth; i++) {
+ a = scanLineArray[i];
+ b = scanLineArray[i + scanlineWidth];
+ c = scanLineArray[i + 2 * scanlineWidth];
+ d = scanLineArray[i + 3 * scanlineWidth];
+
+ const offset = (numScanlines - 1) * scanlineWidth * 3 + i * 3;
+ let output = [];
+ const input = [a, b, c, d];
+ if (model.format === FormatType.FORMAT_32BIT_RLE_XYZE) {
+ // convert from XYZE to RGBE
+ vtkMath.xyz2rgb(input, output);
+ } else {
+ output = rgbe2float(input, model.exposure);
+ }
+ resultArray[offset] = output[0];
+ resultArray[offset + 1] = output[1];
+ resultArray[offset + 2] = output[2];
+ }
+
+ numScanlines--;
+ }
+
+ return resultArray;
+ }
+
+ // Create default dataAccessHelper if not available
+ if (!model.dataAccessHelper) {
+ model.dataAccessHelper = DataAccessHelper.get('http');
+ }
+
+ // Internal method to fetch Array
+ function fetchData(url, option = {}) {
+ const { compression, progressCallback } = model;
+ return model.dataAccessHelper.fetchBinary(url, {
+ compression,
+ progressCallback,
+ });
+ }
+
+ // Set DataSet url
+ publicAPI.setUrl = (url, option = { binary: true }) => {
+ model.url = url;
+
+ // Remove the file in the URL
+ const path = url.split('/');
+ path.pop();
+ model.baseURL = path.join('/');
+
+ model.compression = option.compression;
+
+ // Fetch metadata
+ return publicAPI.loadData({
+ progressCallback: option.progressCallback,
+ });
+ };
+
+ // Fetch the actual data arrays
+ publicAPI.loadData = (option = {}) => {
+ const promise = fetchData(model.url, option);
+ promise.then(publicAPI.parse);
+ return promise;
+ };
+
+ publicAPI.parse = (content) => {
+ publicAPI.parseAsArrayBuffer(content);
+ };
+
+ publicAPI.parseAsArrayBuffer = (content) => {
+ if (!content) {
+ return;
+ }
+
+ model.parseData = content;
+
+ const data = new Uint8Array(model.parseData);
+ const header = readHeader(data);
+
+ if (header.format === '32-bit_rle_rgbe') {
+ model.format = FormatType.FORMAT_32BIT_RLE_RGBE;
+ } else if (header.format === '32-bit_rle_xyze') {
+ model.format = FormatType.FORMAT_32BIT_RLE_XYZE;
+ }
+
+ model.gamma = header.gamma;
+ model.exposure = header.exposure;
+ model.pixelAspect = header.pixelAspect;
+
+ const output = readRGBEPixelsRLE(data, header);
+
+ const dataExtent = [0, header.width - 1, 0, header.height - 1];
+ const dataSpacing = [1, header.pixelAspect, 1];
+
+ const imageData = vtkImageData.newInstance();
+ imageData.setDimensions(header.width, header.height, 1);
+ imageData.setExtent(dataExtent);
+ imageData.setSpacing(dataSpacing);
+
+ const dataArray = vtkDataArray.newInstance({
+ name: 'HDRImage',
+ numberOfComponents: 3,
+ values: output,
+ });
+
+ imageData.getPointData().setScalars(dataArray);
+ model.output[0] = imageData;
+ };
+
+ publicAPI.requestData = (inData, outData) => {
+ publicAPI.parse(model.parseData);
+ };
+}
+
+// ----------------------------------------------------------------------------
+// Object factory
+// ----------------------------------------------------------------------------
+
+const DEFAULT_VALUES = {
+ gamma: 1.0,
+ exposure: 1.0,
+ pixelAspect: 1.0,
+ format: FormatType.FORMAT_32BIT_RLE_RGBE,
+};
+
+// ----------------------------------------------------------------------------
+
+export function extend(publicAPI, model, initialValues = {}) {
+ Object.assign(model, DEFAULT_VALUES, initialValues);
+
+ // Make this a VTK object
+ macro.obj(publicAPI, model);
+
+ // Also make it an algorithm with one input and one output
+ macro.algo(publicAPI, model, 0, 1);
+
+ macro.get(publicAPI, model, [
+ 'gamma',
+ 'exposure',
+ 'pixelAspect',
+ 'url',
+ 'baseURL',
+ ]);
+ macro.setGet(publicAPI, model, ['dataAccessHelper']);
+
+ // Object specific methods
+ vtkHDRReader(publicAPI, model);
+
+ // To support destructuring
+ if (!model.compression) {
+ model.compression = null;
+ }
+ if (!model.progressCallback) {
+ model.progressCallback = null;
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+export const newInstance = macro.newInstance(extend, 'vtkHDRReader');
+
+// ----------------------------------------------------------------------------
+
+export default { newInstance, extend };
diff --git a/Sources/IO/Image/index.js b/Sources/IO/Image/index.js
new file mode 100644
index 00000000000..0fa73fd4be6
--- /dev/null
+++ b/Sources/IO/Image/index.js
@@ -0,0 +1,5 @@
+import vtkHDRReader from './HDRReader';
+
+export default {
+ vtkHDRReader,
+};