/* Copyright (c) 2014 Jesper Öqvist <jesper@llbit.se>
*
* This file is part of Chunky.
*
* Chunky is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Chunky is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with Chunky. If not, see <http://www.gnu.org/licenses/>.
*/
package se.llbit.chunky.resources;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import se.llbit.log.Log;
public class HDRTexture extends AbstractHdriTexture {
public HDRTexture(File file) {
// This RGBE loader was created to be compatible with the RADIANCE
// rendering system (http://radsite.lbl.gov/). I studied the sources
// (src/common/color.c) to understand how RADIANCE worked, then wrote this
// code from scratch in an attempt to implement the same interface.
try {
RandomAccessFile raf = new RandomAccessFile(file, "r");
String fmt = raf.readLine();
if (!fmt.equals("#?RADIANCE")) {
raf.close();
throw new Error("not a recognized HDR format! Can only handle RGBE!");
}
boolean haveFormat = false;
String format = "";
while (true) {
String cmd = raf.readLine();
if (cmd.trim().isEmpty()) {
break;
}
if (cmd.startsWith("FORMAT=")) {
haveFormat = true;
format = cmd;
}
}
if (!haveFormat) {
raf.close();
throw new Error("could not find image format!");
}
if (!format.equals("FORMAT=32-bit_rle_rgbe")) {
raf.close();
throw new Error("only 32-bit RGBE HDR format supported!");
}
String resolution = raf.readLine();
Pattern regex = Pattern.compile("-Y\\s(\\d+)\\s\\+X\\s(\\d+)");
Matcher matcher = regex.matcher(resolution);
if (!matcher.matches()) {
raf.close();
throw new Error("unrecognized pixel order");
}
width = Integer.parseInt(matcher.group(2));
height = Integer.parseInt(matcher.group(1));
long start = raf.getFilePointer();
long byteBufLen = raf.length() - start;
FileChannel channel = raf.getChannel();
MappedByteBuffer byteBuf = channel.map(FileChannel.MapMode.READ_ONLY, start, byteBufLen);
// Precompute exponents.
double exp[] = new double[256];
for (int e = 0; e < 256; ++e) {
exp[e] = Math.pow(2, e - 136);
}
buf = new float[width * height * 3];
byte[][] scanbuf = new byte[width][4];
for (int i = 0; i < height; ++i) {
readScanline(byteBuf, scanbuf, width);
int offset = (height - i - 1) * width * 3;
for (int x = 0; x < width; ++x) {
int r = 0xFF & scanbuf[x][0];
int g = 0xFF & scanbuf[x][1];
int b = 0xFF & scanbuf[x][2];
int e = 0xFF & scanbuf[x][3];
if (e == 0) {
buf[offset + 0] = 0;
buf[offset + 1] = 0;
buf[offset + 2] = 0;
} else {
double f = exp[e];
buf[offset + 0] = (float) ((r + 0.5) * f);
buf[offset + 1] = (float) ((g + 0.5) * f);
buf[offset + 2] = (float) ((b + 0.5) * f);
}
offset += 3;
}
}
raf.close();
} catch (IOException e) {
Log.error("Error loading HRD image: " + e.getMessage());
e.printStackTrace();
}
}
private void readScanline(MappedByteBuffer byteBuf, byte[][] scanline, int len) {
byte h0 = byteBuf.get();
byte h1 = byteBuf.get();
byte h2 = byteBuf.get();
byte h3 = byteBuf.get();
if (h0 != 2 || h1 != 2 || (h2 & 0x80) != 0) {
scanline[0][0] = h0;
scanline[0][1] = h1;
scanline[0][2] = h2;
scanline[0][3] = h2;
readScanlineOldFmt(byteBuf, scanline, len);
}
int width = ((0xFF & h2) << 8) | (0xFF & h3);
if (width != len) {
throw new Error("length mismatch");
}
for (int i = 0; i < 4; ++i) {
for (int j = 0; j < width; ) {
int code = 0xFF & byteBuf.get();
if (code > 128) {
int num = 0x7F & code;
if (j + num > width) {
throw new Error("scanline overrun");
}
byte value = byteBuf.get();
while (num-- > 0) {
scanline[j++][i] = value;
}
} else {
int num = code;
if (j + num > width) {
throw new Error("scanline overrun");
}
while (num-- > 0) {
scanline[j++][i] = byteBuf.get();
}
}
}
}
}
private void readScanlineOldFmt(MappedByteBuffer byteBuf, byte[][] scanline, int len) {
int shift = 0;
for (int i = 1; i < len; ) {
scanline[i][0] = byteBuf.get();
scanline[i][1] = byteBuf.get();
scanline[i][2] = byteBuf.get();
scanline[i][3] = byteBuf.get();
if (scanline[i][0] == 1 && scanline[i][1] == 1 && scanline[i][2] == 1) {
int num = scanline[i][3] << shift;
for (int j = 0; j < num; ++j) {
scanline[i][0] = scanline[i - 1][0];
scanline[i][1] = scanline[i - 1][1];
scanline[i][2] = scanline[i - 1][2];
scanline[i][3] = scanline[i - 1][3];
i += 1;
}
shift += 8;
} else {
shift = 0;
i += 1;
}
}
}
}