当前位置: 首页 > 知识库问答 >
问题:

使用java ImageIO读写TIFF图像时,输出TIFF中的粉红色背景颜色

楚俊逸
2023-03-14

我试图使用open JDK11ImageIO、ImageReader和ImageWriter类将多个输入TIFF文件合并为一个多页输出TIFF文件。我的例程对从许多不同品牌的扫描设备创建的几乎所有示例输入文件都能正常工作。这些设备使用旧的和新的JPEG压缩生成各种TIFF文件。然而,来自一个特定设备的TIFF文件会导致具有粉红色背景的错误输出。更奇怪的是,用纵向扫描产生的TIFF会产生正确的输出,而用同一设备的横向扫描产生的TIFF则会产生带有粉红色背景的不正确输出。我看不出这两个输入文件之间有什么明显的差别,这会导致ImageIO库处理时的行为差异。

我知道输出中的粉红色背景通常表示透明度解释有问题。在读写JEPG图像时,我发现了许多关于这个问题的引用。然而,我没有发现任何参考与TIFF图像类似的问题。当我在调试器中遍历ImageReader和ImageWriter时,我找不到工作的输入TIFF文件和产生糟糕的粉红色输出的文件之间有什么明显的区别。两个文件都不透明。两者具有相同的YCbCr光度解释、波段和子取样。有问题的TIFF文件使用旧的JPEG压缩,因此图像写入参数显式地为ImageWriter指定新的JPEG压缩。但是,对于工作正常的类似的肖像TIFF文件来说是这样的,所以问题一定比仅仅是输出压缩更微妙。

下面是一个简单的命令行应用程序,它再现了我的问题。

package com.example;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;

public class Main {

    private static final String TIFF_FORMAT = "tiff";
    private static final String IMAGEIO_PLUGIN_PACKAGE = "com.sun.imageio.plugins.tiff";
    //private static final String IMAGEIO_PLUGIN_PACKAGE = "com.github.jaiimageio.impl.plugins.tiff";

    public static void main(String[] args) {
        if (args.length != 2) {
            System.out.println("You must specify an input directory and output filename");
            return;
        }

        File sourceDirectory = new File(args[0]);
        if (!sourceDirectory.exists() || !sourceDirectory.isDirectory()) {
            System.out.println(String.format("Source directory '%s' is invalid", args[0]));
        }
        File outputFile = new File(args[1]);
        if (outputFile.exists()) {
            outputFile.delete();
        }
        File inputFiles[] = sourceDirectory.listFiles();

        mergeTiffFiles(inputFiles, outputFile);
    }

    /**
     * Merge a list of TIFF files into a single output TIFF file using the Java ImageIO utilities.
     *
     * @param inputFilePaths list of input file paths to merge
     * @param mergedFilePath destination path for the merged output file
     */
    private static void mergeTiffFiles(
            final File[] inputFilePaths,
            final File mergedFilePath) {
        ImageReader reader = null;
        ImageWriter writer = null;
        File inputFilePath = null;
        try (
                OutputStream outputStream = new FileOutputStream(mergedFilePath);
                ImageOutputStream ios = ImageIO.createImageOutputStream(outputStream)
        ) {
            // Initialise the output writer
            writer = getTiffWriter();
            writer.setOutput(ios);
            writer.prepareWriteSequence(null);

            // Iterate through the source files appending the pages in order within and across files
            reader = getTiffReader();
            for (final File filePath : inputFilePaths) {
                inputFilePath = filePath;
                try (
                        FileInputStream inputFile = new FileInputStream(filePath);
                        ImageInputStream inputStream = ImageIO.createImageInputStream(inputFile)
                ) {
                    reader.setInput(inputStream);
                    int numImages = reader.getNumImages(true);
                    for (int j = 0; j < numImages; j++) {
                        IIOMetadata imageMetadata = reader.getImageMetadata(j); // 0, first image
                        ImageWriteParam writeParams = getTiffWriteParams(writer, imageMetadata);
                        BufferedImage image = reader.read(j);
                        writer.writeToSequence(new IIOImage(image, null, imageMetadata), writeParams);
                    }
                }
            }
            inputFilePath = null;

            // Finalize the output file
            writer.endWriteSequence();
        } catch (Exception e) {
            if (inputFilePath != null) {
                throw new IllegalStateException(String.format("Error while merging TIFF file: %s", inputFilePath), e);
            } else {
                throw new IllegalStateException("Failed to merge TIFFs files", e);
            }
        } finally {
            // Cleanup the reader and writer
            if (writer != null) {
                writer.dispose();
            }
            if (reader != null) {
                reader.dispose();
            }
        }
    }

