diff --git a/imageio/imageio-hdr/pom.xml b/imageio/imageio-hdr/pom.xml
new file mode 100644
index 00000000..dae0b4fb
--- /dev/null
+++ b/imageio/imageio-hdr/pom.xml
@@ -0,0 +1,30 @@
+
+
+ 4.0.0
+
+ com.twelvemonkeys.imageio
+ imageio
+ 3.2-SNAPSHOT
+
+ imageio-hdr
+ TwelveMonkeys :: ImageIO :: HDR plugin
+
+ ImageIO plugin for Radiance RGBE High Dynaimc Range format (HDR).
+
+
+
+
+ com.twelvemonkeys.imageio
+ imageio-core
+
+
+ com.twelvemonkeys.imageio
+ imageio-core
+ tests
+
+
+ com.twelvemonkeys.imageio
+ imageio-metadata
+
+
+
diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDR.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDR.java
new file mode 100644
index 00000000..f85f4b9a
--- /dev/null
+++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDR.java
@@ -0,0 +1,13 @@
+package com.twelvemonkeys.imageio.plugins.hdr;
+
+/**
+ * HDR.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: HDR.java,v 1.0 27/07/15 harald.kuhr Exp$
+ */
+interface HDR {
+ byte[] RADIANCE_MAGIC = new byte[] {'#', '?', 'R', 'A', 'D', 'I', 'A', 'N', 'C', 'E'};
+ byte[] RGBE_MAGIC = new byte[] {'#', '?', 'R', 'G', 'B', 'E'};
+}
diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRHeader.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRHeader.java
new file mode 100644
index 00000000..b7fe68a9
--- /dev/null
+++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRHeader.java
@@ -0,0 +1,95 @@
+package com.twelvemonkeys.imageio.plugins.hdr;
+
+import javax.imageio.IIOException;
+import javax.imageio.stream.ImageInputStream;
+import java.io.IOException;
+
+/**
+ * HDRHeader.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: HDRHeader.java,v 1.0 27/07/15 harald.kuhr Exp$
+ */
+final class HDRHeader {
+ private static final String KEY_FORMAT = "FORMAT=";
+ private static final String KEY_PRIMARIES = "PRIMARIES=";
+ private static final String KEY_EXPOSURE = "EXPOSURE=";
+ private static final String KEY_GAMMA = "GAMMA=";
+ private static final String KEY_SOFTWARE = "SOFTWARE=";
+
+ private int width;
+ private int height;
+
+ private String software;
+
+ public static HDRHeader read(final ImageInputStream stream) throws IOException {
+ HDRHeader header = new HDRHeader();
+
+ while (true) {
+ String line = stream.readLine().trim();
+
+ if (line.isEmpty()) {
+ // This is the last line before the dimensions
+ break;
+ }
+
+ if (line.startsWith("#?")) {
+ // Program specifier, don't need that...
+ }
+ else if (line.startsWith("#")) {
+ // Comment (ignore)
+ }
+ else if (line.startsWith(KEY_FORMAT)) {
+ String format = line.substring(KEY_FORMAT.length()).trim();
+
+ if (!format.equals("32-bit_rle_rgbe")) {
+ throw new IIOException("Unsupported format \"" + format + "\"(expected \"32-bit_rle_rgbe\")");
+ }
+ // TODO: Support the 32-bit_rle_xyze format
+ }
+ else if (line.startsWith(KEY_PRIMARIES)) {
+ // TODO: We are going to need these values...
+ // Should contain 8 (RGB + white point) coordinates
+ }
+ else if (line.startsWith(KEY_EXPOSURE)) {
+ // TODO: We are going to need these values...
+ }
+ else if (line.startsWith(KEY_GAMMA)) {
+ // TODO: We are going to need these values...
+ }
+ else if (line.startsWith(KEY_SOFTWARE)) {
+ header.software = line.substring(KEY_SOFTWARE.length()).trim();
+ }
+ else {
+ // ...ignore
+ }
+ }
+
+ // TODO: Proper parsing of width/height and orientation!
+ String dimensionsLine = stream.readLine().trim();
+ String[] dims = dimensionsLine.split("\\s");
+
+ if (dims[0].equals("-Y") && dims[2].equals("+X")) {
+ header.height = Integer.parseInt(dims[1]);
+ header.width = Integer.parseInt(dims[3]);
+
+ return header;
+ }
+ else {
+ throw new IIOException("Unsupported RGBE orientation (expected \"-Y ... +X ...\")");
+ }
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+ public String getSoftware() {
+ return software;
+ }
+}
diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReadParam.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReadParam.java
new file mode 100644
index 00000000..6a8ee9ea
--- /dev/null
+++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReadParam.java
@@ -0,0 +1,27 @@
+package com.twelvemonkeys.imageio.plugins.hdr;
+
+import com.twelvemonkeys.imageio.plugins.hdr.tonemap.DefaultToneMapper;
+import com.twelvemonkeys.imageio.plugins.hdr.tonemap.ToneMapper;
+
+import javax.imageio.ImageReadParam;
+
+/**
+ * HDRImageReadParam.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: HDRImageReadParam.java,v 1.0 28/07/15 harald.kuhr Exp$
+ */
+public final class HDRImageReadParam extends ImageReadParam {
+ static final ToneMapper DEFAULT_TONE_MAPPER = new DefaultToneMapper(.1f);
+
+ private ToneMapper toneMapper = DEFAULT_TONE_MAPPER;
+
+ public ToneMapper getToneMapper() {
+ return toneMapper;
+ }
+
+ public void setToneMapper(final ToneMapper toneMapper) {
+ this.toneMapper = toneMapper != null ? toneMapper : DEFAULT_TONE_MAPPER;
+ }
+}
diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReader.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReader.java
new file mode 100644
index 00000000..0513cf86
--- /dev/null
+++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReader.java
@@ -0,0 +1,166 @@
+package com.twelvemonkeys.imageio.plugins.hdr;
+
+import com.twelvemonkeys.imageio.ImageReaderBase;
+import com.twelvemonkeys.imageio.plugins.hdr.tonemap.ToneMapper;
+import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
+
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReadParam;
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.spi.ImageReaderSpi;
+import java.awt.*;
+import java.awt.color.ColorSpace;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBuffer;
+import java.awt.image.WritableRaster;
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Iterator;
+
+/**
+ * HDRImageReader.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: HDRImageReader.java,v 1.0 27/07/15 harald.kuhr Exp$
+ */
+public final class HDRImageReader extends ImageReaderBase {
+ // Specs: http://radsite.lbl.gov/radiance/refer/filefmts.pdf
+
+ private HDRHeader header;
+
+ protected HDRImageReader(final ImageReaderSpi provider) {
+ super(provider);
+ }
+
+ @Override
+ protected void resetMembers() {
+ header = null;
+ }
+
+ private void readHeader() throws IOException {
+ if (header == null) {
+ header = HDRHeader.read(imageInput);
+
+ imageInput.flushBefore(imageInput.getStreamPosition());
+ }
+
+ imageInput.seek(imageInput.getFlushedPosition());
+ }
+
+ @Override
+ public int getWidth(int imageIndex) throws IOException {
+ checkBounds(imageIndex);
+ readHeader();
+
+ return header.getWidth();
+ }
+
+ @Override
+ public int getHeight(int imageIndex) throws IOException {
+ checkBounds(imageIndex);
+ readHeader();
+
+ return header.getHeight();
+ }
+
+ @Override
+ public Iterator getImageTypes(int imageIndex) throws IOException {
+ checkBounds(imageIndex);
+ readHeader();
+
+ ColorSpace sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB);
+ return Collections.singletonList(ImageTypeSpecifiers.createInterleaved(sRGB, new int[] {0, 1, 2}, DataBuffer.TYPE_FLOAT, false, false)).iterator();
+ }
+
+ @Override
+ public BufferedImage read(final int imageIndex, final ImageReadParam param) throws IOException {
+ checkBounds(imageIndex);
+ readHeader();
+
+ int width = getWidth(imageIndex);
+ int height = getHeight(imageIndex);
+
+ BufferedImage destination = getDestination(param, getImageTypes(imageIndex), width, height);
+
+ Rectangle srcRegion = new Rectangle();
+ Rectangle destRegion = new Rectangle();
+ computeRegions(param, width, height, destination, srcRegion, destRegion);
+
+ WritableRaster raster = destination.getRaster()
+ .createWritableChild(destRegion.x, destRegion.y, destRegion.width, destRegion.height, 0, 0, null);
+
+ int xSub = param != null ? param.getSourceXSubsampling() : 1;
+ int ySub = param != null ? param.getSourceYSubsampling() : 1;
+
+ // Allow pluggable tone mapper via ImageReadParam
+ ToneMapper toneMapper = param instanceof HDRImageReadParam
+ ? ((HDRImageReadParam) param).getToneMapper()
+ : HDRImageReadParam.DEFAULT_TONE_MAPPER;
+
+ byte[] rowRGBE = new byte[width * 4];
+ float[] rgb = new float[3];
+
+ processImageStarted(imageIndex);
+
+ // Process one scanline of RGBE data at a time
+ for (int srcY = 0; srcY < height; srcY++) {
+ int dstY = ((srcY - srcRegion.y) / ySub) + destRegion.y;
+ if (dstY >= destRegion.height) {
+ break;
+ }
+
+ RGBE.readPixelsRawRLE(imageInput, rowRGBE, 0, width, 1);
+
+ if (srcY % ySub == 0 && dstY >= destRegion.y) {
+ for (int srcX = srcRegion.x; srcX < srcRegion.x + srcRegion.width; srcX += xSub) {
+ int dstX = ((srcX - srcRegion.x) / xSub) + destRegion.x;
+ if (dstX >= destRegion.width) {
+ break;
+ }
+
+ RGBE.rgbe2float(rgb, rowRGBE, srcX * 4);
+
+ // Map/clamp RGB values into visible range, normally [0...1]
+ toneMapper.map(rgb);
+
+ raster.setDataElements(dstX, dstY, rgb);
+ }
+ }
+
+ processImageProgress(srcY * 100f / height);
+
+ if (abortRequested()) {
+ processReadAborted();
+ break;
+ }
+ }
+
+ processImageComplete();
+
+ return destination;
+ }
+
+ @Override
+ public ImageReadParam getDefaultReadParam() {
+ return new HDRImageReadParam();
+ }
+
+ @Override
+ public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
+ checkBounds(imageIndex);
+ readHeader();
+
+ return new HDRMetadata(header);
+ }
+
+ public static void main(final String[] args) throws IOException {
+ File file = new File(args[0]);
+
+ BufferedImage image = ImageIO.read(file);
+
+ showIt(image, file.getName());
+ }
+}
diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReaderSpi.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReaderSpi.java
new file mode 100644
index 00000000..0624e2f6
--- /dev/null
+++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReaderSpi.java
@@ -0,0 +1,56 @@
+package com.twelvemonkeys.imageio.plugins.hdr;
+
+import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
+
+import javax.imageio.ImageReader;
+import javax.imageio.stream.ImageInputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Locale;
+
+/**
+ * HDRImageReaderSpi.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: HDRImageReaderSpi.java,v 1.0 27/07/15 harald.kuhr Exp$
+ */
+public final class HDRImageReaderSpi extends ImageReaderSpiBase {
+ public HDRImageReaderSpi() {
+ super(new HDRProviderInfo());
+ }
+
+ @Override
+ public boolean canDecodeInput(final Object source) throws IOException {
+ if (!(source instanceof ImageInputStream)) {
+ return false;
+ }
+
+ ImageInputStream stream = (ImageInputStream) source;
+
+ stream.mark();
+
+ try {
+ // NOTE: All images I have found starts with #?RADIANCE (or has no #? line at all),
+ // although some sources claim that #?RGBE is also used.
+ byte[] magic = new byte[HDR.RADIANCE_MAGIC.length];
+ stream.readFully(magic);
+
+ return Arrays.equals(HDR.RADIANCE_MAGIC, magic)
+ || Arrays.equals(HDR.RGBE_MAGIC, Arrays.copyOf(magic, 6));
+ }
+ finally {
+ stream.reset();
+ }
+ }
+
+ @Override
+ public ImageReader createReaderInstance(Object extension) throws IOException {
+ return new HDRImageReader(this);
+ }
+
+ @Override
+ public String getDescription(final Locale locale) {
+ return "Radiance RGBE High Dynaimc Range (HDR) image reader";
+ }
+}
diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRMetadata.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRMetadata.java
new file mode 100755
index 00000000..52cabaeb
--- /dev/null
+++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRMetadata.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2014, Harald Kuhr
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name "TwelveMonkeys" nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.twelvemonkeys.imageio.plugins.hdr;
+
+import com.twelvemonkeys.imageio.AbstractMetadata;
+
+import javax.imageio.metadata.IIOMetadataNode;
+
+final class HDRMetadata extends AbstractMetadata {
+ private final HDRHeader header;
+
+ HDRMetadata(final HDRHeader header) {
+ this.header = header;
+ }
+
+ @Override
+ protected IIOMetadataNode getStandardChromaNode() {
+ IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
+
+ IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
+ chroma.appendChild(csType);
+ csType.setAttribute("name", "RGB");
+ // TODO: Support XYZ
+
+ IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
+ numChannels.setAttribute("value", "3");
+ chroma.appendChild(numChannels);
+
+ IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
+ blackIsZero.setAttribute("value", "TRUE");
+ chroma.appendChild(blackIsZero);
+
+ return chroma;
+ }
+
+ // No compression
+
+ @Override
+ protected IIOMetadataNode getStandardCompressionNode() {
+ IIOMetadataNode node = new IIOMetadataNode("Compression");
+
+ IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
+ compressionTypeName.setAttribute("value", "RLE");
+ node.appendChild(compressionTypeName);
+
+ IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
+ lossless.setAttribute("value", "TRUE");
+ node.appendChild(lossless);
+
+ return node;
+ }
+
+ @Override
+ protected IIOMetadataNode getStandardDataNode() {
+ IIOMetadataNode node = new IIOMetadataNode("Data");
+
+ IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
+ sampleFormat.setAttribute("value", "UnsignedIntegral");
+ node.appendChild(sampleFormat);
+
+ IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
+ bitsPerSample.setAttribute("value", "8 8 8 8");
+ node.appendChild(bitsPerSample);
+
+ return node;
+ }
+
+ @Override
+ protected IIOMetadataNode getStandardDimensionNode() {
+ IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
+
+ // TODO: Support other orientations
+ IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
+ imageOrientation.setAttribute("value", "Normal");
+ dimension.appendChild(imageOrientation);
+
+ return dimension;
+ }
+
+ // No document node
+
+ @Override
+ protected IIOMetadataNode getStandardTextNode() {
+ if (header.getSoftware() != null) {
+ IIOMetadataNode text = new IIOMetadataNode("Text");
+
+ IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry");
+ textEntry.setAttribute("keyword", "Software");
+ textEntry.setAttribute("value", header.getSoftware());
+ text.appendChild(textEntry);
+
+ return text;
+ }
+
+ return null;
+ }
+
+ // No tiling
+
+ // No transparency
+}
diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRProviderInfo.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRProviderInfo.java
new file mode 100644
index 00000000..ab1b2b1f
--- /dev/null
+++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRProviderInfo.java
@@ -0,0 +1,27 @@
+package com.twelvemonkeys.imageio.plugins.hdr;
+
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
+
+/**
+ * HDRProviderInfo.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: HDRProviderInfo.java,v 1.0 27/07/15 harald.kuhr Exp$
+ */
+final class HDRProviderInfo extends ReaderWriterProviderInfo {
+ protected HDRProviderInfo() {
+ super(
+ HDRProviderInfo.class,
+ new String[] {"HDR", "hdr", "RGBE", "rgbe"},
+ new String[] {"hdr", "rgbe", "xyze", "pic"},
+ new String[] {"image/vnd.radiance"},
+ "com.twelvemonkeys.imageio.plugins.hdr.HDRImageReader",
+ new String[]{"com.twelvemonkeys.imageio.plugins.hdr.HDRImageReaderSpi"},
+ null,
+ null,
+ false, null, null, null, null,
+ true, null, null, null, null
+ );
+ }
+}
diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/RGBE.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/RGBE.java
new file mode 100644
index 00000000..b64607e1
--- /dev/null
+++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/RGBE.java
@@ -0,0 +1,494 @@
+package com.twelvemonkeys.imageio.plugins.hdr;
+
+import java.io.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This file contains code to read and write four byte rgbe file format
+ * developed by Greg Ward. It handles the conversions between rgbe and
+ * pixels consisting of floats. The data is assumed to be an array of floats.
+ * By default there are three floats per pixel in the order red, green, blue.
+ * (RGBE_DATA_??? values control this.) Only the mimimal header reading and
+ * writing is implemented. Each routine does error checking and will return
+ * a status value as defined below. This code is intended as a skeleton so
+ * feel free to modify it to suit your needs.
+ *
+ * Ported to Java and restructured by Kenneth Russell.
+ * posted to http://www.graphics.cornell.edu/~bjw/
+ * written by Bruce Walter (bjw@graphics.cornell.edu) 5/26/95
+ * based on code written by Greg Ward
+ *
+ * Source: https://java.net/projects/jogl-demos/sources/svn/content/trunk/src/demos/hdr/RGBE.java
+ */
+final class RGBE {
+ // Flags indicating which fields in a Header are valid
+ private static final int VALID_PROGRAMTYPE = 0x01;
+ private static final int VALID_GAMMA = 0x02;
+ private static final int VALID_EXPOSURE = 0x04;
+
+ private static final String gammaString = "GAMMA=";
+ private static final String exposureString = "EXPOSURE=";
+
+ private static final Pattern widthHeightPattern = Pattern.compile("-Y (\\d+) \\+X (\\d+)");
+
+ public static class Header {
+ // Indicates which fields are valid
+ private int valid;
+
+ // Listed at beginning of file to identify it after "#?".
+ // Defaults to "RGBE"
+ private String programType;
+
+ // Image has already been gamma corrected with given gamma.
+ // Defaults to 1.0 (no correction)
+ private float gamma;
+
+ // A value of 1.0 in an image corresponds to
+ // watts/steradian/m^2. Defaults to 1.0.
+ private float exposure;
+
+ // Width and height of image
+ private int width;
+ private int height;
+
+ private Header(int valid,
+ String programType,
+ float gamma,
+ float exposure,
+ int width,
+ int height) {
+ this.valid = valid;
+ this.programType = programType;
+ this.gamma = gamma;
+ this.exposure = exposure;
+ this.width = width;
+ this.height = height;
+ }
+
+ public boolean isProgramTypeValid() {
+ return ((valid & VALID_PROGRAMTYPE) != 0);
+ }
+
+ public boolean isGammaValid() {
+ return ((valid & VALID_GAMMA) != 0);
+ }
+
+ public boolean isExposureValid() {
+ return ((valid & VALID_EXPOSURE) != 0);
+ }
+
+ public String getProgramType() {
+ return programType;
+ }
+
+ public float getGamma() {
+ return gamma;
+ }
+
+ public float getExposure() {
+ return exposure;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+ public String toString() {
+ StringBuffer buf = new StringBuffer();
+ if (isProgramTypeValid()) {
+ buf.append(" Program type: ");
+ buf.append(getProgramType());
+ }
+ buf.append(" Gamma");
+ if (isGammaValid()) {
+ buf.append(" [valid]");
+ }
+ buf.append(": ");
+ buf.append(getGamma());
+ buf.append(" Exposure");
+ if (isExposureValid()) {
+ buf.append(" [valid]");
+ }
+ buf.append(": ");
+ buf.append(getExposure());
+ buf.append(" Width: ");
+ buf.append(getWidth());
+ buf.append(" Height: ");
+ buf.append(getHeight());
+ return buf.toString();
+ }
+ }
+
+ public static Header readHeader(final DataInput in) throws IOException {
+ int valid = 0;
+ String programType = null;
+ float gamma = 1.0f;
+ float exposure = 1.0f;
+ int width = 0;
+ int height = 0;
+
+ String buf = in.readLine();
+ if (buf == null) {
+ throw new IOException("Unexpected EOF reading magic token");
+ }
+ if (buf.charAt(0) == '#' && buf.charAt(1) == '?') {
+ valid |= VALID_PROGRAMTYPE;
+ programType = buf.substring(2);
+ buf = in.readLine();
+ if (buf == null) {
+ throw new IOException("Unexpected EOF reading line after magic token");
+ }
+ }
+
+ boolean foundFormat = false;
+ boolean done = false;
+ while (!done) {
+ if (buf.equals("FORMAT=32-bit_rle_rgbe")) {
+ foundFormat = true;
+ }
+ else if (buf.startsWith(gammaString)) {
+ valid |= VALID_GAMMA;
+ gamma = Float.parseFloat(buf.substring(gammaString.length()));
+ }
+ else if (buf.startsWith(exposureString)) {
+ valid |= VALID_EXPOSURE;
+ exposure = Float.parseFloat(buf.substring(exposureString.length()));
+ }
+ else {
+ Matcher m = widthHeightPattern.matcher(buf);
+ if (m.matches()) {
+ width = Integer.parseInt(m.group(2));
+ height = Integer.parseInt(m.group(1));
+ done = true;
+ }
+ }
+
+ if (!done) {
+ buf = in.readLine();
+ if (buf == null) {
+ throw new IOException("Unexpected EOF reading header");
+ }
+ }
+ }
+
+ if (!foundFormat) {
+ throw new IOException("No FORMAT specifier found");
+ }
+
+ return new Header(valid, programType, gamma, exposure, width, height);
+ }
+
+ /**
+ * Simple read routine. Will not correctly handle run length encoding.
+ */
+ public static void readPixels(DataInput in, float[] data, int numpixels) throws IOException {
+ byte[] rgbe = new byte[4];
+ float[] rgb = new float[3];
+ int offset = 0;
+
+ while (numpixels-- > 0) {
+ in.readFully(rgbe);
+
+ rgbe2float(rgb, rgbe, 0);
+
+ data[offset++] = rgb[0];
+ data[offset++] = rgb[1];
+ data[offset++] = rgb[2];
+ }
+ }
+
+ public static void readPixelsRaw(DataInput in, byte[] data, int offset, int numpixels) throws IOException {
+ int numExpected = 4 * numpixels;
+ in.readFully(data, offset, numExpected);
+ }
+
+ public static void readPixelsRawRLE(DataInput in, byte[] data, int offset,
+ int scanline_width, int num_scanlines) throws IOException {
+ byte[] rgbe = new byte[4];
+ byte[] scanline_buffer = null;
+ int ptr, ptr_end;
+ int count;
+ byte[] buf = new byte[2];
+
+ if ((scanline_width < 8) || (scanline_width > 0x7fff)) {
+ // run length encoding is not allowed so read flat
+ readPixelsRaw(in, data, offset, scanline_width * num_scanlines);
+ }
+
+ // read in each successive scanline
+ while (num_scanlines > 0) {
+ in.readFully(rgbe);
+
+ if ((rgbe[0] != 2) || (rgbe[1] != 2) || ((rgbe[2] & 0x80) != 0)) {
+ // this file is not run length encoded
+ data[offset++] = rgbe[0];
+ data[offset++] = rgbe[1];
+ data[offset++] = rgbe[2];
+ data[offset++] = rgbe[3];
+ readPixelsRaw(in, data, offset, scanline_width * num_scanlines - 1);
+ }
+
+ if ((((rgbe[2] & 0xFF) << 8) | (rgbe[3] & 0xFF)) != scanline_width) {
+ throw new IOException("Wrong scanline width " +
+ (((rgbe[2] & 0xFF) << 8) | (rgbe[3] & 0xFF)) +
+ ", expected " + scanline_width);
+ }
+
+ if (scanline_buffer == null) {
+ scanline_buffer = new byte[4 * scanline_width];
+ }
+
+ ptr = 0;
+ // read each of the four channels for the scanline into the buffer
+ for (int i = 0; i < 4; i++) {
+ ptr_end = (i + 1) * scanline_width;
+ while (ptr < ptr_end) {
+ in.readFully(buf);
+
+ if ((buf[0] & 0xFF) > 128) {
+ // a run of the same value
+ count = (buf[0] & 0xFF) - 128;
+ if ((count == 0) || (count > ptr_end - ptr)) {
+ throw new IOException("Bad scanline data");
+ }
+ while (count-- > 0) {
+ scanline_buffer[ptr++] = buf[1];
+ }
+ }
+ else {
+ // a non-run
+ count = buf[0] & 0xFF;
+ if ((count == 0) || (count > ptr_end - ptr)) {
+ throw new IOException("Bad scanline data");
+ }
+ scanline_buffer[ptr++] = buf[1];
+ if (--count > 0) {
+ in.readFully(scanline_buffer, ptr, count);
+ ptr += count;
+ }
+ }
+ }
+ }
+ // copy byte data to output
+ for (int i = 0; i < scanline_width; i++) {
+ data[offset++] = scanline_buffer[i];
+ data[offset++] = scanline_buffer[i + scanline_width];
+ data[offset++] = scanline_buffer[i + 2 * scanline_width];
+ data[offset++] = scanline_buffer[i + 3 * scanline_width];
+ }
+ num_scanlines--;
+ }
+ }
+
+ /**
+ * Standard conversion from float pixels to rgbe pixels.
+ */
+ public static void float2rgbe(byte[] rgbe, float red, float green, float blue) {
+ float v;
+ int e;
+
+ v = red;
+ if (green > v) {
+ v = green;
+ }
+ if (blue > v) {
+ v = blue;
+ }
+ if (v < 1e-32f) {
+ rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0;
+ }
+ else {
+ FracExp fe = frexp(v);
+ v = (float) (fe.getFraction() * 256.0 / v);
+ rgbe[0] = (byte) (red * v);
+ rgbe[1] = (byte) (green * v);
+ rgbe[2] = (byte) (blue * v);
+ rgbe[3] = (byte) (fe.getExponent() + 128);
+ }
+ }
+
+ /**
+ * Standard conversion from rgbe to float pixels. Note: Ward uses
+ * ldexp(col+0.5,exp-(128+8)). However we wanted pixels in the
+ * range [0,1] to map back into the range [0,1].
+ */
+ public static void rgbe2float(float[] rgb, byte[] rgbe, int startRGBEOffset) {
+ float f;
+
+ if (rgbe[startRGBEOffset + 3] != 0) { // nonzero pixel
+ f = (float) ldexp(1.0, (rgbe[startRGBEOffset + 3] & 0xFF) - (128 + 8));
+ rgb[0] = (rgbe[startRGBEOffset + 0] & 0xFF) * f;
+ rgb[1] = (rgbe[startRGBEOffset + 1] & 0xFF) * f;
+ rgb[2] = (rgbe[startRGBEOffset + 2] & 0xFF) * f;
+ }
+ else {
+ rgb[0] = 0;
+ rgb[1] = 0;
+ rgb[2] = 0;
+ }
+ }
+
+ public static double ldexp(double value, int exp) {
+ if (!finite(value) || value == 0.0) {
+ return value;
+ }
+ value = scalbn(value, exp);
+ // No good way to indicate errno (want to avoid throwing
+ // exceptions because don't know about stability of calculations)
+ // if(!finite(value)||value==0.0) errno = ERANGE;
+ return value;
+ }
+
+ //----------------------------------------------------------------------
+ // Internals only below this point
+ //
+
+ //----------------------------------------------------------------------
+ // Math routines, some fdlibm-derived
+ //
+
+ static class FracExp {
+ private double fraction;
+ private int exponent;
+
+ public FracExp(double fraction, int exponent) {
+ this.fraction = fraction;
+ this.exponent = exponent;
+ }
+
+ public double getFraction() {
+ return fraction;
+ }
+
+ public int getExponent() {
+ return exponent;
+ }
+ }
+
+ private static final double two54 = 1.80143985094819840000e+16; // 43500000 00000000
+ private static final double twom54 = 5.55111512312578270212e-17; // 0x3C900000 0x00000000
+ private static final double huge = 1.0e+300;
+ private static final double tiny = 1.0e-300;
+
+ private static int hi(double x) {
+ long bits = Double.doubleToRawLongBits(x);
+ return (int) (bits >>> 32);
+ }
+
+ private static int lo(double x) {
+ long bits = Double.doubleToRawLongBits(x);
+ return (int) bits;
+ }
+
+ private static double fromhilo(int hi, int lo) {
+ return Double.longBitsToDouble((((long) hi) << 32) |
+ (((long) lo) & 0xFFFFFFFFL));
+ }
+
+ private static FracExp frexp(double x) {
+ int hx = hi(x);
+ int ix = 0x7fffffff & hx;
+ int lx = lo(x);
+ int e = 0;
+ if (ix >= 0x7ff00000 || ((ix | lx) == 0)) {
+ return new FracExp(x, e); // 0,inf,nan
+ }
+ if (ix < 0x00100000) { // subnormal
+ x *= two54;
+ hx = hi(x);
+ ix = hx & 0x7fffffff;
+ e = -54;
+ }
+ e += (ix >> 20) - 1022;
+ hx = (hx & 0x800fffff) | 0x3fe00000;
+ lx = lo(x);
+ return new FracExp(fromhilo(hx, lx), e);
+ }
+
+ private static boolean finite(double x) {
+ int hx;
+ hx = hi(x);
+ return (((hx & 0x7fffffff) - 0x7ff00000) >> 31) != 0;
+ }
+
+ /**
+ * copysign(double x, double y)
+ * copysign(x,y) returns a value with the magnitude of x and
+ * with the sign bit of y.
+ */
+ private static double copysign(double x, double y) {
+ return fromhilo((hi(x) & 0x7fffffff) | (hi(y) & 0x80000000), lo(x));
+ }
+
+ /**
+ * scalbn (double x, int n)
+ * scalbn(x,n) returns x* 2**n computed by exponent
+ * manipulation rather than by actually performing an
+ * exponentiation or a multiplication.
+ */
+ private static double scalbn(double x, int n) {
+ int hx = hi(x);
+ int lx = lo(x);
+ int k = (hx & 0x7ff00000) >> 20; // extract exponent
+ if (k == 0) { // 0 or subnormal x
+ if ((lx | (hx & 0x7fffffff)) == 0) {
+ return x; // +-0
+ }
+ x *= two54;
+ hx = hi(x);
+ k = ((hx & 0x7ff00000) >> 20) - 54;
+ if (n < -50000) {
+ return tiny * x; // underflow
+ }
+ }
+ if (k == 0x7ff) {
+ return x + x; // NaN or Inf
+ }
+ k = k + n;
+ if (k > 0x7fe) {
+ return huge * copysign(huge, x); // overflow
+ }
+ if (k > 0) {
+ // normal result
+ return fromhilo((hx & 0x800fffff) | (k << 20), lo(x));
+ }
+ if (k <= -54) {
+ if (n > 50000) {
+ // in case integer overflow in n+k
+ return huge * copysign(huge, x); // overflow
+ }
+ else {
+ return tiny * copysign(tiny, x); // underflow
+ }
+ }
+ k += 54; // subnormal result
+ x = fromhilo((hx & 0x800fffff) | (k << 20), lo(x));
+ return x * twom54;
+ }
+
+ //----------------------------------------------------------------------
+ // Test harness
+ //
+
+ public static void main(String[] args) {
+ for (int i = 0; i < args.length; i++) {
+ try {
+ DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(args[i])));
+ Header header = RGBE.readHeader(in);
+ System.err.println("Header for file \"" + args[i] + "\":");
+ System.err.println(" " + header);
+ byte[] data = new byte[header.getWidth() * header.getHeight() * 4];
+ readPixelsRawRLE(in, data, 0, header.getWidth(), header.getHeight());
+ in.close();
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+}
diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/DefaultToneMapper.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/DefaultToneMapper.java
new file mode 100644
index 00000000..740c0913
--- /dev/null
+++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/DefaultToneMapper.java
@@ -0,0 +1,35 @@
+package com.twelvemonkeys.imageio.plugins.hdr.tonemap;
+
+/**
+ * DefaultToneMapper.
+ *
+ * Normalizes values to range [0...1] using:
+ *
+ * Vout = Vin / (Vin + C)
+ *
+ * Where C is constant.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: DefaultToneMapper.java,v 1.0 28/07/15 harald.kuhr Exp$
+ */
+public final class DefaultToneMapper implements ToneMapper {
+
+ private final float constant;
+
+ public DefaultToneMapper() {
+ this(1);
+ }
+
+ public DefaultToneMapper(final float constant) {
+ this.constant = constant;
+ }
+
+ @Override
+ public void map(final float[] rgb) {
+ // Default Vo = Vi / (Vi + 1)
+ for (int i = 0; i < rgb.length; i++) {
+ rgb[i] = rgb[i] / (rgb[i] + constant);
+ }
+ }
+}
diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/GammaToneMapper.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/GammaToneMapper.java
new file mode 100644
index 00000000..7a8ba6fb
--- /dev/null
+++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/GammaToneMapper.java
@@ -0,0 +1,38 @@
+package com.twelvemonkeys.imageio.plugins.hdr.tonemap;
+
+/**
+ * GammaToneMapper.
+ *
+ * Normalizes values to range [0...1] using:
+ *
+ * Vout = A Vin\u03b3
+ *
+ * Where A is constant and \u03b3 is the gamma.
+ * Values > 1 are clamped.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: GammaToneMapper.java,v 1.0 28/07/15 harald.kuhr Exp$
+ */
+public final class GammaToneMapper implements ToneMapper {
+
+ private final float constant;
+ private final float gamma;
+
+ public GammaToneMapper() {
+ this(0.5f, .25f);
+ }
+
+ public GammaToneMapper(final float constant, final float gamma) {
+ this.constant = constant;
+ this.gamma = gamma;
+ }
+
+ @Override
+ public void map(final float[] rgb) {
+ // Gamma Vo = A * Vi^y
+ for (int i = 0; i < rgb.length; i++) {
+ rgb[i] = Math.min(1f, (float) (constant * Math.pow(rgb[i], gamma)));
+ }
+ }
+}
diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/NullToneMapper.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/NullToneMapper.java
new file mode 100644
index 00000000..ea6ad349
--- /dev/null
+++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/NullToneMapper.java
@@ -0,0 +1,18 @@
+package com.twelvemonkeys.imageio.plugins.hdr.tonemap;
+
+/**
+ * NullToneMapper.
+ *
+ * This {@code ToneMapper} does *not* normalize or clamp values
+ * to range [0...1], but leaves the values as-is.
+ * Useful for applications that implements custom tone mapping.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: NullToneMapper.java,v 1.0 28/07/15 harald.kuhr Exp$
+ */
+public final class NullToneMapper implements ToneMapper {
+ @Override
+ public void map(float[] rgb) {
+ }
+}
diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/ToneMapper.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/ToneMapper.java
new file mode 100644
index 00000000..2f06374b
--- /dev/null
+++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/ToneMapper.java
@@ -0,0 +1,12 @@
+package com.twelvemonkeys.imageio.plugins.hdr.tonemap;
+
+/**
+ * ToneMapper.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: ToneMapper.java,v 1.0 28/07/15 harald.kuhr Exp$
+ */
+public interface ToneMapper {
+ void map(float[] rgb);
+}
diff --git a/imageio/imageio-hdr/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi b/imageio/imageio-hdr/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi
new file mode 100755
index 00000000..7af7febe
--- /dev/null
+++ b/imageio/imageio-hdr/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi
@@ -0,0 +1 @@
+com.twelvemonkeys.imageio.plugins.hdr.HDRImageReaderSpi
diff --git a/imageio/imageio-hdr/src/test/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReaderTest.java b/imageio/imageio-hdr/src/test/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReaderTest.java
new file mode 100755
index 00000000..2a57572f
--- /dev/null
+++ b/imageio/imageio-hdr/src/test/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReaderTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2014, Harald Kuhr
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name "TwelveMonkeys" nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.twelvemonkeys.imageio.plugins.hdr;
+
+import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
+
+import javax.imageio.spi.ImageReaderSpi;
+import java.awt.*;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * TGAImageReaderTest
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: TGAImageReaderTest.java,v 1.0 03.07.14 22:28 haraldk Exp$
+ */
+public class HDRImageReaderTest extends ImageReaderAbstractTest {
+ @Override
+ protected List getTestData() {
+ return Arrays.asList(
+ new TestData(getClassLoaderResource("/hdr/memorial_o876.hdr"), new Dimension(512, 768))
+ );
+ }
+
+ @Override
+ protected ImageReaderSpi createProvider() {
+ return new HDRImageReaderSpi();
+ }
+
+ @Override
+ protected Class getReaderClass() {
+ return HDRImageReader.class;
+ }
+
+ @Override
+ protected HDRImageReader createReader() {
+ return new HDRImageReader(createProvider());
+ }
+
+ @Override
+ protected List getFormatNames() {
+ return Arrays.asList("HDR", "hdr", "RGBE", "rgbe");
+ }
+
+ @Override
+ protected List getSuffixes() {
+ return Arrays.asList("hdr", "rgbe", "xyze");
+ }
+
+ @Override
+ protected List getMIMETypes() {
+ return Collections.singletonList(
+ "image/vnd.radiance"
+ );
+ }
+}
diff --git a/imageio/imageio-hdr/src/test/resources/hdr/memorial_o876.hdr b/imageio/imageio-hdr/src/test/resources/hdr/memorial_o876.hdr
new file mode 100644
index 00000000..c135ae99
Binary files /dev/null and b/imageio/imageio-hdr/src/test/resources/hdr/memorial_o876.hdr differ
diff --git a/imageio/pom.xml b/imageio/pom.xml
index 019c00ac..1e58b424 100644
--- a/imageio/pom.xml
+++ b/imageio/pom.xml
@@ -30,6 +30,7 @@
imageio-bmp
+ imageio-hdr
imageio-icns
imageio-iff
imageio-jpeg