堆:
> Java – 1.8.0_91
>斯卡拉 – 2.11.8
>图书馆 – it.geosolutions.imageio-ext imageio-ext-tiff 1.1.15
我们正在阅读许多旧的TIF图像,并且出于某种原因,读取是高度不一致的 – 由于某些原因,在不同的运行中读取同一图像可能成功或失败,例外 –
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阅读器
com.sun.media.imageioimpl.plugins.tiff.TIFFImageReader &
it.geosolutions.imageioimpl.plugins.tiff.TIFFImageReader
要么
it.geosolutions.imageioimpl.plugins.tiff.TIFFImageReader
com.sun.media.imageioimpl.plugins.tiff.TIFFImageReader
第一个失败,第二个失败.
如何从ImageIO配置中排除com.sun.media.imageioimpl.plugins.tiff.TIFFImageReader?
解决方法:
这里的问题是ImageIO使用服务提供程序接口(SPI)查找在运行时注册插件,在您的设置中,可以找到多个可以读取TIFF的插件.默认情况下,插件没有任何特定的顺序,这就是为什么你有时首先获得com.sun(JAI)TIFF插件,有时首先获得it.geosolutions(Geosolutions)TIFF插件. ImageIO.read(…)只会尝试第一个插件,如果失败则放弃.
如果可以,最简单的解决方案就是从类路径中删除其中一个插件.但我想你已经想到了这一点.还有其他多种方法可以解决这个问题(我在Java中提供代码示例,因为这是我最熟悉的,我相信你可以在Scala中更优雅地写它;-)).
需要对代码进行最少更改的方法是在运行时取消注册JAI提供程序,在“引导程序”代码中的某个位置(确切地说,这取决于应用程序,可以是静态初始化程序块或Web上下文侦听程序或类似).为此,IIORegistry具有deregisterServiceProvider方法,从注册表中删除提供程序,并使其不可用于ImageIO.
另一种选择是为提供者定义明确的顺序.如果由于某种原因需要为单个格式提供多个提供程序(第三方要求/插件间依赖关系等),这可能很有用. IIORegistry有一个用于此目的的setOrdering方法,它允许设置两个服务提供者的成对排序,使得ImageIO总是优先于另一个.
以下代码显示了上述两个选项:
// 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(…)使用,并且您现有的代码应该正常工作(但现在是稳定的).
一个完全不同的选择是尝试使用所有已注册的TIFF插件读取数据,并使用第一个成功的插件.这比前面的代码更明确,但需要重写图像读取代码:
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();
}
}
}
您当然可以将上面的选项结合起来,以获得两全其美(例如,如果一个读取器出现故障,则稳定顺序和后退).