    /**
     * Get an TIFF reader used to read the source pages - ensure we use the imageIO plugin.
     *
     * @return an TIFF image reader.
     * @throws IOException if an reader plugin cannot be found
     */
    private static ImageReader getTiffReader() throws IOException {
        ImageReader reader = null;
        Iterator readers = ImageIO.getImageReadersByFormatName(TIFF_FORMAT);
        if (readers.hasNext()) {
            do {
                reader = (ImageReader) readers.next();
            } while (!reader.getClass().getPackage().getName().equals(IMAGEIO_PLUGIN_PACKAGE) && readers.hasNext());
        }
        if (reader == null) {
            throw new IOException("No imageio readers for format: " + TIFF_FORMAT);
        }
        return reader;
    }

    /**
     * Get a TIFF writer used to create the merged page - ensure we use the imageIO plugin
     *
     * @return a TIFF image writer
     * @throws IOException if an writer plugin cannot be found
     */
    private static ImageWriter getTiffWriter() throws IOException {
        ImageWriter writer = null;
        Iterator writers = ImageIO.getImageWritersByFormatName(TIFF_FORMAT);
        if (writers.hasNext()) {
            do {
                writer = (ImageWriter) writers.next();
            } while (!writer.getClass().getPackage().getName().equals(IMAGEIO_PLUGIN_PACKAGE) && writers.hasNext());
        }
        if (writer == null) {
            throw new IOException("No imageio writers for format: " + TIFF_FORMAT);
        }
        return writer;
    }

    /**
     * Get the appropriate TIFF write parameters to apply for an input with the given image meta-data.
     * Check the source image compression. If possible use the same compression settings as those from the
     * input image.  However, the ImageIO library doesn't support the legacy JPEG compression format for TIFF
     * images.  Unfortunately, there are a number of devices that create scanned TIFF images of this type
     * (Xerox, HP OXP).  To support the merge operation explicitly force the new JPEG compression with a high
     * quality value.
     *
     * @param writer        TIFF image writer that will use the returned image parameters
     * @param imageMetadata meta-data associated with the image to write
     * @return the adjusted image write parameters
     */
    private static ImageWriteParam getTiffWriteParams(ImageWriter writer, IIOMetadata imageMetadata) {
        // Determine the source compression type
        IIOMetadataNode root =
                (IIOMetadataNode) imageMetadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
        IIOMetadataNode compression =
                (IIOMetadataNode) root.getElementsByTagName("CompressionTypeName").item(0);
        String compressionName = compression.getAttribute("value");
        ImageWriteParam writeParams = writer.getDefaultWriteParam();
        if (compressionName.equalsIgnoreCase("Old JPEG")) {
            // Convert to modern JPEG encoding if the source uses old JPEG compression.
            writeParams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
            writeParams.setCompressionType("JPEG");
            double quality = 0.95;
            quality = Math.max(0, Math.min(1, quality));
            writeParams.setCompressionQuality((float) quality);
        } else {
            // Otherwise use the source image compression if possible
            writeParams.setCompressionMode(ImageWriteParam.MODE_COPY_FROM_METADATA);
        }
        writeParams.setTilingMode(ImageWriteParam.MODE_COPY_FROM_METADATA);
        return writeParams;
    }
}

我希望类似的景观和肖像tiff的输出具有正确的白色背景。我显然在读写程序的设置上做错了什么。然而,可供尝试的选项并不多。ImageReader只支持TIFF文件的一种图像目标类型。该问题发生在最新的开放JDK11.0.4_11版本中。

共有1个答案

孙洋
2023-03-14

好的,通过检查样本文件,我想我已经发现了问题。而且不在你的代码里*。

当用JPEG压缩读写TIFF时,TIFF插件会将嵌入式JPEG流的解码/编码委托给JPEG插件。理论上,这很简单,因为JPEG不包含颜色信息,而TIFF容器262/PhotoetricInterprestation标记中包含正确的颜色信息。

