/*
 * $RCSfile: ImageUtil.java,v $
 *
 * 
 * Copyright (c) 2005 Sun Microsystems, Inc. All  Rights Reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met: 
 * 
 * - Redistribution of source code must retain the above copyright 
 *   notice, this  list of conditions and the following disclaimer.
 * 
 * - Redistribution in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in 
 *   the documentation and/or other materials provided with the
 *   distribution.
 * 
 * Neither the name of Sun Microsystems, Inc. or the names of 
 * contributors may be used to endorse or promote products derived 
 * from this software without specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any 
 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND 
 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
 * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL 
 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF 
 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR 
 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
 * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGES. 
 * 
 * You acknowledge that this software is not designed or intended for 
 * use in the design, construction, operation or maintenance of any 
 * nuclear facility. 
 *
 * $Revision: 1.7 $
 * $Date: 2007-08-28 18:45:06 $
 * $State: Exp $
 */
package com.sun.media.imageioimpl.common;

import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.ComponentSampleModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.awt.image.DataBufferShort;
import java.awt.image.DataBufferUShort;
import java.awt.image.DirectColorModel;
import java.awt.image.IndexColorModel;
import java.awt.image.MultiPixelPackedSampleModel;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.awt.image.SinglePixelPackedSampleModel;
import java.awt.image.WritableRaster;
import java.io.IOException;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;

//import javax.imageio.ImageTypeSpecifier;

import javax.imageio.IIOException;
import javax.imageio.IIOImage;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriter;
import javax.imageio.spi.IIORegistry;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.spi.ImageReaderWriterSpi;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.spi.ServiceRegistry;
import javax.imageio.stream.ImageInputStream;

public class ImageUtil {
    /* XXX testing only
    public static void main(String[] args) {
        ImageTypeSpecifier bilevel =
            ImageTypeSpecifier.createIndexed(new byte[] {(byte)0, (byte)255},
                                             new byte[] {(byte)0, (byte)255},
                                             new byte[] {(byte)0, (byte)255},
                                             null, 1,
                                             DataBuffer.TYPE_BYTE);
        ImageTypeSpecifier gray =
            ImageTypeSpecifier.createGrayscale(8, DataBuffer.TYPE_BYTE, false);
        ImageTypeSpecifier grayAlpha =
            ImageTypeSpecifier.createGrayscale(8, DataBuffer.TYPE_BYTE, false,
                                               false);
        ImageTypeSpecifier rgb =
            ImageTypeSpecifier.createInterleaved(ColorSpace.getInstance(ColorSpace.CS_sRGB),
                                                 new int[] {0, 1, 2},
                                                 DataBuffer.TYPE_BYTE,
                                                 false,
                                                 false);
        ImageTypeSpecifier rgba =
            ImageTypeSpecifier.createInterleaved(ColorSpace.getInstance(ColorSpace.CS_sRGB),
                                                 new int[] {0, 1, 2, 3},
                                                 DataBuffer.TYPE_BYTE,
                                                 true,
                                                 false);
        ImageTypeSpecifier packed =
            ImageTypeSpecifier.createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB),
                                            0xff000000,
                                            0x00ff0000,
                                            0x0000ff00,
                                            0x000000ff,
                                            DataBuffer.TYPE_BYTE,
                                            false);

        SampleModel bandedSM =
            new java.awt.image.BandedSampleModel(DataBuffer.TYPE_BYTE,
                                                 1, 1, 15);

        System.out.println(createColorModel(bilevel.getSampleModel()));
        System.out.println(createColorModel(gray.getSampleModel()));
        System.out.println(createColorModel(grayAlpha.getSampleModel()));
        System.out.println(createColorModel(rgb.getSampleModel()));
        System.out.println(createColorModel(rgba.getSampleModel()));
        System.out.println(createColorModel(packed.getSampleModel()));
        System.out.println(createColorModel(bandedSM));
    }
    */

    /**
     * Creates a <code>ColorModel</code> that may be used with the
     * specified <code>SampleModel</code>.  If a suitable
     * <code>ColorModel</code> cannot be found, this method returns
     * <code>null</code>.
     *
     * <p> Suitable <code>ColorModel</code>s are guaranteed to exist
     * for all instances of <code>ComponentSampleModel</code>.
     * For 1- and 3- banded <code>SampleModel</code>s, the returned
     * <code>ColorModel</code> will be opaque.  For 2- and 4-banded
     * <code>SampleModel</code>s, the output will use alpha transparency
     * which is not premultiplied.  1- and 2-banded data will use a
     * grayscale <code>ColorSpace</code>, and 3- and 4-banded data a sRGB
     * <code>ColorSpace</code>. Data with 5 or more bands will have a
     * <code>BogusColorSpace</code>.</p>
     *
     * <p>An instance of <code>DirectColorModel</code> will be created for
     * instances of <code>SinglePixelPackedSampleModel</code> with no more
     * than 4 bands.</p>
     *
     * <p>An instance of <code>IndexColorModel</code> will be created for
     * instances of <code>MultiPixelPackedSampleModel</code>. The colormap
     * will be a grayscale ramp with <code>1&nbsp;<<&nbsp;numberOfBits</code>
     * entries ranging from zero to at most 255.</p>
     *
     * @return An instance of <code>ColorModel</code> that is suitable for
     *         the supplied <code>SampleModel</code>, or <code>null</code>.
     *
     * @throws IllegalArgumentException  If <code>sampleModel</code> is
     *         <code>null</code>.
     */
    public static final ColorModel createColorModel(SampleModel sampleModel) {
        // Check the parameter.
        if(sampleModel == null) {
            throw new IllegalArgumentException("sampleModel == null!");
        }

        // Get the data type.
        int dataType = sampleModel.getDataType();

        // Check the data type
        switch(dataType) {
        case DataBuffer.TYPE_BYTE:
        case DataBuffer.TYPE_USHORT:
        case DataBuffer.TYPE_SHORT:
        case DataBuffer.TYPE_INT:
        case DataBuffer.TYPE_FLOAT:
        case DataBuffer.TYPE_DOUBLE:
            break;
        default:
            // Return null for other types.
            return null;
        }

        // The return variable.
        ColorModel colorModel = null;

        // Get the sample size.
        int[] sampleSize = sampleModel.getSampleSize();

        // Create a Component ColorModel.
        if(sampleModel instanceof ComponentSampleModel) {
            // Get the number of bands.
            int numBands = sampleModel.getNumBands();

            // Determine the color space.
            ColorSpace colorSpace = null;
            if(numBands <= 2) {
                colorSpace = ColorSpace.getInstance(ColorSpace.CS_GRAY);
            } else if(numBands <= 4) {
                colorSpace = ColorSpace.getInstance(ColorSpace.CS_sRGB);
            } else {
                colorSpace = new BogusColorSpace(numBands);
            }

            boolean hasAlpha = (numBands == 2) || (numBands == 4);
            boolean isAlphaPremultiplied = false;
            int transparency = hasAlpha ?
                Transparency.TRANSLUCENT : Transparency.OPAQUE;

            colorModel = new ComponentColorModel(colorSpace,
                                                 sampleSize,
                                                 hasAlpha,
                                                 isAlphaPremultiplied,
                                                 transparency,
                                                 dataType);
        } else if (sampleModel.getNumBands() <= 4 &&
                   sampleModel instanceof SinglePixelPackedSampleModel) {
            SinglePixelPackedSampleModel sppsm =
                (SinglePixelPackedSampleModel)sampleModel;

            int[] bitMasks = sppsm.getBitMasks();
            int rmask = 0;
            int gmask = 0;
            int bmask = 0;
            int amask = 0;

            int numBands = bitMasks.length;
            if (numBands <= 2) {
                rmask = gmask = bmask = bitMasks[0];
                if (numBands == 2) {
                    amask = bitMasks[1];
                }
            } else {
                rmask = bitMasks[0];
                gmask = bitMasks[1];
                bmask = bitMasks[2];
                if (numBands == 4) {
                    amask = bitMasks[3];
                }
            }

            int bits = 0;
            for (int i = 0; i < sampleSize.length; i++) {
                bits += sampleSize[i];
            }

            return new DirectColorModel(bits, rmask, gmask, bmask, amask);

        } else if(sampleModel instanceof MultiPixelPackedSampleModel) {
            // Load the colormap with a ramp.
            int bitsPerSample = sampleSize[0];
            int numEntries = 1 << bitsPerSample;
            byte[] map = new byte[numEntries];
            for (int i = 0; i < numEntries; i++) {
                map[i] = (byte)(i*255/(numEntries - 1));
            }

            colorModel = new IndexColorModel(bitsPerSample, numEntries,
                                             map, map, map);

        }

        return colorModel;
    }

