mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-04 20:15:28 -04:00
Merge pull request #207 from Schmidor/tiff_sequencewriter
Enable sequence writing on TIFFImageWriter
This commit is contained in:
commit
d9324ef634
@ -158,6 +158,10 @@ public final class EXIFWriter extends MetadataWriter {
|
|||||||
return WORD_LENGTH + computeDataSize(new IFD(directory)) + directory.size() * ENTRY_LENGTH;
|
return WORD_LENGTH + computeDataSize(new IFD(directory)) + directory.size() * ENTRY_LENGTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long computeIFDOffsetSize(final Collection<Entry> directory) {
|
||||||
|
return computeDataSize(new IFD(directory)) + LONGWORD_LENGTH;
|
||||||
|
}
|
||||||
|
|
||||||
private long computeDataSize(final Directory directory) {
|
private long computeDataSize(final Directory directory) {
|
||||||
long dataSize = 0;
|
long dataSize = 0;
|
||||||
|
|
||||||
|
@ -72,7 +72,6 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
// TODO: Support more of the ImageIO metadata (ie. compression from metadata, etc)
|
// TODO: Support more of the ImageIO metadata (ie. compression from metadata, etc)
|
||||||
|
|
||||||
// Long term
|
// Long term
|
||||||
// TODO: Support writing multipage TIFFs using canWriteSequence/prepareWriteSequence/writeToSequence/endWriteSequence
|
|
||||||
// TODO: Support tiling
|
// TODO: Support tiling
|
||||||
// TODO: Support thumbnails
|
// TODO: Support thumbnails
|
||||||
// TODO: Support CCITT Modified Huffman compression (2)
|
// TODO: Support CCITT Modified Huffman compression (2)
|
||||||
@ -102,6 +101,21 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
|
|
||||||
public static final Rational STANDARD_DPI = new Rational(72);
|
public static final Rational STANDARD_DPI = new Rational(72);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag for active sequence writing
|
||||||
|
*/
|
||||||
|
private boolean isWritingSequence = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metadata writer for sequence writing
|
||||||
|
*/
|
||||||
|
private EXIFWriter sequenceExifWriter = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Position of last IFD Pointer on active sequence writing
|
||||||
|
*/
|
||||||
|
private long sequenceLastIFDPos = -1;
|
||||||
|
|
||||||
TIFFImageWriter(final ImageWriterSpi provider) {
|
TIFFImageWriter(final ImageWriterSpi provider) {
|
||||||
super(provider);
|
super(provider);
|
||||||
}
|
}
|
||||||
@ -184,6 +198,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
// TODO: Validate input
|
// TODO: Validate input
|
||||||
|
|
||||||
assertOutput();
|
assertOutput();
|
||||||
|
// TODO: streamMetadata?
|
||||||
|
|
||||||
// TODO: Consider writing TIFF header, offset to IFD0 (leave blank), write image data with correct
|
// TODO: Consider writing TIFF header, offset to IFD0 (leave blank), write image data with correct
|
||||||
// tiling/compression/etc, then write IFD0, go back and update IFD0 offset?
|
// tiling/compression/etc, then write IFD0, go back and update IFD0 offset?
|
||||||
@ -191,11 +206,20 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
// Write minimal TIFF header (required "Baseline" fields)
|
// Write minimal TIFF header (required "Baseline" fields)
|
||||||
// Use EXIFWriter to write leading metadata (TODO: consider rename to TTIFFWriter, again...)
|
// Use EXIFWriter to write leading metadata (TODO: consider rename to TTIFFWriter, again...)
|
||||||
// TODO: Make TIFFEntry and possibly TIFFDirectory? public
|
// TODO: Make TIFFEntry and possibly TIFFDirectory? public
|
||||||
|
EXIFWriter exifWriter = new EXIFWriter();
|
||||||
|
exifWriter.writeTIFFHeader(imageOutput);
|
||||||
|
long IFD0Pos = imageOutput.getStreamPosition();
|
||||||
|
writePage(image, param, exifWriter, IFD0Pos);
|
||||||
|
imageOutput.writeInt(0); // EOF
|
||||||
|
imageOutput.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
private long writePage(IIOImage image, ImageWriteParam param, EXIFWriter exifWriter, long lastIFDPointer)
|
||||||
|
throws IOException {
|
||||||
RenderedImage renderedImage = image.getRenderedImage();
|
RenderedImage renderedImage = image.getRenderedImage();
|
||||||
ColorModel colorModel = renderedImage.getColorModel();
|
ColorModel colorModel = renderedImage.getColorModel();
|
||||||
int numComponents = colorModel.getNumComponents();
|
int numComponents = colorModel.getNumComponents();
|
||||||
|
|
||||||
//TODO: streamMetadata?
|
|
||||||
TIFFImageMetadata metadata;
|
TIFFImageMetadata metadata;
|
||||||
if (image.getMetadata() != null) {
|
if (image.getMetadata() != null) {
|
||||||
metadata = convertImageMetadata(image.getMetadata(), ImageTypeSpecifier.createFromRenderedImage(renderedImage), param);
|
metadata = convertImageMetadata(image.getMetadata(), ImageTypeSpecifier.createFromRenderedImage(renderedImage), param);
|
||||||
@ -335,28 +359,21 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
|
|
||||||
// TODO: If tiled, write tile indexes etc
|
// TODO: If tiled, write tile indexes etc
|
||||||
// Depending on param.getTilingMode
|
// Depending on param.getTilingMode
|
||||||
|
long nextIFDPointer = -1;
|
||||||
EXIFWriter exifWriter = new EXIFWriter();
|
long stripOffset = -1;
|
||||||
|
long stripByteCount = 0;
|
||||||
|
|
||||||
if (compression == TIFFBaseline.COMPRESSION_NONE) {
|
if (compression == TIFFBaseline.COMPRESSION_NONE) {
|
||||||
// This implementation, allows semi-streaming-compatible uncompressed TIFFs
|
long ifdOffset = exifWriter.computeIFDOffsetSize(entries.values());
|
||||||
long streamOffset = exifWriter.computeIFDSize(entries.values()) + 12; // 12 == 4 byte magic, 4 byte IDD 0 pointer, 4 byte EOF
|
long dataLength = renderedImage.getWidth() * renderedImage.getHeight() * numComponents;
|
||||||
|
long pointerPos = imageOutput.getStreamPosition() + dataLength + 4 + ifdOffset;
|
||||||
entries.remove(dummyStripByteCounts);
|
imageOutput.writeInt((int) pointerPos);
|
||||||
entries.put(TIFF.TAG_STRIP_BYTE_COUNTS, new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS,
|
|
||||||
renderedImage.getWidth() * renderedImage.getHeight() * numComponents));
|
|
||||||
entries.remove(dummyStripOffsets);
|
|
||||||
entries.put(TIFF.TAG_STRIP_OFFSETS, new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, streamOffset));
|
|
||||||
|
|
||||||
exifWriter.write(entries.values(), imageOutput); // NOTE: Writer takes case of ordering tags
|
|
||||||
imageOutput.flush();
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Unless compression == 1 / COMPRESSION_NONE (and all offsets known), write only TIFF header/magic + leave room for IFD0 offset
|
imageOutput.writeInt(0); // Update IFD Pointer later
|
||||||
exifWriter.writeTIFFHeader(imageOutput);
|
|
||||||
imageOutput.writeInt(-1); // IFD0 pointer, will be updated later
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stripOffset = imageOutput.getStreamPosition();
|
||||||
// TODO: Create compressor stream per Tile/Strip
|
// TODO: Create compressor stream per Tile/Strip
|
||||||
if (compression == TIFFExtension.COMPRESSION_JPEG) {
|
if (compression == TIFFExtension.COMPRESSION_JPEG) {
|
||||||
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("JPEG");
|
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("JPEG");
|
||||||
@ -380,26 +397,34 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
writeImageData(createCompressorStream(renderedImage, param, entries), renderedImage, numComponents, bandOffsets,
|
writeImageData(createCompressorStream(renderedImage, param, entries), renderedImage, numComponents, bandOffsets,
|
||||||
bitOffsets);
|
bitOffsets);
|
||||||
}
|
}
|
||||||
|
stripByteCount = imageOutput.getStreamPosition() - stripOffset;
|
||||||
|
|
||||||
// Update IFD0-pointer, and write IFD
|
// Update IFD0-pointer, and write IFD
|
||||||
if (compression != TIFFBaseline.COMPRESSION_NONE) {
|
if (compression != TIFFBaseline.COMPRESSION_NONE) {
|
||||||
long streamPosition = imageOutput.getStreamPosition();
|
|
||||||
|
|
||||||
entries.remove(dummyStripOffsets);
|
entries.remove(dummyStripOffsets);
|
||||||
entries.put(TIFF.TAG_STRIP_OFFSETS, new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, 8));
|
entries.put(TIFF.TAG_STRIP_OFFSETS, new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, stripOffset));
|
||||||
entries.remove(dummyStripByteCounts);
|
entries.remove(dummyStripByteCounts);
|
||||||
entries.put(TIFF.TAG_STRIP_BYTE_COUNTS, new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, streamPosition - 8));
|
entries.put(TIFF.TAG_STRIP_BYTE_COUNTS, new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, stripByteCount));
|
||||||
|
|
||||||
long ifdOffset = exifWriter.writeIFD(entries.values(), imageOutput);
|
long idfOffset = exifWriter.writeIFD(entries.values(), imageOutput); // NOTE: Writer takes case of ordering tags
|
||||||
imageOutput.writeInt(0); // Next IFD (none)
|
nextIFDPointer = imageOutput.getStreamPosition();
|
||||||
streamPosition = imageOutput.getStreamPosition();
|
imageOutput.seek(lastIFDPointer);
|
||||||
|
imageOutput.writeInt((int) idfOffset);
|
||||||
// Update IFD0 pointer
|
imageOutput.seek(nextIFDPointer);
|
||||||
imageOutput.seek(4);
|
|
||||||
imageOutput.writeInt((int) ifdOffset);
|
|
||||||
imageOutput.seek(streamPosition);
|
|
||||||
imageOutput.flush();
|
imageOutput.flush();
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
entries.remove(dummyStripOffsets);
|
||||||
|
entries.put(TIFF.TAG_STRIP_OFFSETS, new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, stripOffset));
|
||||||
|
entries.remove(dummyStripByteCounts);
|
||||||
|
entries.put(TIFF.TAG_STRIP_BYTE_COUNTS, new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, stripByteCount));
|
||||||
|
|
||||||
|
exifWriter.writeIFD(entries.values(), imageOutput); // NOTE: Writer takes case of ordering tags
|
||||||
|
nextIFDPointer = imageOutput.getStreamPosition();
|
||||||
|
imageOutput.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextIFDPointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
private DataOutput createCompressorStream(RenderedImage image, ImageWriteParam param, Map<Integer, Entry> entries) {
|
private DataOutput createCompressorStream(RenderedImage image, ImageWriteParam param, Map<Integer, Entry> entries) {
|
||||||
@ -838,6 +863,54 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
return new TIFFImageWriteParam();
|
return new TIFFImageWriteParam();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canWriteSequence() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void prepareWriteSequence(IIOMetadata streamMetadata) throws IOException {
|
||||||
|
if (isWritingSequence) {
|
||||||
|
throw new IllegalStateException("sequence writing has already been started!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore streamMetadata. ByteOrder is determined from OutputStream
|
||||||
|
assertOutput();
|
||||||
|
isWritingSequence = true;
|
||||||
|
sequenceExifWriter = new EXIFWriter();
|
||||||
|
sequenceExifWriter.writeTIFFHeader(imageOutput);
|
||||||
|
sequenceLastIFDPos = imageOutput.getStreamPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToSequence(IIOImage image, ImageWriteParam param) throws IOException {
|
||||||
|
if (!isWritingSequence) {
|
||||||
|
throw new IllegalStateException("prepareWriteSequence() must be called before writeToSequence()!");
|
||||||
|
}
|
||||||
|
sequenceLastIFDPos = writePage(image, param, sequenceExifWriter, sequenceLastIFDPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void endWriteSequence() throws IOException {
|
||||||
|
if (!isWritingSequence) {
|
||||||
|
throw new IllegalStateException("prepareWriteSequence() must be called before endWriteSequence()!");
|
||||||
|
}
|
||||||
|
imageOutput.writeInt(0); // EOF
|
||||||
|
isWritingSequence = false;
|
||||||
|
sequenceExifWriter = null;
|
||||||
|
sequenceLastIFDPos = -1;
|
||||||
|
imageOutput.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void resetMembers() {
|
||||||
|
super.resetMembers();
|
||||||
|
|
||||||
|
isWritingSequence = false;
|
||||||
|
sequenceExifWriter = null;
|
||||||
|
sequenceLastIFDPos = -1;
|
||||||
|
}
|
||||||
|
|
||||||
// Test
|
// Test
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
public static void main(String[] args) throws IOException {
|
||||||
|
@ -37,18 +37,16 @@ import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
|||||||
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTestCase;
|
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTestCase;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import javax.imageio.IIOImage;
|
import javax.imageio.*;
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import javax.imageio.ImageTypeSpecifier;
|
|
||||||
import javax.imageio.ImageWriter;
|
|
||||||
import javax.imageio.metadata.IIOMetadata;
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||||
import javax.imageio.metadata.IIOMetadataNode;
|
import javax.imageio.metadata.IIOMetadataNode;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import javax.imageio.stream.ImageOutputStream;
|
import javax.imageio.stream.ImageOutputStream;
|
||||||
|
import java.awt.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.awt.image.RenderedImage;
|
import java.awt.image.RenderedImage;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.*;
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -277,4 +275,64 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTestCase {
|
|||||||
assertNotNull(software);
|
assertNotNull(software);
|
||||||
assertEquals(softwareString, software.getValueAsString());
|
assertEquals(softwareString, software.getValueAsString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSequenceWriter() throws IOException {
|
||||||
|
ImageWriter writer = createImageWriter();
|
||||||
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
|
ImageOutputStream stream = ImageIO.createImageOutputStream(buffer);
|
||||||
|
writer.setOutput(stream);
|
||||||
|
|
||||||
|
Graphics2D g2d = null;
|
||||||
|
BufferedImage image[] = new BufferedImage[] {
|
||||||
|
new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB),
|
||||||
|
new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB),
|
||||||
|
new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB)
|
||||||
|
};
|
||||||
|
g2d = image[0].createGraphics();
|
||||||
|
g2d.setColor(Color.red);
|
||||||
|
g2d.fillRect(0,0,100,100);
|
||||||
|
g2d.dispose();
|
||||||
|
g2d = image[1].createGraphics();
|
||||||
|
g2d.setColor(Color.green);
|
||||||
|
g2d.fillRect(0,0,100,100);
|
||||||
|
g2d.dispose();
|
||||||
|
g2d = image[2].createGraphics();
|
||||||
|
g2d.setColor(Color.blue);
|
||||||
|
g2d.fillRect(0,0,100,100);
|
||||||
|
g2d.dispose();
|
||||||
|
|
||||||
|
|
||||||
|
ImageWriteParam params = writer.getDefaultWriteParam();
|
||||||
|
params.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
|
||||||
|
|
||||||
|
assertTrue("", writer.canWriteSequence());
|
||||||
|
|
||||||
|
try {
|
||||||
|
writer.prepareWriteSequence(null);
|
||||||
|
|
||||||
|
params.setCompressionType("JPEG");
|
||||||
|
writer.writeToSequence(new IIOImage(image[0], null, null), params);
|
||||||
|
params.setCompressionType("None");
|
||||||
|
writer.writeToSequence(new IIOImage(image[1], null, null), params);
|
||||||
|
params.setCompressionType("JPEG");
|
||||||
|
writer.writeToSequence(new IIOImage(image[2], null, null), params);
|
||||||
|
g2d.dispose();
|
||||||
|
writer.endWriteSequence();
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
fail(e.getMessage());
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
stream.close(); // Force data to be written
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageInputStream input = ImageIO.createImageInputStream(new ByteArrayInputStream(buffer.toByteArray()));
|
||||||
|
ImageReader reader = ImageIO.getImageReaders(input).next();
|
||||||
|
reader.setInput(input);
|
||||||
|
assertEquals("wrong image count", 3, reader.getNumImages(true));
|
||||||
|
for(int i = 0; i < reader.getNumImages(true); i++){
|
||||||
|
reader.read(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user