1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.ImagingException;
27 import org.apache.commons.imaging.bytesource.ByteSource;
28 import org.apache.commons.imaging.common.Allocator;
29 import org.apache.commons.imaging.common.BinaryFunctions;
30 import org.apache.commons.imaging.common.ByteConversions;
31 import org.apache.commons.imaging.common.GenericImageMetadata;
32 import org.apache.commons.imaging.common.ImageMetadata;
33
34 final class RgbeInfo implements Closeable {
35
36 private static final byte[] HEADER = { 0x23, 0x3F, 0x52, 0x41, 0x44, 0x49, 0x41, 0x4E, 0x43, 0x45 };
37 private static final Pattern RESOLUTION_STRING = Pattern.compile("-Y (\\d+) \\+X (\\d+)");
38
39 private static final byte[] TWO_TWO = { 0x2, 0x2 };
40
41 private static void decompress(final InputStream in, final byte[] out) throws IOException, ImagingException {
42 int position = 0;
43 final int total = out.length;
44
45 while (position < total) {
46 final int n = in.read();
47
48 if (n < 0) {
49 throw new ImagingException("Error decompressing RGBE file");
50 }
51
52 if (n > 128) {
53 final int value = in.read();
54
55 for (int i = 0; i < (n & 0x7f); i++) {
56 out[position++] = (byte) value;
57 }
58 } else {
59 for (int i = 0; i < n; i++) {
60 out[position++] = (byte) in.read();
61 }
62 }
63 }
64 }
65
66 private final InputStream in;
67 private GenericImageMetadata metadata;
68 private int width = -1;
69
70 private int height = -1;
71
72 RgbeInfo(final ByteSource byteSource) throws IOException {
73 this.in = byteSource.getInputStream();
74 }
75
76 @Override
77 public void close() throws IOException {
78 in.close();
79 }
80
81 int getHeight() throws IOException, ImagingException {
82 if (-1 == height) {
83 readDimensions();
84 }
85
86 return height;
87 }
88
89 ImageMetadata getMetadata() throws IOException, ImagingException {
90 if (null == metadata) {
91 readMetadata();
92 }
93
94 return metadata;
95 }
96
97 public float[][] getPixelData() throws IOException, ImagingException {
98
99
100 final int ht = getHeight();
101 final int wd = getWidth();
102
103 if (wd >= 32768) {
104 throw new ImagingException("Scan lines must be less than 32768 bytes long");
105 }
106
107 final byte[] scanLineBytes = ByteConversions.toBytes((short) wd, ByteOrder.BIG_ENDIAN);
108 final byte[] rgbe = Allocator.byteArray(wd * 4);
109 final float[][] out = new float[3][Allocator.check(wd * ht)];
110
111 for (int i = 0; i < ht; i++) {
112 BinaryFunctions.readAndVerifyBytes(in, TWO_TWO, "Scan line " + i + " expected to start with 0x2 0x2");
113 BinaryFunctions.readAndVerifyBytes(in, scanLineBytes, "Scan line " + i + " length expected");
114
115 decompress(in, rgbe);
116
117 for (int channel = 0; channel < 3; channel++) {
118 final int channelOffset = channel * wd;
119 final int eOffset = 3 * wd;
120
121 for (int p = 0; p < wd; p++) {
122 final int mantissa = rgbe[p + eOffset] & 0xff;
123 final int pos = p + i * wd;
124
125 if (0 == mantissa) {
126 out[channel][pos] = 0;
127 } else {
128 final float mult = (float) Math.pow(2, mantissa - (128 + 8));
129 out[channel][pos] = ((rgbe[p + channelOffset] & 0xff) + 0.5f) * mult;
130 }
131 }
132 }
133 }
134
135 return out;
136 }
137
138 int getWidth() throws IOException, ImagingException {
139 if (-1 == width) {
140 readDimensions();
141 }
142
143 return width;
144 }
145
146 private void readDimensions() throws IOException, ImagingException {
147 getMetadata();
148
149 final InfoHeaderReader reader = new InfoHeaderReader(in);
150 final String resolution = reader.readNextLine();
151 final Matcher matcher = RESOLUTION_STRING.matcher(resolution);
152
153 if (!matcher.matches()) {
154 throw new ImagingException("Invalid HDR resolution string. Only \"-Y N +X M\" is supported. Found \"" + resolution + "\"");
155 }
156
157 height = Integer.parseInt(matcher.group(1));
158 width = Integer.parseInt(matcher.group(2));
159 }
160
161 private void readMetadata() throws IOException, ImagingException {
162 BinaryFunctions.readAndVerifyBytes(in, HEADER, "Not a valid HDR: Incorrect Header");
163
164 final InfoHeaderReader reader = new InfoHeaderReader(in);
165
166 if (!reader.readNextLine().isEmpty()) {
167 throw new ImagingException("Not a valid HDR: Incorrect Header");
168 }
169
170 metadata = new GenericImageMetadata();
171
172 String info = reader.readNextLine();
173
174 while (!info.isEmpty()) {
175 final int equals = info.indexOf('=');
176
177 if (equals > 0) {
178 final String variable = info.substring(0, equals);
179 final String value = info.substring(equals + 1);
180
181 if ("FORMAT".equals(value) && !"32-bit_rle_rgbe".equals(value)) {
182 throw new ImagingException("Only 32-bit_rle_rgbe images are supported, trying to read " + value);
183 }
184
185 metadata.add(variable, value);
186 } else {
187 metadata.add("<command>", info);
188 }
189
190 info = reader.readNextLine();
191 }
192 }
193 }