001/*
002 * $RCSfile: ImageUtil.java,v $
003 *
004 * 
005 * Copyright (c) 2005 Sun Microsystems, Inc. All  Rights Reserved.
006 * 
007 * Redistribution and use in source and binary forms, with or without
008 * modification, are permitted provided that the following conditions
009 * are met: 
010 * 
011 * - Redistribution of source code must retain the above copyright 
012 *   notice, this  list of conditions and the following disclaimer.
013 * 
014 * - Redistribution in binary form must reproduce the above copyright
015 *   notice, this list of conditions and the following disclaimer in 
016 *   the documentation and/or other materials provided with the
017 *   distribution.
018 * 
019 * Neither the name of Sun Microsystems, Inc. or the names of 
020 * contributors may be used to endorse or promote products derived 
021 * from this software without specific prior written permission.
022 * 
023 * This software is provided "AS IS," without a warranty of any 
024 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND 
025 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 
026 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
027 * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL 
028 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF 
029 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
030 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR 
031 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
032 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
033 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
034 * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
035 * POSSIBILITY OF SUCH DAMAGES. 
036 * 
037 * You acknowledge that this software is not designed or intended for 
038 * use in the design, construction, operation or maintenance of any 
039 * nuclear facility. 
040 *
041 * $Revision: 1.7 $
042 * $Date: 2007/08/28 18:45:06 $
043 * $State: Exp $
044 */
045package com.github.jaiimageio.impl.common;
046
047import java.awt.Rectangle;
048import java.awt.Transparency;
049import java.awt.color.ColorSpace;
050import java.awt.color.ICC_ColorSpace;
051import java.awt.image.BufferedImage;
052import java.awt.image.ColorModel;
053import java.awt.image.ComponentColorModel;
054import java.awt.image.ComponentSampleModel;
055import java.awt.image.DataBuffer;
056import java.awt.image.DataBufferByte;
057import java.awt.image.DataBufferInt;
058import java.awt.image.DataBufferShort;
059import java.awt.image.DataBufferUShort;
060import java.awt.image.DirectColorModel;
061import java.awt.image.IndexColorModel;
062import java.awt.image.MultiPixelPackedSampleModel;
063import java.awt.image.Raster;
064import java.awt.image.RenderedImage;
065import java.awt.image.SampleModel;
066import java.awt.image.SinglePixelPackedSampleModel;
067import java.awt.image.WritableRaster;
068import java.io.IOException;
069import java.util.ArrayList;
070import java.util.Iterator;
071import java.util.List;
072import java.util.Locale;
073import java.util.ServiceLoader;
074//import javax.imageio.ImageTypeSpecifier;
075import javax.imageio.stream.ImageInputStream;
076import javax.imageio.spi.ImageReaderWriterSpi;
077import javax.imageio.spi.ImageReaderSpi;
078import javax.imageio.spi.IIORegistry;
079import javax.imageio.spi.ServiceRegistry;
080import javax.imageio.ImageReadParam;
081import javax.imageio.spi.ImageWriterSpi;
082import javax.imageio.IIOException;
083import javax.imageio.ImageTypeSpecifier;
084import javax.imageio.ImageWriter;
085
086public class ImageUtil {
087    /* XXX testing only
088    public static void main(String[] args) {
089        ImageTypeSpecifier bilevel =
090            ImageTypeSpecifier.createIndexed(new byte[] {(byte)0, (byte)255},
091                                             new byte[] {(byte)0, (byte)255},
092                                             new byte[] {(byte)0, (byte)255},
093                                             null, 1,
094                                             DataBuffer.TYPE_BYTE);
095        ImageTypeSpecifier gray =
096            ImageTypeSpecifier.createGrayscale(8, DataBuffer.TYPE_BYTE, false);
097        ImageTypeSpecifier grayAlpha =
098            ImageTypeSpecifier.createGrayscale(8, DataBuffer.TYPE_BYTE, false,
099                                               false);
100        ImageTypeSpecifier rgb =
101            ImageTypeSpecifier.createInterleaved(ColorSpace.getInstance(ColorSpace.CS_sRGB),
102                                                 new int[] {0, 1, 2},
103                                                 DataBuffer.TYPE_BYTE,
104                                                 false,
105                                                 false);
106        ImageTypeSpecifier rgba =
107            ImageTypeSpecifier.createInterleaved(ColorSpace.getInstance(ColorSpace.CS_sRGB),
108                                                 new int[] {0, 1, 2, 3},
109                                                 DataBuffer.TYPE_BYTE,
110                                                 true,
111                                                 false);
112        ImageTypeSpecifier packed =
113            ImageTypeSpecifier.createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB),
114                                            0xff000000,
115                                            0x00ff0000,
116                                            0x0000ff00,
117                                            0x000000ff,
118                                            DataBuffer.TYPE_BYTE,
119                                            false);
120
121        SampleModel bandedSM =
122            new java.awt.image.BandedSampleModel(DataBuffer.TYPE_BYTE,
123                                                 1, 1, 15);
124
125        System.out.println(createColorModel(bilevel.getSampleModel()));
126        System.out.println(createColorModel(gray.getSampleModel()));
127        System.out.println(createColorModel(grayAlpha.getSampleModel()));
128        System.out.println(createColorModel(rgb.getSampleModel()));
129        System.out.println(createColorModel(rgba.getSampleModel()));
130        System.out.println(createColorModel(packed.getSampleModel()));
131        System.out.println(createColorModel(bandedSM));
132    }
133    */
134
135    /**
136     * Creates a <code>ColorModel</code> that may be used with the
137     * specified <code>SampleModel</code>.  If a suitable
138     * <code>ColorModel</code> cannot be found, this method returns
139     * <code>null</code>.
140     *
141     * <p> Suitable <code>ColorModel</code>s are guaranteed to exist
142     * for all instances of <code>ComponentSampleModel</code>.
143     * For 1- and 3- banded <code>SampleModel</code>s, the returned
144     * <code>ColorModel</code> will be opaque.  For 2- and 4-banded
145     * <code>SampleModel</code>s, the output will use alpha transparency
146     * which is not premultiplied.  1- and 2-banded data will use a
147     * grayscale <code>ColorSpace</code>, and 3- and 4-banded data a sRGB
148     * <code>ColorSpace</code>. Data with 5 or more bands will have a
149     * <code>BogusColorSpace</code>.</p>
150     *
151     * <p>An instance of <code>DirectColorModel</code> will be created for
152     * instances of <code>SinglePixelPackedSampleModel</code> with no more
153     * than 4 bands.</p>
154     *
155     * <p>An instance of <code>IndexColorModel</code> will be created for
156     * instances of <code>MultiPixelPackedSampleModel</code>. The colormap
157     * will be a grayscale ramp with <code>1&nbsp;<<&nbsp;numberOfBits</code>
158     * entries ranging from zero to at most 255.</p>
159     *
160     * @return An instance of <code>ColorModel</code> that is suitable for
161     *         the supplied <code>SampleModel</code>, or <code>null</code>.
162     *
163     * @throws IllegalArgumentException  If <code>sampleModel</code> is
164     *         <code>null</code>.
165     */
166    public static final ColorModel createColorModel(SampleModel sampleModel) {
167        // Check the parameter.
168        if(sampleModel == null) {
169            throw new IllegalArgumentException("sampleModel == null!");
170        }
171
172        // Get the data type.
173        int dataType = sampleModel.getDataType();
174
175        // Check the data type
176        switch(dataType) {
177        case DataBuffer.TYPE_BYTE:
178        case DataBuffer.TYPE_USHORT:
179        case DataBuffer.TYPE_SHORT:
180        case DataBuffer.TYPE_INT:
181        case DataBuffer.TYPE_FLOAT:
182        case DataBuffer.TYPE_DOUBLE:
183            break;
184        default:
185            // Return null for other types.
186            return null;
187        }
188
189        // The return variable.
190        ColorModel colorModel = null;
191
192        // Get the sample size.
193        int[] sampleSize = sampleModel.getSampleSize();
194
195        // Create a Component ColorModel.
196        if(sampleModel instanceof ComponentSampleModel) {
197            // Get the number of bands.
198            int numBands = sampleModel.getNumBands();
199
200            // Determine the color space.
201            ColorSpace colorSpace = null;
202            if(numBands <= 2) {
203                colorSpace = ColorSpace.getInstance(ColorSpace.CS_GRAY);
204            } else if(numBands <= 4) {
205                colorSpace = ColorSpace.getInstance(ColorSpace.CS_sRGB);
206            } else {
207                colorSpace = new BogusColorSpace(numBands);
208            }
209
210            boolean hasAlpha = (numBands == 2) || (numBands == 4);
211            boolean isAlphaPremultiplied = false;
212            int transparency = hasAlpha ?
213                Transparency.TRANSLUCENT : Transparency.OPAQUE;
214
215            colorModel = new ComponentColorModel(colorSpace,
216                                                 sampleSize,
217                                                 hasAlpha,
218                                                 isAlphaPremultiplied,
219                                                 transparency,
220                                                 dataType);
221        } else if (sampleModel.getNumBands() <= 4 &&
222                   sampleModel instanceof SinglePixelPackedSampleModel) {
223            SinglePixelPackedSampleModel sppsm =
224                (SinglePixelPackedSampleModel)sampleModel;
225
226            int[] bitMasks = sppsm.getBitMasks();
227            int rmask = 0;
228            int gmask = 0;
229            int bmask = 0;
230            int amask = 0;
231
232            int numBands = bitMasks.length;
233            if (numBands <= 2) {
234                rmask = gmask = bmask = bitMasks[0];
235                if (numBands == 2) {
236                    amask = bitMasks[1];
237                }
238            } else {
239                rmask = bitMasks[0];
240                gmask = bitMasks[1];
241                bmask = bitMasks[2];
242                if (numBands == 4) {
243                    amask = bitMasks[3];
244                }
245            }
246
247            int bits = 0;
248            for (int i = 0; i < sampleSize.length; i++) {
249                bits += sampleSize[i];
250            }
251
252            return new DirectColorModel(bits, rmask, gmask, bmask, amask);
253
254        } else if(sampleModel instanceof MultiPixelPackedSampleModel) {
255            // Load the colormap with a ramp.
256            int bitsPerSample = sampleSize[0];
257            int numEntries = 1 << bitsPerSample;
258            byte[] map = new byte[numEntries];
259            for (int i = 0; i < numEntries; i++) {
260                map[i] = (byte)(i*255/(numEntries - 1));
261            }
262
263            colorModel = new IndexColorModel(bitsPerSample, numEntries,
264                                             map, map, map);
265
266        }
267
268        return colorModel;
269    }
270
271    /**
272     * For the case of binary data (<code>isBinary()</code> returns
273     * <code>true</code>), return the binary data as a packed byte array.
274     * The data will be packed as eight bits per byte with no bit offset,
275     * i.e., the first bit in each image line will be the left-most of the
276     * first byte of the line.  The line stride in bytes will be
277     * <code>(int)((getWidth()+7)/8)</code>.  The length of the returned
278     * array will be the line stride multiplied by <code>getHeight()</code>
279     *
280     * @return the binary data as a packed array of bytes with zero offset
281     * of <code>null</code> if the data are not binary.
282     * @throws IllegalArgumentException if <code>isBinary()</code> returns
283     * <code>false</code> with the <code>SampleModel</code> of the
284     * supplied <code>Raster</code> as argument.
285     */
286    public static byte[] getPackedBinaryData(Raster raster,
287                                             Rectangle rect) {
288        SampleModel sm = raster.getSampleModel();
289        if(!isBinary(sm)) {
290            throw new IllegalArgumentException(I18N.getString("ImageUtil0"));
291        }
292
293        int rectX = rect.x;
294        int rectY = rect.y;
295        int rectWidth = rect.width;
296        int rectHeight = rect.height;
297
298        DataBuffer dataBuffer = raster.getDataBuffer();
299
300        int dx = rectX - raster.getSampleModelTranslateX();
301        int dy = rectY - raster.getSampleModelTranslateY();
302
303        MultiPixelPackedSampleModel mpp = (MultiPixelPackedSampleModel)sm;
304        int lineStride = mpp.getScanlineStride();
305        int eltOffset = dataBuffer.getOffset() + mpp.getOffset(dx, dy);
306        int bitOffset = mpp.getBitOffset(dx);
307
308        int numBytesPerRow = (rectWidth + 7)/8;
309        if(dataBuffer instanceof DataBufferByte &&
310           eltOffset == 0 && bitOffset == 0 &&
311           numBytesPerRow == lineStride &&
312           ((DataBufferByte)dataBuffer).getData().length ==
313           numBytesPerRow*rectHeight) {
314            return ((DataBufferByte)dataBuffer).getData();
315        }
316
317        byte[] binaryDataArray = new byte[numBytesPerRow*rectHeight];
318
319        int b = 0;
320
321        if(bitOffset == 0) {
322            if(dataBuffer instanceof DataBufferByte) {
323                byte[] data = ((DataBufferByte)dataBuffer).getData();
324                int stride = numBytesPerRow;
325                int offset = 0;
326                for(int y = 0; y < rectHeight; y++) {
327                    System.arraycopy(data, eltOffset,
328                                     binaryDataArray, offset,
329                                     stride);
330                    offset += stride;
331                    eltOffset += lineStride;
332                }
333            } else if(dataBuffer instanceof DataBufferShort ||
334                      dataBuffer instanceof DataBufferUShort) {
335                short[] data = dataBuffer instanceof DataBufferShort ?
336                    ((DataBufferShort)dataBuffer).getData() :
337                    ((DataBufferUShort)dataBuffer).getData();
338
339                for(int y = 0; y < rectHeight; y++) {
340                    int xRemaining = rectWidth;
341                    int i = eltOffset;
342                    while(xRemaining > 8) {
343                        short datum = data[i++];
344                        binaryDataArray[b++] = (byte)((datum >>> 8) & 0xFF);
345                        binaryDataArray[b++] = (byte)(datum & 0xFF);
346                        xRemaining -= 16;
347                    }
348                    if(xRemaining > 0) {
349                        binaryDataArray[b++] = (byte)((data[i] >>> 8) & 0XFF);
350                    }
351                    eltOffset += lineStride;
352                }
353            } else if(dataBuffer instanceof DataBufferInt) {
354                int[] data = ((DataBufferInt)dataBuffer).getData();
355
356                for(int y = 0; y < rectHeight; y++) {
357                    int xRemaining = rectWidth;
358                    int i = eltOffset;
359                    while(xRemaining > 24) {
360                        int datum = data[i++];
361                        binaryDataArray[b++] = (byte)((datum >>> 24) & 0xFF);
362                        binaryDataArray[b++] = (byte)((datum >>> 16) & 0xFF);
363                        binaryDataArray[b++] = (byte)((datum >>> 8) & 0xFF);
364                        binaryDataArray[b++] = (byte)(datum & 0xFF);
365                        xRemaining -= 32;
366                    }
367                    int shift = 24;
368                    while(xRemaining > 0) {
369                        binaryDataArray[b++] =
370                            (byte)((data[i] >>> shift) & 0xFF);
371                        shift -= 8;
372                        xRemaining -= 8;
373                    }
374                    eltOffset += lineStride;
375                }
376            }
377        } else { // bitOffset != 0
378            if(dataBuffer instanceof DataBufferByte) {
379                byte[] data = ((DataBufferByte)dataBuffer).getData();
380
381                if((bitOffset & 7) == 0) {
382                    int stride = numBytesPerRow;
383                    int offset = 0;
384                    for(int y = 0; y < rectHeight; y++) {
385                        System.arraycopy(data, eltOffset,
386                                         binaryDataArray, offset,
387                                         stride);
388                        offset += stride;
389                        eltOffset += lineStride;
390                    }
391                } else { // bitOffset % 8 != 0
392                    int leftShift = bitOffset & 7;
393                    int rightShift = 8 - leftShift;
394                    for(int y = 0; y < rectHeight; y++) {
395                        int i = eltOffset;
396                        int xRemaining = rectWidth;
397                        while(xRemaining > 0) {
398                            if(xRemaining > rightShift) {
399                                binaryDataArray[b++] =
400                                    (byte)(((data[i++]&0xFF) << leftShift) |
401                                           ((data[i]&0xFF) >>> rightShift));
402                            } else {
403                                binaryDataArray[b++] =
404                                    (byte)((data[i]&0xFF) << leftShift);
405                            }
406                            xRemaining -= 8;
407                        }
408                        eltOffset += lineStride;
409                    }
410                }
411            } else if(dataBuffer instanceof DataBufferShort ||
412                      dataBuffer instanceof DataBufferUShort) {
413                short[] data = dataBuffer instanceof DataBufferShort ?
414                    ((DataBufferShort)dataBuffer).getData() :
415                    ((DataBufferUShort)dataBuffer).getData();
416
417                for(int y = 0; y < rectHeight; y++) {
418                    int bOffset = bitOffset;
419                    for(int x = 0; x < rectWidth; x += 8, bOffset += 8) {
420                        int i = eltOffset + bOffset/16;
421                        int mod = bOffset % 16;
422                        int left = data[i] & 0xFFFF;
423                        if(mod <= 8) {
424                            binaryDataArray[b++] = (byte)(left >>> (8 - mod));
425                        } else {
426                            int delta = mod - 8;
427                            int right = data[i+1] & 0xFFFF;
428                            binaryDataArray[b++] =
429                                (byte)((left << delta) |
430                                       (right >>> (16 - delta)));
431                        }
432                    }
433                    eltOffset += lineStride;
434                }
435            } else if(dataBuffer instanceof DataBufferInt) {
436                int[] data = ((DataBufferInt)dataBuffer).getData();
437
438                for(int y = 0; y < rectHeight; y++) {
439                    int bOffset = bitOffset;
440                    for(int x = 0; x < rectWidth; x += 8, bOffset += 8) {
441                        int i = eltOffset + bOffset/32;
442                        int mod = bOffset % 32;
443                        int left = data[i];
444                        if(mod <= 24) {
445                            binaryDataArray[b++] =
446                                (byte)(left >>> (24 - mod));
447                        } else {
448                            int delta = mod - 24;
449                            int right = data[i+1];
450                            binaryDataArray[b++] =
451                                (byte)((left << delta) |
452                                       (right >>> (32 - delta)));
453                        }
454                    }
455                    eltOffset += lineStride;
456                }
457            }
458        }
459
460        return binaryDataArray;
461    }
462
463    /**
464     * Returns the binary data unpacked into an array of bytes.
465     * The line stride will be the width of the <code>Raster</code>.
466     *
467     * @throws IllegalArgumentException if <code>isBinary()</code> returns
468     * <code>false</code> with the <code>SampleModel</code> of the
469     * supplied <code>Raster</code> as argument.
470     */
471    public static byte[] getUnpackedBinaryData(Raster raster,
472                                               Rectangle rect) {
473        SampleModel sm = raster.getSampleModel();
474        if(!isBinary(sm)) {
475            throw new IllegalArgumentException(I18N.getString("ImageUtil0"));
476        }
477
478        int rectX = rect.x;
479        int rectY = rect.y;
480        int rectWidth = rect.width;
481        int rectHeight = rect.height;
482
483        DataBuffer dataBuffer = raster.getDataBuffer();
484
485        int dx = rectX - raster.getSampleModelTranslateX();
486        int dy = rectY - raster.getSampleModelTranslateY();
487
488        MultiPixelPackedSampleModel mpp = (MultiPixelPackedSampleModel)sm;
489        int lineStride = mpp.getScanlineStride();
490        int eltOffset = dataBuffer.getOffset() + mpp.getOffset(dx, dy);
491        int bitOffset = mpp.getBitOffset(dx);
492
493        byte[] bdata = new byte[rectWidth*rectHeight];
494        int maxY = rectY + rectHeight;
495        int maxX = rectX + rectWidth;
496        int k = 0;
497
498        if(dataBuffer instanceof DataBufferByte) {
499            byte[] data = ((DataBufferByte)dataBuffer).getData();
500            for(int y = rectY; y < maxY; y++) {
501                int bOffset = eltOffset*8 + bitOffset;
502                for(int x = rectX; x < maxX; x++) {
503                    byte b = data[bOffset/8];
504                    bdata[k++] =
505                        (byte)((b >>> (7 - bOffset & 7)) & 0x0000001);
506                    bOffset++;
507                }
508                eltOffset += lineStride;
509            }
510        } else if(dataBuffer instanceof DataBufferShort ||
511                  dataBuffer instanceof DataBufferUShort) {
512            short[] data = dataBuffer instanceof DataBufferShort ?
513                ((DataBufferShort)dataBuffer).getData() :
514                ((DataBufferUShort)dataBuffer).getData();
515            for(int y = rectY; y < maxY; y++) {
516                int bOffset = eltOffset*16 + bitOffset;
517                for(int x = rectX; x < maxX; x++) {
518                    short s = data[bOffset/16];
519                    bdata[k++] =
520                        (byte)((s >>> (15 - bOffset % 16)) &
521                               0x0000001);
522                    bOffset++;
523                }
524                eltOffset += lineStride;
525            }
526        } else if(dataBuffer instanceof DataBufferInt) {
527            int[] data = ((DataBufferInt)dataBuffer).getData();
528            for(int y = rectY; y < maxY; y++) {
529                int bOffset = eltOffset*32 + bitOffset;
530                for(int x = rectX; x < maxX; x++) {
531                    int i = data[bOffset/32];
532                    bdata[k++] =
533                        (byte)((i >>> (31 - bOffset % 32)) &
534                               0x0000001);
535                    bOffset++;
536                }
537                eltOffset += lineStride;
538            }
539        }
540
541        return bdata;
542    }
543
544    /**
545     * Sets the supplied <code>Raster</code>'s data from an array
546     * of packed binary data of the form returned by
547     * <code>getPackedBinaryData()</code>.
548     *
549     * @throws IllegalArgumentException if <code>isBinary()</code> returns
550     * <code>false</code> with the <code>SampleModel</code> of the
551     * supplied <code>Raster</code> as argument.
552     */
553    public static void setPackedBinaryData(byte[] binaryDataArray,
554                                           WritableRaster raster,
555                                           Rectangle rect) {
556        SampleModel sm = raster.getSampleModel();
557        if(!isBinary(sm)) {
558            throw new IllegalArgumentException(I18N.getString("ImageUtil0"));
559        }
560
561        int rectX = rect.x;
562        int rectY = rect.y;
563        int rectWidth = rect.width;
564        int rectHeight = rect.height;
565
566        DataBuffer dataBuffer = raster.getDataBuffer();
567
568        int dx = rectX - raster.getSampleModelTranslateX();
569        int dy = rectY - raster.getSampleModelTranslateY();
570
571        MultiPixelPackedSampleModel mpp = (MultiPixelPackedSampleModel)sm;
572        int lineStride = mpp.getScanlineStride();
573        int eltOffset = dataBuffer.getOffset() + mpp.getOffset(dx, dy);
574        int bitOffset = mpp.getBitOffset(dx);
575
576        int b = 0;
577
578        if(bitOffset == 0) {
579            if(dataBuffer instanceof DataBufferByte) {
580                byte[] data = ((DataBufferByte)dataBuffer).getData();
581                if(data == binaryDataArray) {
582                    // Optimal case: simply return.
583                    return;
584                }
585                int stride = (rectWidth + 7)/8;
586                int offset = 0;
587                for(int y = 0; y < rectHeight; y++) {
588                    System.arraycopy(binaryDataArray, offset,
589                                     data, eltOffset,
590                                     stride);
591                    offset += stride;
592                    eltOffset += lineStride;
593                }
594            } else if(dataBuffer instanceof DataBufferShort ||
595                      dataBuffer instanceof DataBufferUShort) {
596                short[] data = dataBuffer instanceof DataBufferShort ?
597                    ((DataBufferShort)dataBuffer).getData() :
598                    ((DataBufferUShort)dataBuffer).getData();
599
600                for(int y = 0; y < rectHeight; y++) {
601                    int xRemaining = rectWidth;
602                    int i = eltOffset;
603                    while(xRemaining > 8) {
604                        data[i++] =
605                            (short)(((binaryDataArray[b++] & 0xFF) << 8) |
606                                    (binaryDataArray[b++] & 0xFF));
607                        xRemaining -= 16;
608                    }
609                    if(xRemaining > 0) {
610                        data[i++] =
611                            (short)((binaryDataArray[b++] & 0xFF) << 8);
612                    }
613                    eltOffset += lineStride;
614                }
615            } else if(dataBuffer instanceof DataBufferInt) {
616                int[] data = ((DataBufferInt)dataBuffer).getData();
617
618                for(int y = 0; y < rectHeight; y++) {
619                    int xRemaining = rectWidth;
620                    int i = eltOffset;
621                    while(xRemaining > 24) {
622                        data[i++] =
623                            (int)(((binaryDataArray[b++] & 0xFF) << 24) |
624                                  ((binaryDataArray[b++] & 0xFF) << 16) |
625                                  ((binaryDataArray[b++] & 0xFF) << 8) |
626                                  (binaryDataArray[b++] & 0xFF));
627                        xRemaining -= 32;
628                    }
629                    int shift = 24;
630                    while(xRemaining > 0) {
631                        data[i] |=
632                            (int)((binaryDataArray[b++] & 0xFF) << shift);
633                        shift -= 8;
634                        xRemaining -= 8;
635                    }
636                    eltOffset += lineStride;
637                }
638            }
639        } else { // bitOffset != 0
640            int stride = (rectWidth + 7)/8;
641            int offset = 0;
642            if(dataBuffer instanceof DataBufferByte) {
643                byte[] data = ((DataBufferByte)dataBuffer).getData();
644
645                if((bitOffset & 7) == 0) {
646                    for(int y = 0; y < rectHeight; y++) {
647                        System.arraycopy(binaryDataArray, offset,
648                                         data, eltOffset,
649                                         stride);
650                        offset += stride;
651                        eltOffset += lineStride;
652                    }
653                } else { // bitOffset % 8 != 0
654                    int rightShift = bitOffset & 7;
655                    int leftShift = 8 - rightShift;
656                    int leftShift8 = 8 + leftShift;
657                    int mask = (byte)(255<<leftShift);
658                    int mask1 = (byte)~mask;
659
660                    for(int y = 0; y < rectHeight; y++) {
661                        int i = eltOffset;
662                        int xRemaining = rectWidth;
663                        while(xRemaining > 0) {
664                            byte datum = binaryDataArray[b++];
665
666                            if (xRemaining > leftShift8) {
667                                // when all the bits in this BYTE will be set
668                                // into the data buffer.
669                                data[i] = (byte)((data[i] & mask ) |
670                                    ((datum&0xFF) >>> rightShift));
671                                data[++i] = (byte)((datum & 0xFF) << leftShift);
672                            } else if (xRemaining > leftShift) {
673                                // All the "leftShift" high bits will be set
674                                // into the data buffer.  But not all the
675                                // "rightShift" low bits will be set.
676                                data[i] = (byte)((data[i] & mask ) |
677                                    ((datum&0xFF) >>> rightShift));
678                                i++;
679                                data[i] =
680                                    (byte)((data[i] & mask1) | ((datum & 0xFF) << leftShift));
681                            }
682                            else {
683                                // Less than "leftShift" high bits will be set.
684                                int remainMask = (1 << leftShift - xRemaining) - 1;
685                                data[i] =
686                                    (byte)((data[i] & (mask | remainMask)) |
687                                    (datum&0xFF) >>> rightShift & ~remainMask);
688                            }
689                            xRemaining -= 8;
690                        }
691                        eltOffset += lineStride;
692                    }
693                }
694            } else if(dataBuffer instanceof DataBufferShort ||
695                      dataBuffer instanceof DataBufferUShort) {
696                short[] data = dataBuffer instanceof DataBufferShort ?
697                    ((DataBufferShort)dataBuffer).getData() :
698                    ((DataBufferUShort)dataBuffer).getData();
699
700                int rightShift = bitOffset & 7;
701                int leftShift = 8 - rightShift;
702                int leftShift16 = 16 + leftShift;
703                int mask = (short)(~(255 << leftShift));
704                int mask1 = (short)(65535 << leftShift);
705                int mask2 = (short)~mask1;
706
707                for(int y = 0; y < rectHeight; y++) {
708                    int bOffset = bitOffset;
709                    int xRemaining = rectWidth;
710                    for(int x = 0; x < rectWidth;
711                        x += 8, bOffset += 8, xRemaining -= 8) {
712                        int i = eltOffset + (bOffset >> 4);
713                        int mod = bOffset & 15;
714                        int datum = binaryDataArray[b++] & 0xFF;
715                        if(mod <= 8) {
716                            // This BYTE is set into one SHORT
717                            if (xRemaining < 8) {
718                                // Mask the bits to be set.
719                                datum &= 255 << 8 - xRemaining;
720                            }
721                            data[i] = (short)((data[i] & mask) | (datum << leftShift));
722                        } else if (xRemaining > leftShift16) {
723                            // This BYTE will be set into two SHORTs
724                            data[i] = (short)((data[i] & mask1) | ((datum >>> rightShift)&0xFFFF));
725                            data[++i] =
726                                (short)((datum << leftShift)&0xFFFF);
727                        } else if (xRemaining > leftShift) {
728                            // This BYTE will be set into two SHORTs;
729                            // But not all the low bits will be set into SHORT
730                            data[i] = (short)((data[i] & mask1) | ((datum >>> rightShift)&0xFFFF));
731                            i++;
732                            data[i] =
733                                (short)((data[i] & mask2) | ((datum << leftShift)&0xFFFF));
734                        } else {
735                            // Only some of the high bits will be set into
736                            // SHORTs
737                            int remainMask = (1 << leftShift - xRemaining) - 1;
738                            data[i] = (short)((data[i] & (mask1 | remainMask)) |
739                                      ((datum >>> rightShift)&0xFFFF & ~remainMask));
740                        }
741                    }
742                    eltOffset += lineStride;
743                }
744            } else if(dataBuffer instanceof DataBufferInt) {
745                int[] data = ((DataBufferInt)dataBuffer).getData();
746                int rightShift = bitOffset & 7;
747                int leftShift = 8 - rightShift;
748                int leftShift32 = 32 + leftShift;
749                int mask = 0xFFFFFFFF << leftShift;
750                int mask1 = ~mask;
751
752                for(int y = 0; y < rectHeight; y++) {
753                    int bOffset = bitOffset;
754                    int xRemaining = rectWidth;
755                    for(int x = 0; x < rectWidth;
756                        x += 8, bOffset += 8, xRemaining -= 8) {
757                        int i = eltOffset + (bOffset >> 5);
758                        int mod = bOffset & 31;
759                        int datum = binaryDataArray[b++] & 0xFF;
760                        if(mod <= 24) {
761                            // This BYTE is set into one INT
762                            int shift = 24 - mod;
763                            if (xRemaining < 8) {
764                                // Mask the bits to be set.
765                                datum &= 255 << 8 - xRemaining;
766                            }
767                            data[i] = (data[i] & (~(255 << shift))) | (datum << shift);
768                        } else if (xRemaining > leftShift32) {
769                            // All the bits of this BYTE will be set into two INTs
770                            data[i] = (data[i] & mask) | (datum >>> rightShift);
771                            data[++i] = datum << leftShift;
772                        } else if (xRemaining > leftShift) {
773                            // This BYTE will be set into two INTs;
774                            // But not all the low bits will be set into INT
775                            data[i] = (data[i] & mask) | (datum >>> rightShift);
776                            i++;
777                            data[i] = (data[i] & mask1) | (datum << leftShift);
778                        } else {
779                            // Only some of the high bits will be set into INT
780                            int remainMask = (1 << leftShift - xRemaining) - 1;
781                            data[i] = (data[i] & (mask | remainMask)) |
782                                      (datum >>> rightShift & ~remainMask);
783                        }
784                    }
785                    eltOffset += lineStride;
786                }
787            }
788        }
789    }
790
791    /**
792     * Copies data into the packed array of the <code>Raster</code>
793     * from an array of unpacked data of the form returned by
794     * <code>getUnpackedBinaryData()</code>.
795     *
796     * <p> If the data are binary, then the target bit will be set if
797     * and only if the corresponding byte is non-zero.
798     *
799     * @throws IllegalArgumentException if <code>isBinary()</code> returns
800     * <code>false</code> with the <code>SampleModel</code> of the
801     * supplied <code>Raster</code> as argument.
802     */
803    public static void setUnpackedBinaryData(byte[] bdata,
804                                             WritableRaster raster,
805                                             Rectangle rect) {
806        SampleModel sm = raster.getSampleModel();
807        if(!isBinary(sm)) {
808            throw new IllegalArgumentException(I18N.getString("ImageUtil0"));
809        }
810
811        int rectX = rect.x;
812        int rectY = rect.y;
813        int rectWidth = rect.width;
814        int rectHeight = rect.height;
815
816        DataBuffer dataBuffer = raster.getDataBuffer();
817
818        int dx = rectX - raster.getSampleModelTranslateX();
819        int dy = rectY - raster.getSampleModelTranslateY();
820
821        MultiPixelPackedSampleModel mpp = (MultiPixelPackedSampleModel)sm;
822        int lineStride = mpp.getScanlineStride();
823        int eltOffset = dataBuffer.getOffset() + mpp.getOffset(dx, dy);
824        int bitOffset = mpp.getBitOffset(dx);
825
826        int k = 0;
827
828        if(dataBuffer instanceof DataBufferByte) {
829            byte[] data = ((DataBufferByte)dataBuffer).getData();
830            for(int y = 0; y < rectHeight; y++) {
831                int bOffset = eltOffset*8 + bitOffset;
832                for(int x = 0; x < rectWidth; x++) {
833                    if(bdata[k++] != (byte)0) {
834                        data[bOffset/8] |=
835                            (byte)(0x00000001 << (7 - bOffset & 7));
836                    }
837                    bOffset++;
838                }
839                eltOffset += lineStride;
840            }
841        } else if(dataBuffer instanceof DataBufferShort ||
842                  dataBuffer instanceof DataBufferUShort) {
843            short[] data = dataBuffer instanceof DataBufferShort ?
844                ((DataBufferShort)dataBuffer).getData() :
845                ((DataBufferUShort)dataBuffer).getData();
846            for(int y = 0; y < rectHeight; y++) {
847                int bOffset = eltOffset*16 + bitOffset;
848                for(int x = 0; x < rectWidth; x++) {
849                    if(bdata[k++] != (byte)0) {
850                        data[bOffset/16] |=
851                            (short)(0x00000001 <<
852                                    (15 - bOffset % 16));
853                    }
854                    bOffset++;
855                }
856                eltOffset += lineStride;
857            }
858        } else if(dataBuffer instanceof DataBufferInt) {
859            int[] data = ((DataBufferInt)dataBuffer).getData();
860            for(int y = 0; y < rectHeight; y++) {
861                int bOffset = eltOffset*32 + bitOffset;
862                for(int x = 0; x < rectWidth; x++) {
863                    if(bdata[k++] != (byte)0) {
864                        data[bOffset/32] |=
865                            (int)(0x00000001 <<
866                                  (31 - bOffset % 32));
867                    }
868                    bOffset++;
869                }
870                eltOffset += lineStride;
871            }
872        }
873    }
874
875    public static boolean isBinary(SampleModel sm) {
876        return sm instanceof MultiPixelPackedSampleModel &&
877            ((MultiPixelPackedSampleModel)sm).getPixelBitStride() == 1 &&
878            sm.getNumBands() == 1;
879    }
880
881    public static ColorModel createColorModel(ColorSpace colorSpace,
882                                              SampleModel sampleModel) {
883        ColorModel colorModel = null;
884
885        if(sampleModel == null) {
886            throw new IllegalArgumentException(I18N.getString("ImageUtil1"));
887        }
888
889        int numBands = sampleModel.getNumBands();
890        if (numBands < 1 || numBands > 4) {
891            return null;
892        }
893
894        int dataType = sampleModel.getDataType();
895        if (sampleModel instanceof ComponentSampleModel) {
896            if (dataType < DataBuffer.TYPE_BYTE ||
897                //dataType == DataBuffer.TYPE_SHORT ||
898                dataType > DataBuffer.TYPE_DOUBLE) {
899                return null;
900            }
901
902            if (colorSpace == null)
903                colorSpace =
904                    numBands <= 2 ?
905                    ColorSpace.getInstance(ColorSpace.CS_GRAY) :
906                    ColorSpace.getInstance(ColorSpace.CS_sRGB);
907
908            boolean useAlpha = (numBands == 2) || (numBands == 4);
909            int transparency = useAlpha ?
910                               Transparency.TRANSLUCENT : Transparency.OPAQUE;
911
912            boolean premultiplied = false;
913
914            int dataTypeSize = DataBuffer.getDataTypeSize(dataType);
915            int[] bits = new int[numBands];
916            for (int i = 0; i < numBands; i++) {
917                bits[i] = dataTypeSize;
918            }
919
920            colorModel = new ComponentColorModel(colorSpace,
921                                                 bits,
922                                                 useAlpha,
923                                                 premultiplied,
924                                                 transparency,
925                                                 dataType);
926        } else if (sampleModel instanceof SinglePixelPackedSampleModel) {
927            SinglePixelPackedSampleModel sppsm =
928                (SinglePixelPackedSampleModel)sampleModel;
929
930            int[] bitMasks = sppsm.getBitMasks();
931            int rmask = 0;
932            int gmask = 0;
933            int bmask = 0;
934            int amask = 0;
935
936            numBands = bitMasks.length;
937            if (numBands <= 2) {
938                rmask = gmask = bmask = bitMasks[0];
939                if (numBands == 2) {
940                    amask = bitMasks[1];
941                }
942            } else {
943                rmask = bitMasks[0];
944                gmask = bitMasks[1];
945                bmask = bitMasks[2];
946                if (numBands == 4) {
947                    amask = bitMasks[3];
948                }
949            }
950
951            int[] sampleSize = sppsm.getSampleSize();
952            int bits = 0;
953            for (int i = 0; i < sampleSize.length; i++) {
954                bits += sampleSize[i];
955            }
956
957            if (colorSpace == null)
958                colorSpace = ColorSpace.getInstance(ColorSpace.CS_sRGB);
959
960            colorModel =
961                new DirectColorModel(colorSpace,
962                                     bits, rmask, gmask, bmask, amask,
963                                     false,
964                                     sampleModel.getDataType());
965        } else if (sampleModel instanceof MultiPixelPackedSampleModel) {
966            int bits =
967                ((MultiPixelPackedSampleModel)sampleModel).getPixelBitStride();
968            int size = 1 << bits;
969            byte[] comp = new byte[size];
970
971            for (int i = 0; i < size; i++)
972                comp[i] = (byte)(255 * i / (size - 1));
973
974            colorModel = new IndexColorModel(bits, size, comp, comp, comp);
975        }
976
977        return colorModel;
978    }
979
980    public static int getElementSize(SampleModel sm) {
981        int elementSize = DataBuffer.getDataTypeSize(sm.getDataType());
982
983        if (sm instanceof MultiPixelPackedSampleModel) {
984            MultiPixelPackedSampleModel mppsm =
985                (MultiPixelPackedSampleModel)sm;
986            return mppsm.getSampleSize(0) * mppsm.getNumBands();
987        } else if (sm instanceof ComponentSampleModel) {
988            return sm.getNumBands() * elementSize;
989        } else if (sm instanceof SinglePixelPackedSampleModel) {
990            return elementSize;
991        }
992
993        return elementSize * sm.getNumBands();
994
995    }
996
997    public static long getTileSize(SampleModel sm) {
998        int elementSize = DataBuffer.getDataTypeSize(sm.getDataType());
999
1000        if (sm instanceof MultiPixelPackedSampleModel) {
1001            MultiPixelPackedSampleModel mppsm =
1002                (MultiPixelPackedSampleModel)sm;
1003            return (mppsm.getScanlineStride() * mppsm.getHeight() +
1004                   (mppsm.getDataBitOffset() + elementSize -1) / elementSize) *
1005                   ((elementSize + 7) / 8);
1006        } else if (sm instanceof ComponentSampleModel) {
1007            ComponentSampleModel csm = (ComponentSampleModel)sm;
1008            int[] bandOffsets = csm.getBandOffsets();
1009            int maxBandOff = bandOffsets[0];
1010            for (int i=1; i<bandOffsets.length; i++)
1011                maxBandOff = Math.max(maxBandOff, bandOffsets[i]);
1012
1013            long size = 0;
1014            int pixelStride = csm.getPixelStride();
1015            int scanlineStride = csm.getScanlineStride();
1016            if (maxBandOff >= 0)
1017                size += maxBandOff + 1;
1018            if (pixelStride > 0)
1019                size += pixelStride * (sm.getWidth() - 1);
1020            if (scanlineStride > 0)
1021                size += scanlineStride * (sm.getHeight() - 1);
1022
1023            int[] bankIndices = csm.getBankIndices();
1024            maxBandOff = bankIndices[0];
1025            for (int i=1; i<bankIndices.length; i++)
1026                maxBandOff = Math.max(maxBandOff, bankIndices[i]);
1027            return size * (maxBandOff + 1) * ((elementSize + 7) / 8);
1028        } else if (sm instanceof SinglePixelPackedSampleModel) {
1029            SinglePixelPackedSampleModel sppsm =
1030                (SinglePixelPackedSampleModel)sm;
1031            long size = sppsm.getScanlineStride() * (sppsm.getHeight() - 1) +
1032                        sppsm.getWidth();
1033            return size * ((elementSize + 7) / 8);
1034        }
1035
1036        return 0;
1037    }
1038
1039    public static long getBandSize(SampleModel sm) {
1040        int elementSize = DataBuffer.getDataTypeSize(sm.getDataType());
1041
1042        if (sm instanceof ComponentSampleModel) {
1043            ComponentSampleModel csm = (ComponentSampleModel)sm;
1044            int pixelStride = csm.getPixelStride();
1045            int scanlineStride = csm.getScanlineStride();
1046            long size = Math.min(pixelStride, scanlineStride);
1047
1048            if (pixelStride > 0)
1049                size += pixelStride * (sm.getWidth() - 1);
1050            if (scanlineStride > 0)
1051                size += scanlineStride * (sm.getHeight() - 1);
1052            return size * ((elementSize + 7) / 8);
1053        } else
1054            return getTileSize(sm);
1055    }
1056
1057    /**
1058     * Tests whether the color indices represent a gray-scale image with
1059     * the indicated number of bits over the color component range [0,255].
1060     * The grayscale mapping may be inverted, i.e., 0 -> 255 and
1061     * mapSize -> 0.
1062     *
1063     * @param icm The gray-to-color mapping.
1064     * @return Whether the <code>IndexColorModel</code> maps index
1065     *         <code>i</code> to <code>((255*i)/icm.getMapSize()-1)</code>.
1066     * @throws IllegalArgumentException if <code>icm</code> is
1067     *         <code>null</code>.
1068     */
1069    public static boolean isGrayscaleMapping(IndexColorModel icm) {
1070        if(icm == null) {
1071            throw new IllegalArgumentException("icm == null!");
1072        }
1073
1074        // Get colormap size and contents.
1075        int mapSize = icm.getMapSize();
1076
1077        byte[] r = new byte[mapSize];
1078        byte[] g = new byte[mapSize];
1079        byte[] b = new byte[mapSize];
1080
1081        icm.getReds(r);
1082        icm.getGreens(g);
1083        icm.getBlues(b);
1084
1085        boolean isGrayToColor = true;
1086
1087        // Check ascending ramp.
1088        for (int i = 0; i < mapSize; i++) {
1089            byte temp = (byte)(i*255/(mapSize - 1));
1090
1091            if (r[i] != temp || g[i] != temp || b[i] != temp) {
1092                isGrayToColor = false;
1093                break;
1094            }
1095        }
1096
1097        if(!isGrayToColor) {
1098            isGrayToColor = true;
1099
1100            // Check descending ramp.
1101            for (int i = 0, j = mapSize - 1; i < mapSize; i++, j--) {
1102                byte temp = (byte)(j*255/(mapSize - 1));
1103
1104                if (r[i] != temp || g[i] != temp || b[i] != temp) {
1105                    isGrayToColor = false;
1106                    break;
1107                }
1108            }
1109        }
1110
1111        return isGrayToColor;
1112    }
1113
1114    /**
1115     * Tests whether the color indices represent a gray-scale image.
1116     *
1117     * @param r The red channel color indices.
1118     * @param g The green channel color indices.
1119     * @param b The blue channel color indices.
1120     * @return If all the indices have 256 entries, and are identical mappings,
1121     *         return <code>true</code>; otherwise, return <code>false</code>.
1122     */
1123    public static boolean isIndicesForGrayscale(byte[] r, byte[] g, byte[] b) {
1124        if (r.length != g.length || r.length != b.length)
1125            return false;
1126
1127        int size = r.length;
1128
1129        if (size != 256)
1130            return false;
1131
1132        for (int i = 0; i < size; i++) {
1133            byte temp = (byte) i;
1134
1135            if (r[i] != temp || g[i] != temp || b[i] != temp)
1136                return false;
1137        }
1138
1139        return true;
1140    }
1141
1142    /** Converts the provided object to <code>String</code> */
1143    public static String convertObjectToString(Object obj) {
1144        if (obj == null)
1145            return "";
1146
1147        String s = "";
1148        if (obj instanceof byte[]) {
1149            byte[] bArray = (byte[])obj;
1150            for (int i = 0; i < bArray.length; i++)
1151                s += bArray[i] + " ";
1152            return s;
1153        }
1154
1155        if (obj instanceof int[]) {
1156            int[] iArray = (int[])obj;
1157            for (int i = 0; i < iArray.length; i++)
1158                s += iArray[i] + " " ;
1159            return s;
1160        }
1161
1162        if (obj instanceof short[]) {
1163            short[] sArray = (short[])obj;
1164            for (int i = 0; i < sArray.length; i++)
1165                s += sArray[i] + " " ;
1166            return s;
1167        }
1168
1169        return obj.toString();
1170
1171    }
1172    
1173    /** Checks that the provided <code>ImageWriter</code> can encode
1174     * the provided <code>ImageTypeSpecifier</code> or not.  If not, an
1175     * <code>IIOException</code> will be thrown.
1176     * @param writer The provided <code>ImageWriter</code>.
1177     * @param type The image to be tested.
1178     * @throws IIOException If the writer cannot encoded the provided image.
1179     */
1180    public static final void canEncodeImage(ImageWriter writer,
1181                                            ImageTypeSpecifier type)
1182        throws IIOException {
1183        ImageWriterSpi spi = writer.getOriginatingProvider();
1184
1185        if(type != null && spi != null && !spi.canEncodeImage(type))  {
1186            throw new IIOException(I18N.getString("ImageUtil2")+" "+
1187                                   writer.getClass().getName());
1188        }
1189    }
1190
1191    /** Checks that the provided <code>ImageWriter</code> can encode
1192     * the provided <code>ColorModel</code> and <code>SampleModel</code>.
1193     * If not, an <code>IIOException</code> will be thrown.
1194     * @param writer The provided <code>ImageWriter</code>.
1195     * @param colorModel The provided <code>ColorModel</code>.
1196     * @param sampleModel The provided <code>SampleModel</code>.
1197     * @throws IIOException If the writer cannot encoded the provided image.
1198     */
1199    public static final void canEncodeImage(ImageWriter writer, 
1200                                            ColorModel colorModel,
1201                                            SampleModel sampleModel)
1202        throws IIOException {
1203        ImageTypeSpecifier type = null;
1204        if (colorModel != null && sampleModel != null)
1205            type = new ImageTypeSpecifier(colorModel, sampleModel);
1206        canEncodeImage(writer, type);
1207    }
1208
1209    /**
1210     * Returns whether the image has contiguous data across rows.
1211     */
1212    public static final boolean imageIsContiguous(RenderedImage image) {
1213        SampleModel sm;
1214        if(image instanceof BufferedImage) {
1215            WritableRaster ras = ((BufferedImage)image).getRaster();
1216            sm = ras.getSampleModel();
1217        } else {
1218            sm = image.getSampleModel();
1219        }
1220        
1221        if (sm instanceof ComponentSampleModel) {
1222            // Ensure image rows samples are stored contiguously
1223            // in a single bank.
1224            ComponentSampleModel csm = (ComponentSampleModel)sm;
1225
1226            if (csm.getPixelStride() != csm.getNumBands()) {
1227                return false;
1228            }
1229
1230            int[] bandOffsets = csm.getBandOffsets();
1231            for (int i = 0; i < bandOffsets.length; i++) {
1232                if (bandOffsets[i] != i) {
1233                    return false;
1234                }
1235            }
1236
1237            int[] bankIndices = csm.getBankIndices();
1238            for (int i = 0; i < bandOffsets.length; i++) {
1239                if (bankIndices[i] != 0) {
1240                    return false;
1241                }
1242            }
1243
1244            return true;
1245        }
1246
1247        // Otherwise true if and only if it's a bilevel image with
1248        // a MultiPixelPackedSampleModel, 1 bit per pixel, and 1 bit
1249        // pixel stride.
1250        return ImageUtil.isBinary(sm);
1251    }
1252
1253    /**
1254     * Gets the destination image type.
1255     */
1256    // NOTE: Code shamelessly copied from ImageReader.getDestination().
1257    public static final ImageTypeSpecifier
1258        getDestinationType(ImageReadParam param,
1259                           Iterator imageTypes) throws IIOException {
1260
1261        if (imageTypes == null || !imageTypes.hasNext()) {
1262            throw new IllegalArgumentException("imageTypes null or empty!");
1263        }
1264
1265        ImageTypeSpecifier imageType = null;
1266
1267        // If param is non-null, use it
1268        if (param != null) {
1269            imageType = param.getDestinationType();
1270        }
1271
1272        // No info from param, use fallback image type
1273        if (imageType == null) {
1274            Object o = imageTypes.next();
1275            if (!(o instanceof ImageTypeSpecifier)) {
1276                throw new IllegalArgumentException
1277                    ("Non-ImageTypeSpecifier retrieved from imageTypes!");
1278            }
1279            imageType = (ImageTypeSpecifier)o;
1280        } else {
1281            boolean foundIt = false;
1282            while (imageTypes.hasNext()) {
1283                ImageTypeSpecifier type =
1284                    (ImageTypeSpecifier)imageTypes.next();
1285                if (type.equals(imageType)) {
1286                    foundIt = true;
1287                    break;
1288                }
1289            }
1290
1291            if (!foundIt) {
1292                throw new IIOException
1293                    ("Destination type from ImageReadParam does not match!");
1294            }
1295        }
1296
1297        return imageType;
1298    }
1299
1300    /**
1301     * Returns <code>true</code> if the given <code>ColorSpace</code> object
1302     * is an instance of <code>ICC_ColorSpace</code> but is not one of the
1303     * standard <code>ColorSpace</code>s returned by
1304     * <code>ColorSpace.getInstance()</code>.
1305     *
1306     * @param cs The <code>ColorSpace</code> to test.
1307     */
1308    public static boolean isNonStandardICCColorSpace(ColorSpace cs) {
1309        boolean retval = false;
1310
1311        try {
1312            // Check the standard ColorSpaces in decreasing order of
1313            // likelihood except check CS_PYCC last as in some JREs
1314            // PYCC.pf used not to be installed.
1315            retval =
1316                (cs instanceof ICC_ColorSpace) &&
1317                !(cs.isCS_sRGB() ||
1318                  cs.equals(ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB)) ||
1319                  cs.equals(ColorSpace.getInstance(ColorSpace.CS_GRAY)) ||
1320                  cs.equals(ColorSpace.getInstance(ColorSpace.CS_CIEXYZ)) ||
1321                  cs.equals(ColorSpace.getInstance(ColorSpace.CS_PYCC)));
1322        } catch(IllegalArgumentException e) {
1323            // PYCC.pf not installed: ignore it - 'retval' is still 'false'.
1324        }
1325
1326        return retval;
1327    }
1328
1329    // Method to return JDK core ImageReaderSPI/ImageWriterSPI for a 
1330    // given formatName.
1331    public static List getJDKImageReaderWriterSPI(ServiceRegistry registry, 
1332                                                  String formatName,
1333                                                  boolean isReader) {
1334
1335        IIORegistry iioRegistry = (IIORegistry)registry;
1336
1337        Class spiClass;
1338        String descPart;
1339        if (isReader) {
1340            spiClass = ImageReaderSpi.class;
1341            descPart = " image reader";
1342        } else {
1343            spiClass = ImageWriterSpi.class;
1344            descPart = " image writer";
1345        }
1346
1347        Iterator iter = ServiceLoader.load(spiClass).iterator(); // useOrdering
1348
1349        String formatNames[];
1350        ImageReaderWriterSpi provider;
1351        String desc = "standard " + formatName + descPart;
1352        String jiioPath = "com.github.jaiimageio.impl";
1353        Locale locale = Locale.getDefault();
1354        ArrayList list = new ArrayList();
1355        while (iter.hasNext()) {
1356            provider = (ImageReaderWriterSpi)iter.next();
1357
1358            // Look for JDK core ImageWriterSpi's
1359            if (provider.getVendorName().startsWith("Sun Microsystems") &&
1360                desc.equalsIgnoreCase(provider.getDescription(locale)) &&
1361                // not JAI Image I/O plugins
1362                !provider.getPluginClassName().startsWith(jiioPath)) {
1363
1364                // Get the formatNames supported by this Spi
1365                formatNames = provider.getFormatNames();
1366                for (int i=0; i<formatNames.length; i++) {
1367                    if (formatNames[i].equalsIgnoreCase(formatName)) {
1368                        // Must be a JDK provided ImageReader/ImageWriter
1369                        list.add(provider);
1370                        break;
1371                    }
1372                }
1373            }
1374        }
1375        
1376        return list;
1377    }
1378
1379    public static void processOnRegistration(ServiceRegistry registry,
1380                                             Class category,
1381                                             String formatName,
1382                                             ImageReaderWriterSpi spi,
1383                                             int deregisterJvmVersion, 
1384                                             int priorityJvmVersion) {
1385
1386        // Check which JVM we are running on
1387        String jvmVendor = System.getProperty("java.vendor");
1388    String jvmSpecificationVersion =
1389                System.getProperty("java.specification.version");
1390    int jvmVersion = getJvmVersion(jvmSpecificationVersion);
1391
1392        if (jvmVendor.equals("Sun Microsystems Inc.")) {
1393
1394            List list;
1395            if (spi instanceof ImageReaderSpi)
1396                list = getJDKImageReaderWriterSPI(registry, formatName, true);
1397            else 
1398                list = getJDKImageReaderWriterSPI(registry, formatName, false);
1399            
1400            if (jvmVersion >= deregisterJvmVersion && list.size() != 0) {
1401                // De-register JIIO's plug-in
1402                registry.deregisterServiceProvider(spi, category);
1403            } else {
1404                for (int i=0; i<list.size(); i++) {
1405                    if (jvmVersion >= priorityJvmVersion) {
1406                        // Set JIIO plug-in to lower priority
1407                        registry.setOrdering(category, 
1408                                             list.get(i),
1409                                             spi);
1410                    } else {
1411                        // Set JIIO plug-in to higher priority
1412                        registry.setOrdering(category,
1413                                             spi,
1414                                             list.get(i));
1415                    }
1416                }
1417            }
1418        }
1419    }
1420
1421
1422    static int getJvmVersion(String jvmSpecificationVersion) {
1423        if (jvmSpecificationVersion.startsWith("1.")) {
1424            // Skip the "1." part to get to the part of the version number that
1425            // actually changes from version to version
1426            // The assumption here is that "java.specification.version"
1427            // up to Java 1.8 is of the format "x.y"
1428            jvmSpecificationVersion = jvmSpecificationVersion.substring(2);
1429
1430            return Integer.parseInt(jvmSpecificationVersion);
1431        } else {
1432            // http://www.oracle.com/technetwork/java/javase/9-relnote-issues-3704069.html#JDK-8085822
1433            return Integer.parseInt(jvmSpecificationVersion);
1434        }
1435    }
1436
1437
1438    public static int readMultiByteInteger(ImageInputStream iis) throws IOException {
1439        int value = iis.readByte();
1440        int result = value & 0x7f;
1441        while((value & 0x80) == 0x80) {
1442            result <<= 7;
1443            value = iis.readByte();
1444            result |= (value & 0x7f);
1445        }
1446        return result;
1447    }
1448}