5

I'm trying to create a Grails application that can display previews of TIFF files, and other images as well.

Background

The images are constructed from a SOAP service that gives me the bytes of the image. In a service method, I take the byte[], construct a ByteArrayInputStream from it, and then create a BufferedImage from that.

def inputStream = new ByteArrayInputStream(bytes)
BufferedImage originalImage = ImageIO.read(inputStream)
ImageIO.write(originalImage, 'png', response.outputStream)

For JPGs, I can easily stream the images to the browser this way as the src of an img tag. TIFFs, though, I'd need to convert the images into some other format (preferably JPG or PNG) to make them the src of an tag.

The Problem

I know that I need JAI in order to read the TIFF files. The jai_core.jar, jai_codec.jar files are in my classpath. In fact, because I'm on Mac OSX, they're installed automatically. However, when I run the grails application and it tries to construct a TIFF image from the bytes received from the SOAP service, I get this error:

| Error 2013-06-18 15:23:38,135 [http-bio-8080-exec-10] ERROR errors.GrailsExceptionResolver  - IllegalArgumentException occurred when processing request: [GET] /BDMPlugin/BDMPlugin/displayImageFromRef - parameters:
pageRef: 28:22072FBCA0A8889D9C041D76A588BCF4DCB40376A23B5FD5C301378C8E66EB9F4933A5DFCA46365F927D9E91B337B6E1E980FB4406644801
type: TIFF
im == null!. Stacktrace follows:
Message: im == null!
    Line | Method
->> 1457 | write                in javax.imageio.ImageIO
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|   1571 | write                in     ''
|     28 | writeImageToResponse in edu.missouristate.bdmplugin.ImageService
|     44 | bytesToPng           in     ''
|     39 | displayImageFromRef  in edu.missouristate.bdmplugin.BDMPluginController
|    895 | runTask              in java.util.concurrent.ThreadPoolExecutor$Worker
|    918 | run . . . . . . . .  in     ''
^    680 | run                  in java.lang.Thread

I tried the following script to figure out which image readers were installed:

IIORegistry reg = IIORegistry.getDefaultInstance();
Iterator spIt = reg.getServiceProviders(ImageReaderSpi.class, false);
spIt.each(){
println it.getVendorName() << " | " << it.getVersion() << " | "<< it.getDescription() ;
}

This outputs the following:

Sun Microsystems, Inc. | 1.0 | Standard BMP Image Reader
Sun Microsystems, Inc. | 1.0 | Standard GIF image reader
Sun Microsystems, Inc. | 1.0 | Standard WBMP Image Reader
Sun Microsystems, Inc. | 1.0 | Standard PNG image reader
Sun Microsystems, Inc. | 0.5 | Standard JPEG Image Reader

However, if I run that same Groovy script in the Groovy Console, I get this output:

Sun Microsystems, Inc. | 0.5 | Standard JPEG Image Reader
Sun Microsystems, Inc. | 1.0 | Standard BMP Image Reader
Sun Microsystems, Inc. | 1.0 | Standard WBMP Image Reader
Sun Microsystems, Inc. | 1.0 | Standard PNG image reader
Sun Microsystems, Inc. | 1.0 | Standard GIF image reader
Apple computer Inc. | 1.0 | Standard TIFF image reader

Same set of readers, but it also includes Apple's TIFF reader. Why is the GroovyConsole able to find it and not my Grails environment, even though they're both using the same JRE? Is there a way that I can manually add the TIFF reader via some import from import com.sun.media.jai or com.sun.media.imageio.plugins.tiff?

I tried adding a manual registration of the TIFFImageReaderSpi to my service method:

import com.sun.imageio.plugins.tiff.TIFFImageReaderSpi
...
IIORegistry reg = IIORegistry.getDefaultInstance()
reg.registerServiceProvider(new TIFFImageReaderSpi())

The originalImage variable still comes back null.

Community
  • 1
  • 1
