Skip to content

Commit

Permalink
Finish parsing of HDR
Browse files Browse the repository at this point in the history
  • Loading branch information
osi committed Jul 9, 2009
1 parent 9b12ff6 commit f29c13d
Show file tree
Hide file tree
Showing 3 changed files with 299 additions and 10 deletions.
89 changes: 79 additions & 10 deletions src/main/java/org/apache/sanselan/formats/hdr/HdrImageParser.java
@@ -1,20 +1,31 @@
package org.apache.sanselan.formats.hdr;

import java.util.Map;
import java.io.IOException;
import java.io.File;
import java.awt.image.BufferedImage;
import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.image.BandedSampleModel;
import java.awt.image.BufferedImage;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferDouble;
import java.awt.image.Raster;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Map;

import org.apache.sanselan.ImageFormat;
import org.apache.sanselan.ImageInfo;
import org.apache.sanselan.ImageParser;
import org.apache.sanselan.ImageReadException;
import org.apache.sanselan.ImageInfo;
import org.apache.sanselan.common.BinaryConstants;
import org.apache.sanselan.common.IImageMetadata;
import org.apache.sanselan.common.byteSources.ByteSource;

/** @author <a href="mailto:peter@electrotank.com">peter royal</a> */
/**
* Parser for Radiance HDR images
*
* @author <a href="mailto:peter@electrotank.com">peter royal</a>
*/
public class HdrImageParser extends ImageParser {

public HdrImageParser() {
Expand All @@ -38,19 +49,77 @@ protected ImageFormat[] getAcceptedTypes() {
}

public IImageMetadata getMetadata( ByteSource byteSource, Map params ) throws ImageReadException, IOException {
throw new UnsupportedOperationException();
HdrInfo info = new HdrInfo( byteSource );

try {
return info.getMetadata();
} finally {
info.close();
}
}

public ImageInfo getImageInfo( ByteSource byteSource, Map params ) throws ImageReadException, IOException {
throw new UnsupportedOperationException();
HdrInfo info = new HdrInfo( byteSource );

try {
return new ImageInfo( getName(),
32, // todo may be 64 if double?
new ArrayList(),
ImageFormat.IMAGE_FORMAT_HDR,
getName(),
info.getHeight(),
"image/vnd.radiance",
1,
-1,
-1,
-1,
-1,
info.getWidth(),
false,
false,
false,
ImageInfo.COLOR_TYPE_RGB,
"Adaptive RLE" );
} finally {
info.close();
}
}

public BufferedImage getBufferedImage( ByteSource byteSource, Map params ) throws ImageReadException, IOException {
throw new UnsupportedOperationException();
HdrInfo info = new HdrInfo( byteSource );

try {
// It is necessary to create our own BufferedImage here as the
// org.apache.sanselan.common.IBufferedImageFactory interface does not expose this complexity
DataBuffer buffer = new DataBufferDouble( info.getPixelData(), info.getWidth() * info.getHeight() );

return new BufferedImage(
new ComponentColorModel( ColorSpace.getInstance( ColorSpace.CS_sRGB ),
false,
false,
Transparency.OPAQUE,
buffer.getDataType() ),
Raster.createWritableRaster( new BandedSampleModel( buffer.getDataType(),
info.getWidth(),
info.getHeight(),
3 ),
buffer,
new Point() ),
false,
null );
} finally {
info.close();
}
}

public Dimension getImageSize( ByteSource byteSource, Map params ) throws ImageReadException, IOException {
throw new UnsupportedOperationException();
HdrInfo info = new HdrInfo( byteSource );

try {
return new Dimension( info.getWidth(), info.getHeight() );
} finally {
info.close();
}
}

public byte[] getICCProfileBytes( ByteSource byteSource, Map params ) throws ImageReadException, IOException {
Expand Down
173 changes: 173 additions & 0 deletions src/main/java/org/apache/sanselan/formats/hdr/HdrInfo.java
@@ -0,0 +1,173 @@
package org.apache.sanselan.formats.hdr;

import java.io.IOException;
import java.io.InputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.sanselan.ImageReadException;
import org.apache.sanselan.common.BinaryConstants;
import org.apache.sanselan.common.BinaryFileFunctions;
import org.apache.sanselan.common.BinaryInputStream;
import org.apache.sanselan.common.IImageMetadata;
import org.apache.sanselan.common.ImageMetadata;
import org.apache.sanselan.common.byteSources.ByteSource;
import org.apache.sanselan.util.Debug;

class HdrInfo extends BinaryFileFunctions {
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 };

HdrInfo( ByteSource byteSource ) throws IOException {
this.in = new BinaryInputStream( byteSource.getInputStream(), BinaryConstants.BYTE_ORDER_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( IOException e ) {
Debug.debug( e );
}
}

private void readDimensions() throws IOException, ImageReadException {
getMetadata(); // Ensure we've read past this

InfoHeaderReader reader = new InfoHeaderReader( in );
String resolution = reader.readLine();
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( HdrConstants.HEADER, "Not a valid HDR: Incorrect Header" );

InfoHeaderReader reader = new InfoHeaderReader( in );

if ( reader.readLine().length() != 0 ) {
throw new ImageReadException( "Not a valid HDR: Incorrect Header" );
}

metadata = new ImageMetadata();

String info = reader.readLine();

while ( info.length() != 0 ) {
int equals = info.indexOf( "=" );

if ( equals > 0 ) {
String variable = info.substring( 0, equals );
String value = info.substring( equals + 1 );

if ( "FORMAT".equals( value ) ) {
if ( !"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.readLine();
}
}

public double[][] getPixelData() throws IOException, ImageReadException {
// Read into local variables to ensure that we have seeked into the file far enough
int height = getHeight();
int width = getWidth();

if ( width >= 32768 ) {
throw new ImageReadException( "Scan lines must be less than 32768 bytes long" );
}

byte[] scanLineBytes = convertShortToByteArray( width, BinaryConstants.BYTE_ORDER_BIG_ENDIAN );
byte[] rgbe = new byte[width * 4];
double[][] out = new double[3][width];

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++ ) {
int channelOffset = channel * width;
int eOffset = 3 * width;

for ( int p = 0; p < width; p++ ) {
int mantissa = rgbe[p + eOffset] & 0xff;

if ( 0 == mantissa ) {
out[channel][p] = 0;
} else {
double mult = Math.pow( 2, mantissa );
out[channel][p] = ( ( rgbe[p + channelOffset] & 0xff ) + 0.5f ) * mult;
}
}
}
}

return out;
}

private static void decompress( InputStream in, byte[] out ) throws IOException {
int position = 0;
int total = out.length;

while ( position < total ) {
int n = in.read();

if ( n > 128 ) {
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();
}
}
}
}
}
@@ -0,0 +1,47 @@
/*
* 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.sanselan.formats.hdr;

import java.io.IOException;
import java.io.InputStream;

class InfoHeaderReader {
private final InputStream is;

public InfoHeaderReader( InputStream is ) {
this.is = is;
}

private char read() throws IOException {
int result = is.read();
if ( result < 0 ) {
throw new IOException( "HDR: Unexpected EOF" );
}
return (char) result;
}

public String readLine() throws IOException {
StringBuffer buffer = new StringBuffer();
char c;

while ( ( c = read() ) != '\n' ) {
buffer.append( c );
}

return buffer.toString();
}
}

0 comments on commit f29c13d

Please sign in to comment.