    /**
     * For the case of binary data (<code>isBinary()</code> returns
     * <code>true</code>), return the binary data as a packed byte array.
     * The data will be packed as eight bits per byte with no bit offset,
     * i.e., the first bit in each image line will be the left-most of the
     * first byte of the line.  The line stride in bytes will be
     * <code>(int)((getWidth()+7)/8)</code>.  The length of the returned
     * array will be the line stride multiplied by <code>getHeight()</code>
     *
     * @return the binary data as a packed array of bytes with zero offset
     * of <code>null</code> if the data are not binary.
     * @throws IllegalArgumentException if <code>isBinary()</code> returns
     * <code>false</code> with the <code>SampleModel</code> of the
     * supplied <code>Raster</code> as argument.
     */
    public static byte[] getPackedBinaryData(Raster raster,
                                             Rectangle rect) {
        SampleModel sm = raster.getSampleModel();
        if(!isBinary(sm)) {
            throw new IllegalArgumentException(I18N.getString("ImageUtil0"));
        }

        int rectX = rect.x;
        int rectY = rect.y;
        int rectWidth = rect.width;
        int rectHeight = rect.height;

        DataBuffer dataBuffer = raster.getDataBuffer();

        int dx = rectX - raster.getSampleModelTranslateX();
        int dy = rectY - raster.getSampleModelTranslateY();

        MultiPixelPackedSampleModel mpp = (MultiPixelPackedSampleModel)sm;
        int lineStride = mpp.getScanlineStride();
        int eltOffset = dataBuffer.getOffset() + mpp.getOffset(dx, dy);
        int bitOffset = mpp.getBitOffset(dx);

        int numBytesPerRow = (rectWidth + 7)/8;
        if(dataBuffer instanceof DataBufferByte &&
           eltOffset == 0 && bitOffset == 0 &&
           numBytesPerRow == lineStride &&
           ((DataBufferByte)dataBuffer).getData().length ==
           numBytesPerRow*rectHeight) {
            return ((DataBufferByte)dataBuffer).getData();
        }

        byte[] binaryDataArray = new byte[numBytesPerRow*rectHeight];

        int b = 0;

        if(bitOffset == 0) {
            if(dataBuffer instanceof DataBufferByte) {
                byte[] data = ((DataBufferByte)dataBuffer).getData();
                int stride = numBytesPerRow;
                int offset = 0;
                for(int y = 0; y < rectHeight; y++) {
                    System.arraycopy(data, eltOffset,
                                     binaryDataArray, offset,
                                     stride);
                    offset += stride;
                    eltOffset += lineStride;
                }
            } else if(dataBuffer instanceof DataBufferShort ||
                      dataBuffer instanceof DataBufferUShort) {
                short[] data = dataBuffer instanceof DataBufferShort ?
                    ((DataBufferShort)dataBuffer).getData() :
                    ((DataBufferUShort)dataBuffer).getData();

                for(int y = 0; y < rectHeight; y++) {
                    int xRemaining = rectWidth;
                    int i = eltOffset;
                    while(xRemaining > 8) {
                        short datum = data[i++];
                        binaryDataArray[b++] = (byte)((datum >>> 8) & 0xFF);
                        binaryDataArray[b++] = (byte)(datum & 0xFF);
                        xRemaining -= 16;
                    }
                    if(xRemaining > 0) {
                        binaryDataArray[b++] = (byte)((data[i] >>> 8) & 0XFF);
                    }
                    eltOffset += lineStride;
                }
            } else if(dataBuffer instanceof DataBufferInt) {
                int[] data = ((DataBufferInt)dataBuffer).getData();

                for(int y = 0; y < rectHeight; y++) {
                    int xRemaining = rectWidth;
                    int i = eltOffset;
                    while(xRemaining > 24) {
                        int datum = data[i++];
                        binaryDataArray[b++] = (byte)((datum >>> 24) & 0xFF);
                        binaryDataArray[b++] = (byte)((datum >>> 16) & 0xFF);
                        binaryDataArray[b++] = (byte)((datum >>> 8) & 0xFF);
                        binaryDataArray[b++] = (byte)(datum & 0xFF);
                        xRemaining -= 32;
                    }
                    int shift = 24;
                    while(xRemaining > 0) {
                        binaryDataArray[b++] =
                            (byte)((data[i] >>> shift) & 0xFF);
                        shift -= 8;
                        xRemaining -= 8;
                    }
                    eltOffset += lineStride;
                }
            }
        } else { // bitOffset != 0
            if(dataBuffer instanceof DataBufferByte) {
                byte[] data = ((DataBufferByte)dataBuffer).getData();

                if((bitOffset & 7) == 0) {
                    int stride = numBytesPerRow;
                    int offset = 0;
                    for(int y = 0; y < rectHeight; y++) {
                        System.arraycopy(data, eltOffset,
                                         binaryDataArray, offset,
                                         stride);
                        offset += stride;
                        eltOffset += lineStride;
                    }
                } else { // bitOffset % 8 != 0
                    int leftShift = bitOffset & 7;
                    int rightShift = 8 - leftShift;
                    for(int y = 0; y < rectHeight; y++) {
                        int i = eltOffset;
                        int xRemaining = rectWidth;
                        while(xRemaining > 0) {
                            if(xRemaining > rightShift) {
                                binaryDataArray[b++] =
                                    (byte)(((data[i++]&0xFF) << leftShift) |
                                           ((data[i]&0xFF) >>> rightShift));
                            } else {
                                binaryDataArray[b++] =
                                    (byte)((data[i]&0xFF) << leftShift);
                            }
                            xRemaining -= 8;
                        }
                        eltOffset += lineStride;
                    }
                }
            } else if(dataBuffer instanceof DataBufferShort ||
                      dataBuffer instanceof DataBufferUShort) {
                short[] data = dataBuffer instanceof DataBufferShort ?
                    ((DataBufferShort)dataBuffer).getData() :
                    ((DataBufferUShort)dataBuffer).getData();

                for(int y = 0; y < rectHeight; y++) {
                    int bOffset = bitOffset;
                    for(int x = 0; x < rectWidth; x += 8, bOffset += 8) {
                        int i = eltOffset + bOffset/16;
                        int mod = bOffset % 16;
                        int left = data[i] & 0xFFFF;
                        if(mod <= 8) {
                            binaryDataArray[b++] = (byte)(left >>> (8 - mod));
                        } else {
                            int delta = mod - 8;
                            int right = data[i+1] & 0xFFFF;
                            binaryDataArray[b++] =
                                (byte)((left << delta) |
                                       (right >>> (16 - delta)));
                        }
                    }
                    eltOffset += lineStride;
                }
            } else if(dataBuffer instanceof DataBufferInt) {
                int[] data = ((DataBufferInt)dataBuffer).getData();

                for(int y = 0; y < rectHeight; y++) {
                    int bOffset = bitOffset;
                    for(int x = 0; x < rectWidth; x += 8, bOffset += 8) {
                        int i = eltOffset + bOffset/32;
                        int mod = bOffset % 32;
                        int left = data[i];
                        if(mod <= 24) {
                            binaryDataArray[b++] =
                                (byte)(left >>> (24 - mod));
                        } else {
                            int delta = mod - 24;
                            int right = data[i+1];
                            binaryDataArray[b++] =
                                (byte)((left << delta) |
                                       (right >>> (32 - delta)));
                        }
                    }
                    eltOffset += lineStride;
                }
            }
        }

        return binaryDataArray;
    }

