/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.imaging.formats.rgbe;
import java.io.IOException;
import java.io.InputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.imaging.ImageReadException;
import org.apache.commons.imaging.common.BinaryFunctions;
import org.apache.commons.imaging.common.BinaryInputStream;
import org.apache.commons.imaging.common.ByteConversions;
import org.apache.commons.imaging.common.ByteOrder;
import org.apache.commons.imaging.common.IImageMetadata;
import org.apache.commons.imaging.common.ImageMetadata;
import org.apache.commons.imaging.common.bytesource.ByteSource;
import org.apache.commons.imaging.util.Debug;
class RgbeInfo extends BinaryFunctions {
private static final Pattern RESOLUTION_STRING = Pattern
.compile("-Y (\\d+) \\+X (\\d+)");
private final BinaryInputStream in;
private ImageMetadata metadata;
private int width = -1;
private int height = -1;
private static final byte[] TWO_TWO = new byte[] { 0x2, 0x2 };
RgbeInfo(final ByteSource byteSource) throws IOException {
this.in = new BinaryInputStream(byteSource.getInputStream(),
ByteOrder.BIG_ENDIAN);
}
IImageMetadata getMetadata() throws IOException, ImageReadException {
if (null == metadata) {
readMetadata();
}
return metadata;
}
int getWidth() throws IOException, ImageReadException {
if (-1 == width) {
readDimensions();
}
return width;
}
int getHeight() throws IOException, ImageReadException {
if (-1 == height) {
readDimensions();
}
return height;
}
void close() {
try {
in.close();
} catch (final IOException e) {
Debug.debug(e);
}
}
private void readDimensions() throws IOException, ImageReadException {
getMetadata(); // Ensure we've read past this
final InfoHeaderReader reader = new InfoHeaderReader(in);
final String resolution = reader.readNextLine();
final Matcher matcher = RESOLUTION_STRING.matcher(resolution);
if (!matcher.matches()) {
throw new ImageReadException(
"Invalid HDR resolution string. Only \"-Y N +X M\" is supported. Found \""
+ resolution + "\"");
}
height = Integer.parseInt(matcher.group(1));
width = Integer.parseInt(matcher.group(2));
}
private void readMetadata() throws IOException, ImageReadException {
in.readAndVerifyBytes(RgbeConstants.HEADER,
"Not a valid HDR: Incorrect Header");
final InfoHeaderReader reader = new InfoHeaderReader(in);
if (reader.readNextLine().length() != 0) {
throw new ImageReadException("Not a valid HDR: Incorrect Header");
}
metadata = new ImageMetadata();
String info = reader.readNextLine();
while (info.length() != 0) {
final int equals = info.indexOf("=");
if (equals > 0) {
final String variable = info.substring(0, equals);
final String value = info.substring(equals + 1);
if ("FORMAT".equals(value) && !"32-bit_rle_rgbe".equals(value)) {
throw new ImageReadException(
"Only 32-bit_rle_rgbe images are supported, trying to read " +
value);
}
metadata.add(variable, value);
} else {
metadata.add("<command>", info);
}
info = reader.readNextLine();
}
}
public float[][] getPixelData() throws IOException, ImageReadException {
// Read into local variables to ensure that we have seeked into the file
// far enough
final int height = getHeight();
final int width = getWidth();
if (width >= 32768) {
throw new ImageReadException(
"Scan lines must be less than 32768 bytes long");
}
final byte[] scanLineBytes = ByteConversions.toBytes((short)width,
ByteOrder.BIG_ENDIAN);
final byte[] rgbe = new byte[width * 4];
final float[][] out = new float[3][width * height];
for (int i = 0; i < height; i++) {
in.readAndVerifyBytes(TWO_TWO, "Scan line " + i
+ " expected to start with 0x2 0x2");
in.readAndVerifyBytes(scanLineBytes, "Scan line " + i
+ " length expected");
decompress(in, rgbe);
for (int channel = 0; channel < 3; channel++) {
final int channelOffset = channel * width;
final int eOffset = 3 * width;
for (int p = 0; p < width; p++) {
final int mantissa = rgbe[p + eOffset] & 0xff;
final int pos = p + i * width;
if (0 == mantissa) {
out[channel][pos] = 0;
} else {
final float mult = (float) Math.pow(2, mantissa - (128 + 8));
out[channel][pos] = ((rgbe[p + channelOffset] & 0xff) + 0.5f)
* mult;
}
}
}
}
return out;
}
private static void decompress(final InputStream in, final byte[] out)
throws IOException {
int position = 0;
final int total = out.length;
while (position < total) {
final int n = in.read();
if (n > 128) {
final int value = in.read();
for (int i = 0; i < (n & 0x7f); i++) {
out[position++] = (byte) value;
}
} else {
for (int i = 0; i < n; i++) {
out[position++] = (byte) in.read();
}
}
}
}
}