/* * 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(); } } } }