    /**
     * Returns the binary data unpacked into an array of bytes.
     * The line stride will be the width of the <code>Raster</code>.
     *
     * @throws IllegalArgumentException if <code>isBinary()</code> returns
     * <code>false</code> with the <code>SampleModel</code> of the
     * supplied <code>Raster</code> as argument.
     */
    public static byte[] getUnpackedBinaryData(Raster raster,
                                               Rectangle rect) {
        SampleModel sm = raster.getSampleModel();
        if(!isBinary(sm)) {
            throw new IllegalArgumentException(I18N.getString("ImageUtil0"));
        }

        int rectX = rect.x;
        int rectY = rect.y;
        int rectWidth = rect.width;
        int rectHeight = rect.height;

        DataBuffer dataBuffer = raster.getDataBuffer();

        int dx = rectX - raster.getSampleModelTranslateX();
        int dy = rectY - raster.getSampleModelTranslateY();

        MultiPixelPackedSampleModel mpp = (MultiPixelPackedSampleModel)sm;
        int lineStride = mpp.getScanlineStride();
        int eltOffset = dataBuffer.getOffset() + mpp.getOffset(dx, dy);
        int bitOffset = mpp.getBitOffset(dx);

        byte[] bdata = new byte[rectWidth*rectHeight];
        int maxY = rectY + rectHeight;
        int maxX = rectX + rectWidth;
        int k = 0;

        if(dataBuffer instanceof DataBufferByte) {
            byte[] data = ((DataBufferByte)dataBuffer).getData();
            for(int y = rectY; y < maxY; y++) {
                int bOffset = eltOffset*8 + bitOffset;
                for(int x = rectX; x < maxX; x++) {
                    byte b = data[bOffset/8];
                    bdata[k++] =
                        (byte)((b >>> (7 - bOffset & 7)) & 0x0000001);
                    bOffset++;
                }
                eltOffset += lineStride;
            }
        } else if(dataBuffer instanceof DataBufferShort ||
                  dataBuffer instanceof DataBufferUShort) {
            short[] data = dataBuffer instanceof DataBufferShort ?
                ((DataBufferShort)dataBuffer).getData() :
                ((DataBufferUShort)dataBuffer).getData();
            for(int y = rectY; y < maxY; y++) {
                int bOffset = eltOffset*16 + bitOffset;
                for(int x = rectX; x < maxX; x++) {
                    short s = data[bOffset/16];
                    bdata[k++] =
                        (byte)((s >>> (15 - bOffset % 16)) &
                               0x0000001);
                    bOffset++;
                }
                eltOffset += lineStride;
            }
        } else if(dataBuffer instanceof DataBufferInt) {
            int[] data = ((DataBufferInt)dataBuffer).getData();
            for(int y = rectY; y < maxY; y++) {
                int bOffset = eltOffset*32 + bitOffset;
                for(int x = rectX; x < maxX; x++) {
                    int i = data[bOffset/32];
                    bdata[k++] =
                        (byte)((i >>> (31 - bOffset % 32)) &
                               0x0000001);
                    bOffset++;
                }
                eltOffset += lineStride;
            }
        }

        return bdata;
    }

