簡體   English   中英

使用java ImageIO讀取和寫入一些TIFF圖像時輸出TIFF中的粉紅色背景顏色

[英]Pink background colour in output TIFF when reading and writing some TIFF images with java ImageIO

我正在嘗試使用打開的JDK 11 imageIO ImageReader和ImageWriter類將許多輸入TIFF文件合並到單個多頁輸出TIFF文件中。 我的例程適用於從許多不同品牌的掃描設備創建的幾乎所有樣本輸入文件。 這些設備使用新舊JPEG壓縮生成各種TIFF文件。 但是,來自某個特定設備的TIFF文件會導致輸出不正確,背景為粉紅色。 更奇怪的是,使用縱向掃描生成的TIFF可以創建正確的輸出,而使用同一設備進行橫向掃描生成的TIFF會產生帶有粉紅色背景的錯誤輸出。 我發現2個輸入文件之間沒有明顯區別,這些文件會在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文件的一種圖像目標類型。 最新的JDK 11.0.4_11版本會出現問題。

好的,通過檢查示例文件,我想我已經發現了問題。 它不在你的代碼中*。

當使用JPEG壓縮讀取和寫入TIFF時,TIFF插件會將嵌入的JPEG流的解碼/編碼委托給JPEG插件。 理論上,這很簡單,因為JPEG不包含顏色信息,並且TIFF容器在262/PhotometricInterpretation標記中包含正確的顏色信息。

在現實生活中,這要復雜得多,因為有時TIFF標簽丟失或不正確(特別是與259/Compression標簽組合使用值為6 (“舊JPEG”)。或者JPEG編碼器/解碼器會做出自己的假設顏色空間(基於獨立JPEG的約定,通常是JFIF或Exif),這就是我認為的情況。與JRE捆綁在一起的JPEG插件使用此處記錄約定 ,顏色空間是從組件ID中推斷出來的。 SOFn標記。

對於您的文件,我們可以看到組件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]]

縱向文件中的組件ID是正常的1,2和3,而橫向具有ID 0,1和2.兩個文件都沒有子采樣(即1:1)。

從公約:

如果3通道圖像的這些值為1-3,則假定圖像為YCbCr [...]

否則,假設3通道二次采樣圖像是YCbCr,假設3通道非二次采樣圖像是RGB

因此,橫向圖像將被視為RGB(並且錯誤地,不會從YCbCr轉換),從而產生粉紅色調。 即使TIFF容器中的其他所有內容都清楚地表明它是YCbCr。

為了解決這個問題(以及許多其他問題),我創建了自己的JPEG插件 ,可以用作JRE插件的替代品。 它遵循IJG的libJPEG中的(更簡單的)約定,從而與其他應用程序產生更好的色彩空間一致性。 結合同一項目的TIFF插件,您的輸入都可以正確讀取(白色背景)。 我沒有使用JRE TIFF插件測試它,但從理論上講,它應該/也可以工作。 不幸的是,TwelveMonkeys TIFF插件(尚未)具有您使用的寫入功能(平鋪),並且對它所寫的元數據有一些限制。


PS:由於您似乎主要處理重新編碼時質量下降的JPEG,您可能希望在不解碼圖像數據的情況下合並TIFF。 你可以在TIFFUtilities找到一個由Oliver Schmidtmer編寫的例子。

*)在技術上可以解決代碼中的問題,但正確處理所有情況有點復雜。 如果您想自己實現,或者只是好奇,我建議您查看TwelveMonkeys ImageIO JPEG插件的源代碼。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM