@babylonjs/core
Version:
Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.
234 lines • 9.06 kB
JavaScript
import { PanoramaToCubeMapTools } from "./panoramaToCubemap.js";
/* This groups tools to convert HDR texture to native colors array. */
function Ldexp(mantissa, exponent) {
if (exponent > 1023) {
return mantissa * Math.pow(2, 1023) * Math.pow(2, exponent - 1023);
}
if (exponent < -1074) {
return mantissa * Math.pow(2, -1074) * Math.pow(2, exponent + 1074);
}
return mantissa * Math.pow(2, exponent);
}
function Rgbe2float(float32array, red, green, blue, exponent, index) {
if (exponent > 0) {
/*nonzero pixel*/
exponent = Ldexp(1.0, exponent - (128 + 8));
float32array[index + 0] = red * exponent;
float32array[index + 1] = green * exponent;
float32array[index + 2] = blue * exponent;
}
else {
float32array[index + 0] = 0;
float32array[index + 1] = 0;
float32array[index + 2] = 0;
}
}
function ReadStringLine(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;
}
/**
* 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 The binary file stored in native array.
* @returns The header information.
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
export function RGBE_ReadHeader(uint8array) {
let height = 0;
let width = 0;
let line = ReadStringLine(uint8array, 0);
if (line[0] != "#" || line[1] != "?") {
// eslint-disable-next-line no-throw-literal
throw "Bad HDR Format.";
}
let endOfHeader = false;
let findFormat = false;
let lineIndex = 0;
do {
lineIndex += line.length + 1;
line = ReadStringLine(uint8array, lineIndex);
if (line == "FORMAT=32-bit_rle_rgbe") {
findFormat = true;
}
else if (line.length == 0) {
endOfHeader = true;
}
} while (!endOfHeader);
if (!findFormat) {
// eslint-disable-next-line no-throw-literal
throw "HDR Bad header format, unsupported FORMAT";
}
lineIndex += line.length + 1;
line = ReadStringLine(uint8array, lineIndex);
const sizeRegexp = /^-Y (.*) \+X (.*)$/g;
const match = sizeRegexp.exec(line);
// TODO. Support +Y and -X if needed.
if (!match || match.length < 3) {
// eslint-disable-next-line no-throw-literal
throw "HDR Bad header format, no size";
}
width = parseInt(match[2]);
height = parseInt(match[1]);
if (width < 8 || width > 0x7fff) {
// eslint-disable-next-line no-throw-literal
throw "HDR Bad header format, unsupported size";
}
lineIndex += line.length + 1;
return {
height: height,
width: width,
dataPosition: lineIndex,
};
}
/**
* Returns the cubemap information (each faces texture data) extracted from an RGBE texture.
* This RGBE texture needs to store the information as a panorama.
*
* More information on this format are available here:
* https://en.wikipedia.org/wiki/RGBE_image_format
*
* @param buffer The binary file stored in an array buffer.
* @param size The expected size of the extracted cubemap.
* @param supersample enable supersampling the cubemap (default: false)
* @returns The Cube Map information.
*/
export function GetCubeMapTextureData(buffer, size, supersample = false) {
const uint8array = new Uint8Array(buffer);
const hdrInfo = RGBE_ReadHeader(uint8array);
const data = RGBE_ReadPixels(uint8array, hdrInfo);
const cubeMapData = PanoramaToCubeMapTools.ConvertPanoramaToCubemap(data, hdrInfo.width, hdrInfo.height, size, supersample);
return cubeMapData;
}
/**
* Returns the pixels data extracted from an RGBE texture.
* This pixels will be stored left to right up to down in the R G B order in one array.
*
* More information on this format are available here:
* https://en.wikipedia.org/wiki/RGBE_image_format
*
* @param uint8array The binary file stored in an array buffer.
* @param hdrInfo The header information of the file.
* @returns The pixels data in RGB right to left up to down order.
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
export function RGBE_ReadPixels(uint8array, hdrInfo) {
return ReadRGBEPixelsRLE(uint8array, hdrInfo);
}
// eslint-disable-next-line @typescript-eslint/naming-convention
function ReadRGBEPixelsRLE(uint8array, hdrInfo) {
let numScanlines = hdrInfo.height;
const scanlineWidth = hdrInfo.width;
let a, b, c, d, count;
let dataIndex = hdrInfo.dataPosition;
let index = 0, endIndex = 0, 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(hdrInfo.width * hdrInfo.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 || hdrInfo.width < 8 || hdrInfo.width > 32767) {
return ReadRGBEPixelsNotRLE(uint8array, hdrInfo);
}
if (((c << 8) | d) != scanlineWidth) {
// eslint-disable-next-line no-throw-literal
throw "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) {
// eslint-disable-next-line no-throw-literal
throw "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) {
// eslint-disable-next-line no-throw-literal
throw "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];
Rgbe2float(resultArray, a, b, c, d, (hdrInfo.height - numScanlines) * scanlineWidth * 3 + i * 3);
}
numScanlines--;
}
return resultArray;
}
// eslint-disable-next-line @typescript-eslint/naming-convention
function ReadRGBEPixelsNotRLE(uint8array, hdrInfo) {
// this file is not run length encoded
// read values sequentially
let numScanlines = hdrInfo.height;
const scanlineWidth = hdrInfo.width;
let a, b, c, d, i;
let dataIndex = hdrInfo.dataPosition;
// 3 channels of 4 bytes per pixel in float.
const resultBuffer = new ArrayBuffer(hdrInfo.width * hdrInfo.height * 4 * 3);
const resultArray = new Float32Array(resultBuffer);
// read in each successive scanline
while (numScanlines > 0) {
for (i = 0; i < hdrInfo.width; i++) {
a = uint8array[dataIndex++];
b = uint8array[dataIndex++];
c = uint8array[dataIndex++];
d = uint8array[dataIndex++];
Rgbe2float(resultArray, a, b, c, d, (hdrInfo.height - numScanlines) * scanlineWidth * 3 + i * 3);
}
numScanlines--;
}
return resultArray;
}
/**
* @deprecated Use functions separately
*/
export const HDRTools = {
// eslint-disable-next-line @typescript-eslint/naming-convention
RGBE_ReadHeader,
// eslint-disable-next-line @typescript-eslint/naming-convention
GetCubeMapTextureData,
// eslint-disable-next-line @typescript-eslint/naming-convention
RGBE_ReadPixels,
};
//# sourceMappingURL=hdr.js.map