    /**
     * Sets the supplied <code>Raster</code>'s data from an array
     * of packed binary data of the form returned by
     * <code>getPackedBinaryData()</code>.
     *
     * @throws IllegalArgumentException if <code>isBinary()</code> returns
     * <code>false</code> with the <code>SampleModel</code> of the
     * supplied <code>Raster</code> as argument.
     */
    public static void setPackedBinaryData(byte[] binaryDataArray,
                                           WritableRaster raster,
                                           Rectangle rect) {
        SampleModel sm = raster.getSampleModel();
        if(!isBinary(sm)) {
            throw new IllegalArgumentException(I18N.getString("ImageUtil0"));
        }

        int rectX = rect.x;
        int rectY = rect.y;
        int rectWidth = rect.width;
        int rectHeight = rect.height;

        DataBuffer dataBuffer = raster.getDataBuffer();

        int dx = rectX - raster.getSampleModelTranslateX();
        int dy = rectY - raster.getSampleModelTranslateY();

        MultiPixelPackedSampleModel mpp = (MultiPixelPackedSampleModel)sm;
        int lineStride = mpp.getScanlineStride();
        int eltOffset = dataBuffer.getOffset() + mpp.getOffset(dx, dy);
        int bitOffset = mpp.getBitOffset(dx);

        int b = 0;

        if(bitOffset == 0) {
            if(dataBuffer instanceof DataBufferByte) {
                byte[] data = ((DataBufferByte)dataBuffer).getData();
                if(data == binaryDataArray) {
                    // Optimal case: simply return.
                    return;
                }
                int stride = (rectWidth + 7)/8;
                int offset = 0;
                for(int y = 0; y < rectHeight; y++) {
                    System.arraycopy(binaryDataArray, offset,
                                     data, eltOffset,
                                     stride);
                    offset += stride;
                    eltOffset += lineStride;
                }
            } else if(dataBuffer instanceof DataBufferShort ||
                      dataBuffer instanceof DataBufferUShort) {
                short[] data = dataBuffer instanceof DataBufferShort ?
                    ((DataBufferShort)dataBuffer).getData() :
                    ((DataBufferUShort)dataBuffer).getData();

                for(int y = 0; y < rectHeight; y++) {
                    int xRemaining = rectWidth;
                    int i = eltOffset;
                    while(xRemaining > 8) {
                        data[i++] =
                            (short)(((binaryDataArray[b++] & 0xFF) << 8) |
                                    (binaryDataArray[b++] & 0xFF));
                        xRemaining -= 16;
                    }
                    if(xRemaining > 0) {
                        data[i++] =
                            (short)((binaryDataArray[b++] & 0xFF) << 8);
                    }
                    eltOffset += lineStride;
                }
            } else if(dataBuffer instanceof DataBufferInt) {
                int[] data = ((DataBufferInt)dataBuffer).getData();

                for(int y = 0; y < rectHeight; y++) {
                    int xRemaining = rectWidth;
                    int i = eltOffset;
                    while(xRemaining > 24) {
                        data[i++] =
                            (int)(((binaryDataArray[b++] & 0xFF) << 24) |
                                  ((binaryDataArray[b++] & 0xFF) << 16) |
                                  ((binaryDataArray[b++] & 0xFF) << 8) |
                                  (binaryDataArray[b++] & 0xFF));
                        xRemaining -= 32;
                    }
                    int shift = 24;
                    while(xRemaining > 0) {
                        data[i] |=
                            (int)((binaryDataArray[b++] & 0xFF) << shift);
                        shift -= 8;
                        xRemaining -= 8;
                    }
                    eltOffset += lineStride;
                }
            }
        } else { // bitOffset != 0
            int stride = (rectWidth + 7)/8;
            int offset = 0;
            if(dataBuffer instanceof DataBufferByte) {
                byte[] data = ((DataBufferByte)dataBuffer).getData();

                if((bitOffset & 7) == 0) {
                    for(int y = 0; y < rectHeight; y++) {
                        System.arraycopy(binaryDataArray, offset,
                                         data, eltOffset,
                                         stride);
                        offset += stride;
                        eltOffset += lineStride;
                    }
                } else { // bitOffset % 8 != 0
                    int rightShift = bitOffset & 7;
                    int leftShift = 8 - rightShift;
                    int leftShift8 = 8 + leftShift;
		    int mask = (byte)(255<<leftShift);
		    int mask1 = (byte)~mask;

                    for(int y = 0; y < rectHeight; y++) {
                        int i = eltOffset;
                        int xRemaining = rectWidth;
                        while(xRemaining > 0) {
                            byte datum = binaryDataArray[b++];

                            if (xRemaining > leftShift8) {
				// when all the bits in this BYTE will be set
				// into the data buffer.
                                data[i] = (byte)((data[i] & mask ) |
                                    ((datum&0xFF) >>> rightShift));
                                data[++i] = (byte)((datum & 0xFF) << leftShift);
                            } else if (xRemaining > leftShift) {
				// All the "leftShift" high bits will be set
				// into the data buffer.  But not all the
				// "rightShift" low bits will be set.
				data[i] = (byte)((data[i] & mask ) |
				    ((datum&0xFF) >>> rightShift));
				i++;
				data[i] =
				    (byte)((data[i] & mask1) | ((datum & 0xFF) << leftShift));
			    }
			    else {
				// Less than "leftShift" high bits will be set.
				int remainMask = (1 << leftShift - xRemaining) - 1;
                                data[i] =
                                    (byte)((data[i] & (mask | remainMask)) |
				    (datum&0xFF) >>> rightShift & ~remainMask);
                            }
                            xRemaining -= 8;
                        }
                        eltOffset += lineStride;
                    }
                }
            } else if(dataBuffer instanceof DataBufferShort ||
                      dataBuffer instanceof DataBufferUShort) {
                short[] data = dataBuffer instanceof DataBufferShort ?
                    ((DataBufferShort)dataBuffer).getData() :
                    ((DataBufferUShort)dataBuffer).getData();

		int rightShift = bitOffset & 7;
		int leftShift = 8 - rightShift;
                int leftShift16 = 16 + leftShift;
		int mask = (short)(~(255 << leftShift));
		int mask1 = (short)(65535 << leftShift);
		int mask2 = (short)~mask1;

                for(int y = 0; y < rectHeight; y++) {
                    int bOffset = bitOffset;
		    int xRemaining = rectWidth;
                    for(int x = 0; x < rectWidth;
			x += 8, bOffset += 8, xRemaining -= 8) {
                        int i = eltOffset + (bOffset >> 4);
                        int mod = bOffset & 15;
                        int datum = binaryDataArray[b++] & 0xFF;
                        if(mod <= 8) {
			    // This BYTE is set into one SHORT
			    if (xRemaining < 8) {
				// Mask the bits to be set.
				datum &= 255 << 8 - xRemaining;
			    }
                            data[i] = (short)((data[i] & mask) | (datum << leftShift));
                        } else if (xRemaining > leftShift16) {
			    // This BYTE will be set into two SHORTs
                            data[i] = (short)((data[i] & mask1) | ((datum >>> rightShift)&0xFFFF));
                            data[++i] =
                                (short)((datum << leftShift)&0xFFFF);
                        } else if (xRemaining > leftShift) {
			    // This BYTE will be set into two SHORTs;
			    // But not all the low bits will be set into SHORT
			    data[i] = (short)((data[i] & mask1) | ((datum >>> rightShift)&0xFFFF));
			    i++;
			    data[i] =
			        (short)((data[i] & mask2) | ((datum << leftShift)&0xFFFF));
			} else {
			    // Only some of the high bits will be set into
			    // SHORTs
			    int remainMask = (1 << leftShift - xRemaining) - 1;
			    data[i] = (short)((data[i] & (mask1 | remainMask)) |
				      ((datum >>> rightShift)&0xFFFF & ~remainMask));
			}
                    }
                    eltOffset += lineStride;
                }
            } else if(dataBuffer instanceof DataBufferInt) {
                int[] data = ((DataBufferInt)dataBuffer).getData();
                int rightShift = bitOffset & 7;
		int leftShift = 8 - rightShift;
		int leftShift32 = 32 + leftShift;
		int mask = 0xFFFFFFFF << leftShift;
		int mask1 = ~mask;

                for(int y = 0; y < rectHeight; y++) {
                    int bOffset = bitOffset;
		    int xRemaining = rectWidth;
                    for(int x = 0; x < rectWidth;
			x += 8, bOffset += 8, xRemaining -= 8) {
                        int i = eltOffset + (bOffset >> 5);
                        int mod = bOffset & 31;
                        int datum = binaryDataArray[b++] & 0xFF;
                        if(mod <= 24) {
			    // This BYTE is set into one INT
			    int shift = 24 - mod;
			    if (xRemaining < 8) {
				// Mask the bits to be set.
				datum &= 255 << 8 - xRemaining;
			    }
                            data[i] = (data[i] & (~(255 << shift))) | (datum << shift);
                        } else if (xRemaining > leftShift32) {
			    // All the bits of this BYTE will be set into two INTs
                            data[i] = (data[i] & mask) | (datum >>> rightShift);
                            data[++i] = datum << leftShift;
                        } else if (xRemaining > leftShift) {
			    // This BYTE will be set into two INTs;
			    // But not all the low bits will be set into INT
                            data[i] = (data[i] & mask) | (datum >>> rightShift);
			    i++;
                            data[i] = (data[i] & mask1) | (datum << leftShift);
                        } else {
			    // Only some of the high bits will be set into INT
			    int remainMask = (1 << leftShift - xRemaining) - 1;
			    data[i] = (data[i] & (mask | remainMask)) |
				      (datum >>> rightShift & ~remainMask);
			}
                    }
                    eltOffset += lineStride;
                }
            }
        }
    }

