Home » openjdk-7 » com.sun.imageio.plugins » jpeg » [javadoc | source]

    1   /*
    2    * Copyright (c) 2000, 2007, Oracle and/or its affiliates. All rights reserved.
    3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
    4    *
    5    * This code is free software; you can redistribute it and/or modify it
    6    * under the terms of the GNU General Public License version 2 only, as
    7    * published by the Free Software Foundation.  Oracle designates this
    8    * particular file as subject to the "Classpath" exception as provided
    9    * by Oracle in the LICENSE file that accompanied this code.
   10    *
   11    * This code is distributed in the hope that it will be useful, but WITHOUT
   12    * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   13    * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   14    * version 2 for more details (a copy is included in the LICENSE file that
   15    * accompanied this code).
   16    *
   17    * You should have received a copy of the GNU General Public License version
   18    * 2 along with this work; if not, write to the Free Software Foundation,
   19    * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   20    *
   21    * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   22    * or visit www.oracle.com if you need additional information or have any
   23    * questions.
   24    */
   25   
   26   package com.sun.imageio.plugins.jpeg;
   27   
   28   import javax.imageio.IIOException;
   29   import javax.imageio.ImageReader;
   30   import javax.imageio.ImageReadParam;
   31   import javax.imageio.ImageTypeSpecifier;
   32   import javax.imageio.metadata.IIOMetadata;
   33   import javax.imageio.spi.ImageReaderSpi;
   34   import javax.imageio.stream.ImageInputStream;
   35   import javax.imageio.plugins.jpeg.JPEGImageReadParam;
   36   import javax.imageio.plugins.jpeg.JPEGQTable;
   37   import javax.imageio.plugins.jpeg.JPEGHuffmanTable;
   38   
   39   import java.awt.Point;
   40   import java.awt.Rectangle;
   41   import java.awt.color.ColorSpace;
   42   import java.awt.color.ICC_Profile;
   43   import java.awt.color.ICC_ColorSpace;
   44   import java.awt.color.CMMException;
   45   import java.awt.image.BufferedImage;
   46   import java.awt.image.Raster;
   47   import java.awt.image.WritableRaster;
   48   import java.awt.image.DataBuffer;
   49   import java.awt.image.DataBufferByte;
   50   import java.awt.image.ColorModel;
   51   import java.awt.image.IndexColorModel;
   52   import java.awt.image.ColorConvertOp;
   53   import java.io.IOException;
   54   import java.util.List;
   55   import java.util.Iterator;
   56   import java.util.ArrayList;
   57   import java.util.NoSuchElementException;
   58   
   59   import sun.java2d.Disposer;
   60   import sun.java2d.DisposerRecord;
   61   
   62   public class JPEGImageReader extends ImageReader {
   63   
   64       private boolean debug = false;
   65   
   66       /**
   67        * The following variable contains a pointer to the IJG library
   68        * structure for this reader.  It is assigned in the constructor
   69        * and then is passed in to every native call.  It is set to 0
   70        * by dispose to avoid disposing twice.
   71        */
   72       private long structPointer = 0;
   73   
   74       /** The input stream we read from */
   75       private ImageInputStream iis = null;
   76   
   77       /**
   78        * List of stream positions for images, reinitialized every time
   79        * a new input source is set.
   80        */
   81       private List imagePositions = null;
   82   
   83       /**
   84        * The number of images in the stream, or 0.
   85        */
   86       private int numImages = 0;
   87   
   88       static {
   89           java.security.AccessController.doPrivileged(
   90               new sun.security.action.LoadLibraryAction("jpeg"));
   91           initReaderIDs(ImageInputStream.class,
   92                         JPEGQTable.class,
   93                         JPEGHuffmanTable.class);
   94       }
   95   
   96       // The following warnings are converted to strings when used
   97       // as keys to get localized resources from JPEGImageReaderResources
   98       // and its children.
   99   
  100       /**
  101        * Warning code to be passed to warningOccurred to indicate
  102        * that the EOI marker is missing from the end of the stream.
  103        * This usually signals that the stream is corrupted, but
  104        * everything up to the last MCU should be usable.
  105        */
  106       protected static final int WARNING_NO_EOI = 0;
  107   
  108       /**
  109        * Warning code to be passed to warningOccurred to indicate
  110        * that a JFIF segment was encountered inside a JFXX JPEG
  111        * thumbnail and is being ignored.
  112        */
  113       protected static final int WARNING_NO_JFIF_IN_THUMB = 1;
  114   
  115       /**
  116        * Warning code to be passed to warningOccurred to indicate
  117        * that embedded ICC profile is invalid and will be ignored.
  118        */
  119       protected static final int WARNING_IGNORE_INVALID_ICC = 2;
  120   
  121       private static final int MAX_WARNING = WARNING_IGNORE_INVALID_ICC;
  122   
  123       /**
  124        * Image index of image for which header information
  125        * is available.
  126        */
  127       private int currentImage = -1;
  128   
  129       // The following is copied out from C after reading the header.
  130       // Unlike metadata, which may never be retrieved, we need this
  131       // if we are to read an image at all.
  132   
  133       /** Set by setImageData native code callback */
  134       private int width;
  135       /** Set by setImageData native code callback */
  136       private int height;
  137       /**
  138        * Set by setImageData native code callback.  A modified
  139        * IJG+NIFTY colorspace code.
  140        */
  141       private int colorSpaceCode;
  142       /**
  143        * Set by setImageData native code callback.  A modified
  144        * IJG+NIFTY colorspace code.
  145        */
  146       private int outColorSpaceCode;
  147       /** Set by setImageData native code callback */
  148       private int numComponents;
  149       /** Set by setImageData native code callback */
  150       private ColorSpace iccCS = null;
  151   
  152   
  153       /** If we need to post-convert in Java, convert with this op */
  154       private ColorConvertOp convert = null;
  155   
  156       /** The image we are going to fill */
  157       private BufferedImage image = null;
  158   
  159       /** An intermediate Raster to hold decoded data */
  160       private WritableRaster raster = null;
  161   
  162       /** A view of our target Raster that we can setRect to */
  163       private WritableRaster target = null;
  164   
  165       /** The databuffer for the above Raster */
  166       private DataBufferByte buffer = null;
  167   
  168       /** The region in the destination where we will write pixels */
  169       private Rectangle destROI = null;
  170   
  171       /** The list of destination bands, if any */
  172       private int [] destinationBands = null;
  173   
  174       /** Stream metadata, cached, even when the stream is changed. */
  175       private JPEGMetadata streamMetadata = null;
  176   
  177       /** Image metadata, valid for the imageMetadataIndex only. */
  178       private JPEGMetadata imageMetadata = null;
  179       private int imageMetadataIndex = -1;
  180   
  181       /**
  182        * Set to true every time we seek in the stream; used to
  183        * invalidate the native buffer contents in C.
  184        */
  185       private boolean haveSeeked = false;
  186   
  187       /**
  188        * Tables that have been read from a tables-only image at the
  189        * beginning of a stream.
  190        */
  191       private JPEGQTable [] abbrevQTables = null;
  192       private JPEGHuffmanTable[] abbrevDCHuffmanTables = null;
  193       private JPEGHuffmanTable[] abbrevACHuffmanTables = null;
  194   
  195       private int minProgressivePass = 0;
  196       private int maxProgressivePass = Integer.MAX_VALUE;
  197   
  198       /**
  199        * Variables used by progress monitoring.
  200        */
  201       private static final int UNKNOWN = -1;  // Number of passes
  202       private static final int MIN_ESTIMATED_PASSES = 10; // IJG default
  203       private int knownPassCount = UNKNOWN;
  204       private int pass = 0;
  205       private float percentToDate = 0.0F;
  206       private float previousPassPercentage = 0.0F;
  207       private int progInterval = 0;
  208   
  209       /**
  210        * Set to true once stream has been checked for stream metadata
  211        */
  212       private boolean tablesOnlyChecked = false;
  213   
  214       /** The referent to be registered with the Disposer. */
  215       private Object disposerReferent = new Object();
  216   
  217       /** The DisposerRecord that handles the actual disposal of this reader. */
  218       private DisposerRecord disposerRecord;
  219   
  220       /** Sets up static C structures. */
  221       private static native void initReaderIDs(Class iisClass,
  222                                                Class qTableClass,
  223                                                Class huffClass);
  224   
  225       public JPEGImageReader(ImageReaderSpi originator) {
  226           super(originator);
  227           structPointer = initJPEGImageReader();
  228           disposerRecord = new JPEGReaderDisposerRecord(structPointer);
  229           Disposer.addRecord(disposerReferent, disposerRecord);
  230       }
  231   
  232       /** Sets up per-reader C structure and returns a pointer to it. */
  233       private native long initJPEGImageReader();
  234   
  235       /**
  236        * Called by the native code or other classes to signal a warning.
  237        * The code is used to lookup a localized message to be used when
  238        * sending warnings to listeners.
  239        */
  240       protected void warningOccurred(int code) {
  241           if ((code < 0) || (code > MAX_WARNING)){
  242               throw new InternalError("Invalid warning index");
  243           }
  244           processWarningOccurred
  245               ("com.sun.imageio.plugins.jpeg.JPEGImageReaderResources",
  246                Integer.toString(code));
  247       }
  248   
  249       /**
  250        * The library has it's own error facility that emits warning messages.
  251        * This routine is called by the native code when it has already
  252        * formatted a string for output.
  253        * XXX  For truly complete localization of all warning messages,
  254        * the sun_jpeg_output_message routine in the native code should
  255        * send only the codes and parameters to a method here in Java,
  256        * which will then format and send the warnings, using localized
  257        * strings.  This method will have to deal with all the parameters
  258        * and formats (%u with possibly large numbers, %02d, %02x, etc.)
  259        * that actually occur in the JPEG library.  For now, this prevents
  260        * library warnings from being printed to stderr.
  261        */
  262       protected void warningWithMessage(String msg) {
  263           processWarningOccurred(msg);
  264       }
  265   
  266       public void setInput(Object input,
  267                            boolean seekForwardOnly,
  268                            boolean ignoreMetadata)
  269       {
  270           setThreadLock();
  271           try {
  272               super.setInput(input, seekForwardOnly, ignoreMetadata);
  273               this.ignoreMetadata = ignoreMetadata;
  274               resetInternalState();
  275               iis = (ImageInputStream) input; // Always works
  276               setSource(structPointer, iis);
  277           } finally {
  278               clearThreadLock();
  279           }
  280       }
  281   
  282       private native void setSource(long structPointer,
  283                                     ImageInputStream source);
  284   
  285       private void checkTablesOnly() throws IOException {
  286           if (debug) {
  287               System.out.println("Checking for tables-only image");
  288           }
  289           long savePos = iis.getStreamPosition();
  290           if (debug) {
  291               System.out.println("saved pos is " + savePos);
  292               System.out.println("length is " + iis.length());
  293           }
  294           // Read the first header
  295           boolean tablesOnly = readNativeHeader(true);
  296           if (tablesOnly) {
  297               if (debug) {
  298                   System.out.println("tables-only image found");
  299                   long pos = iis.getStreamPosition();
  300                   System.out.println("pos after return from native is " + pos);
  301               }
  302               // This reads the tables-only image twice, once from C
  303               // and once from Java, but only if ignoreMetadata is false
  304               if (ignoreMetadata == false) {
  305                   iis.seek(savePos);
  306                   haveSeeked = true;
  307                   streamMetadata = new JPEGMetadata(true, false,
  308                                                     iis, this);
  309                   long pos = iis.getStreamPosition();
  310                   if (debug) {
  311                       System.out.println
  312                           ("pos after constructing stream metadata is " + pos);
  313                   }
  314               }
  315               // Now we are at the first image if there are any, so add it
  316               // to the list
  317               if (hasNextImage()) {
  318                   imagePositions.add(new Long(iis.getStreamPosition()));
  319               }
  320           } else { // Not tables only, so add original pos to the list
  321               imagePositions.add(new Long(savePos));
  322               // And set current image since we've read it now
  323               currentImage = 0;
  324           }
  325           if (seekForwardOnly) {
  326               Long pos = (Long) imagePositions.get(imagePositions.size()-1);
  327               iis.flushBefore(pos.longValue());
  328           }
  329           tablesOnlyChecked = true;
  330       }
  331   
  332       public int getNumImages(boolean allowSearch) throws IOException {
  333           setThreadLock();
  334           try { // locked thread
  335               return getNumImagesOnThread(allowSearch);
  336           } finally {
  337               clearThreadLock();
  338           }
  339       }
  340   
  341       private int getNumImagesOnThread(boolean allowSearch)
  342         throws IOException {
  343           if (numImages != 0) {
  344               return numImages;
  345           }
  346           if (iis == null) {
  347               throw new IllegalStateException("Input not set");
  348           }
  349           if (allowSearch == true) {
  350               if (seekForwardOnly) {
  351                   throw new IllegalStateException(
  352                       "seekForwardOnly and allowSearch can't both be true!");
  353               }
  354               // Otherwise we have to read the entire stream
  355   
  356               if (!tablesOnlyChecked) {
  357                   checkTablesOnly();
  358               }
  359   
  360               iis.mark();
  361   
  362               gotoImage(0);
  363   
  364               JPEGBuffer buffer = new JPEGBuffer(iis);
  365               buffer.loadBuf(0);
  366   
  367               boolean done = false;
  368               while (!done) {
  369                   done = buffer.scanForFF(this);
  370                   switch (buffer.buf[buffer.bufPtr] & 0xff) {
  371                   case JPEG.SOI:
  372                       numImages++;
  373                       // FALL THROUGH to decrement buffer vars
  374                       // This first set doesn't have a length
  375                   case 0: // not a marker, just a data 0xff
  376                   case JPEG.RST0:
  377                   case JPEG.RST1:
  378                   case JPEG.RST2:
  379                   case JPEG.RST3:
  380                   case JPEG.RST4:
  381                   case JPEG.RST5:
  382                   case JPEG.RST6:
  383                   case JPEG.RST7:
  384                   case JPEG.EOI:
  385                       buffer.bufAvail--;
  386                       buffer.bufPtr++;
  387                       break;
  388                       // All the others have a length
  389                   default:
  390                       buffer.bufAvail--;
  391                       buffer.bufPtr++;
  392                       buffer.loadBuf(2);
  393                       int length = ((buffer.buf[buffer.bufPtr++] & 0xff) << 8) |
  394                           (buffer.buf[buffer.bufPtr++] & 0xff);
  395                       buffer.bufAvail -= 2;
  396                       length -= 2; // length includes itself
  397                       buffer.skipData(length);
  398                   }
  399               }
  400   
  401   
  402               iis.reset();
  403   
  404               return numImages;
  405           }
  406   
  407           return -1;  // Search is necessary for JPEG
  408       }
  409   
  410       /**
  411        * Sets the input stream to the start of the requested image.
  412        * <pre>
  413        * @exception IllegalStateException if the input source has not been
  414        * set.
  415        * @exception IndexOutOfBoundsException if the supplied index is
  416        * out of bounds.
  417        * </pre>
  418        */
  419       private void gotoImage(int imageIndex) throws IOException {
  420           if (iis == null) {
  421               throw new IllegalStateException("Input not set");
  422           }
  423           if (imageIndex < minIndex) {
  424               throw new IndexOutOfBoundsException();
  425           }
  426           if (!tablesOnlyChecked) {
  427               checkTablesOnly();
  428           }
  429           if (imageIndex < imagePositions.size()) {
  430               iis.seek(((Long)(imagePositions.get(imageIndex))).longValue());
  431           } else {
  432               // read to start of image, saving positions
  433               // First seek to the last position we already have, and skip the
  434               // entire image
  435               Long pos = (Long) imagePositions.get(imagePositions.size()-1);
  436               iis.seek(pos.longValue());
  437               skipImage();
  438               // Now add all intervening positions, skipping images
  439               for (int index = imagePositions.size();
  440                    index <= imageIndex;
  441                    index++) {
  442                   // Is there an image?
  443                   if (!hasNextImage()) {
  444                       throw new IndexOutOfBoundsException();
  445                   }
  446                   pos = new Long(iis.getStreamPosition());
  447                   imagePositions.add(pos);
  448                   if (seekForwardOnly) {
  449                       iis.flushBefore(pos.longValue());
  450                   }
  451                   if (index < imageIndex) {
  452                       skipImage();
  453                   }  // Otherwise we are where we want to be
  454               }
  455           }
  456   
  457           if (seekForwardOnly) {
  458               minIndex = imageIndex;
  459           }
  460   
  461           haveSeeked = true;  // No way is native buffer still valid
  462       }
  463   
  464       /**
  465        * Skip over a complete image in the stream, leaving the stream
  466        * positioned such that the next byte to be read is the first
  467        * byte of the next image.  For JPEG, this means that we read
  468        * until we encounter an EOI marker or until the end of the stream.
  469        * If the stream ends before an EOI marker is encountered, an
  470        * IndexOutOfBoundsException is thrown.
  471        */
  472       private void skipImage() throws IOException {
  473           if (debug) {
  474               System.out.println("skipImage called");
  475           }
  476           boolean foundFF = false;
  477           for (int byteval = iis.read();
  478                byteval != -1;
  479                byteval = iis.read()) {
  480   
  481               if (foundFF == true) {
  482                   if (byteval == JPEG.EOI) {
  483                       return;
  484                   }
  485               }
  486               foundFF = (byteval == 0xff) ? true : false;
  487           }
  488           throw new IndexOutOfBoundsException();
  489       }
  490   
  491       /**
  492        * Returns <code>true</code> if there is an image beyond
  493        * the current stream position.  Does not disturb the
  494        * stream position.
  495        */
  496       private boolean hasNextImage() throws IOException {
  497           if (debug) {
  498               System.out.print("hasNextImage called; returning ");
  499           }
  500           iis.mark();
  501           boolean foundFF = false;
  502           for (int byteval = iis.read();
  503                byteval != -1;
  504                byteval = iis.read()) {
  505   
  506               if (foundFF == true) {
  507                   if (byteval == JPEG.SOI) {
  508                       iis.reset();
  509                       if (debug) {
  510                           System.out.println("true");
  511                       }
  512                       return true;
  513                   }
  514               }
  515               foundFF = (byteval == 0xff) ? true : false;
  516           }
  517           // We hit the end of the stream before we hit an SOI, so no image
  518           iis.reset();
  519           if (debug) {
  520               System.out.println("false");
  521           }
  522           return false;
  523       }
  524   
  525       /**
  526        * Push back the given number of bytes to the input stream.
  527        * Called by the native code at the end of each image so
  528        * that the next one can be identified from Java.
  529        */
  530       private void pushBack(int num) throws IOException {
  531           if (debug) {
  532               System.out.println("pushing back " + num + " bytes");
  533           }
  534           iis.seek(iis.getStreamPosition()-num);
  535           // The buffer is clear after this, so no need to set haveSeeked.
  536       }
  537   
  538       /**
  539        * Reads header information for the given image, if possible.
  540        */
  541       private void readHeader(int imageIndex, boolean reset)
  542           throws IOException {
  543           gotoImage(imageIndex);
  544           readNativeHeader(reset); // Ignore return
  545           currentImage = imageIndex;
  546       }
  547   
  548       private boolean readNativeHeader(boolean reset) throws IOException {
  549           boolean retval = false;
  550           retval = readImageHeader(structPointer, haveSeeked, reset);
  551           haveSeeked = false;
  552           return retval;
  553       }
  554   
  555       /**
  556        * Read in the header information starting from the current
  557        * stream position, returning <code>true</code> if the
  558        * header was a tables-only image.  After this call, the
  559        * native IJG decompression struct will contain the image
  560        * information required by most query calls below
  561        * (e.g. getWidth, getHeight, etc.), if the header was not
  562        * a tables-only image.
  563        * If reset is <code>true</code>, the state of the IJG
  564        * object is reset so that it can read a header again.
  565        * This happens automatically if the header was a tables-only
  566        * image.
  567        */
  568       private native boolean readImageHeader(long structPointer,
  569                                              boolean clearBuffer,
  570                                              boolean reset)
  571           throws IOException;
  572   
  573       /*
  574        * Called by the native code whenever an image header has been
  575        * read.  Whether we read metadata or not, we always need this
  576        * information, so it is passed back independently of
  577        * metadata, which may never be read.
  578        */
  579       private void setImageData(int width,
  580                                 int height,
  581                                 int colorSpaceCode,
  582                                 int outColorSpaceCode,
  583                                 int numComponents,
  584                                 byte [] iccData) {
  585           this.width = width;
  586           this.height = height;
  587           this.colorSpaceCode = colorSpaceCode;
  588           this.outColorSpaceCode = outColorSpaceCode;
  589           this.numComponents = numComponents;
  590   
  591           if (iccData == null) {
  592               iccCS = null;
  593               return;
  594           }
  595   
  596           ICC_Profile newProfile = null;
  597           try {
  598               newProfile = ICC_Profile.getInstance(iccData);
  599           } catch (IllegalArgumentException e) {
  600               /*
  601                * Color profile data seems to be invalid.
  602                * Ignore this profile.
  603                */
  604               iccCS = null;
  605               warningOccurred(WARNING_IGNORE_INVALID_ICC);
  606   
  607               return;
  608           }
  609           byte[] newData = newProfile.getData();
  610   
  611           ICC_Profile oldProfile = null;
  612           if (iccCS instanceof ICC_ColorSpace) {
  613               oldProfile = ((ICC_ColorSpace)iccCS).getProfile();
  614           }
  615           byte[] oldData = null;
  616           if (oldProfile != null) {
  617               oldData = oldProfile.getData();
  618           }
  619   
  620           /*
  621            * At the moment we can't rely on the ColorSpace.equals()
  622            * and ICC_Profile.equals() because they do not detect
  623            * the case when two profiles are created from same data.
  624            *
  625            * So, we have to do data comparison in order to avoid
  626            * creation of different ColorSpace instances for the same
  627            * embedded data.
  628            */
  629           if (oldData == null ||
  630               !java.util.Arrays.equals(oldData, newData))
  631           {
  632               iccCS = new ICC_ColorSpace(newProfile);
  633               // verify new color space
  634               try {
  635                   float[] colors = iccCS.fromRGB(new float[] {1f, 0f, 0f});
  636               } catch (CMMException e) {
  637                   /*
  638                    * Embedded profile seems to be corrupted.
  639                    * Ignore this profile.
  640                    */
  641                   iccCS = null;
  642                   warningOccurred(WARNING_IGNORE_INVALID_ICC);
  643               }
  644           }
  645       }
  646   
  647       public int getWidth(int imageIndex) throws IOException {
  648           setThreadLock();
  649           try {
  650               if (currentImage != imageIndex) {
  651                   readHeader(imageIndex, true);
  652               }
  653               return width;
  654           } finally {
  655               clearThreadLock();
  656           }
  657       }
  658   
  659       public int getHeight(int imageIndex) throws IOException {
  660           setThreadLock();
  661           try {
  662               if (currentImage != imageIndex) {
  663                   readHeader(imageIndex, true);
  664               }
  665               return height;
  666           } finally {
  667               clearThreadLock();
  668           }
  669       }
  670   
  671       /////////// Color Conversion and Image Types
  672   
  673       /**
  674        * Return an ImageTypeSpecifier corresponding to the given
  675        * color space code, or null if the color space is unsupported.
  676        */
  677       private ImageTypeProducer getImageType(int code) {
  678           ImageTypeProducer ret = null;
  679   
  680           if ((code > 0) && (code < JPEG.NUM_JCS_CODES)) {
  681               ret = ImageTypeProducer.getTypeProducer(code);
  682           }
  683           return ret;
  684       }
  685   
  686       public ImageTypeSpecifier getRawImageType(int imageIndex)
  687           throws IOException {
  688           setThreadLock();
  689           try {
  690               if (currentImage != imageIndex) {
  691                   readHeader(imageIndex, true);
  692               }
  693   
  694               // Returns null if it can't be represented
  695               return getImageType(colorSpaceCode).getType();
  696           } finally {
  697               clearThreadLock();
  698           }
  699       }
  700   
  701       public Iterator getImageTypes(int imageIndex)
  702           throws IOException {
  703           setThreadLock();
  704           try {
  705               return getImageTypesOnThread(imageIndex);
  706           } finally {
  707               clearThreadLock();
  708           }
  709       }
  710   
  711       private Iterator getImageTypesOnThread(int imageIndex)
  712           throws IOException {
  713           if (currentImage != imageIndex) {
  714               readHeader(imageIndex, true);
  715           }
  716   
  717           // We return an iterator containing the default, any
  718           // conversions that the library provides, and
  719           // all the other default types with the same number
  720           // of components, as we can do these as a post-process.
  721           // As we convert Rasters rather than images, images
  722           // with alpha cannot be converted in a post-process.
  723   
  724           // If this image can't be interpreted, this method
  725           // returns an empty Iterator.
  726   
  727           // Get the raw ITS, if there is one.  Note that this
  728           // won't always be the same as the default.
  729           ImageTypeProducer raw = getImageType(colorSpaceCode);
  730   
  731           // Given the encoded colorspace, build a list of ITS's
  732           // representing outputs you could handle starting
  733           // with the default.
  734   
  735           ArrayList<ImageTypeProducer> list = new ArrayList<ImageTypeProducer>(1);
  736   
  737           switch (colorSpaceCode) {
  738           case JPEG.JCS_GRAYSCALE:
  739               list.add(raw);
  740               list.add(getImageType(JPEG.JCS_RGB));
  741               break;
  742           case JPEG.JCS_RGB:
  743               list.add(raw);
  744               list.add(getImageType(JPEG.JCS_GRAYSCALE));
  745               list.add(getImageType(JPEG.JCS_YCC));
  746               break;
  747           case JPEG.JCS_RGBA:
  748               list.add(raw);
  749               break;
  750           case JPEG.JCS_YCC:
  751               if (raw != null) {  // Might be null if PYCC.pf not installed
  752                   list.add(raw);
  753                   list.add(getImageType(JPEG.JCS_RGB));
  754               }
  755               break;
  756           case JPEG.JCS_YCCA:
  757               if (raw != null) {  // Might be null if PYCC.pf not installed
  758                   list.add(raw);
  759               }
  760               break;
  761           case JPEG.JCS_YCbCr:
  762               // As there is no YCbCr ColorSpace, we can't support
  763               // the raw type.
  764   
  765               // due to 4705399, use RGB as default in order to avoid
  766               // slowing down of drawing operations with result image.
  767               list.add(getImageType(JPEG.JCS_RGB));
  768   
  769               if (iccCS != null) {
  770                   list.add(new ImageTypeProducer() {
  771                       protected ImageTypeSpecifier produce() {
  772                           return ImageTypeSpecifier.createInterleaved
  773                            (iccCS,
  774                             JPEG.bOffsRGB,  // Assume it's for RGB
  775                             DataBuffer.TYPE_BYTE,
  776                             false,
  777                             false);
  778                       }
  779                   });
  780   
  781               }
  782   
  783               list.add(getImageType(JPEG.JCS_GRAYSCALE));
  784               list.add(getImageType(JPEG.JCS_YCC));
  785               break;
  786           case JPEG.JCS_YCbCrA:  // Default is to convert to RGBA
  787               // As there is no YCbCr ColorSpace, we can't support
  788               // the raw type.
  789               list.add(getImageType(JPEG.JCS_RGBA));
  790               break;
  791           }
  792   
  793           return new ImageTypeIterator(list.iterator());
  794       }
  795   
  796       /**
  797        * Checks the implied color conversion between the stream and
  798        * the target image, altering the IJG output color space if necessary.
  799        * If a java color conversion is required, then this sets up
  800        * <code>convert</code>.
  801        * If bands are being rearranged at all (either source or destination
  802        * bands are specified in the param), then the default color
  803        * conversions are assumed to be correct.
  804        * Throws an IIOException if there is no conversion available.
  805        */
  806       private void checkColorConversion(BufferedImage image,
  807                                         ImageReadParam param)
  808           throws IIOException {
  809   
  810           // If we are rearranging channels at all, the default
  811           // conversions remain in place.  If the user wants
  812           // raw channels then he should do this while reading
  813           // a Raster.
  814           if (param != null) {
  815               if ((param.getSourceBands() != null) ||
  816                   (param.getDestinationBands() != null)) {
  817                   // Accept default conversions out of decoder, silently
  818                   return;
  819               }
  820           }
  821   
  822           // XXX - We do not currently support any indexed color models,
  823           // though we could, as IJG will quantize for us.
  824           // This is a performance and memory-use issue, as
  825           // users can read RGB and then convert to indexed in Java.
  826   
  827           ColorModel cm = image.getColorModel();
  828   
  829           if (cm instanceof IndexColorModel) {
  830               throw new IIOException("IndexColorModel not supported");
  831           }
  832   
  833           // Now check the ColorSpace type against outColorSpaceCode
  834           // We may want to tweak the default
  835           ColorSpace cs = cm.getColorSpace();
  836           int csType = cs.getType();
  837           convert = null;
  838           switch (outColorSpaceCode) {
  839           case JPEG.JCS_GRAYSCALE:  // Its gray in the file
  840               if  (csType == ColorSpace.TYPE_RGB) { // We want RGB
  841                   // IJG can do this for us more efficiently
  842                   setOutColorSpace(structPointer, JPEG.JCS_RGB);
  843                   // Update java state according to changes
  844                   // in the native part of decoder.
  845                   outColorSpaceCode = JPEG.JCS_RGB;
  846                   numComponents = 3;
  847               } else if (csType != ColorSpace.TYPE_GRAY) {
  848                   throw new IIOException("Incompatible color conversion");
  849               }
  850               break;
  851           case JPEG.JCS_RGB:  // IJG wants to go to RGB
  852               if (csType ==  ColorSpace.TYPE_GRAY) {  // We want gray
  853                   if (colorSpaceCode == JPEG.JCS_YCbCr) {
  854                       // If the jpeg space is YCbCr, IJG can do it
  855                       setOutColorSpace(structPointer, JPEG.JCS_GRAYSCALE);
  856                       // Update java state according to changes
  857                       // in the native part of decoder.
  858                       outColorSpaceCode = JPEG.JCS_GRAYSCALE;
  859                       numComponents = 1;
  860                   }
  861               } else if ((iccCS != null) &&
  862                          (cm.getNumComponents() == numComponents) &&
  863                          (cs != iccCS)) {
  864                   // We have an ICC profile but it isn't used in the dest
  865                   // image.  So convert from the profile cs to the target cs
  866                   convert = new ColorConvertOp(iccCS, cs, null);
  867                   // Leave IJG conversion in place; we still need it
  868               } else if ((iccCS == null) &&
  869                          (!cs.isCS_sRGB()) &&
  870                          (cm.getNumComponents() == numComponents)) {
  871                   // Target isn't sRGB, so convert from sRGB to the target
  872                   convert = new ColorConvertOp(JPEG.JCS.sRGB, cs, null);
  873               } else if (csType != ColorSpace.TYPE_RGB) {
  874                   throw new IIOException("Incompatible color conversion");
  875               }
  876               break;
  877           case JPEG.JCS_RGBA:
  878               // No conversions available; image must be RGBA
  879               if ((csType != ColorSpace.TYPE_RGB) ||
  880                   (cm.getNumComponents() != numComponents)) {
  881                   throw new IIOException("Incompatible color conversion");
  882               }
  883               break;
  884           case JPEG.JCS_YCC:
  885               {
  886                   ColorSpace YCC = JPEG.JCS.getYCC();
  887                   if (YCC == null) { // We can't do YCC at all
  888                       throw new IIOException("Incompatible color conversion");
  889                   }
  890                   if ((cs != YCC) &&
  891                       (cm.getNumComponents() == numComponents)) {
  892                       convert = new ColorConvertOp(YCC, cs, null);
  893                   }
  894               }
  895               break;
  896           case JPEG.JCS_YCCA:
  897               {
  898                   ColorSpace YCC = JPEG.JCS.getYCC();
  899                   // No conversions available; image must be YCCA
  900                   if ((YCC == null) || // We can't do YCC at all
  901                       (cs != YCC) ||
  902                       (cm.getNumComponents() != numComponents)) {
  903                       throw new IIOException("Incompatible color conversion");
  904                   }
  905               }
  906               break;
  907           default:
  908               // Anything else we can't handle at all
  909               throw new IIOException("Incompatible color conversion");
  910           }
  911       }
  912   
  913       /**
  914        * Set the IJG output space to the given value.  The library will
  915        * perform the appropriate colorspace conversions.
  916        */
  917       private native void setOutColorSpace(long structPointer, int id);
  918   
  919       /////// End of Color Conversion & Image Types
  920   
  921       public ImageReadParam getDefaultReadParam() {
  922           return new JPEGImageReadParam();
  923       }
  924   
  925       public IIOMetadata getStreamMetadata() throws IOException {
  926           setThreadLock();
  927           try {
  928               if (!tablesOnlyChecked) {
  929                   checkTablesOnly();
  930               }
  931               return streamMetadata;
  932           } finally {
  933               clearThreadLock();
  934           }
  935       }
  936   
  937       public IIOMetadata getImageMetadata(int imageIndex)
  938           throws IOException {
  939           setThreadLock();
  940           try {
  941               // imageMetadataIndex will always be either a valid index or
  942               // -1, in which case imageMetadata will not be null.
  943               // So we can leave checking imageIndex for gotoImage.
  944               if ((imageMetadataIndex == imageIndex)
  945                   && (imageMetadata != null)) {
  946                   return imageMetadata;
  947               }
  948   
  949               gotoImage(imageIndex);
  950   
  951               imageMetadata = new JPEGMetadata(false, false, iis, this);
  952   
  953               imageMetadataIndex = imageIndex;
  954   
  955               return imageMetadata;
  956           } finally {
  957               clearThreadLock();
  958           }
  959       }
  960   
  961       public BufferedImage read(int imageIndex, ImageReadParam param)
  962           throws IOException {
  963           setThreadLock();
  964           try {
  965               try {
  966                   readInternal(imageIndex, param, false);
  967               } catch (RuntimeException e) {
  968                   resetLibraryState(structPointer);
  969                   throw e;
  970               } catch (IOException e) {
  971                   resetLibraryState(structPointer);
  972                   throw e;
  973               }
  974   
  975               BufferedImage ret = image;
  976               image = null;  // don't keep a reference here
  977               return ret;
  978           } finally {
  979               clearThreadLock();
  980           }
  981       }
  982   
  983       private Raster readInternal(int imageIndex,
  984                                   ImageReadParam param,
  985                                   boolean wantRaster) throws IOException {
  986           readHeader(imageIndex, false);
  987   
  988           WritableRaster imRas = null;
  989           int numImageBands = 0;
  990   
  991           if (!wantRaster){
  992               // Can we read this image?
  993               Iterator imageTypes = getImageTypes(imageIndex);
  994               if (imageTypes.hasNext() == false) {
  995                   throw new IIOException("Unsupported Image Type");
  996               }
  997   
  998               image = getDestination(param, imageTypes, width, height);
  999               imRas = image.getRaster();
 1000   
 1001               // The destination may still be incompatible.
 1002   
 1003               numImageBands = image.getSampleModel().getNumBands();
 1004   
 1005               // Check whether we can handle any implied color conversion
 1006   
 1007               // Throws IIOException if the stream and the image are
 1008               // incompatible, and sets convert if a java conversion
 1009               // is necessary
 1010               checkColorConversion(image, param);
 1011   
 1012               // Check the source and destination bands in the param
 1013               checkReadParamBandSettings(param, numComponents, numImageBands);
 1014           } else {
 1015               // Set the output color space equal to the input colorspace
 1016               // This disables all conversions
 1017               setOutColorSpace(structPointer, colorSpaceCode);
 1018               image = null;
 1019           }
 1020   
 1021           // Create an intermediate 1-line Raster that will hold the decoded,
 1022           // subsampled, clipped, band-selected image data in a single
 1023           // byte-interleaved buffer.  The above transformations
 1024           // will occur in C for performance.  Every time this Raster
 1025           // is filled we will call back to acceptPixels below to copy
 1026           // this to whatever kind of buffer our image has.
 1027   
 1028           int [] srcBands = JPEG.bandOffsets[numComponents-1];
 1029           int numRasterBands = (wantRaster ? numComponents : numImageBands);
 1030           destinationBands = null;
 1031   
 1032           Rectangle srcROI = new Rectangle(0, 0, 0, 0);
 1033           destROI = new Rectangle(0, 0, 0, 0);
 1034           computeRegions(param, width, height, image, srcROI, destROI);
 1035   
 1036           int periodX = 1;
 1037           int periodY = 1;
 1038   
 1039           minProgressivePass = 0;
 1040           maxProgressivePass = Integer.MAX_VALUE;
 1041   
 1042           if (param != null) {
 1043               periodX = param.getSourceXSubsampling();
 1044               periodY = param.getSourceYSubsampling();
 1045   
 1046               int[] sBands = param.getSourceBands();
 1047               if (sBands != null) {
 1048                   srcBands = sBands;
 1049                   numRasterBands = srcBands.length;
 1050               }
 1051               if (!wantRaster) {  // ignore dest bands for Raster
 1052                   destinationBands = param.getDestinationBands();
 1053               }
 1054   
 1055               minProgressivePass = param.getSourceMinProgressivePass();
 1056               maxProgressivePass = param.getSourceMaxProgressivePass();
 1057   
 1058               if (param instanceof JPEGImageReadParam) {
 1059                   JPEGImageReadParam jparam = (JPEGImageReadParam) param;
 1060                   if (jparam.areTablesSet()) {
 1061                       abbrevQTables = jparam.getQTables();
 1062                       abbrevDCHuffmanTables = jparam.getDCHuffmanTables();
 1063                       abbrevACHuffmanTables = jparam.getACHuffmanTables();
 1064                   }
 1065               }
 1066           }
 1067   
 1068           int lineSize = destROI.width*numRasterBands;
 1069   
 1070           buffer = new DataBufferByte(lineSize);
 1071   
 1072           int [] bandOffs = JPEG.bandOffsets[numRasterBands-1];
 1073   
 1074           raster = Raster.createInterleavedRaster(buffer,
 1075                                                   destROI.width, 1,
 1076                                                   lineSize,
 1077                                                   numRasterBands,
 1078                                                   bandOffs,
 1079                                                   null);
 1080   
 1081           // Now that we have the Raster we'll decode to, get a view of the
 1082           // target Raster that will permit a simple setRect for each scanline
 1083           if (wantRaster) {
 1084               target =  Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
 1085                                                        destROI.width,
 1086                                                        destROI.height,
 1087                                                        lineSize,
 1088                                                        numRasterBands,
 1089                                                        bandOffs,
 1090                                                        null);
 1091           } else {
 1092               target = imRas;
 1093           }
 1094           int [] bandSizes = target.getSampleModel().getSampleSize();
 1095   
 1096           /*
 1097            * If the process is sequential, and we have restart markers,
 1098            * we could skip to the correct restart marker, if the library
 1099            * lets us.  That's an optimization to investigate later.
 1100            */
 1101   
 1102           // Check for update listeners (don't call back if none)
 1103           boolean callbackUpdates = ((updateListeners != null)
 1104                                      || (progressListeners != null));
 1105   
 1106           // Set up progression data
 1107           initProgressData();
 1108           // if we have a metadata object, we can count the scans
 1109           // and set knownPassCount
 1110           if (imageIndex == imageMetadataIndex) { // We have metadata
 1111               knownPassCount = 0;
 1112               for (Iterator iter = imageMetadata.markerSequence.iterator();
 1113                    iter.hasNext();) {
 1114                   if (iter.next() instanceof SOSMarkerSegment) {
 1115                       knownPassCount++;
 1116                   }
 1117               }
 1118           }
 1119           progInterval = Math.max((target.getHeight()-1) / 20, 1);
 1120           if (knownPassCount > 0) {
 1121               progInterval *= knownPassCount;
 1122           } else if (maxProgressivePass != Integer.MAX_VALUE) {
 1123               progInterval *= (maxProgressivePass - minProgressivePass + 1);
 1124           }
 1125   
 1126           if (debug) {
 1127               System.out.println("**** Read Data *****");
 1128               System.out.println("numRasterBands is " + numRasterBands);
 1129               System.out.print("srcBands:");
 1130               for (int i = 0; i<srcBands.length;i++)
 1131                   System.out.print(" " + srcBands[i]);
 1132               System.out.println();
 1133               System.out.println("destination bands is " + destinationBands);
 1134               if (destinationBands != null) {
 1135                   for (int i = 0; i < destinationBands.length; i++) {
 1136                       System.out.print(" " + destinationBands[i]);
 1137                   }
 1138                   System.out.println();
 1139               }
 1140               System.out.println("sourceROI is " + srcROI);
 1141               System.out.println("destROI is " + destROI);
 1142               System.out.println("periodX is " + periodX);
 1143               System.out.println("periodY is " + periodY);
 1144               System.out.println("minProgressivePass is " + minProgressivePass);
 1145               System.out.println("maxProgressivePass is " + maxProgressivePass);
 1146               System.out.println("callbackUpdates is " + callbackUpdates);
 1147           }
 1148   
 1149           // Finally, we are ready to read
 1150   
 1151           processImageStarted(currentImage);
 1152   
 1153           boolean aborted = false;
 1154   
 1155           // Note that getData disables acceleration on buffer, but it is
 1156           // just a 1-line intermediate data transfer buffer that will not
 1157           // affect the acceleration of the resulting image.
 1158           aborted = readImage(structPointer,
 1159                               buffer.getData(),
 1160                               numRasterBands,
 1161                               srcBands,
 1162                               bandSizes,
 1163                               srcROI.x, srcROI.y,
 1164                               srcROI.width, srcROI.height,
 1165                               periodX, periodY,
 1166                               abbrevQTables,
 1167                               abbrevDCHuffmanTables,
 1168                               abbrevACHuffmanTables,
 1169                               minProgressivePass, maxProgressivePass,
 1170                               callbackUpdates);
 1171   
 1172           if (aborted) {
 1173               processReadAborted();
 1174           } else {
 1175               processImageComplete();
 1176           }
 1177   
 1178           return target;
 1179   
 1180       }
 1181   
 1182       /**
 1183        * This method is called back from C when the intermediate Raster
 1184        * is full.  The parameter indicates the scanline in the target
 1185        * Raster to which the intermediate Raster should be copied.
 1186        * After the copy, we notify update listeners.
 1187        */
 1188       private void acceptPixels(int y, boolean progressive) {
 1189           if (convert != null) {
 1190               convert.filter(raster, raster);
 1191           }
 1192           target.setRect(destROI.x, destROI.y + y, raster);
 1193   
 1194           processImageUpdate(image,
 1195                              destROI.x, destROI.y+y,
 1196                              raster.getWidth(), 1,
 1197                              1, 1,
 1198                              destinationBands);
 1199           if ((y > 0) && (y%progInterval == 0)) {
 1200               int height = target.getHeight()-1;
 1201               float percentOfPass = ((float)y)/height;
 1202               if (progressive) {
 1203                   if (knownPassCount != UNKNOWN) {
 1204                       processImageProgress((pass + percentOfPass)*100.0F
 1205                                            / knownPassCount);
 1206                   } else if (maxProgressivePass != Integer.MAX_VALUE) {
 1207                       // Use the range of allowed progressive passes
 1208                       processImageProgress((pass + percentOfPass)*100.0F
 1209                           / (maxProgressivePass - minProgressivePass + 1));
 1210                   } else {
 1211                       // Assume there are a minimum of MIN_ESTIMATED_PASSES
 1212                       // and that there is always one more pass
 1213                       // Compute the percentage as the percentage at the end
 1214                       // of the previous pass, plus the percentage of this
 1215                       // pass scaled to be the percentage of the total remaining,
 1216                       // assuming a minimum of MIN_ESTIMATED_PASSES passes and
 1217                       // that there is always one more pass.  This is monotonic
 1218                       // and asymptotic to 1.0, which is what we need.
 1219                       int remainingPasses = // including this one
 1220                           Math.max(2, MIN_ESTIMATED_PASSES-pass);
 1221                       int totalPasses = pass + remainingPasses-1;
 1222                       progInterval = Math.max(height/20*totalPasses,
 1223                                               totalPasses);
 1224                       if (y%progInterval == 0) {
 1225                           percentToDate = previousPassPercentage +
 1226                               (1.0F - previousPassPercentage)
 1227                               * (percentOfPass)/remainingPasses;
 1228                           if (debug) {
 1229                               System.out.print("pass= " + pass);
 1230                               System.out.print(", y= " + y);
 1231                               System.out.print(", progInt= " + progInterval);
 1232                               System.out.print(", % of pass: " + percentOfPass);
 1233                               System.out.print(", rem. passes: "
 1234                                                + remainingPasses);
 1235                               System.out.print(", prev%: "
 1236                                                + previousPassPercentage);
 1237                               System.out.print(", %ToDate: " + percentToDate);
 1238                               System.out.print(" ");
 1239                           }
 1240                           processImageProgress(percentToDate*100.0F);
 1241                       }
 1242                   }
 1243               } else {
 1244                   processImageProgress(percentOfPass * 100.0F);
 1245               }
 1246           }
 1247       }
 1248   
 1249       private void initProgressData() {
 1250           knownPassCount = UNKNOWN;
 1251           pass = 0;
 1252           percentToDate = 0.0F;
 1253           previousPassPercentage = 0.0F;
 1254           progInterval = 0;
 1255       }
 1256   
 1257       private void passStarted (int pass) {
 1258           this.pass = pass;
 1259           previousPassPercentage = percentToDate;
 1260           processPassStarted(image,
 1261                              pass,
 1262                              minProgressivePass,
 1263                              maxProgressivePass,
 1264                              0, 0,
 1265                              1,1,
 1266                              destinationBands);
 1267       }
 1268   
 1269       private void passComplete () {
 1270           processPassComplete(image);
 1271       }
 1272   
 1273       void thumbnailStarted(int thumbnailIndex) {
 1274           processThumbnailStarted(currentImage, thumbnailIndex);
 1275       }
 1276   
 1277       // Provide access to protected superclass method
 1278       void thumbnailProgress(float percentageDone) {
 1279           processThumbnailProgress(percentageDone);
 1280       }
 1281   
 1282       // Provide access to protected superclass method
 1283       void thumbnailComplete() {
 1284           processThumbnailComplete();
 1285       }
 1286   
 1287       /**
 1288        * Returns <code>true</code> if the read was aborted.
 1289        */
 1290       private native boolean readImage(long structPointer,
 1291                                        byte [] buffer,
 1292                                        int numRasterBands,
 1293                                        int [] srcBands,
 1294                                        int [] bandSizes,
 1295                                        int sourceXOffset, int sourceYOffset,
 1296                                        int sourceWidth, int sourceHeight,
 1297                                        int periodX, int periodY,
 1298                                        JPEGQTable [] abbrevQTables,
 1299                                        JPEGHuffmanTable [] abbrevDCHuffmanTables,
 1300                                        JPEGHuffmanTable [] abbrevACHuffmanTables,
 1301                                        int minProgressivePass,
 1302                                        int maxProgressivePass,
 1303                                        boolean wantUpdates);
 1304   
 1305       public void abort() {
 1306           setThreadLock();
 1307           try {
 1308               super.abort();
 1309               abortRead(structPointer);
 1310           } finally {
 1311               clearThreadLock();
 1312           }
 1313       }
 1314   
 1315       /** Set the C level abort flag. Keep it atomic for thread safety. */
 1316       private native void abortRead(long structPointer);
 1317   
 1318       /** Resets library state when an exception occurred during a read. */
 1319       private native void resetLibraryState(long structPointer);
 1320   
 1321       public boolean canReadRaster() {
 1322           return true;
 1323       }
 1324   
 1325       public Raster readRaster(int imageIndex, ImageReadParam param)
 1326           throws IOException {
 1327           setThreadLock();
 1328           Raster retval = null;
 1329           try {
 1330               /*
 1331                * This could be further optimized by not resetting the dest.
 1332                * offset and creating a translated raster in readInternal()
 1333                * (see bug 4994702 for more info).
 1334                */
 1335   
 1336               // For Rasters, destination offset is logical, not physical, so
 1337               // set it to 0 before calling computeRegions, so that the destination
 1338               // region is not clipped.
 1339               Point saveDestOffset = null;
 1340               if (param != null) {
 1341                   saveDestOffset = param.getDestinationOffset();
 1342                   param.setDestinationOffset(new Point(0, 0));
 1343               }
 1344               retval = readInternal(imageIndex, param, true);
 1345               // Apply the destination offset, if any, as a logical offset
 1346               if (saveDestOffset != null) {
 1347                   target = target.createWritableTranslatedChild(saveDestOffset.x,
 1348                                                                 saveDestOffset.y);
 1349               }
 1350           } catch (RuntimeException e) {
 1351               resetLibraryState(structPointer);
 1352               throw e;
 1353           } catch (IOException e) {
 1354               resetLibraryState(structPointer);
 1355               throw e;
 1356           } finally {
 1357               clearThreadLock();
 1358           }
 1359           return retval;
 1360       }
 1361   
 1362       public boolean readerSupportsThumbnails() {
 1363           return true;
 1364       }
 1365   
 1366       public int getNumThumbnails(int imageIndex) throws IOException {
 1367           setThreadLock();
 1368           try {
 1369               getImageMetadata(imageIndex);  // checks iis state for us
 1370               // Now check the jfif segments
 1371               JFIFMarkerSegment jfif =
 1372                   (JFIFMarkerSegment) imageMetadata.findMarkerSegment
 1373                   (JFIFMarkerSegment.class, true);
 1374               int retval = 0;
 1375               if (jfif != null) {
 1376                   retval = (jfif.thumb == null) ? 0 : 1;
 1377                   retval += jfif.extSegments.size();
 1378               }
 1379               return retval;
 1380           } finally {
 1381               clearThreadLock();
 1382           }
 1383       }
 1384   
 1385       public int getThumbnailWidth(int imageIndex, int thumbnailIndex)
 1386           throws IOException {
 1387           setThreadLock();
 1388           try {
 1389               if ((thumbnailIndex < 0)
 1390                   || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
 1391                   throw new IndexOutOfBoundsException("No such thumbnail");
 1392               }
 1393               // Now we know that there is a jfif segment
 1394               JFIFMarkerSegment jfif =
 1395                   (JFIFMarkerSegment) imageMetadata.findMarkerSegment
 1396                   (JFIFMarkerSegment.class, true);
 1397               return  jfif.getThumbnailWidth(thumbnailIndex);
 1398           } finally {
 1399               clearThreadLock();
 1400           }
 1401       }
 1402   
 1403       public int getThumbnailHeight(int imageIndex, int thumbnailIndex)
 1404           throws IOException {
 1405           setThreadLock();
 1406           try {
 1407               if ((thumbnailIndex < 0)
 1408                   || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
 1409                   throw new IndexOutOfBoundsException("No such thumbnail");
 1410               }
 1411               // Now we know that there is a jfif segment
 1412               JFIFMarkerSegment jfif =
 1413                   (JFIFMarkerSegment) imageMetadata.findMarkerSegment
 1414                   (JFIFMarkerSegment.class, true);
 1415               return  jfif.getThumbnailHeight(thumbnailIndex);
 1416           } finally {
 1417               clearThreadLock();
 1418           }
 1419       }
 1420   
 1421       public BufferedImage readThumbnail(int imageIndex,
 1422                                          int thumbnailIndex)
 1423           throws IOException {
 1424           setThreadLock();
 1425           try {
 1426               if ((thumbnailIndex < 0)
 1427                   || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
 1428                   throw new IndexOutOfBoundsException("No such thumbnail");
 1429               }
 1430               // Now we know that there is a jfif segment and that iis is good
 1431               JFIFMarkerSegment jfif =
 1432                   (JFIFMarkerSegment) imageMetadata.findMarkerSegment
 1433                   (JFIFMarkerSegment.class, true);
 1434               return  jfif.getThumbnail(iis, thumbnailIndex, this);
 1435           } finally {
 1436               clearThreadLock();
 1437           }
 1438       }
 1439   
 1440       private void resetInternalState() {
 1441           // reset C structures
 1442           resetReader(structPointer);
 1443   
 1444           // reset local Java structures
 1445           numImages = 0;
 1446           imagePositions = new ArrayList();
 1447           currentImage = -1;
 1448           image = null;
 1449           raster = null;
 1450           target = null;
 1451           buffer = null;
 1452           destROI = null;
 1453           destinationBands = null;
 1454           streamMetadata = null;
 1455           imageMetadata = null;
 1456           imageMetadataIndex = -1;
 1457           haveSeeked = false;
 1458           tablesOnlyChecked = false;
 1459           iccCS = null;
 1460           initProgressData();
 1461       }
 1462   
 1463       public void reset() {
 1464           setThreadLock();
 1465           try {
 1466               super.reset();
 1467           } finally {
 1468               clearThreadLock();
 1469           }
 1470       }
 1471   
 1472       private native void resetReader(long structPointer);
 1473   
 1474       public void dispose() {
 1475           setThreadLock();
 1476           try {
 1477               if (structPointer != 0) {
 1478                   disposerRecord.dispose();
 1479                   structPointer = 0;
 1480               }
 1481           } finally {
 1482               clearThreadLock();
 1483           }
 1484       }
 1485   
 1486       private static native void disposeReader(long structPointer);
 1487   
 1488       private static class JPEGReaderDisposerRecord implements DisposerRecord {
 1489           private long pData;
 1490   
 1491           public JPEGReaderDisposerRecord(long pData) {
 1492               this.pData = pData;
 1493           }
 1494   
 1495           public synchronized void dispose() {
 1496               if (pData != 0) {
 1497                   disposeReader(pData);
 1498                   pData = 0;
 1499               }
 1500           }
 1501       }
 1502   
 1503       private Thread theThread = null;
 1504       private int theLockCount = 0;
 1505   
 1506       private synchronized void setThreadLock() {
 1507           Thread currThread = Thread.currentThread();
 1508           if (theThread != null) {
 1509               if (theThread != currThread) {
 1510                   // it looks like that this reader instance is used
 1511                   // by multiple threads.
 1512                   throw new IllegalStateException("Attempt to use instance of " +
 1513                                                   this + " locked on thread " +
 1514                                                   theThread + " from thread " +
 1515                                                   currThread);
 1516               } else {
 1517                   theLockCount ++;
 1518               }
 1519           } else {
 1520               theThread = currThread;
 1521               theLockCount = 1;
 1522           }
 1523       }
 1524   
 1525       private synchronized void clearThreadLock() {
 1526           Thread currThread = Thread.currentThread();
 1527           if (theThread == null || theThread != currThread) {
 1528               throw new IllegalStateException("Attempt to clear thread lock " +
 1529                                               " form wrong thread." +
 1530                                               " Locked thread: " + theThread +
 1531                                               "; current thread: " + currThread);
 1532           }
 1533           theLockCount --;
 1534           if (theLockCount == 0) {
 1535               theThread = null;
 1536           }
 1537       }
 1538   }
 1539   
 1540   /**
 1541    * An internal helper class that wraps producer's iterator
 1542    * and extracts specifier instances on demand.
 1543    */
 1544   class ImageTypeIterator implements Iterator<ImageTypeSpecifier> {
 1545        private Iterator<ImageTypeProducer> producers;
 1546        private ImageTypeSpecifier theNext = null;
 1547   
 1548        public ImageTypeIterator(Iterator<ImageTypeProducer> producers) {
 1549            this.producers = producers;
 1550        }
 1551   
 1552        public boolean hasNext() {
 1553            if (theNext != null) {
 1554                return true;
 1555            }
 1556            if (!producers.hasNext()) {
 1557                return false;
 1558            }
 1559            do {
 1560                theNext = producers.next().getType();
 1561            } while (theNext == null && producers.hasNext());
 1562   
 1563            return (theNext != null);
 1564        }
 1565   
 1566        public ImageTypeSpecifier next() {
 1567            if (theNext != null || hasNext()) {
 1568                ImageTypeSpecifier t = theNext;
 1569                theNext = null;
 1570                return t;
 1571            } else {
 1572                throw new NoSuchElementException();
 1573            }
 1574        }
 1575   
 1576        public void remove() {
 1577            producers.remove();
 1578        }
 1579   }
 1580   
 1581   /**
 1582    * An internal helper class that provides means for deferred creation
 1583    * of ImageTypeSpecifier instance required to describe available
 1584    * destination types.
 1585    *
 1586    * This implementation only supports standard
 1587    * jpeg color spaces (defined by corresponding JCS color space code).
 1588    *
 1589    * To support other color spaces one can override produce() method to
 1590    * return custom instance of ImageTypeSpecifier.
 1591    */
 1592   class ImageTypeProducer {
 1593   
 1594       private ImageTypeSpecifier type = null;
 1595       boolean failed = false;
 1596       private int csCode;
 1597   
 1598       public ImageTypeProducer(int csCode) {
 1599           this.csCode = csCode;
 1600       }
 1601   
 1602       public ImageTypeProducer() {
 1603           csCode = -1; // undefined
 1604       }
 1605   
 1606       public synchronized ImageTypeSpecifier getType() {
 1607           if (!failed && type == null) {
 1608               try {
 1609                   type = produce();
 1610               } catch (Throwable e) {
 1611                   failed = true;
 1612               }
 1613           }
 1614           return type;
 1615       }
 1616   
 1617       private static final ImageTypeProducer [] defaultTypes =
 1618               new ImageTypeProducer [JPEG.NUM_JCS_CODES];
 1619   
 1620       public synchronized static ImageTypeProducer getTypeProducer(int csCode) {
 1621           if (csCode < 0 || csCode >= JPEG.NUM_JCS_CODES) {
 1622               return null;
 1623           }
 1624           if (defaultTypes[csCode] == null) {
 1625               defaultTypes[csCode] = new ImageTypeProducer(csCode);
 1626           }
 1627           return defaultTypes[csCode];
 1628       }
 1629   
 1630       protected ImageTypeSpecifier produce() {
 1631           switch (csCode) {
 1632               case JPEG.JCS_GRAYSCALE:
 1633                   return ImageTypeSpecifier.createFromBufferedImageType
 1634                           (BufferedImage.TYPE_BYTE_GRAY);
 1635               case JPEG.JCS_RGB:
 1636                   return ImageTypeSpecifier.createInterleaved(JPEG.JCS.sRGB,
 1637                           JPEG.bOffsRGB,
 1638                           DataBuffer.TYPE_BYTE,
 1639                           false,
 1640                           false);
 1641               case JPEG.JCS_RGBA:
 1642                   return ImageTypeSpecifier.createPacked(JPEG.JCS.sRGB,
 1643                           0xff000000,
 1644                           0x00ff0000,
 1645                           0x0000ff00,
 1646                           0x000000ff,
 1647                           DataBuffer.TYPE_INT,
 1648                           false);
 1649               case JPEG.JCS_YCC:
 1650                   if (JPEG.JCS.getYCC() != null) {
 1651                       return ImageTypeSpecifier.createInterleaved(
 1652                               JPEG.JCS.getYCC(),
 1653                           JPEG.bandOffsets[2],
 1654                           DataBuffer.TYPE_BYTE,
 1655                           false,
 1656                           false);
 1657                   } else {
 1658                       return null;
 1659                   }
 1660               case JPEG.JCS_YCCA:
 1661                   if (JPEG.JCS.getYCC() != null) {
 1662                       return ImageTypeSpecifier.createInterleaved(
 1663                               JPEG.JCS.getYCC(),
 1664                           JPEG.bandOffsets[3],
 1665                           DataBuffer.TYPE_BYTE,
 1666                           true,
 1667                           false);
 1668                   } else {
 1669                       return null;
 1670                   }
 1671               default:
 1672                   return null;
 1673           }
 1674       }
 1675   }

Home » openjdk-7 » com.sun.imageio.plugins » jpeg » [javadoc | source]