• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
HDRLoader(boolean writeRGBE)57     public HDRLoader(boolean writeRGBE){
58         this.writeRGBE = writeRGBE;
59     }
60 
HDRLoader()61     public HDRLoader(){
62     }
63 
convertFloatToRGBE(byte[] rgbe, float red, float green, float blue)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 
convertRGBEtoFloat(byte[] rgbe, float[] rgbf)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 
convertRGBEtoFloat2(byte[] rgbe, float[] rgbf)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 
convertRGBEtoFloat3(byte[] rgbe, float[] rgbf)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 
flip(int in)116     private short flip(int in){
117         return (short) ((in << 8 & 0xFF00) | (in >> 8));
118     }
119 
writeRGBE(byte[] rgbe)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 
readString(InputStream is)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 
decodeScanlineRLE(InputStream in, int width)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 
decodeScanlineUncompressed(InputStream in, int width)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 
decodeScanline(InputStream in, int width)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 
load(InputStream in, boolean flipY)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 
load(AssetInfo info)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