From 1ddab866fd54b8bc89414db402b654640b4c34b9 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Mon, 9 Aug 2021 21:11:40 +0200 Subject: [PATCH] #617 BigTIFF write support. --- .../imageio/util/ImageWriterAbstractTest.java | 2 +- .../imageio/metadata/tiff/TIFFWriter.java | 147 ++++++-- .../metadata/tiff/BigTIFFWriterTest.java | 345 ++++++++++++++++++ .../imageio/metadata/tiff/TIFFWriterTest.java | 19 +- .../plugins/tiff/BigTIFFImageWriterSpi.java | 67 ++++ .../plugins/tiff/BigTIFFProviderInfo.java | 4 +- .../imageio/plugins/tiff/TIFFImageWriter.java | 32 +- .../services/javax.imageio.spi.ImageWriterSpi | 1 + .../plugins/tiff/BigTIFFImageWriterTest.java | 117 ++++++ 9 files changed, 674 insertions(+), 60 deletions(-) create mode 100644 imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/tiff/BigTIFFWriterTest.java create mode 100644 imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/BigTIFFImageWriterSpi.java create mode 100644 imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/BigTIFFImageWriterTest.java diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageWriterAbstractTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageWriterAbstractTest.java index 4bc19264..5671e2db 100755 --- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageWriterAbstractTest.java +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageWriterAbstractTest.java @@ -79,7 +79,7 @@ public abstract class ImageWriterAbstractTest { protected abstract ImageWriterSpi createProvider(); protected final T createWriter() throws IOException { - return writerClass.cast(provider.createWriterInstance(null)); + return writerClass.cast(provider.createWriterInstance()); } protected abstract List getTestData(); diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/tiff/TIFFWriter.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/tiff/TIFFWriter.java index f8ed4104..08dfc2a5 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/tiff/TIFFWriter.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/tiff/TIFFWriter.java @@ -40,7 +40,6 @@ import javax.imageio.IIOException; import javax.imageio.stream.ImageOutputStream; import java.io.IOException; import java.nio.ByteOrder; -import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.*; @@ -58,7 +57,24 @@ public final class TIFFWriter extends MetadataWriter { private static final int WORD_LENGTH = 2; private static final int LONGWORD_LENGTH = 4; - private static final int ENTRY_LENGTH = 12; + + // TODO: We probably want to gloss over client code writing IFDs in BigTIFF (or vice versa) somehow... Silently convert IFD -> IFD8 + private final boolean longOffsets; + private final int offsetSize; + private final long entryLength; + private final int directoryCountLength; + + public TIFFWriter() { + this(LONGWORD_LENGTH); + } + + public TIFFWriter(int offsetSize) { + this.offsetSize = Validate.isTrue(offsetSize == 4 || offsetSize == 8, offsetSize, "offsetSize must be 4 for TIFF or 8 for BigTIFF"); + + longOffsets = offsetSize == 8; + directoryCountLength = longOffsets ? 8 : WORD_LENGTH; + entryLength = 2 * WORD_LENGTH + 2 * offsetSize; + } public boolean write(final Collection entries, final ImageOutputStream stream) throws IOException { return write(new IFD(entries), stream); @@ -87,7 +103,7 @@ public final class TIFFWriter extends MetadataWriter { } // Offset to next IFD (EOF) - stream.writeInt(0); + writeOffset(stream, 0); return true; } @@ -96,7 +112,12 @@ public final class TIFFWriter extends MetadataWriter { // Header ByteOrder byteOrder = stream.getByteOrder(); stream.writeShort(byteOrder == ByteOrder.BIG_ENDIAN ? TIFF.BYTE_ORDER_MARK_BIG_ENDIAN : TIFF.BYTE_ORDER_MARK_LITTLE_ENDIAN); - stream.writeShort(42); + stream.writeShort(longOffsets ? TIFF.BIGTIFF_MAGIC : TIFF.TIFF_MAGIC); + + if (longOffsets) { + stream.writeShort(offsetSize); // Always 8 in this case + stream.writeShort(0); + } } public long writeIFD(final Collection entries, final ImageOutputStream stream) throws IOException { @@ -118,37 +139,42 @@ public final class TIFFWriter extends MetadataWriter { long dataSize = computeDataSize(ordered); // Offset to this IFD - final long ifdOffset = stream.getStreamPosition() + dataSize + LONGWORD_LENGTH; + final long ifdOffset = stream.getStreamPosition() + dataSize + offsetSize; if (!isSubIFD) { - stream.writeInt(assertIntegerOffset(ifdOffset)); - dataOffset += LONGWORD_LENGTH; + writeOffset(stream, ifdOffset); + dataOffset += offsetSize; // Seek to offset stream.seek(ifdOffset); } else { - dataOffset += WORD_LENGTH + ordered.size() * ENTRY_LENGTH; + dataOffset += directoryCountLength + ordered.size() * entryLength; } // Write directory - stream.writeShort(ordered.size()); + writeDirectoryCount(stream, ordered.size()); for (Entry entry : ordered) { - // Write tag id + // Write tag id, type & value count stream.writeShort((Integer) entry.getIdentifier()); - // Write tag type stream.writeShort(getType(entry)); - // Write value count - stream.writeInt(getCount(entry)); + writeValueCount(stream, getCount(entry)); // Write value - if (entry.getValue() instanceof Directory) { - // TODO: This could possibly be a compound directory, in which case the count should be > 1 - stream.writeInt(assertIntegerOffset(dataOffset)); - long streamPosition = stream.getStreamPosition(); + Object value = entry.getValue(); + if (value instanceof Directory) { + if (value instanceof CompoundDirectory) { + // Can't have both nested and linked IFDs + throw new AssertionError("SubIFD cannot contain linked IFDs"); + } + + // We can't write offset here, we need to write value, as both LONG/IFD and LONG8/IFD8 is allowed + // TODO: Or possibly gloss over, by always writing IFD8 for BigTIFF? + long streamPosition = stream.getStreamPosition() + offsetSize; + writeValueInline(dataOffset, getType(entry), stream); stream.seek(dataOffset); - Directory subIFD = (Directory) entry.getValue(); + Directory subIFD = (Directory) value; writeIFD(subIFD, stream, true); dataOffset += computeDataSize(subIFD); stream.seek(streamPosition); @@ -161,8 +187,26 @@ public final class TIFFWriter extends MetadataWriter { return ifdOffset; } - public long computeIFDSize(final Collection directory) { - return WORD_LENGTH + computeDataSize(new IFD(directory)) + directory.size() * ENTRY_LENGTH; + private void writeDirectoryCount(ImageOutputStream stream, int count) throws IOException { + if (longOffsets) { + stream.writeLong(count); + } + else { + stream.writeShort(count); + } + } + + private void writeValueCount(ImageOutputStream stream, int count) throws IOException { + if (longOffsets) { + stream.writeLong(count); + } + else { + stream.writeInt(count); + } + } + + public long computeIFDSize(final Collection directory) { + return directoryCountLength + computeDataSize(new IFD(directory)) + directory.size() * entryLength; } private long computeDataSize(final Directory directory) { @@ -175,13 +219,13 @@ public final class TIFFWriter extends MetadataWriter { throw new IllegalArgumentException(String.format("Unknown size for entry %s", entry)); } - if (length > LONGWORD_LENGTH) { + if (length > offsetSize) { dataSize += length; } if (entry.getValue() instanceof Directory) { Directory subIFD = (Directory) entry.getValue(); - long subIFDSize = WORD_LENGTH + subIFD.size() * ENTRY_LENGTH + computeDataSize(subIFD); + long subIFDSize = directoryCountLength + computeDataSize(subIFD) + subIFD.size() * entryLength; dataSize += subIFDSize; } } @@ -229,11 +273,11 @@ public final class TIFFWriter extends MetadataWriter { short type = getType(entry); long valueLength = getValueLength(type, getCount(entry)); - if (valueLength <= LONGWORD_LENGTH) { + if (valueLength <= offsetSize) { writeValueInline(entry.getValue(), type, stream); // Pad - for (long i = valueLength; i < LONGWORD_LENGTH; i++) { + for (long i = valueLength; i < offsetSize; i++) { stream.write(0); } @@ -248,7 +292,7 @@ public final class TIFFWriter extends MetadataWriter { private int getCount(final Entry entry) { Object value = entry.getValue(); - return value instanceof String ? ((String) value).getBytes(Charset.forName("UTF-8")).length + 1 : entry.valueCount(); + return value instanceof String ? ((String) value).getBytes(StandardCharsets.UTF_8).length + 1 : entry.valueCount(); } private void writeValueInline(final Object value, final short type, final ImageOutputStream stream) throws IOException { @@ -344,12 +388,28 @@ public final class TIFFWriter extends MetadataWriter { doubles = (double[]) value; } else { - throw new IllegalArgumentException("Unsupported type for TIFF FLOAT: " + value.getClass()); + throw new IllegalArgumentException("Unsupported type for TIFF DOUBLE: " + value.getClass()); } stream.writeDoubles(doubles, 0, doubles.length); break; + case TIFF.TYPE_LONG8: + case TIFF.TYPE_SLONG8: + if (longOffsets) { + long[] longs; + + if (value instanceof long[]) { + longs = (long[]) value; + } + else { + throw new IllegalArgumentException("Unsupported type for TIFF LONG8: " + value.getClass()); + } + + stream.writeLongs(longs, 0, longs.length); + + break; + } default: throw new IllegalArgumentException("Unsupported TIFF type: " + type); @@ -373,6 +433,7 @@ public final class TIFFWriter extends MetadataWriter { break; case TIFF.TYPE_LONG: case TIFF.TYPE_SLONG: + case TIFF.TYPE_IFD: stream.writeInt(((Number) value).intValue()); break; case TIFF.TYPE_RATIONAL: @@ -387,6 +448,13 @@ public final class TIFFWriter extends MetadataWriter { case TIFF.TYPE_DOUBLE: stream.writeDouble(((Number) value).doubleValue()); break; + case TIFF.TYPE_LONG8: + case TIFF.TYPE_SLONG8: + case TIFF.TYPE_IFD8: + if (longOffsets) { + stream.writeLong(((Number) value).longValue()); + break; + } default: throw new IllegalArgumentException("Unsupported TIFF type: " + type); @@ -395,18 +463,39 @@ public final class TIFFWriter extends MetadataWriter { } private void writeValueAt(final long dataOffset, final Object value, final short type, final ImageOutputStream stream) throws IOException { - stream.writeInt(assertIntegerOffset(dataOffset)); + writeOffset(stream, dataOffset); long position = stream.getStreamPosition(); stream.seek(dataOffset); writeValueInline(value, type, stream); stream.seek(position); } - private int assertIntegerOffset(long offset) throws IIOException { - if (offset > Integer.MAX_VALUE - (long) Integer.MIN_VALUE) { + public void writeOffset(final ImageOutputStream output, long offset) throws IOException { + if (longOffsets) { + output.writeLong(assertLongOffset(offset)); + } + else { + output.writeInt(assertIntegerOffset(offset)); // Treated as unsigned + } + } + + public int offsetSize() { + return offsetSize; + } + + private int assertIntegerOffset(final long offset) throws IIOException { + if (offset < 0 || offset > Integer.MAX_VALUE - (long) Integer.MIN_VALUE) { throw new IIOException("Integer overflow for TIFF stream"); } return (int) offset; } + + private long assertLongOffset(final long offset) throws IIOException { + if (offset < 0) { + throw new IIOException("Long overflow for BigTIFF stream"); + } + + return offset; + } } diff --git a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/tiff/BigTIFFWriterTest.java b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/tiff/BigTIFFWriterTest.java new file mode 100644 index 00000000..25713c41 --- /dev/null +++ b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/tiff/BigTIFFWriterTest.java @@ -0,0 +1,345 @@ +/* + * Copyright (c) 2013, 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 of the copyright holder 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 HOLDER 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.metadata.tiff; + +import com.twelvemonkeys.imageio.metadata.*; +import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream; +import com.twelvemonkeys.io.FastByteArrayOutputStream; + +import org.junit.Test; + +import javax.imageio.ImageIO; +import javax.imageio.stream.ImageOutputStream; +import javax.imageio.stream.ImageOutputStreamImpl; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * TIFFWriterTest + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: TIFFWriterTest.java,v 1.0 18.07.13 09:53 haraldk Exp$ + */ +public class BigTIFFWriterTest extends MetadataWriterAbstractTest { + + @Override + protected InputStream getData() throws IOException { + // TODO: Replace with BigTIFF resource + return getResource("/exif/exif-jpeg-segment.bin").openStream(); + } + + protected TIFFReader createReader() { + return new TIFFReader(); + } + + @Override + protected TIFFWriter createWriter() { + return new TIFFWriter(8); + } + + @Test + public void testWriteReadSimple() throws IOException { + ArrayList entries = new ArrayList<>(); + entries.add(new TIFFEntry(TIFF.TAG_ORIENTATION, TIFF.TYPE_SHORT, 1)); + entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_SHORT, 1600)); + entries.add(new AbstractEntry(TIFF.TAG_IMAGE_HEIGHT, 900) {}); + entries.add(new TIFFEntry(TIFF.TAG_ARTIST, TIFF.TYPE_ASCII, "Harald K.")); + entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {}); + Directory directory = new AbstractDirectory(entries) {}; + + ByteArrayOutputStream output = new FastByteArrayOutputStream(1024); + ImageOutputStream imageStream = ImageIO.createImageOutputStream(output); + createWriter().write(directory, imageStream); + imageStream.flush(); + + assertEquals(output.size(), imageStream.getStreamPosition()); + + byte[] data = output.toByteArray(); + + assertEquals(164, data.length); + assertEquals('M', data[0]); + assertEquals('M', data[1]); + assertEquals(0, data[2]); + assertEquals(43, data[3]); + + Directory read = createReader().read(new ByteArrayImageInputStream(data)); + + assertNotNull(read); + assertEquals(5, read.size()); + + // TODO: Assert that the tags are written in ascending order (don't test the read directory, but the file structure)! + + assertNotNull(read.getEntryById(TIFF.TAG_SOFTWARE)); + assertEquals("TwelveMonkeys ImageIO", read.getEntryById(TIFF.TAG_SOFTWARE).getValue()); + + assertNotNull(read.getEntryById(TIFF.TAG_IMAGE_WIDTH)); + assertEquals(1600, read.getEntryById(TIFF.TAG_IMAGE_WIDTH).getValue()); + + assertNotNull(read.getEntryById(TIFF.TAG_IMAGE_HEIGHT)); + assertEquals(900, read.getEntryById(TIFF.TAG_IMAGE_HEIGHT).getValue()); + + assertNotNull(read.getEntryById(TIFF.TAG_ORIENTATION)); + assertEquals(1, read.getEntryById(TIFF.TAG_ORIENTATION).getValue()); + + assertNotNull(read.getEntryById(TIFF.TAG_ARTIST)); + assertEquals("Harald K.", read.getEntryById(TIFF.TAG_ARTIST).getValue()); + } + + @Test + public void testWriteMotorola() throws IOException { + ArrayList entries = new ArrayList<>(); + entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {}); + entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_LONG, Integer.MAX_VALUE)); + Directory directory = new AbstractDirectory(entries) {}; + + ByteArrayOutputStream output = new FastByteArrayOutputStream(1024); + ImageOutputStream imageStream = ImageIO.createImageOutputStream(output); + + imageStream.setByteOrder(ByteOrder.BIG_ENDIAN); // BE = Motorola + + createWriter().write(directory, imageStream); + imageStream.flush(); + + assertEquals(output.size(), imageStream.getStreamPosition()); + + byte[] data = output.toByteArray(); + + assertEquals(94, data.length); + assertEquals('M', data[0]); + assertEquals('M', data[1]); + assertEquals(0, data[2]); + assertEquals(43, data[3]); + + Directory read = new TIFFReader().read(new ByteArrayImageInputStream(data)); + + assertNotNull(read); + assertEquals(2, read.size()); + assertNotNull(read.getEntryById(TIFF.TAG_SOFTWARE)); + assertEquals("TwelveMonkeys ImageIO", read.getEntryById(TIFF.TAG_SOFTWARE).getValue()); + assertNotNull(read.getEntryById(TIFF.TAG_IMAGE_WIDTH)); + assertEquals((long) Integer.MAX_VALUE, read.getEntryById(TIFF.TAG_IMAGE_WIDTH).getValue()); + } + + @Test + public void testWriteIntel() throws IOException { + ArrayList entries = new ArrayList<>(); + entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {}); + entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_LONG, Integer.MAX_VALUE)); + Directory directory = new AbstractDirectory(entries) {}; + + ByteArrayOutputStream output = new FastByteArrayOutputStream(1024); + ImageOutputStream imageStream = ImageIO.createImageOutputStream(output); + + imageStream.setByteOrder(ByteOrder.LITTLE_ENDIAN); // LE = Intel + + createWriter().write(directory, imageStream); + imageStream.flush(); + + assertEquals(output.size(), imageStream.getStreamPosition()); + + byte[] data = output.toByteArray(); + + assertEquals(94, data.length); + assertEquals('I', data[0]); + assertEquals('I', data[1]); + assertEquals(43, data[2]); + assertEquals(0, data[3]); + + Directory read = new TIFFReader().read(new ByteArrayImageInputStream(data)); + + assertNotNull(read); + assertEquals(2, read.size()); + assertNotNull(read.getEntryById(TIFF.TAG_SOFTWARE)); + assertEquals("TwelveMonkeys ImageIO", read.getEntryById(TIFF.TAG_SOFTWARE).getValue()); + assertNotNull(read.getEntryById(TIFF.TAG_IMAGE_WIDTH)); + assertEquals((long) Integer.MAX_VALUE, read.getEntryById(TIFF.TAG_IMAGE_WIDTH).getValue()); + } + + @Test + public void testNestingIFD8Long8() throws IOException { + TIFFEntry artist = new TIFFEntry(TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, "TwelveMonkeys ImageIO"); + + TIFFEntry subSubSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD8, new IFD(Collections.singletonList(artist))); + TIFFEntry subSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG8, new IFD(Collections.singletonList(subSubSubSubIFD))); + TIFFEntry subSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG8, new IFD(Collections.singletonList(subSubSubIFD))); + TIFFEntry subIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD8, new IFD(Collections.singletonList(subSubIFD))); + + Directory directory = new IFD(Collections.singletonList(subIFD)); + + ByteArrayOutputStream output = new FastByteArrayOutputStream(1024); + ImageOutputStream imageStream = ImageIO.createImageOutputStream(output); + + createWriter().write(directory, imageStream); + imageStream.flush(); + + assertEquals(output.size(), imageStream.getStreamPosition()); + + Directory read = new TIFFReader().read(new ByteArrayImageInputStream(output.toByteArray())); + + assertNotNull(read); + assertEquals(1, read.size()); + assertEquals(subIFD, read.getEntryById(TIFF.TAG_SUB_IFD)); // Recursively tests content! + } + + @Test + public void testNestingIFDLong() throws IOException { + TIFFEntry artist = new TIFFEntry(TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, "TwelveMonkeys ImageIO"); + + TIFFEntry subSubSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD, new IFD(Collections.singletonList(artist))); + TIFFEntry subSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG, new IFD(Collections.singletonList(subSubSubSubIFD))); + TIFFEntry subSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG, new IFD(Collections.singletonList(subSubSubIFD))); + TIFFEntry subIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD, new IFD(Collections.singletonList(subSubIFD))); + + Directory directory = new IFD(Collections.singletonList(subIFD)); + + ByteArrayOutputStream output = new FastByteArrayOutputStream(1024); + ImageOutputStream imageStream = ImageIO.createImageOutputStream(output); + + createWriter().write(directory, imageStream); + imageStream.flush(); + + assertEquals(output.size(), imageStream.getStreamPosition()); + + Directory read = new TIFFReader().read(new ByteArrayImageInputStream(output.toByteArray())); + + assertNotNull(read); + assertEquals(1, read.size()); + assertEquals(subIFD, read.getEntryById(TIFF.TAG_SUB_IFD)); // Recursively tests content! + } + + @Test + public void testReadWriteRead() throws IOException { + Directory original = createReader().read(getDataAsIIS()); + + ByteArrayOutputStream output = new FastByteArrayOutputStream(256); + ImageOutputStream imageOutput = ImageIO.createImageOutputStream(output); + + try { + createWriter().write(original, imageOutput); + } + finally { + imageOutput.close(); + } + + Directory read = createReader().read(new ByteArrayImageInputStream(output.toByteArray())); + + assertEquals(original, read); + } + + @Test + public void testComputeIFDSize() throws IOException { + ArrayList entries = new ArrayList<>(); + entries.add(new TIFFEntry(TIFF.TAG_ORIENTATION, TIFF.TYPE_SHORT, 1)); + entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_SHORT, 1600)); + entries.add(new AbstractEntry(TIFF.TAG_IMAGE_HEIGHT, 900) {}); + entries.add(new TIFFEntry(TIFF.TAG_ARTIST, TIFF.TYPE_ASCII, "Harald K.")); + entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {}); + + TIFFWriter writer = createWriter(); + + ImageOutputStream stream = new NullImageOutputStream(); + writer.writeIFD(entries, stream); + + assertEquals(140, writer.computeIFDSize(entries)); + assertEquals(148, stream.getStreamPosition()); + } + + @Test + public void testComputeIFDSizeNestedIFD8Long8() throws IOException { + TIFFEntry artist = new TIFFEntry(TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, "TwelveMonkeys ImageIO"); + + TIFFEntry subSubSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD8, new IFD(Collections.singletonList(artist))); + TIFFEntry subSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG8, new IFD(Collections.singletonList(subSubSubSubIFD))); + TIFFEntry subSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG8, new IFD(Collections.singletonList(subSubSubIFD))); + TIFFEntry subIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD8, new IFD(Collections.singletonList(subSubIFD))); + + List entries = Collections.singletonList(subIFD); + + TIFFWriter writer = createWriter(); + + ImageOutputStream stream = new NullImageOutputStream(); + writer.writeIFD(entries, stream); + + assertEquals(162, writer.computeIFDSize(entries)); + assertEquals(170, stream.getStreamPosition()); + } + + @Test + public void testComputeIFDSizeNestedIFDLong() throws IOException { + TIFFEntry artist = new TIFFEntry(TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, "TwelveMonkeys ImageIO"); + + TIFFEntry subSubSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD, new IFD(Collections.singletonList(artist))); + TIFFEntry subSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG, new IFD(Collections.singletonList(subSubSubSubIFD))); + TIFFEntry subSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG, new IFD(Collections.singletonList(subSubSubIFD))); + TIFFEntry subIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD, new IFD(Collections.singletonList(subSubIFD))); + + List entries = Collections.singletonList(subIFD); + + TIFFWriter writer = createWriter(); + + ImageOutputStream stream = new NullImageOutputStream(); + writer.writeIFD(entries, stream); + + assertEquals(162, writer.computeIFDSize(entries)); // 162 = 5 * (8 + 20) + 22 + assertEquals(170, stream.getStreamPosition()); // 170 = 8 + 5 * (8 + 20) + 22 + } + + private static class NullImageOutputStream extends ImageOutputStreamImpl { + @Override + public void write(int b) throws IOException { + streamPos++; + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + streamPos += len; + } + + @Override + public int read() throws IOException { + throw new UnsupportedOperationException("Method read not implemented"); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + throw new UnsupportedOperationException("Method read not implemented"); + } + } +} diff --git a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/tiff/TIFFWriterTest.java b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/tiff/TIFFWriterTest.java index 5debc1c0..f75f2f46 100644 --- a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/tiff/TIFFWriterTest.java +++ b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/tiff/TIFFWriterTest.java @@ -33,6 +33,7 @@ package com.twelvemonkeys.imageio.metadata.tiff; import com.twelvemonkeys.imageio.metadata.*; import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream; import com.twelvemonkeys.io.FastByteArrayOutputStream; + import org.junit.Test; import javax.imageio.ImageIO; @@ -84,7 +85,7 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest { ByteArrayOutputStream output = new FastByteArrayOutputStream(1024); ImageOutputStream imageStream = ImageIO.createImageOutputStream(output); - new TIFFWriter().write(directory, imageStream); + createWriter().write(directory, imageStream); imageStream.flush(); assertEquals(output.size(), imageStream.getStreamPosition()); @@ -132,7 +133,7 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest { imageStream.setByteOrder(ByteOrder.BIG_ENDIAN); // BE = Motorola - new TIFFWriter().write(directory, imageStream); + createWriter().write(directory, imageStream); imageStream.flush(); assertEquals(output.size(), imageStream.getStreamPosition()); @@ -167,7 +168,7 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest { imageStream.setByteOrder(ByteOrder.LITTLE_ENDIAN); // LE = Intel - new TIFFWriter().write(directory, imageStream); + createWriter().write(directory, imageStream); imageStream.flush(); assertEquals(output.size(), imageStream.getStreamPosition()); @@ -204,7 +205,7 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest { ByteArrayOutputStream output = new FastByteArrayOutputStream(1024); ImageOutputStream imageStream = ImageIO.createImageOutputStream(output); - new TIFFWriter().write(directory, imageStream); + createWriter().write(directory, imageStream); imageStream.flush(); assertEquals(output.size(), imageStream.getStreamPosition()); @@ -247,9 +248,10 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest { TIFFWriter writer = createWriter(); ImageOutputStream stream = new NullImageOutputStream(); - writer.write(new IFD(entries), stream); + writer.writeIFD(entries, stream); - assertEquals(stream.getStreamPosition(), writer.computeIFDSize(entries) + 12); + assertEquals(94, writer.computeIFDSize(entries)); + assertEquals(98, stream.getStreamPosition()); } @Test @@ -266,9 +268,10 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest { TIFFWriter writer = createWriter(); ImageOutputStream stream = new NullImageOutputStream(); - writer.write(new IFD(entries), stream); + writer.writeIFD(entries, stream); - assertEquals(stream.getStreamPosition(), writer.computeIFDSize(entries) + 12); + assertEquals(92, writer.computeIFDSize(entries)); // 92 = 5 * (2 + 12) + 22 + assertEquals(96, stream.getStreamPosition()); // 96 = 4 + 5 * (2 + 12) + 22 } private static class NullImageOutputStream extends ImageOutputStreamImpl { diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/BigTIFFImageWriterSpi.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/BigTIFFImageWriterSpi.java new file mode 100644 index 00000000..763b2a4d --- /dev/null +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/BigTIFFImageWriterSpi.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2021, 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 of the copyright holder 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 HOLDER 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.tiff; + +import com.twelvemonkeys.imageio.spi.ImageWriterSpiBase; + +import javax.imageio.ImageTypeSpecifier; +import java.util.Locale; + +/** + * BigTIFFImageWriterSpi + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: BigTIFFImageWriterSpi.java,v 1.0 18.09.13 12:46 haraldk Exp$ + */ +public final class BigTIFFImageWriterSpi extends ImageWriterSpiBase { + // TODO: Implement canEncodeImage better + + public BigTIFFImageWriterSpi() { + super(new BigTIFFProviderInfo()); + } + + @Override + public boolean canEncodeImage(final ImageTypeSpecifier type) { + // TODO: Test bit depths compatibility + return true; + } + + @Override + public TIFFImageWriter createWriterInstance(final Object extension) { + return new TIFFImageWriter(this); + } + + @Override + public String getDescription(final Locale locale) { + return "BigTIFF image writer"; + } +} diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/BigTIFFProviderInfo.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/BigTIFFProviderInfo.java index 71495539..ff1470aa 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/BigTIFFProviderInfo.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/BigTIFFProviderInfo.java @@ -50,8 +50,8 @@ final class BigTIFFProviderInfo extends ReaderWriterProviderInfo { }, "com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReader", new String[] {"com.twelvemonkeys.imageio.plugins.tiff.BigTIFFImageReaderSpi"}, - null, - null, + "com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriter", + new String[] {"com.twelvemonkeys.imageio.plugins.tiff.BigTIFFImageWriterSpi"}, false, TIFFStreamMetadata.SUN_NATIVE_STREAM_METADATA_FORMAT_NAME, "com.twelvemonkeys.imageio.plugins.tiff.TIFFStreamMetadataFormat", null, null, true, TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, "com.twelvemonkeys.imageio.plugins.tiff.TIFFMedataFormat", null, null ); diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriter.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriter.java index dcb08074..3ab899bd 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriter.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriter.java @@ -202,26 +202,26 @@ public final class TIFFImageWriter extends ImageWriterBase { long streamPosition = imageOutput.getStreamPosition(); long ifdSize = tiffWriter.computeIFDSize(entries.values()); - long stripOffset = streamPosition + 4 + ifdSize + 4; + long stripOffset = streamPosition + tiffWriter.offsetSize() + ifdSize + tiffWriter.offsetSize(); long stripByteCount = ((long) renderedImage.getWidth() * renderedImage.getHeight() * pixelSize + 7L) / 8L; entries.put(TIFF.TAG_STRIP_OFFSETS, new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, TIFF.TYPE_LONG, stripOffset)); entries.put(TIFF.TAG_STRIP_BYTE_COUNTS, new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, TIFF.TYPE_LONG, stripByteCount)); - long ifdPointer = tiffWriter.writeIFD(entries.values(), imageOutput); // NOTE: Writer takes case of ordering tags + long ifdPointer = tiffWriter.writeIFD(entries.values(), imageOutput); // NOTE: Writer takes care of ordering tags nextIFDPointerOffset = imageOutput.getStreamPosition(); // If we have a previous IFD, update pointer if (streamPosition > lastIFDPointerOffset) { imageOutput.seek(lastIFDPointerOffset); - imageOutput.writeInt((int) ifdPointer); + tiffWriter.writeOffset(imageOutput, ifdPointer); imageOutput.seek(nextIFDPointerOffset); } - imageOutput.writeInt(0); // Update next IFD pointer later + tiffWriter.writeOffset(imageOutput, 0); // Update next IFD pointer later } else { - imageOutput.writeInt(0); // Update current IFD pointer later + tiffWriter.writeOffset(imageOutput, 0); // Update current IFD pointer later } long stripOffset = imageOutput.getStreamPosition(); @@ -260,7 +260,7 @@ public final class TIFFImageWriter extends ImageWriterBase { entries.put(TIFF.TAG_STRIP_OFFSETS, new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, TIFF.TYPE_LONG, stripOffset)); entries.put(TIFF.TAG_STRIP_BYTE_COUNTS, new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, TIFF.TYPE_LONG, stripByteCount)); - long ifdPointer = tiffWriter.writeIFD(entries.values(), imageOutput); // NOTE: Writer takes case of ordering tags + long ifdPointer = tiffWriter.writeIFD(entries.values(), imageOutput); // NOTE: Writer takes care of ordering tags nextIFDPointerOffset = imageOutput.getStreamPosition(); @@ -268,10 +268,10 @@ public final class TIFFImageWriter extends ImageWriterBase { // However, need to update here, because to the writeIFD method writes the pointer, but at the incorrect offset // TODO: Refactor writeIFD to take an offset imageOutput.seek(lastIFDPointerOffset); - imageOutput.writeInt((int) ifdPointer); + tiffWriter.writeOffset(imageOutput, ifdPointer); imageOutput.seek(nextIFDPointerOffset); - imageOutput.writeInt(0); // Next IFD pointer updated later + tiffWriter.writeOffset(imageOutput, 0); // Next IFD pointer updated later } return nextIFDPointerOffset; @@ -955,7 +955,7 @@ public final class TIFFImageWriter extends ImageWriterBase { configureStreamByteOrder(streamMetadata, imageOutput); writingSequence = true; - sequenceTIFFWriter = new TIFFWriter(); + sequenceTIFFWriter = new TIFFWriter("bigtiff".equalsIgnoreCase(getFormatName()) ? 8 : 4); sequenceTIFFWriter.writeTIFFHeader(imageOutput); sequenceLastIFDPos = imageOutput.getStreamPosition(); } @@ -1015,8 +1015,7 @@ public final class TIFFImageWriter extends ImageWriterBase { BufferedImage original; // BufferedImage original = ImageIO.read(file); - ImageInputStream inputStream = ImageIO.createImageInputStream(file); - try { + try (ImageInputStream inputStream = ImageIO.createImageInputStream(file)) { Iterator readers = ImageIO.getImageReaders(inputStream); if (!readers.hasNext()) { @@ -1046,9 +1045,6 @@ public final class TIFFImageWriter extends ImageWriterBase { original = reader.read(0, param); } - finally { - inputStream.close(); - } System.err.println("original: " + original); @@ -1084,12 +1080,11 @@ public final class TIFFImageWriter extends ImageWriterBase { // output.deleteOnExit(); System.err.println("output: " + output); - TIFFImageWriter writer = new TIFFImageWriter(null); + TIFFImageWriter writer = new TIFFImageWriter(new TIFFImageWriterSpi()); // ImageWriter writer = ImageIO.getImageWritersByFormatName("PNG").next(); // ImageWriter writer = ImageIO.getImageWritersByFormatName("BMP").next(); - ImageOutputStream stream = ImageIO.createImageOutputStream(output); - try { + try (ImageOutputStream stream = ImageIO.createImageOutputStream(output)) { writer.setOutput(stream); ImageWriteParam param = writer.getDefaultWriteParam(); @@ -1107,9 +1102,6 @@ public final class TIFFImageWriter extends ImageWriterBase { writer.write(null, new IIOImage(image, null, null), param); System.err.println("Write time: " + (System.currentTimeMillis() - start) + " ms"); } - finally { - stream.close(); - } System.err.println("output.length: " + output.length()); diff --git a/imageio/imageio-tiff/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi b/imageio/imageio-tiff/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi index 208d610d..5c114f2a 100755 --- a/imageio/imageio-tiff/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi +++ b/imageio/imageio-tiff/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi @@ -1 +1,2 @@ com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriterSpi +com.twelvemonkeys.imageio.plugins.tiff.BigTIFFImageWriterSpi diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/BigTIFFImageWriterTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/BigTIFFImageWriterTest.java new file mode 100644 index 00000000..1b77c0b8 --- /dev/null +++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/BigTIFFImageWriterTest.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2021, 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 of the copyright holder 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 HOLDER 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.tiff; + +import com.twelvemonkeys.imageio.metadata.tiff.TIFF; +import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream; +import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest; + +import org.junit.Test; + +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.spi.ImageWriterSpi; +import javax.imageio.stream.ImageInputStream; +import javax.imageio.stream.ImageOutputStream; +import java.awt.image.BufferedImage; +import java.awt.image.RenderedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import static com.twelvemonkeys.imageio.util.ImageReaderAbstractTest.assertRGBEquals; +import static org.junit.Assert.assertArrayEquals; + +/** + * BigTIFFImageWriterTest + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: BigTIFFImageWriterTest.java,v 1.0 19.09.13 13:22 haraldk Exp$ + */ +public class BigTIFFImageWriterTest extends ImageWriterAbstractTest { + @Override + protected ImageWriterSpi createProvider() { + return new BigTIFFImageWriterSpi(); + } + + @Override + protected List getTestData() { + return Arrays.asList( + new BufferedImage(300, 200, BufferedImage.TYPE_INT_RGB), + new BufferedImage(301, 199, BufferedImage.TYPE_INT_ARGB), + new BufferedImage(299, 201, BufferedImage.TYPE_3BYTE_BGR), + new BufferedImage(160, 90, BufferedImage.TYPE_4BYTE_ABGR), + new BufferedImage(90, 160, BufferedImage.TYPE_BYTE_GRAY), + new BufferedImage(30, 20, BufferedImage.TYPE_USHORT_GRAY) + ); + } + + @Test + public void roundrtip() throws IOException { + TIFFImageWriter writer = createWriter(); + ImageReader reader = ImageIO.getImageReader(writer); + + try (ImageInputStream input = ImageIO.createImageInputStream(getClassLoaderResource("/bigtiff/BigTIFF.tif"))) { + reader.setInput(input); + BufferedImage image = reader.read(0); + + ByteArrayOutputStream temp = new ByteArrayOutputStream(); + try (ImageOutputStream output = ImageIO.createImageOutputStream(temp)) { + writer.setOutput(output); + writer.write(image); + } + finally { + writer.dispose(); + } + + // Validate we actually write BigTIFF + byte[] data = temp.toByteArray(); + assertArrayEquals(new byte[] { 'M', 'M', 0, TIFF.BIGTIFF_MAGIC}, Arrays.copyOf(data, 4)); + + // Read image back and see that it is the same + try (ImageInputStream stream = new ByteArrayImageInputStream(data)) { + reader.setInput(stream); + BufferedImage after = reader.read(0); + + for (int y = 0; y < image.getHeight(); y++) { + for (int x = 0; x < image.getWidth(); x++) { + assertRGBEquals("Pixel values differ: ", image.getRGB(x, y), after.getRGB(x, y), 0); + } + } + } + } + finally { + reader.dispose(); + } + } +}