问题描述
Stack:
- Java - 1.8.0_91
- Scala - 2.11.8
- 图书馆 - it.geosolutions.imageio-ext imageio-ext-tiff 1.1.15
我们正在阅读大量旧的TIF图像,由于某些原因,读取非常不一致 - 由于某些原因,在不同的运行中读取相同的图像可能会成功或失败,异常 -
We are reading lots of old TIF images and for some reason read is highly inconsistent - for some reasons on a different run reading the same image can succeed or fail with exception -
javax.imageio.IIOException: Invalid component ID 3 in SOS
at com.sun.imageio.plugins.jpeg.JPEGImageReader.readImage(Native Method)
at com.sun.imageio.plugins.jpeg.JPEGImageReader.readInternal(JPEGImageReader.java:1236)
at com.sun.imageio.plugins.jpeg.JPEGImageReader.read(JPEGImageReader.java:1039)
at com.sun.media.imageioimpl.plugins.tiff.TIFFOldJPEGDecompressor.decodeRaw(TIFFOldJPEGDecompressor.java:654)
at com.sun.media.imageio.plugins.tiff.TIFFDecompressor.decode(TIFFDecompressor.java:2527)
at com.sun.media.imageioimpl.plugins.tiff.TIFFImageReader.decodeTile(TIFFImageReader.java:1137)
at com.sun.media.imageioimpl.plugins.tiff.TIFFImageReader.read(TIFFImageReader.java:1417)
代码是这样的:
import java.io.{ByteArrayInputStream, ByteArrayOutputStream}
import javax.imageio.ImageIO
def convertToPng(data: Array[Byte]): Array[Byte] = {
val inputStream = new ByteArrayInputStream(data)
val image = ImageIO.read(inputStream)
val outputStream = new ByteArrayOutputStream(inputStream.available())
ImageIO.write(image, "png", outputStream)
outputStream.toByteArray
}
问题是ImageIO同时初始化2个TIFF阅读器
The problem is ImageIO initializes 2 TIFF readers at the same time
com.sun.media.imageioimpl.plugins.tiff.TIFFImageReader &
it.geosolutions.imageioimpl.plugins.tiff.TIFFImageReader
OR
it.geosolutions.imageioimpl.plugins.tiff.TIFFImageReader
com.sun.media.imageioimpl.plugins.tiff.TIFFImageReader
第一个失败,第二个失败。
如何从ImageIO配置中排除com.sun.media.imageioimpl.plugins.tiff.TIFFImageReader?
The first one fails, the second one works.How to exclude com.sun.media.imageioimpl.plugins.tiff.TIFFImageReader from ImageIO configuration?
推荐答案
这里的问题是ImageIO使用服务提供程序接口(SPI)查找在运行时注册插件,在您的设置中,可以找到多个可以读取TIFF的插件。默认情况下,插件没有任何特定的顺序,这就是为什么你有时首先获得 com.sun
(JAI)TIFF插件,有时候 it.geosolutions
(Geosolutions)首先是TIFF插件。 ImageIO.read(...)
只会尝试第一个插件,如果失败则放弃。
The issue here is that ImageIO uses a service provider interface (SPI) lookup to register plugins at runtime, and in your setup, multiple plugins that can read TIFF is found. By default, the plugins does not have any specific order, which is why you sometimes get the com.sun
(JAI) TIFF plugin first and sometimes the it.geosolutions
(Geosolutions) TIFF plugin first. ImageIO.read(...)
will only try this first plugin and give up if it fails.
如果你可以,最简单的解决方案就是从类路径中删除其中一个插件。但我认为你已经想到了这一点。还有其他多种方法可以解决这个问题(我在Java中提供代码示例,因为这是我最熟悉的,我相信你可以在Scala中更优雅地写它;-))。
If you can, the easiest solution is just to remove one of the plugins from class path. But I assume you already thought of that. There are still multiple other ways to solve this (I give code examples in Java, as that's what I'm most familiar with, I'm sure you can write it more elegant in Scala ;-)).
需要对代码进行最少更改的那个是在运行时取消注册JAI提供程序,在bootstrap代码中的某个地方(具体取决于它的位置,取决于应用程序,可以是静态初始化程序块或Web上下文监听程序或类似程序)。为此目的, IIORegistry
有一个 deregisterServiceProvider
方法,从注册表中删除提供程序,并使其不可用于 ImageIO
。
The one that requires the least changes to your code, is to unregister the JAI provider at runtime, somewhere in your "bootstrap" code (exactly where this is, depends on the application, could be a static initializer block or a web context listener or similar). The IIORegistry
has a deregisterServiceProvider
method for this purpose, removing the provider from the registry, and making it unavailable for ImageIO
.
另一种选择是为提供商定义明确的订单。如果由于某种原因需要为单个格式提供多个提供程序(第三方要求/插件间依赖关系等),这可能很有用。为此目的, IIORegistry
有一个 setOrdering
方法,允许设置成对两个服务提供商,使 ImageIO
总是先选择一个。
Another option is to define an explicit order for the providers. This can be useful if you need to to have multiple providers for a single format for some reason (third-party requirements/inter-plugin dependencies etc). The IIORegistry
has a setOrdering
method for this purpose, that allows setting pairwise ordering of two service providers, making ImageIO
always prefer one before the other.
以下代码显示了上述两个选项:
The below code shows both of the above options:
// Get the global registry
IIORegistry registry = IIORegistry.getDefaultInstance();
// Lookup the known TIFF providers
ImageReaderSpi jaiProvider = lookupProviderByName(registry, "com.sun.media.imageioimpl.plugins.tiff.TIFFImageReaderSpi");
ImageReaderSpi geoProvider = lookupProviderByName(registry, "it.geosolutions.imageioimpl.plugins.tiff.TIFFImageReaderSpi");
if (jaiProvider != null && geoProvider != null) {
// If both are found, EITHER
// order the it.geosolutions provider BEFORE the com.sun (JAI) provider
registry.setOrdering(ImageReaderSpi.class, geoProvider, jaiProvider);
// OR
// un-register the JAI provider
registry.deregisterServiceProvider(jaiProvider);
}
// New and improved (shorter) version. :-)
private static <T> T lookupProviderByName(final ServiceRegistry registry, final String providerClassName) {
try {
return (T) registry.getServiceProviderByClass(Class.forName(providerClassName));
}
catch (ClassNotFoundException ignore) {
return null;
}
}
以上代码将确保Geosolutions TIFF插件将会始终由 ImageIO.read(...)
使用,现有代码应该正常工作(但现在稳定)。
The above code will make sure the Geosolutions TIFF plugin will always be used by ImageIO.read(...)
, and your existing code should just work (but now be stable).
一个完全不同的选择,是尝试使用所有已注册的TIFF插件读取数据,并使用第一个成功的插件。这比前面的代码更明确,但需要重写图像读取代码:
A completely different option, is to try reading the data using all registered TIFF plugins, and use the first one that succeeds. This is more explicit than the previous code, but requires rewriting the image reading code:
byte[] data;
BufferedImage image;
try (ImageInputStream inputStream = ImageIO.createImageInputStream(new ByteArrayInputStream(data))) {
Iterator<ImageReader> readers = ImageIO.getImageReaders(inputStream);
// Try reading the data, using each reader until we succeed (or have no more readers)
while (readers.hasNext()) {
ImageReader reader = readers.next();
try {
reader.setInput(inputStream);
image = reader.read(0);
break; // Image is now correctly decoded
}
catch (Exception e) {
// TODO: Log exception?
e.printStackTrace();
// Reading failed, try the next Reader
inputStream.seek(0);
}
finally {
reader.dispose();
}
}
}
您当然可以合并以上选项,以获得两全其美(例如,如果一个读者失败,则稳定订单和后备)。
You can of course combine the options above, to have the best of both worlds (ie. stable order and fallback if one reader fails).
这篇关于如何从ImageIO中排除特定的TIFF阅读器?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!