UNPKG

@kitware/vtk.js

Version:

Visualization Toolkit for the Web

371 lines (338 loc) 11.2 kB
import '../Core/DataAccessHelper/LiteHttpDataAccessHelper.js'; import { m as macro } from '../../macros2.js'; import DataAccessHelper from '../Core/DataAccessHelper.js'; import { D as xyz2rgb } from '../../Common/Core/Math/index.js'; import vtkImageData from '../../Common/DataModel/ImageData.js'; import vtkDataArray from '../../Common/Core/DataArray.js'; import { readLine, rgbe2float } from './HDRReader/Utils.js'; /* eslint-disable no-bitwise */ 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 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 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 }; // ---------------------------------------------------------------------------- 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; } } // ---------------------------------------------------------------------------- const newInstance = macro.newInstance(extend, 'vtkHDRReader'); // ---------------------------------------------------------------------------- var vtkHDRReader$1 = { newInstance, extend }; export { vtkHDRReader$1 as default, extend, newInstance };