Skip to content

Commit

Permalink
feat(HDRReader): add vtkHDRReader
Browse files Browse the repository at this point in the history
Fixes #3145
  • Loading branch information
Adnane Belmadiaf committed Oct 14, 2024
1 parent 4691eea commit 0b1ad5e
Show file tree
Hide file tree
Showing 7 changed files with 728 additions and 0 deletions.
5 changes: 5 additions & 0 deletions Sources/Common/Core/Math/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
11 changes: 11 additions & 0 deletions Sources/Common/Core/Math/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -2227,6 +2237,7 @@ export function float2CssRGBA(rgbArray) {

export default {
Pi,
ldexp,
radiansFromDegrees,
degreesFromRadians,
round,
Expand Down
43 changes: 43 additions & 0 deletions Sources/IO/Image/HDRReader/Utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import * as vtkMath from '@kitware/vtk.js/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 };
122 changes: 122 additions & 0 deletions Sources/IO/Image/HDRReader/example/index.js
Original file line number Diff line number Diff line change
@@ -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 =
'<div>Select a hdr file.<br/><input type="file" class="file"/></div>';
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();
});
});
}
121 changes: 121 additions & 0 deletions Sources/IO/Image/HDRReader/index.d.ts
Original file line number Diff line number Diff line change
@@ -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<any>;

/**
* 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<string | any>;
}

/**
* 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;
Loading

0 comments on commit 0b1ad5e

Please sign in to comment.