簡體   English   中英

如何從ImageIO中排除特定的TIFF閱讀器?

[英]How to exclude specific TIFF reader from ImageIO?

堆:

  • 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總是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();
        }
    }
}

您當然可以將上面的選項結合起來,以獲得兩全其美(例如,如果一個讀取器出現故障,則穩定順序和后退)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM