Home | History | Annotate | Download | only in plugins
      1 /*
      2  * Copyright (c) 2009-2010 jMonkeyEngine
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  * * Redistributions of source code must retain the above copyright
     10  *   notice, this list of conditions and the following disclaimer.
     11  *
     12  * * Redistributions in binary form must reproduce the above copyright
     13  *   notice, this list of conditions and the following disclaimer in the
     14  *   documentation and/or other materials provided with the distribution.
     15  *
     16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
     17  *   may be used to endorse or promote products derived from this software
     18  *   without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 
     33 package com.jme3.texture.plugins;
     34 
     35 import com.jme3.asset.AssetInfo;
     36 import com.jme3.asset.AssetLoader;
     37 import com.jme3.asset.TextureKey;
     38 import com.jme3.math.FastMath;
     39 import com.jme3.texture.Image;
     40 import com.jme3.texture.Image.Format;
     41 import com.jme3.util.BufferUtils;
     42 import java.io.IOException;
     43 import java.io.InputStream;
     44 import java.nio.ByteBuffer;
     45 import java.util.logging.Level;
     46 import java.util.logging.Logger;
     47 
     48 public class HDRLoader implements AssetLoader {
     49 
     50     private static final Logger logger = Logger.getLogger(HDRLoader.class.getName());
     51 
     52     private boolean writeRGBE = false;
     53     private ByteBuffer rleTempBuffer;
     54     private ByteBuffer dataStore;
     55     private final float[] tempF = new float[3];
     56 
     57     public HDRLoader(boolean writeRGBE){
     58         this.writeRGBE = writeRGBE;
     59     }
     60 
     61     public HDRLoader(){
     62     }
     63 
     64     public static void convertFloatToRGBE(byte[] rgbe, float red, float green, float blue){
     65         double max = red;
     66         if (green > max) max = green;
     67         if (blue > max) max = blue;
     68         if (max < 1.0e-32){
     69             rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0;
     70         }else{
     71             double exp = Math.ceil( Math.log10(max) / Math.log10(2) );
     72             double divider = Math.pow(2.0, exp);
     73             rgbe[0] = (byte) ((red   / divider) * 255.0);
     74             rgbe[1] = (byte) ((green / divider) * 255.0);
     75             rgbe[2] = (byte) ((blue  / divider) * 255.0);
     76             rgbe[3] = (byte) (exp + 128.0);
     77       }
     78     }
     79 
     80     public static void convertRGBEtoFloat(byte[] rgbe, float[] rgbf){
     81         int R = rgbe[0] & 0xFF,
     82             G = rgbe[1] & 0xFF,
     83             B = rgbe[2] & 0xFF,
     84             E = rgbe[3] & 0xFF;
     85 
     86         float e = (float) Math.pow(2f, E - (128 + 8) );
     87         rgbf[0] = R * e;
     88         rgbf[1] = G * e;
     89         rgbf[2] = B * e;
     90     }
     91 
     92     public static void convertRGBEtoFloat2(byte[] rgbe, float[] rgbf){
     93         int R = rgbe[0] & 0xFF,
     94             G = rgbe[1] & 0xFF,
     95             B = rgbe[2] & 0xFF,
     96             E = rgbe[3] & 0xFF;
     97 
     98         float e = (float) Math.pow(2f, E - 128);
     99         rgbf[0] = (R / 256.0f) * e;
    100         rgbf[1] = (G / 256.0f) * e;
    101         rgbf[2] = (B / 256.0f) * e;
    102     }
    103 
    104     public static void convertRGBEtoFloat3(byte[] rgbe, float[] rgbf){
    105         int R = rgbe[0] & 0xFF,
    106             G = rgbe[1] & 0xFF,
    107             B = rgbe[2] & 0xFF,
    108             E = rgbe[3] & 0xFF;
    109 
    110         float e = (float) Math.pow(2f, E - (128 + 8) );
    111         rgbf[0] = R * e;
    112         rgbf[1] = G * e;
    113         rgbf[2] = B * e;
    114     }
    115 
    116     private short flip(int in){
    117         return (short) ((in << 8 & 0xFF00) | (in >> 8));
    118     }
    119 
    120     private void writeRGBE(byte[] rgbe){
    121         if (writeRGBE){
    122             dataStore.put(rgbe);
    123         }else{
    124             convertRGBEtoFloat(rgbe, tempF);
    125             dataStore.putShort(FastMath.convertFloatToHalf(tempF[0]))
    126                      .putShort(FastMath.convertFloatToHalf(tempF[1])).
    127                       putShort(FastMath.convertFloatToHalf(tempF[2]));
    128         }
    129     }
    130 
    131     private String readString(InputStream is) throws IOException{
    132         StringBuilder sb = new StringBuilder();
    133         while (true){
    134             int i = is.read();
    135             if (i == 0x0a || i == -1) // new line or EOF
    136                 return sb.toString();
    137 
    138             sb.append((char)i);
    139         }
    140     }
    141 
    142     private boolean decodeScanlineRLE(InputStream in, int width) throws IOException{
    143         // must deocde RLE data into temp buffer before converting to float
    144         if (rleTempBuffer == null){
    145             rleTempBuffer = BufferUtils.createByteBuffer(width * 4);
    146         }else{
    147             rleTempBuffer.clear();
    148             if (rleTempBuffer.remaining() < width * 4)
    149                 rleTempBuffer = BufferUtils.createByteBuffer(width * 4);
    150         }
    151 
    152 	// read each component seperately
    153         for (int i = 0; i < 4; i++) {
    154             // read WIDTH bytes for the channel
    155             for (int j = 0; j < width;) {
    156                 int code = in.read();
    157                 if (code > 128) { // run
    158                     code -= 128;
    159                     int val = in.read();
    160                     while ((code--) != 0) {
    161                         rleTempBuffer.put( (j++) * 4 + i , (byte)val);
    162                         //scanline[j++][i] = val;
    163                     }
    164                 } else {	// non-run
    165                     while ((code--) != 0) {
    166                         int val = in.read();
    167                         rleTempBuffer.put( (j++) * 4 + i, (byte)val);
    168                         //scanline[j++][i] = in.read();
    169                     }
    170                 }
    171             }
    172         }
    173 
    174         rleTempBuffer.rewind();
    175         byte[] rgbe = new byte[4];
    176 //        float[] temp = new float[3];
    177 
    178         // decode temp buffer into float data
    179         for (int i = 0; i < width; i++){
    180             rleTempBuffer.get(rgbe);
    181             writeRGBE(rgbe);
    182         }
    183 
    184         return true;
    185     }
    186 
    187     private boolean decodeScanlineUncompressed(InputStream in, int width) throws IOException{
    188         byte[] rgbe = new byte[4];
    189 
    190         for (int i = 0; i < width; i+=3){
    191             if (in.read(rgbe) < 1)
    192                 return false;
    193 
    194             writeRGBE(rgbe);
    195         }
    196         return true;
    197     }
    198 
    199     private void decodeScanline(InputStream in, int width) throws IOException{
    200         if (width < 8 || width > 0x7fff){
    201             // too short/long for RLE compression
    202             decodeScanlineUncompressed(in, width);
    203         }
    204 
    205         // check format
    206         byte[] data = new byte[4];
    207         in.read(data);
    208         if (data[0] != 0x02 || data[1] != 0x02 || (data[2] & 0x80) != 0){
    209             // not RLE data
    210             decodeScanlineUncompressed(in, width-1);
    211         }else{
    212             // check scanline width
    213             int readWidth = (data[2] & 0xFF) << 8 | (data[3] & 0xFF);
    214             if (readWidth != width)
    215                 throw new IOException("Illegal scanline width in HDR file: "+width+" != "+readWidth);
    216 
    217             // RLE data
    218             decodeScanlineRLE(in, width);
    219         }
    220     }
    221 
    222     public Image load(InputStream in, boolean flipY) throws IOException{
    223         float gamma = -1f;
    224         float exposure = -1f;
    225         float[] colorcorr = new float[]{ -1f, -1f, -1f };
    226 
    227         int width = -1, height = -1;
    228         boolean verifiedFormat = false;
    229 
    230         while (true){
    231             String ln = readString(in);
    232             ln = ln.trim();
    233             if (ln.startsWith("#") || ln.equals("")){
    234                 if (ln.equals("#?RADIANCE") || ln.equals("#?RGBE"))
    235                     verifiedFormat = true;
    236 
    237                 continue; // comment or empty statement
    238             } else if (ln.startsWith("+") || ln.startsWith("-")){
    239                 // + or - mark image resolution and start of data
    240                 String[] resData = ln.split("\\s");
    241                 if (resData.length != 4){
    242                     throw new IOException("Invalid resolution string in HDR file");
    243                 }
    244 
    245                 if (!resData[0].equals("-Y") || !resData[2].equals("+X")){
    246                     logger.warning("Flipping/Rotating attributes ignored!");
    247                 }
    248 
    249                 //if (resData[0].endsWith("X")){
    250                     // first width then height
    251                 //    width = Integer.parseInt(resData[1]);
    252                 //    height = Integer.parseInt(resData[3]);
    253                 //}else{
    254                     width = Integer.parseInt(resData[3]);
    255                     height = Integer.parseInt(resData[1]);
    256                 //}
    257 
    258                 break;
    259             } else {
    260                 // regular command
    261                 int index = ln.indexOf("=");
    262                 if (index < 1){
    263                     logger.log(Level.FINE, "Ignored string: {0}", ln);
    264                     continue;
    265                 }
    266 
    267                 String var = ln.substring(0, index).trim().toLowerCase();
    268                 String value = ln.substring(index+1).trim().toLowerCase();
    269                 if (var.equals("format")){
    270                     if (!value.equals("32-bit_rle_rgbe") && !value.equals("32-bit_rle_xyze")){
    271                         throw new IOException("Unsupported format in HDR picture");
    272                     }
    273                 }else if (var.equals("exposure")){
    274                     exposure = Float.parseFloat(value);
    275                 }else if (var.equals("gamma")){
    276                     gamma = Float.parseFloat(value);
    277                 }else{
    278                     logger.log(Level.WARNING, "HDR Command ignored: {0}", ln);
    279                 }
    280             }
    281         }
    282 
    283         assert width != -1 && height != -1;
    284 
    285         if (!verifiedFormat)
    286             logger.warning("Unsure if specified image is Radiance HDR");
    287 
    288         // some HDR images can get pretty big
    289         System.gc();
    290 
    291         // each pixel times size of component times # of components
    292         Format pixelFormat;
    293         if (writeRGBE){
    294             pixelFormat = Format.RGBA8;
    295         }else{
    296             pixelFormat = Format.RGB16F;
    297         }
    298 
    299         dataStore = BufferUtils.createByteBuffer(width * height * pixelFormat.getBitsPerPixel());
    300 
    301         int bytesPerPixel = pixelFormat.getBitsPerPixel() / 8;
    302         int scanLineBytes = bytesPerPixel * width;
    303         for (int y = height - 1; y >= 0; y--) {
    304             if (flipY)
    305                 dataStore.position(scanLineBytes * y);
    306 
    307             decodeScanline(in, width);
    308         }
    309         in.close();
    310 
    311         dataStore.rewind();
    312         return new Image(pixelFormat, width, height, dataStore);
    313     }
    314 
    315     public Object load(AssetInfo info) throws IOException {
    316         if (!(info.getKey() instanceof TextureKey))
    317             throw new IllegalArgumentException("Texture assets must be loaded using a TextureKey");
    318 
    319         boolean flip = ((TextureKey) info.getKey()).isFlipY();
    320         InputStream in = null;
    321         try {
    322             in = info.openStream();
    323             Image img = load(in, flip);
    324             return img;
    325         } finally {
    326             if (in != null){
    327                 in.close();
    328             }
    329         }
    330     }
    331 
    332 }
    333