    /**
     * Copies data into the packed array of the <code>Raster</code>
     * from an array of unpacked data of the form returned by
     * <code>getUnpackedBinaryData()</code>.
     *
     * <p> If the data are binary, then the target bit will be set if
     * and only if the corresponding byte is non-zero.
     *
     * @throws IllegalArgumentException if <code>isBinary()</code> returns
     * <code>false</code> with the <code>SampleModel</code> of the
     * supplied <code>Raster</code> as argument.
     */
    public static void setUnpackedBinaryData(byte[] bdata,
                                             WritableRaster raster,
                                             Rectangle rect) {
        SampleModel sm = raster.getSampleModel();
        if(!isBinary(sm)) {
            throw new IllegalArgumentException(I18N.getString("ImageUtil0"));
        }

        int rectX = rect.x;
        int rectY = rect.y;
        int rectWidth = rect.width;
        int rectHeight = rect.height;

        DataBuffer dataBuffer = raster.getDataBuffer();

        int dx = rectX - raster.getSampleModelTranslateX();
        int dy = rectY - raster.getSampleModelTranslateY();

        MultiPixelPackedSampleModel mpp = (MultiPixelPackedSampleModel)sm;
        int lineStride = mpp.getScanlineStride();
        int eltOffset = dataBuffer.getOffset() + mpp.getOffset(dx, dy);
        int bitOffset = mpp.getBitOffset(dx);

        int k = 0;

        if(dataBuffer instanceof DataBufferByte) {
            byte[] data = ((DataBufferByte)dataBuffer).getData();
            for(int y = 0; y < rectHeight; y++) {
                int bOffset = eltOffset*8 + bitOffset;
                for(int x = 0; x < rectWidth; x++) {
                    if(bdata[k++] != (byte)0) {
                        data[bOffset/8] |=
                            (byte)(0x00000001 << (7 - bOffset & 7));
                    }
                    bOffset++;
                }
                eltOffset += lineStride;
            }
        } else if(dataBuffer instanceof DataBufferShort ||
                  dataBuffer instanceof DataBufferUShort) {
            short[] data = dataBuffer instanceof DataBufferShort ?
                ((DataBufferShort)dataBuffer).getData() :
                ((DataBufferUShort)dataBuffer).getData();
            for(int y = 0; y < rectHeight; y++) {
                int bOffset = eltOffset*16 + bitOffset;
                for(int x = 0; x < rectWidth; x++) {
                    if(bdata[k++] != (byte)0) {
                        data[bOffset/16] |=
                            (short)(0x00000001 <<
                                    (15 - bOffset % 16));
                    }
                    bOffset++;
                }
                eltOffset += lineStride;
            }
        } else if(dataBuffer instanceof DataBufferInt) {
            int[] data = ((DataBufferInt)dataBuffer).getData();
            for(int y = 0; y < rectHeight; y++) {
                int bOffset = eltOffset*32 + bitOffset;
                for(int x = 0; x < rectWidth; x++) {
                    if(bdata[k++] != (byte)0) {
                        data[bOffset/32] |=
                            (int)(0x00000001 <<
                                  (31 - bOffset % 32));
                    }
                    bOffset++;
                }
                eltOffset += lineStride;
            }
        }
    }

