Reputation: 1794
I've seen a few questions regarding multipage tiffs and a few questions about compression, but none (that I've seen) linking the two. This question is as close as I've seen and puts me incredibly close, so I hope. I went into the Oracle forum thread mentioned (it is talking about a multipage PDF to TIFF with compression) and I think I'm closish to having the code finished to do this. Can anyone assist? I'm going to remove the try/catches to try and shorten this down a hair (basically all they did was output a message in console and return false).
public static boolean CompressedTiff(List<BufferedImage> images, File path)
{
if (!path.getParentFile().exists())
path.getParentFile().mkdirs();
path.createNewFile();
ImageOutputStream ios;
ios = ImageIO.createImageOutputStream(path);
Iterator<ImageWriter> imageWriters = ImageIO.getImageWritersByFormatName("TIFF");
ImageWriter writer = (ImageWriter)imageWriters.next();
writer.setOutput(ios);
TIFFImageWriteParam writeParam = (TIFFImageWriteParam)writer.getDefaultWriteParam();
writeParam.setCompressionMode(2);
writeParam.setCompressionType("LZW");
writer.prepareWriteSequence(null);
for(int i = 0; i < images.size(); i++)
{
ImageTypeSpecifier spec = ImageTypeSpecifier.createFromRenderedImage(images.get(i));
javax.imageio.metadata.IIOMetadata metadata = writer.getDefaultImageMetadata(spec, writeParam);
IIOImage iioImage = new IIOImage(images.get(i), null, metadata);
writer.writeToSequence(iioImage, writeParam);
images.get(i).flush();//modified after release.
images.get(i).flush();
writer.endWriteSequence();
ios.flush();
writer.dispose();
ios.close();
}
return true;
}
It failed out on the next pass at writer.writeToSequence saying I needed to call prepareWriteSequence. I changed it to
writer.prepareWriteSequence(metadata);
writer.writeToSequence(iioImage, writeParam);
also removed the earlier writer.prepareWriteSequence(null);
and it appears to be navigating the files properly, however, the output isn't of any type of renderable tif. Multipage or otherwise.
I have JAI installed so if it is possible to use that in some way to achieve the compressed image, that'd be fantastic. The code I am using that generates a TIFF is using this, but I haven't seen anything that worked as far as adding compression to the pages.
edit: I added a bunch of ios.flush(); ios.close(); calls in the catch blocks and it prevents the non-renderable TIFF issue. However, it isn't adding any page beyond the first.
Upvotes: 4
Views: 9074
Reputation: 49209
If it helps, this is code that I use for modifying a TiffImageWriteParam to set the compression:
try {
jWriteParam.setCompressionMode(_compression != TiffCompression.NO_COMPRESSION
? ImageWriteParam.MODE_EXPLICIT : ImageWriteParam.MODE_DISABLED);
if (_compression != TiffCompression.NO_COMPRESSION) {
// this code corrects the compression if, say, the client code asked for
// CCITT but the actual image pixel format was CMYK or some other non-1 bit
// image type.
TiffCompression mode = recastToValidCompression(_compression, pf);
jWriteParam.setCompressionType(getCompressionType(mode));
TIFFCompressor compressor = getTiffCompressor(mode, jWriteParam, shouldUsePredictor(pf));
jWriteParam.setTIFFCompressor(compressor);
if (_compression == TiffCompression.JPEG_COMPRESSION) {
// Java supports setting to 1.0 (ie 100), but it will not actually do lossless (maybe)
if (_jpegQuality == 100 && !jWriteParam.isCompressionLossless())
continue;
jWriteParam.setCompressionQuality(toJavaJpegQuality());
}
}
}
catch (UnsupportedOperationException e)
{
// this shouldn't get here, but you should consider what to do if it does.
// set a default? throw?
}
here is getTiffCompressor():
private TIFFCompressor getTiffCompressor (TiffCompression compression, TIFFImageWriteParam writeParam, boolean usePredictor)
{
int predictor = usePredictor
? BaselineTIFFTagSet.PREDICTOR_HORIZONTAL_DIFFERENCING
: BaselineTIFFTagSet.PREDICTOR_NONE;
switch (compression)
{
case GROUP_3_FAX_ENCODING:
return new TIFFT4Compressor();
case GROUP_4_FAX_ENCODING:
return new TIFFT6Compressor();
case JPEG_COMPRESSION:
return new TIFFJPEGCompressor(writeParam);
case MACINTOSH_PACKBITS:
return new TIFFPackBitsCompressor();
case DEFLATE:
return new TIFFDeflateCompressor(writeParam, predictor);
case LZW:
return new TIFFLZWCompressor(predictor);
case MODIFIED_HUFFMAN:
return new TIFFRLECompressor();
case NO_COMPRESSION:
case DEFAULT:
default:
return null;
}
}
TiffCompression is my own enum that models the compressions that I offer for TIFF files. Finally, here is getCompressionType():
private String getCompressionType (TiffCompression compression)
{
switch (compression)
{
case GROUP_3_FAX_ENCODING:
return "CCITT T.4";
case GROUP_4_FAX_ENCODING:
return "CCITT T.6";
case JPEG_COMPRESSION:
return "JPEG";
case MACINTOSH_PACKBITS:
return "PackBits";
case DEFLATE:
return "Deflate";
case LZW:
return "LZW";
case MODIFIED_HUFFMAN:
return "CCITT RLE";
case NO_COMPRESSION:
case DEFAULT:
default:
return null;
}
}
Now, I can't show you everything because my code is built to encode an arbitrary number of images and yours is not, so our code structures differ wildly. In my case, I set up the encoder for using a sequence writer with a much more open architecture. I pull in an image, fire an event to optionally change the default compression, create the writer and write param, set metadata/image tags, fire progress events then write the sequence. Then I have to go in and patch the last written ifd because the tiff encoder writes them broken, so they need to be patched.
Upvotes: 3