public static BufferedImage attemptDeriveBufferedImageFromBytes(
            Stream stream, Library library, Hashtable streamDictionary, Color fill) {
        if (!USE_JAI_IMAGE_LIBRARY)
            return null;

        boolean imageMask = stream.isImageMask();
        Vector decodeArray = (Vector) library.getObject(streamDictionary, "Decode");
        // get decode parameters from stream properties
        Hashtable decodeParmsDictionary = library.getDictionary(streamDictionary, "DecodeParms");
        Boolean blackIs1Obj = stream.getBlackIs1OrNull(library, decodeParmsDictionary);
        float k = library.getFloat(decodeParmsDictionary, "K");
//if( k < 0 ) return null; //TODO 4243: Temporarily disable Group4 JAI
        boolean hasHeader = false;

        InputStream input = stream.getInputStreamForDecodedStreamBytes();
        if (input == null)
            return null;
        input = new ZeroPaddedInputStream(input);
        BufferedInputStream bufferedInput = new BufferedInputStream(input, 1024);
        bufferedInput.mark(4);
        try {
            int hb1 = bufferedInput.read();
            int hb2 = bufferedInput.read();
            bufferedInput.reset();
            if (hb1 < 0 || hb2 < 0) {
                input.close();
                return null;
            }
            hasHeader = ((hb1 == 0x4d && hb2 == 0x4d) || (hb1 == 0x49 && hb2 == 0x49));
        }
        catch (IOException e) {
            try {
                input.close();
            } catch (IOException ioe) {
            }
            return null;
        }
        input = bufferedInput;

        BufferedImage img = null;

        byte[] fakeHeaderBytes = null;
        if (!hasHeader) {
            // Apparently if the stream dictionary contains all the necessary info about
            //   the TIFF data in the stream, then some encoders omit the standard
            //   TIFF header in the stream, which confuses some image decoders, like JAI,
            //   in which case we inject a TIFF header which is derived from the stream
            //   dictionary.
            fakeHeaderBytes = new byte[]{
                    // TIFF Header
                    0x4d, 0x4d,                                        // 00 : Big (sane) endian
                    0x00, 0x2a,                                        // 02 : Magic 42
                    0x00, 0x00, 0x00, 0x08,                            // 04 : Offset to first IFD

                    // First IFD
                    0x00, 0x0c,                                        // 08 : Num Directory Entries
                    // Directory Entries: ushort tag, ushort type, uint count, uint valueOrOffset
                    0x00, (byte) 0xfe, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,  // 0a : NewSubfileType
                    0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,         // 16 : ImageWidth
                    0x01, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,         // 22 : ImageLength
                    0x01, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,         // 2E : BitsPerSample
                    0x01, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,         // 3A : Compression
                    0x01, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,         // 46 : PhotometricInterpretation
                    0x01, 0x11, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, (byte) 0xAE,  // 52 : StripOffsets
                    0x01, 0x16, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,         // 5E : RowsPerStrip
                    0x01, 0x17, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,         // 6A : StripByteCounts
                    0x01, 0x1A, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, (byte) 0x9E,  // 76 : XResolution
                    0x01, 0x1B, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, (byte) 0xA6,  // 82 : YResolution
                    0x01, 0x28, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,         // 8E : ResolutionUnit
                    0x00, 0x00, 0x00, 0x00,                            // 9A : Next IFD
                    // Values from IFD, which don't fit in value field
                    0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,   // 9E : XResolution RATIONAL value
                    0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01}; // A6 : YResolution RATIONAL value
            // AE : Begin data

            // Have to fill in values for: ImageWidth, ImageLength, BitsPerSample, Compression,
            //   PhotometricIntrerpretation, RowsPerStrip, StripByteCounts

            boolean pdfStatesBlackAndWhite = false;
            boolean blackIs1 = false;  // Default value is false
            if (blackIs1Obj != null) {
                blackIs1 = blackIs1Obj.booleanValue();
                pdfStatesBlackAndWhite = true;
            }
            int width = library.getInt(streamDictionary, "Width");
            int height = library.getInt(streamDictionary, "Height");

            Object columnsObj = library.getObject(decodeParmsDictionary, "Columns");
            if (columnsObj != null && columnsObj instanceof Number) {
                int columns = ((Number) columnsObj).intValue();
                if (columns > width)
                    width = columns;
            }

            Utils.setIntIntoByteArrayBE(width, fakeHeaderBytes, 0x1E);       // ImageWidth
            Utils.setIntIntoByteArrayBE(height, fakeHeaderBytes, 0x2A);      // ImageLength
            Object bitsPerComponent =                                          // BitsPerSample
                    library.getObject(streamDictionary, "BitsPerComponent");
            if (bitsPerComponent != null && bitsPerComponent instanceof Number) {
                Utils.setShortIntoByteArrayBE(((Number) bitsPerComponent).shortValue(), fakeHeaderBytes, 0x36);
            }
            short compression = TIFF_COMPRESSION_NONE_default;
            if (k < 0) compression = TIFF_COMPRESSION_GROUP4;
            else if (k > 0) compression = TIFF_COMPRESSION_GROUP3_2D;
            else if (k == 0) compression = TIFF_COMPRESSION_GROUP3_1D;
            Utils.setShortIntoByteArrayBE(compression, fakeHeaderBytes, 0x42);
            short photometricInterpretation = TIFF_PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO_default;
            // PDF has default BlackIs1=false               ==> White=1, Black=0
            // TIFF has default PhotometricInterpretation=0 ==> White=0, Black=1
            // So, if PDF doesn't state what black and white are, then use TIFF's default
            if (pdfStatesBlackAndWhite) {
                if (!blackIs1)
                    photometricInterpretation = TIFF_PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO;
            }
            Utils.setShortIntoByteArrayBE(                                     // PhotometricInterpretation
                    photometricInterpretation, fakeHeaderBytes, 0x4E);
            Utils.setIntIntoByteArrayBE(height, fakeHeaderBytes, 0x66);      // RowsPerStrip
            int lengthOfCompressedData = Integer.MAX_VALUE - 1;                // StripByteCounts
            Object lengthValue = library.getObject(streamDictionary, "Length");
            if (lengthValue != null && lengthValue instanceof Number)
                lengthOfCompressedData = ((Number) lengthValue).intValue();
            else {
                // JAI's SeekableStream pukes if we give a number too large
                int approxLen = width * height;
                if (approxLen > 0)
                    lengthOfCompressedData = approxLen;
            }
            Utils.setIntIntoByteArrayBE(lengthOfCompressedData, fakeHeaderBytes, 0x72);

            ByteArrayInputStream fakeHeaderBytesIn = new ByteArrayInputStream(fakeHeaderBytes);
            org.icepdf.core.io.SequenceInputStream sin = new org.icepdf.core.io.SequenceInputStream(fakeHeaderBytesIn, input);

            img = deriveBufferedImageFromTIFFBytes(sin, library, lengthOfCompressedData, width, height);
            if (img == null) {
                for (int i = 1; i <= 2; i++) { // Try the three other types of compression (1, 2, 3, 4)
                    compression++;
                    // We don't try the default uncompressed format, because it sometimes
                    //  returns a blank image, which we don't want.  If JAI fails, we
                    //  want it to return null, so that the fallback code can have a try
                    if (compression > TIFF_COMPRESSION_GROUP4)
                        compression = TIFF_COMPRESSION_GROUP3_1D;

                    Utils.setShortIntoByteArrayBE(compression, fakeHeaderBytes, 0x42);
                    input = stream.getInputStreamForDecodedStreamBytes();
                    if (input == null)
                        return null;
                    input = new ZeroPaddedInputStream(input);
                    fakeHeaderBytesIn = new ByteArrayInputStream(fakeHeaderBytes);
                    sin = new org.icepdf.core.io.SequenceInputStream(fakeHeaderBytesIn, input);
                    img = deriveBufferedImageFromTIFFBytes(sin, library, lengthOfCompressedData, width, height);
                    if (img != null) {
                        if (Tagger.tagging)
                            Tagger.tagImage("CCITTFaxDecode_JAI_TIFF_COMPRESSION=" + TIFF_COMPRESSION_NAMES[compression]);
                        break;
                    }
                }
            }
        } else {
            int width = library.getInt(streamDictionary, "Width");
            int height = library.getInt(streamDictionary, "Height");
            int approxLen = width * height;
            img = deriveBufferedImageFromTIFFBytes(input, library, approxLen, width, height);
        }

        if (img != null) {
            if (Tagger.tagging) {
                Tagger.tagImage("HandledBy=CCITTFaxDecode_JAI");
                Tagger.tagImage("CCITTFaxDecode_DecodeParms_BlackIs1=" + blackIs1Obj);
                Tagger.tagImage("CCITTFaxDecode_DecodeParms_K=" + k);
                Tagger.tagImage("CCITTFaxDecode_hasHeader=" + hasHeader);
            }
            img = applyImageMaskAndDecodeArray(img, imageMask, blackIs1Obj, decodeArray, fill);
        }

        return img;
    }