    public static boolean isBinary(SampleModel sm) {
        return sm instanceof MultiPixelPackedSampleModel &&
            ((MultiPixelPackedSampleModel)sm).getPixelBitStride() == 1 &&
            sm.getNumBands() == 1;
    }

    public static ColorModel createColorModel(ColorSpace colorSpace,
                                              SampleModel sampleModel) {
        ColorModel colorModel = null;

        if(sampleModel == null) {
            throw new IllegalArgumentException(I18N.getString("ImageUtil1"));
        }

        int numBands = sampleModel.getNumBands();
        if (numBands < 1 || numBands > 4) {
            return null;
        }

        int dataType = sampleModel.getDataType();
        if (sampleModel instanceof ComponentSampleModel) {
            if (dataType < DataBuffer.TYPE_BYTE ||
                //dataType == DataBuffer.TYPE_SHORT ||
                dataType > DataBuffer.TYPE_DOUBLE) {
                return null;
            }

            if (colorSpace == null)
                colorSpace =
                    numBands <= 2 ?
                    ColorSpace.getInstance(ColorSpace.CS_GRAY) :
                    ColorSpace.getInstance(ColorSpace.CS_sRGB);

            boolean useAlpha = (numBands == 2) || (numBands == 4);
            int transparency = useAlpha ?
                               Transparency.TRANSLUCENT : Transparency.OPAQUE;

            boolean premultiplied = false;

            int dataTypeSize = DataBuffer.getDataTypeSize(dataType);
            int[] bits = new int[numBands];
            for (int i = 0; i < numBands; i++) {
                bits[i] = dataTypeSize;
            }

            colorModel = new ComponentColorModel(colorSpace,
                                                 bits,
                                                 useAlpha,
                                                 premultiplied,
                                                 transparency,
                                                 dataType);
        } else if (sampleModel instanceof SinglePixelPackedSampleModel) {
            SinglePixelPackedSampleModel sppsm =
                (SinglePixelPackedSampleModel)sampleModel;

            int[] bitMasks = sppsm.getBitMasks();
            int rmask = 0;
            int gmask = 0;
            int bmask = 0;
            int amask = 0;

            numBands = bitMasks.length;
            if (numBands <= 2) {
                rmask = gmask = bmask = bitMasks[0];
                if (numBands == 2) {
                    amask = bitMasks[1];
                }
            } else {
                rmask = bitMasks[0];
                gmask = bitMasks[1];
                bmask = bitMasks[2];
                if (numBands == 4) {
                    amask = bitMasks[3];
                }
            }

            int[] sampleSize = sppsm.getSampleSize();
            int bits = 0;
            for (int i = 0; i < sampleSize.length; i++) {
                bits += sampleSize[i];
            }

            if (colorSpace == null)
                colorSpace = ColorSpace.getInstance(ColorSpace.CS_sRGB);

            colorModel =
                new DirectColorModel(colorSpace,
                                     bits, rmask, gmask, bmask, amask,
                                     false,
                                     sampleModel.getDataType());
        } else if (sampleModel instanceof MultiPixelPackedSampleModel) {
            int bits =
                ((MultiPixelPackedSampleModel)sampleModel).getPixelBitStride();
            int size = 1 << bits;
            byte[] comp = new byte[size];

            for (int i = 0; i < size; i++)
                comp[i] = (byte)(255 * i / (size - 1));

            colorModel = new IndexColorModel(bits, size, comp, comp, comp);
        }

        return colorModel;
    }

    public static int getElementSize(SampleModel sm) {
        int elementSize = DataBuffer.getDataTypeSize(sm.getDataType());

        if (sm instanceof MultiPixelPackedSampleModel) {
            MultiPixelPackedSampleModel mppsm =
                (MultiPixelPackedSampleModel)sm;
            return mppsm.getSampleSize(0) * mppsm.getNumBands();
        } else if (sm instanceof ComponentSampleModel) {
            return sm.getNumBands() * elementSize;
        } else if (sm instanceof SinglePixelPackedSampleModel) {
            return elementSize;
        }

	return elementSize * sm.getNumBands();

    }

    public static long getTileSize(SampleModel sm) {
        int elementSize = DataBuffer.getDataTypeSize(sm.getDataType());

        if (sm instanceof MultiPixelPackedSampleModel) {
            MultiPixelPackedSampleModel mppsm =
                (MultiPixelPackedSampleModel)sm;
            return (mppsm.getScanlineStride() * mppsm.getHeight() +
                   (mppsm.getDataBitOffset() + elementSize -1) / elementSize) *
                   ((elementSize + 7) / 8);
        } else if (sm instanceof ComponentSampleModel) {
            ComponentSampleModel csm = (ComponentSampleModel)sm;
            int[] bandOffsets = csm.getBandOffsets();
            int maxBandOff = bandOffsets[0];
            for (int i=1; i<bandOffsets.length; i++)
                maxBandOff = Math.max(maxBandOff, bandOffsets[i]);

            long size = 0;
            int pixelStride = csm.getPixelStride();
            int scanlineStride = csm.getScanlineStride();
            if (maxBandOff >= 0)
                size += maxBandOff + 1;
            if (pixelStride > 0)
                size += pixelStride * (sm.getWidth() - 1);
            if (scanlineStride > 0)
                size += scanlineStride * (sm.getHeight() - 1);

            int[] bankIndices = csm.getBankIndices();
            maxBandOff = bankIndices[0];
            for (int i=1; i<bankIndices.length; i++)
                maxBandOff = Math.max(maxBandOff, bankIndices[i]);
            return size * (maxBandOff + 1) * ((elementSize + 7) / 8);
        } else if (sm instanceof SinglePixelPackedSampleModel) {
            SinglePixelPackedSampleModel sppsm =
                (SinglePixelPackedSampleModel)sm;
            long size = sppsm.getScanlineStride() * (sppsm.getHeight() - 1) +
                        sppsm.getWidth();
            return size * ((elementSize + 7) / 8);
        }

        return 0;
    }

