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