diff options
Diffstat (limited to 'engine/src/core-plugins/com/jme3/texture/plugins/HDRLoader.java')
-rw-r--r-- | engine/src/core-plugins/com/jme3/texture/plugins/HDRLoader.java | 332 |
1 files changed, 332 insertions, 0 deletions
diff --git a/engine/src/core-plugins/com/jme3/texture/plugins/HDRLoader.java b/engine/src/core-plugins/com/jme3/texture/plugins/HDRLoader.java new file mode 100644 index 0000000..4758ecf --- /dev/null +++ b/engine/src/core-plugins/com/jme3/texture/plugins/HDRLoader.java @@ -0,0 +1,332 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.texture.plugins; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetLoader; +import com.jme3.asset.TextureKey; +import com.jme3.math.FastMath; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.util.BufferUtils; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class HDRLoader implements AssetLoader { + + private static final Logger logger = Logger.getLogger(HDRLoader.class.getName()); + + private boolean writeRGBE = false; + private ByteBuffer rleTempBuffer; + private ByteBuffer dataStore; + private final float[] tempF = new float[3]; + + public HDRLoader(boolean writeRGBE){ + this.writeRGBE = writeRGBE; + } + + public HDRLoader(){ + } + + public static void convertFloatToRGBE(byte[] rgbe, float red, float green, float blue){ + double max = red; + if (green > max) max = green; + if (blue > max) max = blue; + if (max < 1.0e-32){ + rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; + }else{ + double exp = Math.ceil( Math.log10(max) / Math.log10(2) ); + double divider = Math.pow(2.0, exp); + rgbe[0] = (byte) ((red / divider) * 255.0); + rgbe[1] = (byte) ((green / divider) * 255.0); + rgbe[2] = (byte) ((blue / divider) * 255.0); + rgbe[3] = (byte) (exp + 128.0); + } + } + + public static void convertRGBEtoFloat(byte[] rgbe, float[] rgbf){ + int R = rgbe[0] & 0xFF, + G = rgbe[1] & 0xFF, + B = rgbe[2] & 0xFF, + E = rgbe[3] & 0xFF; + + float e = (float) Math.pow(2f, E - (128 + 8) ); + rgbf[0] = R * e; + rgbf[1] = G * e; + rgbf[2] = B * e; + } + + public static void convertRGBEtoFloat2(byte[] rgbe, float[] rgbf){ + int R = rgbe[0] & 0xFF, + G = rgbe[1] & 0xFF, + B = rgbe[2] & 0xFF, + E = rgbe[3] & 0xFF; + + float e = (float) Math.pow(2f, E - 128); + rgbf[0] = (R / 256.0f) * e; + rgbf[1] = (G / 256.0f) * e; + rgbf[2] = (B / 256.0f) * e; + } + + public static void convertRGBEtoFloat3(byte[] rgbe, float[] rgbf){ + int R = rgbe[0] & 0xFF, + G = rgbe[1] & 0xFF, + B = rgbe[2] & 0xFF, + E = rgbe[3] & 0xFF; + + float e = (float) Math.pow(2f, E - (128 + 8) ); + rgbf[0] = R * e; + rgbf[1] = G * e; + rgbf[2] = B * e; + } + + private short flip(int in){ + return (short) ((in << 8 & 0xFF00) | (in >> 8)); + } + + private void writeRGBE(byte[] rgbe){ + if (writeRGBE){ + dataStore.put(rgbe); + }else{ + convertRGBEtoFloat(rgbe, tempF); + dataStore.putShort(FastMath.convertFloatToHalf(tempF[0])) + .putShort(FastMath.convertFloatToHalf(tempF[1])). + putShort(FastMath.convertFloatToHalf(tempF[2])); + } + } + + private String readString(InputStream is) throws IOException{ + StringBuilder sb = new StringBuilder(); + while (true){ + int i = is.read(); + if (i == 0x0a || i == -1) // new line or EOF + return sb.toString(); + + sb.append((char)i); + } + } + + private boolean decodeScanlineRLE(InputStream in, int width) throws IOException{ + // must deocde RLE data into temp buffer before converting to float + if (rleTempBuffer == null){ + rleTempBuffer = BufferUtils.createByteBuffer(width * 4); + }else{ + rleTempBuffer.clear(); + if (rleTempBuffer.remaining() < width * 4) + rleTempBuffer = BufferUtils.createByteBuffer(width * 4); + } + + // read each component seperately + for (int i = 0; i < 4; i++) { + // read WIDTH bytes for the channel + for (int j = 0; j < width;) { + int code = in.read(); + if (code > 128) { // run + code -= 128; + int val = in.read(); + while ((code--) != 0) { + rleTempBuffer.put( (j++) * 4 + i , (byte)val); + //scanline[j++][i] = val; + } + } else { // non-run + while ((code--) != 0) { + int val = in.read(); + rleTempBuffer.put( (j++) * 4 + i, (byte)val); + //scanline[j++][i] = in.read(); + } + } + } + } + + rleTempBuffer.rewind(); + byte[] rgbe = new byte[4]; +// float[] temp = new float[3]; + + // decode temp buffer into float data + for (int i = 0; i < width; i++){ + rleTempBuffer.get(rgbe); + writeRGBE(rgbe); + } + + return true; + } + + private boolean decodeScanlineUncompressed(InputStream in, int width) throws IOException{ + byte[] rgbe = new byte[4]; + + for (int i = 0; i < width; i+=3){ + if (in.read(rgbe) < 1) + return false; + + writeRGBE(rgbe); + } + return true; + } + + private void decodeScanline(InputStream in, int width) throws IOException{ + if (width < 8 || width > 0x7fff){ + // too short/long for RLE compression + decodeScanlineUncompressed(in, width); + } + + // check format + byte[] data = new byte[4]; + in.read(data); + if (data[0] != 0x02 || data[1] != 0x02 || (data[2] & 0x80) != 0){ + // not RLE data + decodeScanlineUncompressed(in, width-1); + }else{ + // check scanline width + int readWidth = (data[2] & 0xFF) << 8 | (data[3] & 0xFF); + if (readWidth != width) + throw new IOException("Illegal scanline width in HDR file: "+width+" != "+readWidth); + + // RLE data + decodeScanlineRLE(in, width); + } + } + + public Image load(InputStream in, boolean flipY) throws IOException{ + float gamma = -1f; + float exposure = -1f; + float[] colorcorr = new float[]{ -1f, -1f, -1f }; + + int width = -1, height = -1; + boolean verifiedFormat = false; + + while (true){ + String ln = readString(in); + ln = ln.trim(); + if (ln.startsWith("#") || ln.equals("")){ + if (ln.equals("#?RADIANCE") || ln.equals("#?RGBE")) + verifiedFormat = true; + + continue; // comment or empty statement + } else if (ln.startsWith("+") || ln.startsWith("-")){ + // + or - mark image resolution and start of data + String[] resData = ln.split("\\s"); + if (resData.length != 4){ + throw new IOException("Invalid resolution string in HDR file"); + } + + if (!resData[0].equals("-Y") || !resData[2].equals("+X")){ + logger.warning("Flipping/Rotating attributes ignored!"); + } + + //if (resData[0].endsWith("X")){ + // first width then height + // width = Integer.parseInt(resData[1]); + // height = Integer.parseInt(resData[3]); + //}else{ + width = Integer.parseInt(resData[3]); + height = Integer.parseInt(resData[1]); + //} + + break; + } else { + // regular command + int index = ln.indexOf("="); + if (index < 1){ + logger.log(Level.FINE, "Ignored string: {0}", ln); + continue; + } + + String var = ln.substring(0, index).trim().toLowerCase(); + String value = ln.substring(index+1).trim().toLowerCase(); + if (var.equals("format")){ + if (!value.equals("32-bit_rle_rgbe") && !value.equals("32-bit_rle_xyze")){ + throw new IOException("Unsupported format in HDR picture"); + } + }else if (var.equals("exposure")){ + exposure = Float.parseFloat(value); + }else if (var.equals("gamma")){ + gamma = Float.parseFloat(value); + }else{ + logger.log(Level.WARNING, "HDR Command ignored: {0}", ln); + } + } + } + + assert width != -1 && height != -1; + + if (!verifiedFormat) + logger.warning("Unsure if specified image is Radiance HDR"); + + // some HDR images can get pretty big + System.gc(); + + // each pixel times size of component times # of components + Format pixelFormat; + if (writeRGBE){ + pixelFormat = Format.RGBA8; + }else{ + pixelFormat = Format.RGB16F; + } + + dataStore = BufferUtils.createByteBuffer(width * height * pixelFormat.getBitsPerPixel()); + + int bytesPerPixel = pixelFormat.getBitsPerPixel() / 8; + int scanLineBytes = bytesPerPixel * width; + for (int y = height - 1; y >= 0; y--) { + if (flipY) + dataStore.position(scanLineBytes * y); + + decodeScanline(in, width); + } + in.close(); + + dataStore.rewind(); + return new Image(pixelFormat, width, height, dataStore); + } + + public Object load(AssetInfo info) throws IOException { + if (!(info.getKey() instanceof TextureKey)) + throw new IllegalArgumentException("Texture assets must be loaded using a TextureKey"); + + boolean flip = ((TextureKey) info.getKey()).isFlipY(); + InputStream in = null; + try { + in = info.openStream(); + Image img = load(in, flip); + return img; + } finally { + if (in != null){ + in.close(); + } + } + } + +} |