    public static long getBandSize(SampleModel sm) {
        int elementSize = DataBuffer.getDataTypeSize(sm.getDataType());

        if (sm instanceof ComponentSampleModel) {
            ComponentSampleModel csm = (ComponentSampleModel)sm;
            int pixelStride = csm.getPixelStride();
            int scanlineStride = csm.getScanlineStride();
            long size = Math.min(pixelStride, scanlineStride);

            if (pixelStride > 0)
                size += pixelStride * (sm.getWidth() - 1);
            if (scanlineStride > 0)
                size += scanlineStride * (sm.getHeight() - 1);
            return size * ((elementSize + 7) / 8);
        } else
            return getTileSize(sm);
    }

    /**
     * Tests whether the color indices represent a gray-scale image with
     * the indicated number of bits over the color component range [0,255].
     * The grayscale mapping may be inverted, i.e., 0 -> 255 and
     * mapSize -> 0.
     *
     * @param icm The gray-to-color mapping.
     * @return Whether the <code>IndexColorModel</code> maps index
     *         <code>i</code> to <code>((255*i)/icm.getMapSize()-1)</code>.
     * @throws IllegalArgumentException if <code>icm</code> is
     *         <code>null</code>.
     */
    public static boolean isGrayscaleMapping(IndexColorModel icm) {
        if(icm == null) {
            throw new IllegalArgumentException("icm == null!");
        }

        // Get colormap size and contents.
        int mapSize = icm.getMapSize();

        byte[] r = new byte[mapSize];
        byte[] g = new byte[mapSize];
        byte[] b = new byte[mapSize];

        icm.getReds(r);
        icm.getGreens(g);
        icm.getBlues(b);

        boolean isGrayToColor = true;

        // Check ascending ramp.
        for (int i = 0; i < mapSize; i++) {
            byte temp = (byte)(i*255/(mapSize - 1));

            if (r[i] != temp || g[i] != temp || b[i] != temp) {
                isGrayToColor = false;
                break;
            }
        }

        if(!isGrayToColor) {
            isGrayToColor = true;

            // Check descending ramp.
            for (int i = 0, j = mapSize - 1; i < mapSize; i++, j--) {
                byte temp = (byte)(j*255/(mapSize - 1));

                if (r[i] != temp || g[i] != temp || b[i] != temp) {
                    isGrayToColor = false;
                    break;
                }
            }
        }

        return isGrayToColor;
    }

    /**
     * Tests whether the color indices represent a gray-scale image.
     *
     * @param r The red channel color indices.
     * @param g The green channel color indices.
     * @param b The blue channel color indices.
     * @return If all the indices have 256 entries, and are identical mappings,
     *         return <code>true</code>; otherwise, return <code>false</code>.
     */
    public static boolean isIndicesForGrayscale(byte[] r, byte[] g, byte[] b) {
        if (r.length != g.length || r.length != b.length)
            return false;

        int size = r.length;

        if (size != 256)
            return false;

        for (int i = 0; i < size; i++) {
            byte temp = (byte) i;

            if (r[i] != temp || g[i] != temp || b[i] != temp)
                return false;
        }

        return true;
    }

    /** Converts the provided object to <code>String</code> */
    public static String convertObjectToString(Object obj) {
        if (obj == null)
            return "";

        String s = "";
        if (obj instanceof byte[]) {
            byte[] bArray = (byte[])obj;
            for (int i = 0; i < bArray.length; i++)
                s += bArray[i] + " ";
            return s;
        }

        if (obj instanceof int[]) {
            int[] iArray = (int[])obj;
            for (int i = 0; i < iArray.length; i++)
                s += iArray[i] + " " ;
            return s;
        }

        if (obj instanceof short[]) {
            short[] sArray = (short[])obj;
            for (int i = 0; i < sArray.length; i++)
                s += sArray[i] + " " ;
            return s;
        }

        return obj.toString();

    }
    
    /** Checks that the provided <code>ImageWriter</code> can encode
     * the provided <code>ImageTypeSpecifier</code> or not.  If not, an
     * <code>IIOException</code> will be thrown.
     * @param writer The provided <code>ImageWriter</code>.
     * @param type The image to be tested.
     * @throws IIOException If the writer cannot encoded the provided image.
     */
    public static final void canEncodeImage(ImageWriter writer,
                                            ImageTypeSpecifier type)
        throws IIOException {
	ImageWriterSpi spi = writer.getOriginatingProvider();

	if(type != null && spi != null && !spi.canEncodeImage(type))  {
	    throw new IIOException(I18N.getString("ImageUtil2")+" "+
                                   writer.getClass().getName());
	}
    }

    /** Checks that the provided <code>ImageWriter</code> can encode
     * the provided <code>ColorModel</code> and <code>SampleModel</code>.
     * If not, an <code>IIOException</code> will be thrown.
     * @param writer The provided <code>ImageWriter</code>.
     * @param colorModel The provided <code>ColorModel</code>.
     * @param sampleModel The provided <code>SampleModel</code>.
     * @throws IIOException If the writer cannot encoded the provided image.
     */
    public static final void canEncodeImage(ImageWriter writer, 
                                            ColorModel colorModel,
                                            SampleModel sampleModel)
        throws IIOException {
	ImageTypeSpecifier type = null;
	if (colorModel != null && sampleModel != null)
	    type = new ImageTypeSpecifier(colorModel, sampleModel);
	canEncodeImage(writer, type);
    }

    /**
     * Returns whether the image has contiguous data across rows.
     */
    public static final boolean imageIsContiguous(RenderedImage image) {
        SampleModel sm;
        if(image instanceof BufferedImage) {
            WritableRaster ras = ((BufferedImage)image).getRaster();
            sm = ras.getSampleModel();
        } else {
            sm = image.getSampleModel();
        }
        
        if (sm instanceof ComponentSampleModel) {
            // Ensure image rows samples are stored contiguously
            // in a single bank.
            ComponentSampleModel csm = (ComponentSampleModel)sm;

            if (csm.getPixelStride() != csm.getNumBands()) {
                return false;
            }

            int[] bandOffsets = csm.getBandOffsets();
            for (int i = 0; i < bandOffsets.length; i++) {
                if (bandOffsets[i] != i) {
                    return false;
                }
            }

            int[] bankIndices = csm.getBankIndices();
            for (int i = 0; i < bandOffsets.length; i++) {
                if (bankIndices[i] != 0) {
                    return false;
                }
            }

            return true;
        }

        // Otherwise true if and only if it's a bilevel image with
        // a MultiPixelPackedSampleModel, 1 bit per pixel, and 1 bit
        // pixel stride.
        return ImageUtil.isBinary(sm);
    }