在现实生活中,这要复杂得多,因为有时TIFF标记会丢失或不正确(特别是与值6259/compress标记(“旧JPEG”)结合使用)。或者JPEG编码器/解码器会对颜色空间做出自己的假设(基于独立JPEG的约定,通常是JFIF或Exif)。我相信这里就是这样。与JRE捆绑的JPEG插件使用这里记录的约定,颜色空间是从SOFn标记中的组件ID推断出来的。

对于您的文件,我们可以看到组件ID不同。

肖像文件:

SOF0[ffc0, precision: 8, lines: 3520, samples/line: 2496, 
     components: [id: 1, sub: 1/1, sel: 0, id: 2, sub: 1/1, sel: 1, id: 3, sub: 1/1, sel: 1]]

横向文件:

SOF0[ffc0, precision: 8, lines: 2496, samples/line: 3520, 
    components: [id: 0, sub: 1/1, sel: 0, id: 1, sub: 1/1, sel: 1, id: 2, sub: 1/1, sel: 1]]

为了解决这个问题(以及许多其他问题),我创建了自己的JPEG插件,它可以作为JRE插件的替换。它遵循IJG的libJPEG中的(简单得多的)约定,从而与其他应用程序实现了更好的颜色空间一致性。与来自同一项目的TIFF插件相结合,您的两个输入都被正确读取(白色背景)。我没有用JRE TIFF插件测试它,但理论上,它应该/也可以工作。不幸的是,TwelveMonkeys TIFF插件还不具备您所使用的写功能(平铺),并且对于它所写的元数据有一些限制。

PS:由于您似乎主要处理的是在重新编码时质量下降的JPEG,您可能想看看合并TIFF而不解码图像数据。您可以在Oliver Schmidtmer编写的TiffUtilities中找到这方面的示例。

*)从技术上讲,可以在代码中解决这个问题,但是要正确处理所有的情况是很复杂的。如果你想自己实现这个,或者只是好奇,我建议你看看TwleveMonkeys ImageIO JPEG插件的源代码。

 类似资料:
  • 问题内容: 我尝试了以下代码来完成读取和写入tiff图像的任务: 但是,当我运行代码时,出现以下错误消息: 知道如何解决这个问题吗? 问题答案: 读取TIFF并输出BMP的最简单方法是使用ImageIO类: 要使此功能正常工作,您唯一需要做的另一件事是确保已将JAI ImageIO JAR添加到类路径中,因为如果没有此库中的插件,JRE不会处理BMP和TIFF。 如果由于某种原因不能使用JAI I

  • 问题内容: 有没有人有一种方法可以在Python中导入每通道16位,3通道TIFF图像? 我还没有找到一种在处理TIFF格式时可以保留每个通道16位深度的方法。我希望一些乐于助人的人能够解决。 这是到目前为止我没有成功尝试过的结果以及结果的列表: 到目前为止,我发现的唯一解决方案是使用ImageMagick将图像转换为PNG。然后,沼泽标准会毫无问题地读取PNG文件。 我遇到的另一个问题是将任何n

  • 问题内容: 假设我要在CSS中渲染箭头,箭头应具有头部,尾部和灵活的宽度,以便可以包含文本。我当然可以创建多个div来获得所需的内容,但是如何在CSS3中完成呢? 我可以使用多个背景图片: 的HTML: 这给了我一个带有箭头和尾巴的透明中间部分。似乎不可能在中间部分指定颜色。 仅使用一张背景图像,您可以执行以下操作: 我知道这在很多方面都是可行的,但是背景颜色属性是否真的从速记定义中丢失了? 问题

  • 我目前正在编写一个应用程序来读取TIFF文件,然后执行一些压缩算法。我已经成功地做到了这一点...但是现在,我想读取TIFF图像的元数据,但似乎找不到正确的方法。 我应该使用什么库和函数?

  • 我在写一个程序,它应该接受一堆tiff的,并把它们放在一起。我让它对我读入的大多数图像文件都起作用,但当我尝试读入它们时,它们中的一大部分会抛出一个错误。 是的,我捕捉到了越界异常,所以我们不用担心这个。我的问题是我得到以下错误: iIOException:读取图像元数据时出现I/O错误!在com.sun.media.imageioimpl.plugins.tiff.tiffimagereader

  • 如何从Python中的TIFF图像中读取元数据(如坐标)?我尝试了来自PIL的,但收到消息: AttributeError:“TiffImageFile”对象没有属性“\u getexif” 有可能用PIL得到它吗?