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.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
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();
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
131
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 }