堆栈:
我们正在读取许多旧的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(...)只会尝试第一个插件,如果失败则放弃。
com.sun
it.geosolutions
ImageIO.read(...)
如果可以的话,最简单的解决方案是从类路径中删除其中一个插件。但是我想你已经想到了这一点。还有许多其他方法可以解决此问题(我用Java给出了代码示例,因为这是我最熟悉的示例,因此我相信您可以在Scala中将其编写得更优雅;-)。
需要对代码进行最少更改的一种方法是在运行时注销“引导”代码中某处的JAI提供程序(确切的位置取决于应用程序,可能是静态初始化程序块或Web上下文侦听器,或者类似)。该IIORegistry有一个deregisterServiceProvider用于此目的的方法,去除从注册表中供应商,使其不能用于ImageIO。
IIORegistry
deregisterServiceProvider
ImageIO
另一种选择是为提供者定义明确的顺序。如果出于某种原因(第三方要求/插件间的依存关系等)需要以单一格式拥有多个提供程序,这将很有用。该IIORegistry有一个setOrdering用于此目的,即允许设置方法 成对 两个服务提供商的排序,使得ImageIO总是喜欢一个先于另一个。
setOrdering
下面的代码显示了以上两个选项:
// 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(); } } }
当然,您可以组合使用以上选项,以兼顾两个方面的优势(即,如果一个阅读器出现故障,则订单稳定且回退)。