* @return RenderedImage if could derive one, else null
     */
    private static BufferedImage deriveBufferedImageFromTIFFBytes(
            InputStream in, Library library, int compressedBytes, int width, int height) {
        BufferedImage img = null;
        try {
            library.memoryManager.checkMemory(compressedBytes);

            /*
            com.sun.media.jai.codec.SeekableStream s = com.sun.media.jai.codec.SeekableStream.wrapInputStream( in, true );
            ParameterBlock pb = new ParameterBlock();
            pb.add( s );
            javax.media.jai.RenderedOp op = javax.media.jai.JAI.create( "tiff", pb );
            */
            Object com_sun_media_jai_codec_SeekableStream_s = ssWrapInputStream.invoke(null, new Object[]{in, Boolean.TRUE});
            ParameterBlock pb = new ParameterBlock();
            pb.add(com_sun_media_jai_codec_SeekableStream_s);
            Object javax_media_jai_RenderedOp_op = jaiCreate.invoke(null, new Object[]{"tiff", pb});

            /*
             * This was another approach:

             TIFFDecodeParam tiffDecodeParam = new TIFFDecodeParam();
             // tiffDecodeParam.setDecodePaletteAsShorts(true);

             ImageDecoder dec = ImageCodec.createImageDecoder("TIFF", s, tiffDecodeParam );

             NullOpImage op = new NullOpImage( dec.decodeAsRenderedImage(0), null, null, OpImage.OP_IO_BOUND );

             // RenderedImage img = dec.decodeAsRenderedImage();
             // RenderedImageAdapter ria = new RenderedImageAdapter(img);
             // BufferedImage bi = ria.getAsBufferedImage();

             */

            if (javax_media_jai_RenderedOp_op != null) {
                library.memoryManager.checkMemory(
                        Math.max(compressedBytes * 60, (int) (width * height * 0.15)));

                // This forces the image to decode, so we can see if that fails,
                //   and then potentially try a different compression setting
                /* op.getTile( 0, 0 ); */
                RenderedImage ri = (RenderedImage) javax_media_jai_RenderedOp_op;
                Raster r = ri.getTile(0, 0);

                // Calling op.getAsBufferedImage() causes a spike in memory usage
                // For example, for RenderedOp that's 100KB in size, we spike 18MB,
                //   with 1MB remaining and 17MB getting gc'ed
                // So, we try to build it piecemeal instead
                //System.out.println("Memory free: " + Runtime.getRuntime().freeMemory() + ", total:" + Runtime.getRuntime().totalMemory() + ", used: " + (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()));
                if (r instanceof WritableRaster) {
                    library.memoryManager.checkMemory(1024);

                    ColorModel cm = ri.getColorModel();
                    img = new BufferedImage(cm, (WritableRaster) r, false, null);
                } else {
                    library.memoryManager.checkMemory((int) (width * height * 2.5));

                    /* img = op.getAsBufferedImage(); */
                    img = (BufferedImage) roGetAsBufferedImage.invoke(javax_media_jai_RenderedOp_op, new Object[]{});
                }
            }
        }
        catch (Throwable e) {
            img = null;
            logger.log(Level.FINE,
                    "Could not derive image from data bytes via JAI.");
        }
        finally {
            try {
                in.close();
            } catch (IOException e) {
            }
        }
        return img;
    }