View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.imaging.formats.rgbe;
18  
19  import java.io.Closeable;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.nio.ByteOrder;
23  import java.util.regex.Matcher;
24  import java.util.regex.Pattern;
25  
26  import org.apache.commons.imaging.ImageReadException;
27  import org.apache.commons.imaging.common.BinaryFunctions;
28  import org.apache.commons.imaging.common.ByteConversions;
29  import org.apache.commons.imaging.common.GenericImageMetadata;
30  import org.apache.commons.imaging.common.ImageMetadata;
31  import org.apache.commons.imaging.common.bytesource.ByteSource;
32  
33  class RgbeInfo implements Closeable {
34      // #?RADIANCE
35      private static final byte[] HEADER = new byte[] {
36          0x23, 0x3F, 0x52, 0x41, 0x44, 0x49, 0x41, 0x4E, 0x43, 0x45
37      };
38      private static final Pattern RESOLUTION_STRING = Pattern.compile("-Y (\\d+) \\+X (\\d+)");
39  
40      private final InputStream in;
41      private GenericImageMetadata metadata;
42      private int width = -1;
43      private int height = -1;
44      private static final byte[] TWO_TWO = new byte[] { 0x2, 0x2 };
45  
46      RgbeInfo(final ByteSource byteSource) throws IOException {
47          this.in = byteSource.getInputStream();
48      }
49  
50      ImageMetadata getMetadata() throws IOException, ImageReadException {
51          if (null == metadata) {
52              readMetadata();
53          }
54  
55          return metadata;
56      }
57  
58      int getWidth() throws IOException, ImageReadException {
59          if (-1 == width) {
60              readDimensions();
61          }
62  
63          return width;
64      }
65  
66      int getHeight() throws IOException, ImageReadException {
67          if (-1 == height) {
68              readDimensions();
69          }
70  
71          return height;
72      }
73  
74      @Override
75      public void close() throws IOException {
76          in.close();
77      }
78  
79      private void readDimensions() throws IOException, ImageReadException {
80          getMetadata(); // Ensure we've read past this
81  
82          final InfoHeaderReaderts/rgbe/InfoHeaderReader.html#InfoHeaderReader">InfoHeaderReader reader = new InfoHeaderReader(in);
83          final String resolution = reader.readNextLine();
84          final Matcher matcher = RESOLUTION_STRING.matcher(resolution);
85  
86          if (!matcher.matches()) {
87              throw new ImageReadException(
88                      "Invalid HDR resolution string. Only \"-Y N +X M\" is supported. Found \""
89                              + resolution + "\"");
90          }
91  
92          height = Integer.parseInt(matcher.group(1));
93          width = Integer.parseInt(matcher.group(2));
94      }
95  
96      private void readMetadata() throws IOException, ImageReadException {
97          BinaryFunctions.readAndVerifyBytes(in, HEADER, "Not a valid HDR: Incorrect Header");
98  
99          final InfoHeaderReaderts/rgbe/InfoHeaderReader.html#InfoHeaderReader">InfoHeaderReader reader = new InfoHeaderReader(in);
100 
101         if (!reader.readNextLine().isEmpty()) {
102             throw new ImageReadException("Not a valid HDR: Incorrect Header");
103         }
104 
105         metadata = new GenericImageMetadata();
106 
107         String info = reader.readNextLine();
108 
109         while (!info.isEmpty()) {
110             final int equals = info.indexOf('=');
111 
112             if (equals > 0) {
113                 final String variable = info.substring(0, equals);
114                 final String value = info.substring(equals + 1);
115 
116                 if ("FORMAT".equals(value) && !"32-bit_rle_rgbe".equals(value)) {
117                     throw new ImageReadException("Only 32-bit_rle_rgbe images are supported, trying to read " + value);
118                 }
119 
120                 metadata.add(variable, value);
121             } else {
122                 metadata.add("<command>", info);
123             }
124 
125             info = reader.readNextLine();
126         }
127     }
128 
129     public float[][] getPixelData() throws IOException, ImageReadException {
130         // Read into local variables to ensure that we have seeked into the file
131         // far enough
132         final int ht = getHeight();
133         final int wd = getWidth();
134 
135         if (wd >= 32768) {
136             throw new ImageReadException("Scan lines must be less than 32768 bytes long");
137         }
138 
139         final byte[] scanLineBytes = ByteConversions.toBytes((short) wd,
140                 ByteOrder.BIG_ENDIAN);
141         final byte[] rgbe = new byte[wd * 4];
142         final float[][] out = new float[3][wd * ht];
143 
144         for (int i = 0; i < ht; i++) {
145             BinaryFunctions.readAndVerifyBytes(in, TWO_TWO, "Scan line " + i + " expected to start with 0x2 0x2");
146             BinaryFunctions.readAndVerifyBytes(in, scanLineBytes, "Scan line " + i + " length expected");
147 
148             decompress(in, rgbe);
149 
150             for (int channel = 0; channel < 3; channel++) {
151                 final int channelOffset = channel * wd;
152                 final int eOffset = 3 * wd;
153 
154                 for (int p = 0; p < wd; p++) {
155                     final int mantissa = rgbe[p + eOffset] & 0xff;
156                     final int pos = p + i * wd;
157 
158                     if (0 == mantissa) {
159                         out[channel][pos] = 0;
160                     } else {
161                         final float mult = (float) Math.pow(2, mantissa - (128 + 8));
162                         out[channel][pos] = ((rgbe[p + channelOffset] & 0xff) + 0.5f) * mult;
163                     }
164                 }
165             }
166         }
167 
168         return out;
169     }
170 
171     private static void decompress(final InputStream in, final byte[] out)
172             throws IOException,ImageReadException {
173         int position = 0;
174         final int total = out.length;
175 
176         while (position < total) {
177             final int n = in.read();
178 
179             if (n < 0) {
180                 throw new ImageReadException("Error decompressing RGBE file");
181             }
182 
183             if (n > 128) {
184                 final int value = in.read();
185 
186                 for (int i = 0; i < (n & 0x7f); i++) {
187                     out[position++] = (byte) value;
188                 }
189             } else {
190                 for (int i = 0; i < n; i++) {
191                     out[position++] = (byte) in.read();
192                 }
193             }
194         }
195     }
196 }