jonnybot
  • 2,435
  • 1
  • 32
  • 56
  • I'm reasonably sure that (at least part of) the problem is that I need to register an ImageWriter that can handle TIFF files as well. However, there doesn't seem to be a class for that in the JAI classes I have. In searching around for it, it seems like JAI is somewhat defunct as a library. I'm open to alternatives for converting TIFFs into more web-friendly images if anyone's had success with that in Grails. – jonnybot Jun 18 '13 at 22:10
  • +1 for a well-formatted question. – dmahapatro Jun 19 '13 at 00:56
  • @jonnybot ImageIO uses a service lookup mechanism. There's usually no need to manually register `Spi`s, just place the appropriate JARs on classpath. Also, you don't need an ImageWriter that supports TIFF, as you just want to write JPEG and PNG. – Harald K Jun 19 '13 at 09:17
  • The reason for your exception is obviously because `originalImage` is `null`, meaning ImageIO couldn't read it (probably because of no reader, as you have discovered). I'm not *exactly* sure about the Apple TIFF reader, but it seems to be registered "magically" when you use certain Swing components or the Toolkit. – Harald K Jun 19 '13 at 09:29
  • @heraldK - Any idea which Swing components I should use? And when you say "the Toolkit..."? What Toolkit? You were correct about not needing to register the Reader. I discovered that my lib folder wasn't in my classpath! Though my local JRE stuff was, so I'm not sure why that didn't pick up the reader. Still, the TIFF Image Reader is definitely registered now... so why in the heck can't ImageIO read the InputStream that came from the byte array, that came from the SOAP Service, that accessed the imaging system that ate the rat, that ate the malt, that lay in the house that Jack built? – jonnybot Jun 19 '13 at 14:51
  • Okay, I lied. After removing the manual registering of the TIFFImageReaderSpi and restarting the Grails app, the TIFF Image Reader disappears from the list of registered Image Readers. – jonnybot Jun 19 '13 at 15:05
  • 1
    @jonnybot Leaving the rat and Jack out of it, the problem might be that the particular TIFFImageReader you are using can't read the TIFF file that came with the soap. Toolkit is java.awt.Toolkit, but unfortunately can't say what components make it appear (thus, "magic"). I wouldn't spend too much time on making the TIFFImageReaderSpi work. As I wrote, it isn't the JAI one. It's a thin wrapper around OS X's native TIFF support. – Harald K Jun 19 '13 at 15:08

3 Answers3

6

So there seem to have been a couple layers to the problem.

First, the import com.sun.imageio.plugins.tiff.TIFFImageReaderSpi statement was importing the Apple TIFF reader, which apparently just wasn't up to the job of reading my TIFF.

What I really needed was import com.sun.media.imageioimpl.plugins.tiff.TIFFImageReaderSpi, but that presented me with a couple of different errors; don't worry, I was able to fix them. :)

First, the import wasn't resolving. To get the com.sun.media.imageioimpl package, I got the source for a bundled JAI from https://github.com/stain/jai-imageio-core. I imported that into Eclipse, then built a JAR using Eclipse's Export tool. This I placed in my project's lib folder, but the import still wasn't resolving. I had to manually add that jar to my project's classpath, and then the import would resolve.

Second, when I ran the app, I'd get this error:

| Error 2013-06-19 11:15:27,665 [http-bio-8080-exec-3] ERROR errors.GrailsExceptionResolver  - IllegalArgumentException occurred when processing request: [GET] /pluginproject/Controller/action - parameters:
vendorName == null!. Stacktrace follows:
Message: vendorName == null!
   Line | Method
->>  59 | <init>              in javax.imageio.spi.IIOServiceProvider
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|   214 | <init>              in javax.imageio.spi.ImageReaderWriterSpi
|   192 | <init> . . . . . .  in javax.imageio.spi.ImageReaderSpi
|    88 | <init>              in com.sun.media.imageioimpl.plugins.tiff.TIFFImageReaderSpi
|    31 | bytesToPng . . . .  in edu.mystateu.pluginproject.ImageService

vendorName == null? Fortunately, I found this question/answer.

When creating the jar file for jai-imageio-core, I had to manually specify the location of the manifest file, rather than letting Eclipse generate a new blank one. The manifest file was located in /jai-imageio-core/src/main/resources/META-INF/MANIFEST.MF, and once I specified to use that one, the imported lib resolved and read my image.

In the end, the service method's code was perfectly fine. I just needed to actually get JAI imported into my project correctly. Thanks very much to @haraldK, whose feedback got me on the right track.

Community
  • 1
  • 1
jonnybot
  • 2,435
  • 1
  • 32
  • 56
4

I had the same problem and while one can manually register single plugins there is also a method to register all plugins which are available in the classpath which is probably a little bit cleaner:

ImageIO.scanForPlugins();
jo-
  • 234
  • 1
  • 9
3

One solution that could work, is to create a file

/META-INF/services/ImageReaderSpi

containing one line

com.sun.imageio.plugins.tiff.TIFFImageReaderSpi

and place it on the classpath.

This should make sure that the provider is properly registered.

However, be aware that the Apple provided TIFFImageReader is not the same as the one from JAI, even though the package/class names are the same.

If you want to use the JAI TIFF ImageReader, you will need the jai-imageio.jar. The project is currently, as you noted, left in limbo by Oracle.

If you don't like to use JAI for any reason, I've created a pure Java TIFF plugin for ImageIO, available on GitHub: Twelvemonkeys TIFF plugin. The plugin is available under a business-friendly open source licence (BSD).

Harald K
  • 26,314
  • 7
  • 65
  • 111
  • It would make my evil overlords extraordinarily happy if your TIFF plugin had a license associated with it. :) – jonnybot Jun 19 '13 at 14:52
  • 1
    @jonnybot Sounds reasonable.. :-) Just pushed a license.txt to the repo. Everything twelvemonkeys is under BSD-license though. – Harald K Jun 19 '13 at 15:00