diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/tiff/Half.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/tiff/Half.java
new file mode 100644
index 00000000..459c7f2e
--- /dev/null
+++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/tiff/Half.java
@@ -0,0 +1,160 @@
+package com.twelvemonkeys.imageio.metadata.tiff;
+
+/**
+ * IEEE 754 half-precision floating point data type.
+ *
+ * @see Stack Overflow answer by x4u
+ * @see Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: Half.java,v 1.0 10/04/2021 haraldk Exp$
+ */
+public final class Half extends Number implements Comparable {
+ // Short, Int, Long
+ // Half, Float, Double :-)
+
+ public static final int SIZE = 16;
+
+ private final short shortBits;
+ private final transient float floatValue;
+
+ public Half(short shortBits) {
+ this.shortBits = shortBits;
+ this.floatValue = shortBitsToFloat(shortBits);
+ }
+
+ @Override
+ public int intValue() {
+ return (int) floatValue;
+ }
+
+ @Override
+ public long longValue() {
+ return (long) floatValue;
+ }
+
+ @Override
+ public float floatValue() {
+ return floatValue;
+ }
+
+ @Override
+ public double doubleValue() {
+ return floatValue;
+ }
+
+ public int hashCode() {
+ return shortBits;
+ }
+
+ public boolean equals(Object other) {
+ return (other instanceof Half)
+ && ((Half) other).shortBits == shortBits;
+ }
+
+ @Override
+ public int compareTo(final Half other) {
+ return Float.compare(floatValue, other.floatValue);
+ }
+
+ @Override
+ public String toString() {
+ return Float.toString(floatValue);
+ }
+
+ public static Half valueOf(String value) throws NumberFormatException {
+ return new Half(parseHalf(value));
+ }
+
+ public static short parseHalf(String value) throws NumberFormatException {
+ return floatToShortBits(Float.parseFloat(value));
+ }
+
+ /**
+ * Converts an IEEE 754 half-precision data type to single-precision.
+ *
+ * @param shortBits a 16 bit half precision value
+ * @return an IEE 754 single precision float
+ *
+ */
+ public static float shortBitsToFloat(final short shortBits) {
+ int mantissa = shortBits & 0x03ff; // 10 bits mantissa
+ int exponent = shortBits & 0x7c00; // 5 bits exponent
+
+ if (exponent == 0x7c00) { // NaN/Inf
+ exponent = 0x3fc00; // -> NaN/Inf
+ }
+ else if (exponent != 0) { // Normalized value
+ exponent += 0x1c000; // exp - 15 + 127
+
+ // Smooth transition
+ if (mantissa == 0 && exponent > 0x1c400) {
+ return Float.intBitsToFloat((shortBits & 0x8000) << 16 | exponent << 13 | 0x3ff);
+ }
+ }
+ else if (mantissa != 0) { // && exp == 0 -> subnormal
+ exponent = 0x1c400; // Make it normal
+
+ do {
+ mantissa <<= 1; // mantissa * 2
+ exponent -= 0x400; // Decrease exp by 1
+ } while ((mantissa & 0x400) == 0); // while not normal
+
+ mantissa &= 0x3ff; // Discard subnormal bit
+ } // else +/-0 -> +/-0
+
+ // Combine all parts, sign << (31 - 15), value << (23 - 10)
+ return Float.intBitsToFloat((shortBits & 0x8000) << 16 | (exponent | mantissa) << 13);
+ }
+
+ /**
+ * Converts a float value to IEEE 754 half-precision bits.
+ *
+ * @param floatValue a float value
+ * @return the IEE 754 single precision 16 bits value
+ *
+ */
+ public static short floatToShortBits(final float floatValue) {
+ // TODO: Is this okay? Need test
+ return (short) floatTo16Bits(floatValue);
+ }
+
+ private static int floatTo16Bits(final float floatValue) {
+ int fbits = Float.floatToIntBits(floatValue);
+ int sign = fbits >>> 16 & 0x8000; // sign only
+ int val = (fbits & 0x7fffffff) + 0x1000; // rounded value
+
+ if (val >= 0x47800000) { // might be or become NaN/Inf, avoid Inf due to rounding
+ if ((fbits & 0x7fffffff) >= 0x47800000) { // is or must become NaN/Inf
+ if (val < 0x7f800000) { // was value but too large
+ return sign | 0x7c00; // make it +/-Inf
+ }
+
+ return sign | 0x7c00 | // remains +/-Inf or NaN
+ (fbits & 0x007fffff) >>> 13;// keep NaN (and Inf) bits
+ }
+
+ return sign | 0x7bff; // unrounded not quite Inf
+ }
+
+ if (val >= 0x38800000) { // remains normalized value
+ return sign | val - 0x38000000 >>> 13; // exp - 127 + 15
+ }
+
+ if (val < 0x33000000) { // too small for subnormal
+ return sign; // becomes +/-0
+ }
+
+ val = (fbits & 0x7fffffff) >>> 23; // tmp exp for subnormal calc
+
+ return sign | ((fbits & 0x7fffff | 0x800000)// add subnormal bit
+ + (0x800000 >>> val - 102) // round depending on cut off
+ >>> 126 - val); // div by 2^(1-(exp-127+15)) and >> 13 | exp=0
+ }
+
+ // Restores the floatValue on de-serialization
+ private Object readResolve() {
+ return new Half(shortBits);
+ }
+}
diff --git a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/tiff/HalfTest.java b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/tiff/HalfTest.java
new file mode 100644
index 00000000..fa27cd51
--- /dev/null
+++ b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/tiff/HalfTest.java
@@ -0,0 +1,181 @@
+package com.twelvemonkeys.imageio.metadata.tiff;
+
+import com.twelvemonkeys.io.FastByteArrayOutputStream;
+
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Random;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * HalfTest.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: HalfTest.java,v 1.0 10/04/2021 haraldk Exp$
+ */
+public class HalfTest {
+ Random random = new Random(8374698541237L);
+
+ @Test
+ public void testSize() {
+ assertEquals(16, Half.SIZE);
+ }
+
+ @Test
+ public void testRoundTrip() {
+ for (int i = 0; i < 1024; i++) {
+ short half = (short) random.nextInt(Short.MAX_VALUE & 0x3FFF);
+ float floatValue = Half.shortBitsToFloat(half);
+ assertEquals(half, Half.floatToShortBits(floatValue));
+ }
+ }
+
+ @Test
+ public void testRoundTripBack() {
+ for (int i = 0; i < 1024; i++) {
+ float floatValue = random.nextFloat();
+ short half = Half.floatToShortBits(floatValue);
+ assertEquals(floatValue, Half.shortBitsToFloat(half), 0.0003); // Might lose some precision 32 -> 16 bit
+ }
+ }
+
+ @Test
+ public void testHashCode() {
+ for (int i = 0; i < 1024; i++) {
+ short halfBits = (short) random.nextInt(Short.MAX_VALUE);
+ Half half = new Half(halfBits);
+ assertEquals(halfBits, half.hashCode());
+ }
+ }
+
+ @Test
+ public void testEquals() {
+ for (int i = 0; i < 1024; i++) {
+ short halfBits = (short) random.nextInt(Short.MAX_VALUE);
+ Half half = new Half(halfBits);
+ assertEquals(new Half(halfBits), half);
+ }
+ }
+
+ @Test
+ public void testCompareEquals() {
+ for (int i = 0; i < 1024; i++) {
+ short halfBits = (short) random.nextInt(Short.MAX_VALUE);
+ Half half = new Half(halfBits);
+ assertEquals(0, new Half(halfBits).compareTo(half));
+ }
+ }
+
+ @Test
+ public void testCompareLess() {
+ for (int i = 0; i < 1024; i++) {
+ short halfBits = (short) random.nextInt(Short.MAX_VALUE & 0x3FFF);
+ Half half = new Half(halfBits );
+ assertEquals(-1, new Half((short) (halfBits - 2)).compareTo(half));
+ }
+ }
+
+ @Test
+ public void testCompareGreater() {
+ for (int i = 0; i < 1024; i++) {
+ short halfBits = (short) random.nextInt(Short.MAX_VALUE & 0x3FFF);
+ Half half = new Half(halfBits);
+ assertEquals(1, new Half((short) (halfBits + 2)).compareTo(half));
+ }
+ }
+
+ @Test
+ public void testToString() {
+ assertEquals("0.0", new Half((short) 0).toString());
+ // TODO: More... But we just delegate to Float.toString, so no worries... :-)
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testParseHAlfNull() {
+ Half.parseHalf(null);
+ }
+
+ @Test(expected = NumberFormatException.class)
+ public void testParseHalfBad() {
+ Half.parseHalf("foo");
+ }
+
+ @Test
+ public void testParseHalf() {
+ short half = Half.parseHalf("9876.5432");
+ assertEquals(Half.floatToShortBits(9876.5432f), half);
+ // TODO: More... But we just delegate to Float.valueOf, so no worries... :-)
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testValueOfNull() {
+ Half.valueOf(null);
+ }
+
+ @Test(expected = NumberFormatException.class)
+ public void testValueOfBad() {
+ Half.valueOf("foo");
+ }
+
+ @Test
+ public void testValueOf() {
+ Half half = Half.valueOf("12.3456");
+ assertEquals(new Half(Half.floatToShortBits(12.3456f)), half);
+ // TODO: More... But we just delegate to Float.valueOf, so no worries... :-)
+ }
+
+ @Test
+ public void testIntValue() {
+ for (int i = 0; i < 1024; i++) {
+ int intValue = i << 1;
+ Half half = new Half(Half.floatToShortBits((float) intValue));
+ assertEquals(intValue, half.intValue());
+ }
+ }
+
+ @Test
+ public void testLongValue() {
+ for (int i = 0; i < 1024; i++) {
+ long longValue = i << 2;
+ Half half = new Half(Half.floatToShortBits((float) longValue));
+ assertEquals(longValue, half.longValue());
+ }
+ }
+
+ @Test
+ public void testFloatValue() {
+ for (int i = 0; i < 1024; i++) {
+ float floatValue = random.nextFloat();
+ Half half = new Half(Half.floatToShortBits(floatValue));
+ assertEquals(floatValue, half.floatValue(), 0.0003);
+ }
+ }
+
+ @Test
+ public void testDoubleValue() {
+ for (int i = 0; i < 1024; i++) {
+ double doubleValue = random.nextDouble();
+ Half half = new Half(Half.floatToShortBits((float) doubleValue));
+ assertEquals(doubleValue, half.doubleValue(), 0.0003);
+ }
+ }
+
+ @Test
+ public void testSerializationRoundTrip() throws IOException, ClassNotFoundException {
+ Half original = new Half((short) 0x3D75);
+ FastByteArrayOutputStream bytes = new FastByteArrayOutputStream(64);
+ new ObjectOutputStream(bytes).writeObject(original);
+
+ Object restored = new ObjectInputStream(bytes.createInputStream()).readObject();
+ assertTrue(restored instanceof Half);
+ assertEquals(original, restored); // Only tests bits, not transient float value
+
+ assertEquals(original.floatValue(), ((Half) restored).floatValue(), 0);
+ }
+}
\ No newline at end of file
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java
index 2a9b2ffe..986dc906 100644
--- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java
@@ -42,6 +42,7 @@ import com.twelvemonkeys.imageio.metadata.iptc.IPTCReader;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
import com.twelvemonkeys.imageio.metadata.psd.PSD;
import com.twelvemonkeys.imageio.metadata.psd.PSDReader;
+import com.twelvemonkeys.imageio.metadata.tiff.Half;
import com.twelvemonkeys.imageio.metadata.tiff.Rational;
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
@@ -2019,49 +2020,10 @@ public final class TIFFImageReader extends ImageReaderBase {
private void toFloat(final float[] rowDataFloat, final short[] rowDataShort) {
for (int i = 0; i < rowDataFloat.length; i++) {
- rowDataFloat[i] = toFloat(rowDataShort[i]);
+ rowDataFloat[i] = Half.shortBitsToFloat(rowDataShort[i]);
}
}
- /**
- * Converts an IEEE 754 half-precision data type to single-precision.
- *
- * @param shortValue a 16 bit half precision value
- * @return an IEE 754 single precision float
- *
- * @see Stack Overflow answer by x4u
- * @see