gpt4 book ai didi

java - 使用 java ImageIO 读取和写入一些 TIFF 图像时输出 TIFF 中的粉红色背景色

转载 作者:搜寻专家 更新时间:2023-11-01 03:15:13 27 4
gpt4 key购买 nike

我正在尝试使用开放的 JDK 11 imageIO 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 文件的一种图像目标类型。问题出现在最新打开的JDK 11.0.4_11版本。

最佳答案

好的,通过检查示例文件,我想我找到了问题所在。而且它不在您的代码中*。

当读取和写入带有 JPEG 压缩的 TIFF 时,TIFF 插件会将嵌入的 JPEG 流的解码/编码委托(delegate)给 JPEG 插件。理论上,这很简单,因为 JPEG 不包含颜色信息,而 TIFF 容器在 262/PhotometricInterpretation 标签中包含正确的颜色信息。

在现实生活中,这要复杂得多,因为有时 TIFF 标签会丢失或不正确(尤其是与值为 6259/Compression 标签结合使用时("Old JPEG")。或者 JPEG 编码器/解码器将对颜色空间做出自己的假设(基于独立 JPEG 的约定,通常是 JFIF 或 Exif),我相信这里就是这种情况。与 JRE 捆绑在一起的 JPEG 插件使用 conventions documented here ,颜色空间是从 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]]

肖像文件中的组件 ID 是正常的 1、2 和 3,而风景文件的 ID 是 0、1 和 2。两个文件都没有二次采样(即 1:1)。

来自约定:

If these values are 1-3 for a 3-channel image, then the image is assumed to be YCbCr [...]

Otherwise, 3-channel subsampled images are assumed to be YCbCr, 3-channel non-subsampled images are assumed to be RGB.

因此,风景图像将被视为已经在 RGB 中(并且错误地,未从 YCbCr 转换),从而导致粉红色调。即使 TIFF 容器中的所有其他内容都清楚地表明它是 YCbCr。

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


PS:由于您似乎主要处理在重新编码时质量下降的 JPEG,因此您可能需要考虑在不解码图像数据的情况下合并 TIFF。您可以在 TIFFUtilities 中找到相关示例,由奥利弗·施密特默 (Oliver Schmidtmer) 撰写。

*) 在您的代码中解决问题在技术上是可能的,但是正确处理所有情况有点复杂。如果你想自己实现这个,或者只是好奇,我建议你看一下 TwelveMonkeys ImageIO JPEG plugin 的源代码。 .

关于java - 使用 java ImageIO 读取和写入一些 TIFF 图像时输出 TIFF 中的粉红色背景色,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57573356/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com