使用有损 jpeg 压缩多页 tiff 图像

Compressing a multi-page tiff image with lossy jpeg

我需要压缩一个包含多个灰色 16 位图像(多页)的 tif 文件。我已经尝试使用 ImageIO,如下所示:Tiff compression using Java ImageIO 最初,tif 文件中的每个图像都来自另一个 tiff 文件。当我想使用压缩器时,我有以下选项:

  1. CCITT RLE、CCITT T.4、CCITT T.6:他们给我错误:"javax.imageio.IIOException: I/O error writing TIFF file!"
  2. LZW。我不能使用它。我的图片是16bit的,LZW增加了16bit图片的大小
  3. JPEG。不适用于 16 位图像。
  4. ZLIB。即使我指定 setCompressionQuality(0.0f);
  5. 它也只会减少 10%
  6. PackBits。不压缩。
  7. 放气。喜欢 ZLIB.
  8. EXIF JPEG。它给了我错误:"javax.imageio.IIOException: Old JPEG compression not supported!"

有没有人知道其他选择?我看到一个 apache 图像库,但 tif 压缩只支持以上或更少的选项。有人知道 JPEG2000 压缩器吗?还有其他选择吗?

PNG 无损压缩 16 位图像。库和实用程序随处可见。 JPEG2000 具有有损 16 位模式,但您必须找到支持它的软件。 Open JPEG 可能。

但是我不得不问:您的标准是什么时候可以接受图像质量,什么时候不能?如果它是可视的,那么您可能最终会得到普通的 JPEG,每个有效像素少于 8 位。

正在将图像从 16 位缩小到 8 位。假设您有一个 byte[] 变量 plane16,其中包含图像的所有像素。

注意:我的byte[] plane16是从16bit的图片中获取数据,但是byte是8bit=1byte。因此,此数组行中的 2 个元素为 2byte = 16bit。这就是为什么我在操作之前将其转换为 short[] 的原因。如果您从短 [] 开始,省略 "ByteBuffer.wrap(plane16).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(shorts);"

byte[] plane16; //Fill it with your image!!!

//Do stuff with imageIO. Set writer and compresion method
ImageIO.scanForPlugins();
TIFFImageWriterSpi tiffspi = new TIFFImageWriterSpi();
javax.imageio.ImageWriter writerIO = tiffspi.createWriterInstance();
ImageWriteParam param = writerIO.getDefaultWriteParam();
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionType("ZLib");
param.setCompressionQuality(0.5f);
File fOutputFile = new File(route+".tif");
ImageOutputStream ios = ImageIO.createImageOutputStream(fOutputFile);
writerIO.setOutput(ios);

//Reducing 16bit to 8bit
short[] shorts = new short[plane16.length/2];
ByteBuffer.wrap(plane16).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(shorts);

int max = 0;
int min = 999999;
for (int v = 0; v<shorts.length;v++){
    if (max<shorts[v]) max = shorts[v];
    if (min>shorts[v]) min = shorts[v];
}

double range = 255./(max-min);
byte[] plane8 = new byte[shorts.length];
for (int v = 0; v<plane8.length;v++){
    plane8[v] = (byte) ((shorts[v]+min)*range - 128);   
}                       
//16bit:    
/*BufferedImage convertedGrayscale = new BufferedImage(width,
                            heigth, BufferedImage.TYPE_USHORT_GRAY);    
  convertedGrayscale.getRaster().setDataElements(0, 0, width,
                            heigth, shorts);*/

//8bit:
BufferedImage convertedGrayscale = new BufferedImage(width,
                            heigth, BufferedImage.TYPE_BYTE_GRAY);  
convertedGrayscale.getRaster().setDataElements(0, 0, width,
                            heigth, plane8);

//Save image
//If you have a stack of images in tiff, do this trick. "image" is the image number you are setting inside the tiff. If you only have 1 image, remove the if and take the expression from the else.
if (image!=0){
    writerIO.writeInsert(image, new IIOImage(convertedGrayscale, null, null), param);                   
}else{
    writerIO.write(null, new IIOImage(convertedGrayscale, null, null), param);
}

//do the next only after the last image to be saved
writerIO.dispose();
ios.flush();
ios.close();