如何从 ImageIO 中排除特定的 TIFF 阅读器? [英] How to exclude specific TIFF reader from ImageIO?

查看:36
本文介绍了如何从 ImageIO 中排除特定的 TIFF 阅读器?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

堆栈:

  • Java - 1.8.0_91
  • Scala - 2.11.8
  • 库 - it.geosolutions.imageio-ext imageio-ext-tiff 1.1.15

我们正在读取大量旧的 TIF 图像,并且由于某种原因读取高度不一致 - 由于某些原因,在不同的运行中读取相同的图像可能成功或失败 -

javax.imageio.IIOException:SOS 中的组件 ID 3 无效在 com.sun.imageio.plugins.jpeg.JPEGImageReader.readImage(本机方法)在 com.sun.imageio.plugins.jpeg.JPEGImageReader.readInternal(JPEGImageReader.java:1236)在 com.sun.imageio.plugins.jpeg.JPEGImageReader.read(JPEGImageReader.java:1039)在 com.sun.media.imageioimpl.plugins.tiff.TIFFOldJPEGDecompressor.decodeRaw(TIFFOldJPEGDecompressor.java:654)在 com.sun.media.imageio.plugins.tiff.TIFFDecompressor.decode(TIFFDecompressor.java:2527)在 com.sun.media.imageioimpl.plugins.tiff.TIFFImageReader.decodeTile(TIFFImageReader.java:1137)在 com.sun.media.imageioimpl.plugins.tiff.TIFFImageReader.read(TIFFImageReader.java:1417)

代码是这样的:

import java.io.{ByteArrayInputStream, ByteArrayOutputStream}导入 javax.imageio.ImageIOdef convertToPng(data: Array[Byte]): Array[Byte] = {val inputStream = new ByteArrayInputStream(data)val 图像 = ImageIO.read(inputStream)val outputStream = new ByteArrayOutputStream(inputStream.available())ImageIO.write(image, "png", outputStream)outputStream.toByteArray}

问题是ImageIO同时初始化了2个TIFF reader

 com.sun.media.imageioimpl.plugins.tiff.TIFFImageReader &it.geosolutions.imageioimpl.plugins.tiff.TIFFImageReader

 it.geosolutions.imageioimpl.plugins.tiff.TIFFImageReadercom.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总是喜欢一个在另一个之前.

以下代码显示了上述两个选项:

//获取全局注册表IIORegistry 注册表 = IIORegistry.getDefaultInstance();//查找已知的 TIFF 提供者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) {//如果两者都找到,要么//在 com.sun (JAI) 提供程序之前订购 it.geosolutions 提供程序registry.setOrdering(ImageReaderSpi.class,geoProvider,jaiProvider);//或者//取消注册 JAI 提供者registry.deregisterServiceProvider(jaiProvider);}

<小时>

//新的和改进的(较短的)版本.:-)私有静态<T>T lookupProviderByName(final ServiceRegistry registry, final String providerClassName) {尝试 {return (T) registry.getServiceProviderByClass(Class.forName(providerClassName));}捕获(ClassNotFoundException 忽略){返回空;}}

以上代码将确保 ImageIO.read(...) 始终使用 Geosolutions TIFF 插件,并且您现有的代码应该可以正常工作(但现在是稳定的).

一个完全不同的选择,是尝试使用所有已注册的 TIFF 插件读取数据,并使用第一个成功的插件.这比前面的代码更明确,但需要重写图像读取代码:

byte[] 数据;BufferedImage 图像;尝试 (ImageInputStream inputStream = ImageIO.createImageInputStream(new ByteArrayInputStream(data))) {迭代器读者 = ImageIO.getImageReaders(inputStream);//尝试读取数据,使用每个读取器直到我们成功(或者没有更多的读取器)而 (readers.hasNext()) {ImageReader reader = reader.next();尝试 {reader.setInput(inputStream);图像 = reader.read(0);休息;//图像现在已正确解码}捕获(异常 e){//TODO: 记录异常?e.printStackTrace();//读取失败,尝试下一个ReaderinputStream.seek(0);}最后 {阅读器处理();}}}

您当然可以结合上述选项,以实现两全其美(即稳定的顺序和在一位读者失败时的回退).

Stack:

  • Java - 1.8.0_91
  • Scala - 2.11.8
  • Library - it.geosolutions.imageio-ext imageio-ext-tiff 1.1.15

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)

The code is something like this:

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
}

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 

The first one fails, the second one works. How to exclude com.sun.media.imageioimpl.plugins.tiff.TIFFImageReader from ImageIO configuration?

解决方案

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.

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 ;-)).

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.

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;
    }
}

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).

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 阅读器?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