    /**
     * Gets the destination image type.
     */
    // NOTE: Code shamelessly copied from ImageReader.getDestination().
    public static final ImageTypeSpecifier
        getDestinationType(ImageReadParam param,
                           Iterator imageTypes) throws IIOException {

        if (imageTypes == null || !imageTypes.hasNext()) {
            throw new IllegalArgumentException("imageTypes null or empty!");
        }

        ImageTypeSpecifier imageType = null;

        // If param is non-null, use it
        if (param != null) {
            imageType = param.getDestinationType();
        }

        // No info from param, use fallback image type
        if (imageType == null) {
            Object o = imageTypes.next();
            if (!(o instanceof ImageTypeSpecifier)) {
                throw new IllegalArgumentException
                    ("Non-ImageTypeSpecifier retrieved from imageTypes!");
            }
            imageType = (ImageTypeSpecifier)o;
        } else {
            boolean foundIt = false;
            while (imageTypes.hasNext()) {
                ImageTypeSpecifier type =
                    (ImageTypeSpecifier)imageTypes.next();
                if (type.equals(imageType)) {
                    foundIt = true;
                    break;
                }
            }

            if (!foundIt) {
                throw new IIOException
                    ("Destination type from ImageReadParam does not match!");
            }
        }

        return imageType;
    }

    /**
     * Returns <code>true</code> if the given <code>ColorSpace</code> object
     * is an instance of <code>ICC_ColorSpace</code> but is not one of the
     * standard <code>ColorSpace</code>s returned by
     * <code>ColorSpace.getInstance()</code>.
     *
     * @param cs The <code>ColorSpace</code> to test.
     */
    public static boolean isNonStandardICCColorSpace(ColorSpace cs) {
        boolean retval = false;

        try {
            // Check the standard ColorSpaces in decreasing order of
            // likelihood except check CS_PYCC last as in some JREs
            // PYCC.pf used not to be installed.
            retval =
                (cs instanceof ICC_ColorSpace) &&
                !(cs.isCS_sRGB() ||
                  cs.equals(ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB)) ||
                  cs.equals(ColorSpace.getInstance(ColorSpace.CS_GRAY)) ||
                  cs.equals(ColorSpace.getInstance(ColorSpace.CS_CIEXYZ)) ||
                  cs.equals(ColorSpace.getInstance(ColorSpace.CS_PYCC)));
        } catch(IllegalArgumentException e) {
            // PYCC.pf not installed: ignore it - 'retval' is still 'false'.
        }

        return retval;
    }

    // Method to return JDK core ImageReaderSPI/ImageWriterSPI for a 
    // given formatName.
    public static List getJDKImageReaderWriterSPI(ServiceRegistry registry, 
						  String formatName,
						  boolean isReader) {

	IIORegistry iioRegistry = (IIORegistry)registry;

	Class spiClass;
	String descPart;
	if (isReader) {
	    spiClass = ImageReaderSpi.class;
	    descPart = " image reader";
	} else {
	    spiClass = ImageWriterSpi.class;
	    descPart = " image writer";
	}

	Iterator iter = iioRegistry.getServiceProviders(spiClass, 
							true); // useOrdering

	String formatNames[];
	ImageReaderWriterSpi provider;
	String desc = "standard " + formatName + descPart;
	String jiioPath = "com.sun.media.imageioimpl";
	Locale locale = Locale.getDefault();
	ArrayList list = new ArrayList();
	while (iter.hasNext()) {
            provider = (ImageReaderWriterSpi)iter.next();

	    // Look for JDK core ImageWriterSpi's
	    if (provider.getVendorName().startsWith("Sun Microsystems") &&
		desc.equalsIgnoreCase(provider.getDescription(locale)) &&
		// not JAI Image I/O plugins
		!provider.getPluginClassName().startsWith(jiioPath)) {

		// Get the formatNames supported by this Spi
		formatNames = provider.getFormatNames();
		for (int i=0; i<formatNames.length; i++) {
		    if (formatNames[i].equalsIgnoreCase(formatName)) {
			// Must be a JDK provided ImageReader/ImageWriter
			list.add(provider);
			break;
		    }
		}
	    }
	}
	
	return list;
    }

    public static void processOnRegistration(ServiceRegistry registry,
					     Class category,
					     String formatName,
					     ImageReaderWriterSpi spi,
					     int deregisterJvmVersion, 
					     int priorityJvmVersion) {

	// Check which JVM we are running on
	String jvmVendor = System.getProperty("java.vendor");
	String jvmVersionString = 
	    System.getProperty("java.specification.version");
	int verIndex = jvmVersionString.indexOf("1.");
	// Skip the "1." part to get to the part of the version number that
	// actually changes from version to version
	// The assumption here is that "java.specification.version" is 
	// always of the format "x.y" and not "x.y.z" since that is what has
	// been practically observed in all JDKs to-date, an examination of
	// the code returning this property bears this out. However this does
	// not guarantee that the format won't change in the future, 
	// though that seems unlikely.
	jvmVersionString = jvmVersionString.substring(verIndex+2);
	
	int jvmVersion = Integer.parseInt(jvmVersionString);
	
	if (jvmVendor.equals("Sun Microsystems Inc.")) {
	    
	    List list;
	    if (spi instanceof ImageReaderSpi)
		list = getJDKImageReaderWriterSPI(registry, formatName, true);
	    else 
		list = getJDKImageReaderWriterSPI(registry, formatName, false);
	    
	    if (jvmVersion >= deregisterJvmVersion && list.size() != 0) {
		// De-register JIIO's plug-in
		registry.deregisterServiceProvider(spi, category);
	    } else {
		for (int i=0; i<list.size(); i++) {
		    if (jvmVersion >= priorityJvmVersion) {
			// Set JIIO plug-in to lower priority
			registry.setOrdering(category, 
					     list.get(i),
					     spi);
		    } else {
			// Set JIIO plug-in to higher priority
			registry.setOrdering(category,
					     spi,
					     list.get(i));
		    }
		}
	    }
	}
    }
    
    public static int readMultiByteInteger(ImageInputStream iis) throws IOException {
        int value = iis.readByte();
        int result = value & 0x7f;
        while((value & 0x80) == 0x80) {
            result <<= 7;
            value = iis.readByte();
            result |= (value & 0x7f);
        }
        return result;
    }
}
