- Java 双重比较
- java - 比较器与 Apache BeanComparator
- Objective-C 完成 block 导致额外的方法调用?
- database - RESTful URI 是否应该公开数据库主键?
我正在尝试使用开放的 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 标签会丢失或不正确(尤其是与值为 6
的 259/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/
我正在编写一个具有以下签名的 Java 方法。 void Logger(Method method, Object[] args); 如果一个方法(例如 ABC() )调用此方法 Logger,它应该
我是 Java 新手。 我的问题是我的 Java 程序找不到我试图用作的图像文件一个 JButton。 (目前这段代码什么也没做,因为我只是得到了想要的外观第一的)。这是我的主课 代码: packag
好的,今天我在接受采访,我已经编写 Java 代码多年了。采访中说“Java 垃圾收集是一个棘手的问题,我有几个 friend 一直在努力弄清楚。你在这方面做得怎么样?”。她是想骗我吗?还是我的一生都
我的 friend 给了我一个谜语让我解开。它是这样的: There are 100 people. Each one of them, in his turn, does the following
如果我将使用 Java 5 代码的应用程序编译成字节码,生成的 .class 文件是否能够在 Java 1.4 下运行? 如果后者可以工作并且我正在尝试在我的 Java 1.4 应用程序中使用 Jav
有关于why Java doesn't support unsigned types的问题以及一些关于处理无符号类型的问题。我做了一些搜索,似乎 Scala 也不支持无符号数据类型。限制是Java和S
我只是想知道在一个 java 版本中生成的字节码是否可以在其他 java 版本上运行 最佳答案 通常,字节码无需修改即可在 较新 版本的 Java 上运行。它不会在旧版本上运行,除非您使用特殊参数 (
我有一个关于在命令提示符下执行 java 程序的基本问题。 在某些机器上我们需要指定 -cp 。 (类路径)同时执行java程序 (test为java文件名与.class文件存在于同一目录下) jav
我已经阅读 StackOverflow 有一段时间了,现在我才鼓起勇气提出问题。我今年 20 岁,目前在我的家乡(罗马尼亚克卢日-纳波卡)就读 IT 大学。足以介绍:D。 基本上,我有一家提供簿记应用
我有 public JSONObject parseXML(String xml) { JSONObject jsonObject = XML.toJSONObject(xml); r
我已经在 Java 中实现了带有动态类型的简单解释语言。不幸的是我遇到了以下问题。测试时如下代码: def main() { def ks = Map[[1, 2]].keySet()
一直提示输入 1 到 10 的数字 - 结果应将 st、rd、th 和 nd 添加到数字中。编写一个程序,提示用户输入 1 到 10 之间的任意整数,然后以序数形式显示该整数并附加后缀。 public
我有这个 DownloadFile.java 并按预期下载该文件: import java.io.*; import java.net.URL; public class DownloadFile {
我想在 GUI 上添加延迟。我放置了 2 个 for 循环,然后重新绘制了一个标签,但这 2 个 for 循环一个接一个地执行,并且标签被重新绘制到最后一个。 我能做什么? for(int i=0;
我正在对对象 Student 的列表项进行一些测试,但是我更喜欢在 java 类对象中创建硬编码列表,然后从那里提取数据,而不是连接到数据库并在结果集中选择记录。然而,自从我这样做以来已经很长时间了,
我知道对象创建分为三个部分: 声明 实例化 初始化 classA{} classB extends classA{} classA obj = new classB(1,1); 实例化 它必须使用
我有兴趣使用 GPRS 构建车辆跟踪系统。但是,我有一些问题要问以前做过此操作的人: GPRS 是最好的技术吗?人们意识到任何问题吗? 我计划使用 Java/Java EE - 有更好的技术吗? 如果
我可以通过递归方法反转数组,例如:数组={1,2,3,4,5} 数组结果={5,4,3,2,1}但我的结果是相同的数组,我不知道为什么,请帮助我。 public class Recursion { p
有这样的标准方式吗? 包括 Java源代码-测试代码- Ant 或 Maven联合单元持续集成(可能是巡航控制)ClearCase 版本控制工具部署到应用服务器 最后我希望有一个自动构建和集成环境。
我什至不知道这是否可能,我非常怀疑它是否可能,但如果可以,您能告诉我怎么做吗?我只是想知道如何从打印机打印一些文本。 有什么想法吗? 最佳答案 这里有更简单的事情。 import javax.swin
我是一名优秀的程序员,十分优秀!