playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
142 lines (141 loc) • 3.95 kB
JavaScript
import { ReadStream } from "../../../core/read-stream.js";
import {
TEXHINT_ASSET,
ADDRESS_REPEAT,
ADDRESS_CLAMP_TO_EDGE,
FILTER_NEAREST,
PIXELFORMAT_RGBA8,
TEXTURETYPE_RGBE
} from "../../../platform/graphics/constants.js";
import { Texture } from "../../../platform/graphics/texture.js";
import { Http } from "../../../platform/net/http.js";
import { TextureParser } from "./texture.js";
class HdrParser extends TextureParser {
canParse(context) {
return context.ext === "hdr";
}
load(url, callback, asset) {
this.handler.fetch(url, Http.ResponseType.ARRAY_BUFFER, callback, asset);
if (asset.data && !asset.data.type) {
asset.data.type = TEXTURETYPE_RGBE;
}
}
open(url, data, device, textureOptions = {}) {
const textureData = this.parse(data);
if (!textureData) {
return null;
}
const texture = new Texture(device, {
name: url,
profilerHint: TEXHINT_ASSET,
addressU: ADDRESS_REPEAT,
addressV: ADDRESS_CLAMP_TO_EDGE,
minFilter: FILTER_NEAREST,
magFilter: FILTER_NEAREST,
width: textureData.width,
height: textureData.height,
levels: textureData.levels,
format: PIXELFORMAT_RGBA8,
type: TEXTURETYPE_RGBE,
...textureOptions,
// requirements of RGBE encoded data, which override any asset-provided options: it
// must stay in a linear format, and as it can't be filtered, it cannot use mipmaps
srgb: false,
mipmaps: false
});
texture.upload();
return texture;
}
// https://floyd.lbl.gov/radiance/refer/filefmts.pdf with help from http://www.graphics.cornell.edu/~bjw/rgbe/rgbe.c
parse(data) {
const readStream = new ReadStream(data);
const magic = readStream.readLine();
if (!magic.startsWith("#?RADIANCE")) {
return null;
}
const variables = {};
while (true) {
const line = readStream.readLine();
if (line.length === 0) {
break;
} else {
const parts = line.split("=");
if (parts.length === 2) {
variables[parts[0]] = parts[1];
}
}
}
if (!variables.hasOwnProperty("FORMAT")) {
return null;
}
const resolution = readStream.readLine().split(" ");
if (resolution.length !== 4) {
return null;
}
const height = parseInt(resolution[1], 10);
const width = parseInt(resolution[3], 10);
const pixels = this._readPixels(readStream, width, height, resolution[0] === "-Y");
if (!pixels) {
return null;
}
return {
width,
height,
levels: [pixels]
};
}
_readPixels(readStream, width, height, flipY) {
if (width < 8 || width > 32767) {
return this._readPixelsFlat(readStream, width, height);
}
const rgbe = [0, 0, 0, 0];
readStream.readArray(rgbe);
if (rgbe[0] !== 2 || rgbe[1] !== 2 || (rgbe[2] & 128) !== 0) {
readStream.skip(-4);
return this._readPixelsFlat(readStream, width, height);
}
const buffer = new ArrayBuffer(width * height * 4);
const view = new Uint8Array(buffer);
let scanstart = flipY ? 0 : width * 4 * (height - 1);
let x, y, i, channel, count, value;
for (y = 0; y < height; ++y) {
if (y) {
readStream.readArray(rgbe);
}
if ((rgbe[2] << 8) + rgbe[3] !== width) {
return null;
}
for (channel = 0; channel < 4; ++channel) {
x = 0;
while (x < width) {
count = readStream.readU8();
if (count > 128) {
count -= 128;
if (x + count > width) {
return null;
}
value = readStream.readU8();
for (i = 0; i < count; ++i) {
view[scanstart + channel + 4 * x++] = value;
}
} else {
if (count === 0 || x + count > width) {
return null;
}
for (i = 0; i < count; ++i) {
view[scanstart + channel + 4 * x++] = readStream.readU8();
}
}
}
}
scanstart += width * 4 * (flipY ? 1 : -1);
}
return view;
}
_readPixelsFlat(readStream, width, height) {
return readStream.remainingBytes === width * height * 4 ? new Uint8Array(readStream.arraybuffer, readStream.offset) : null;
}
}
export {
HdrParser
};