KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sun > imageio > plugins > jpeg > JPEGImageReader


1 /*
2  * @(#)JPEGImageReader.java 1.49 04/03/29
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 package com.sun.imageio.plugins.jpeg;
9
10 import javax.imageio.IIOException JavaDoc;
11 import javax.imageio.ImageReader JavaDoc;
12 import javax.imageio.ImageReadParam JavaDoc;
13 import javax.imageio.ImageTypeSpecifier JavaDoc;
14 import javax.imageio.metadata.IIOMetadata JavaDoc;
15 import javax.imageio.spi.ImageReaderSpi JavaDoc;
16 import javax.imageio.stream.ImageInputStream JavaDoc;
17 import javax.imageio.plugins.jpeg.JPEGImageReadParam JavaDoc;
18 import javax.imageio.plugins.jpeg.JPEGQTable JavaDoc;
19 import javax.imageio.plugins.jpeg.JPEGHuffmanTable JavaDoc;
20
21 import java.awt.Point JavaDoc;
22 import java.awt.Rectangle JavaDoc;
23 import java.awt.color.ColorSpace JavaDoc;
24 import java.awt.color.ICC_Profile JavaDoc;
25 import java.awt.color.ICC_ColorSpace JavaDoc;
26 import java.awt.image.BufferedImage JavaDoc;
27 import java.awt.image.Raster JavaDoc;
28 import java.awt.image.WritableRaster JavaDoc;
29 import java.awt.image.DataBuffer JavaDoc;
30 import java.awt.image.DataBufferByte JavaDoc;
31 import java.awt.image.ColorModel JavaDoc;
32 import java.awt.image.IndexColorModel JavaDoc;
33 import java.awt.image.ColorConvertOp JavaDoc;
34 import java.io.IOException JavaDoc;
35 import java.util.List JavaDoc;
36 import java.util.Iterator JavaDoc;
37 import java.util.ArrayList JavaDoc;
38
39 import sun.java2d.Disposer;
40 import sun.java2d.DisposerRecord;
41
42 public class JPEGImageReader extends ImageReader JavaDoc {
43
44     private boolean debug = false;
45
46     /**
47      * The following variable contains a pointer to the IJG library
48      * structure for this reader. It is assigned in the constructor
49      * and then is passed in to every native call. It is set to 0
50      * by dispose to avoid disposing twice.
51      */

52     private long structPointer = 0;
53
54     /** The input stream we read from */
55     private ImageInputStream JavaDoc iis = null;
56
57     /**
58      * List of stream positions for images, reinitialized every time
59      * a new input source is set.
60      */

61     private List JavaDoc imagePositions = null;
62
63     /**
64      * The number of images in the stream, or 0.
65      */

66     private int numImages = 0;
67
68     static {
69         java.security.AccessController.doPrivileged(
70             new sun.security.action.LoadLibraryAction("jpeg"));
71         initReaderIDs(ImageInputStream JavaDoc.class,
72                       JPEGQTable JavaDoc.class,
73                       JPEGHuffmanTable JavaDoc.class);
74     }
75
76     // The following warnings are converted to strings when used
77
// as keys to get localized resources from JPEGImageReaderResources
78
// and its children.
79

80     /**
81      * Warning code to be passed to warningOccurred to indicate
82      * that the EOI marker is missing from the end of the stream.
83      * This usually signals that the stream is corrupted, but
84      * everything up to the last MCU should be usable.
85      */

86     protected static final int WARNING_NO_EOI = 0;
87
88     /**
89      * Warning code to be passed to warningOccurred to indicate
90      * that a JFIF segment was encountered inside a JFXX JPEG
91      * thumbnail and is being ignored.
92      */

93     protected static final int WARNING_NO_JFIF_IN_THUMB = 1;
94
95     private static final int MAX_WARNING = WARNING_NO_JFIF_IN_THUMB;
96
97     /**
98      * Image index of image for which header information
99      * is available.
100      */

101     private int currentImage = -1;
102     
103     // The following is copied out from C after reading the header.
104
// Unlike metadata, which may never be retrieved, we need this
105
// if we are to read an image at all.
106

107     /** Set by setImageData native code callback */
108     private int width;
109     /** Set by setImageData native code callback */
110     private int height;
111     /**
112      * Set by setImageData native code callback. A modified
113      * IJG+NIFTY colorspace code.
114      */

115     private int colorSpaceCode;
116     /**
117      * Set by setImageData native code callback. A modified
118      * IJG+NIFTY colorspace code.
119      */

120     private int outColorSpaceCode;
121     /** Set by setImageData native code callback */
122     private int numComponents;
123     /** Set by setImageData native code callback */
124     private ColorSpace JavaDoc iccCS = null;
125
126
127     /** If we need to post-convert in Java, convert with this op */
128     private ColorConvertOp JavaDoc convert = null;
129
130     /** The image we are going to fill */
131     private BufferedImage JavaDoc image = null;
132
133     /** An intermediate Raster to hold decoded data */
134     private WritableRaster JavaDoc raster = null;
135
136     /** A view of our target Raster that we can setRect to */
137     private WritableRaster JavaDoc target = null;
138
139     /** The databuffer for the above Raster */
140     private DataBufferByte JavaDoc buffer = null;
141
142     /** The region in the destination where we will write pixels */
143     private Rectangle JavaDoc destROI = null;
144
145     /** The list of destination bands, if any */
146     private int [] destinationBands = null;
147
148     /** Stream metadata, cached, even when the stream is changed. */
149     private JPEGMetadata streamMetadata = null;
150
151     /** Image metadata, valid for the imageMetadataIndex only. */
152     private JPEGMetadata imageMetadata = null;
153     private int imageMetadataIndex = -1;
154
155     /**
156      * Set to true every time we seek in the stream; used to
157      * invalidate the native buffer contents in C.
158      */

159     private boolean haveSeeked = false;
160
161     /**
162      * Tables that have been read from a tables-only image at the
163      * beginning of a stream.
164      */

165     private JPEGQTable JavaDoc [] abbrevQTables = null;
166     private JPEGHuffmanTable JavaDoc[] abbrevDCHuffmanTables = null;
167     private JPEGHuffmanTable JavaDoc[] abbrevACHuffmanTables = null;
168
169     private int minProgressivePass = 0;
170     private int maxProgressivePass = Integer.MAX_VALUE;
171
172     /**
173      * Variables used by progress monitoring.
174      */

175     private static final int UNKNOWN = -1; // Number of passes
176
private static final int MIN_ESTIMATED_PASSES = 10; // IJG default
177
private int knownPassCount = UNKNOWN;
178     private int pass = 0;
179     private float percentToDate = 0.0F;
180     private float previousPassPercentage = 0.0F;
181     private int progInterval = 0;
182
183     /**
184      * Set to true once stream has been checked for stream metadata
185      */

186     private boolean tablesOnlyChecked = false;
187
188     /** The referent to be registered with the Disposer. */
189     private Object JavaDoc disposerReferent = new Object JavaDoc();
190
191     /** The DisposerRecord that handles the actual disposal of this reader. */
192     private DisposerRecord disposerRecord;
193
194     /**
195      * Maintain an array of the default image types corresponding to the
196      * various supported IJG colorspace codes.
197      */

198     private static final ImageTypeSpecifier JavaDoc [] defaultTypes =
199         new ImageTypeSpecifier JavaDoc [JPEG.NUM_JCS_CODES];
200
201     static {
202         defaultTypes[JPEG.JCS_GRAYSCALE] =
203             ImageTypeSpecifier.createFromBufferedImageType
204             (BufferedImage.TYPE_BYTE_GRAY);
205         defaultTypes[JPEG.JCS_RGB] =
206             ImageTypeSpecifier.createInterleaved
207             (JPEG.sRGB,
208              JPEG.bOffsRGB,
209              DataBuffer.TYPE_BYTE,
210              false,
211              false);
212         defaultTypes[JPEG.JCS_RGBA] =
213             ImageTypeSpecifier.createPacked
214             (JPEG.sRGB,
215              0xff000000,
216              0x00ff0000,
217              0x0000ff00,
218              0x000000ff,
219              DataBuffer.TYPE_INT,
220              false);
221         if (JPEG.YCC != null) {
222             defaultTypes[JPEG.JCS_YCC] =
223                 ImageTypeSpecifier.createInterleaved
224                 (JPEG.YCC,
225                  JPEG.bandOffsets[2],
226                  DataBuffer.TYPE_BYTE,
227                  false,
228                  false);
229             defaultTypes[JPEG.JCS_YCCA] =
230                 ImageTypeSpecifier.createInterleaved
231                 (JPEG.YCC,
232                  JPEG.bandOffsets[3],
233                  DataBuffer.TYPE_BYTE,
234                  true,
235                  false);
236         }
237     }
238
239     /** Sets up static C structures. */
240     private static native void initReaderIDs(Class JavaDoc iisClass,
241                                              Class JavaDoc qTableClass,
242                                              Class JavaDoc huffClass);
243
244     public JPEGImageReader(ImageReaderSpi JavaDoc originator) {
245         super(originator);
246         structPointer = initJPEGImageReader();
247         disposerRecord = new JPEGReaderDisposerRecord(structPointer);
248         Disposer.addRecord(disposerReferent, disposerRecord);
249     }
250
251     /** Sets up per-reader C structure and returns a pointer to it. */
252     private native long initJPEGImageReader();
253
254     /**
255      * Called by the native code or other classes to signal a warning.
256      * The code is used to lookup a localized message to be used when
257      * sending warnings to listeners.
258      */

259     protected void warningOccurred(int code) {
260         if ((code < 0) || (code > MAX_WARNING)){
261             throw new InternalError JavaDoc("Invalid warning index");
262         }
263         processWarningOccurred
264             ("com.sun.imageio.plugins.jpeg.JPEGImageReaderResources",
265              Integer.toString(code));
266     }
267
268     /**
269      * The library has it's own error facility that emits warning messages.
270      * This routine is called by the native code when it has already
271      * formatted a string for output.
272      * XXX For truly complete localization of all warning messages,
273      * the sun_jpeg_output_message routine in the native code should
274      * send only the codes and parameters to a method here in Java,
275      * which will then format and send the warnings, using localized
276      * strings. This method will have to deal with all the parameters
277      * and formats (%u with possibly large numbers, %02d, %02x, etc.)
278      * that actually occur in the JPEG library. For now, this prevents
279      * library warnings from being printed to stderr.
280      */

281     protected void warningWithMessage(String JavaDoc msg) {
282         processWarningOccurred(msg);
283     }
284
285     public void setInput(Object JavaDoc input,
286                          boolean seekForwardOnly,
287                          boolean ignoreMetadata)
288     {
289         super.setInput(input, seekForwardOnly, ignoreMetadata);
290         this.ignoreMetadata = ignoreMetadata;
291         resetInternalState();
292         iis = (ImageInputStream JavaDoc) input; // Always works
293
setSource(structPointer, iis);
294     }
295
296     private native void setSource(long structPointer,
297                                   ImageInputStream JavaDoc source);
298
299     private void checkTablesOnly() throws IOException JavaDoc {
300         if (debug) {
301             System.out.println("Checking for tables-only image");
302         }
303         long savePos = iis.getStreamPosition();
304         if (debug) {
305             System.out.println("saved pos is " + savePos);
306             System.out.println("length is " + iis.length());
307         }
308         // Read the first header
309
boolean tablesOnly = readNativeHeader(true);
310         if (tablesOnly) {
311             if (debug) {
312                 System.out.println("tables-only image found");
313                 long pos = iis.getStreamPosition();
314                 System.out.println("pos after return from native is " + pos);
315             }
316             // This reads the tables-only image twice, once from C
317
// and once from Java, but only if ignoreMetadata is false
318
if (ignoreMetadata == false) {
319                 iis.seek(savePos);
320                 haveSeeked = true;
321                 streamMetadata = new JPEGMetadata(true, false,
322                                                   iis, this);
323                 long pos = iis.getStreamPosition();
324                 if (debug) {
325                     System.out.println
326                         ("pos after constructing stream metadata is " + pos);
327                 }
328             }
329             // Now we are at the first image if there are any, so add it
330
// to the list
331
if (hasNextImage()) {
332                 imagePositions.add(new Long JavaDoc(iis.getStreamPosition()));
333             }
334         } else { // Not tables only, so add original pos to the list
335
imagePositions.add(new Long JavaDoc(savePos));
336             // And set current image since we've read it now
337
currentImage = 0;
338         }
339         if (seekForwardOnly) {
340             Long JavaDoc pos = (Long JavaDoc) imagePositions.get(imagePositions.size()-1);
341             iis.flushBefore(pos.longValue());
342         }
343         tablesOnlyChecked = true;
344     }
345
346     public int getNumImages(boolean allowSearch) throws IOException JavaDoc {
347         if (numImages != 0) {
348             return numImages;
349         }
350         if (iis == null) {
351             throw new IllegalStateException JavaDoc("Input not set");
352         }
353         if (allowSearch == true) {
354             if (seekForwardOnly) {
355                 throw new IllegalStateException JavaDoc(
356                     "seekForwardOnly and allowSearch can't both be true!");
357             }
358             // Otherwise we have to read the entire stream
359

360             if (!tablesOnlyChecked) {
361                 checkTablesOnly();
362             }
363             
364             iis.mark();
365
366             gotoImage(0);
367
368             JPEGBuffer buffer = new JPEGBuffer(iis);
369             buffer.loadBuf(0);
370
371             boolean done = false;
372             while (!done) {
373                 done = buffer.scanForFF(this);
374                 switch (buffer.buf[buffer.bufPtr] & 0xff) {
375                 case JPEG.SOI:
376                     numImages++;
377                     // FALL THROUGH to decrement buffer vars
378
// This first set doesn't have a length
379
case 0: // not a marker, just a data 0xff
380
case JPEG.RST0:
381                 case JPEG.RST1:
382                 case JPEG.RST2:
383                 case JPEG.RST3:
384                 case JPEG.RST4:
385                 case JPEG.RST5:
386                 case JPEG.RST6:
387                 case JPEG.RST7:
388                 case JPEG.EOI:
389                     buffer.bufAvail--;
390                     buffer.bufPtr++;
391                     break;
392                     // All the others have a length
393
default:
394                     buffer.bufAvail--;
395                     buffer.bufPtr++;
396                     buffer.loadBuf(2);
397                     int length = ((buffer.buf[buffer.bufPtr++] & 0xff) << 8) |
398                         (buffer.buf[buffer.bufPtr++] & 0xff);
399                     buffer.bufAvail -= 2;
400                     length -= 2; // length includes itself
401
buffer.skipData(length);
402                 }
403             }
404             
405             
406             iis.reset();
407
408             return numImages;
409         }
410
411         return -1; // Search is necessary for JPEG
412
}
413
414     /**
415      * Sets the input stream to the start of the requested image.
416      * <pre>
417      * @exception IllegalStateException if the input source has not been
418      * set.
419      * @exception IndexOutOfBoundsException if the supplied index is
420      * out of bounds.
421      * </pre>
422      */

423     private void gotoImage(int imageIndex) throws IOException JavaDoc {
424         if (iis == null) {
425             throw new IllegalStateException JavaDoc("Input not set");
426         }
427         if (imageIndex < minIndex) {
428             throw new IndexOutOfBoundsException JavaDoc();
429         }
430         if (!tablesOnlyChecked) {
431             checkTablesOnly();
432         }
433         if (imageIndex < imagePositions.size()) {
434             iis.seek(((Long JavaDoc)(imagePositions.get(imageIndex))).longValue());
435         } else {
436             // read to start of image, saving positions
437
// First seek to the last position we already have, and skip the
438
// entire image
439
Long JavaDoc pos = (Long JavaDoc) imagePositions.get(imagePositions.size()-1);
440             iis.seek(pos.longValue());
441             skipImage();
442             // Now add all intervening positions, skipping images
443
for (int index = imagePositions.size();
444                  index <= imageIndex;
445                  index++) {
446                 // Is there an image?
447
if (!hasNextImage()) {
448                     throw new IndexOutOfBoundsException JavaDoc();
449                 }
450                 pos = new Long JavaDoc(iis.getStreamPosition());
451                 imagePositions.add(pos);
452                 if (seekForwardOnly) {
453                     iis.flushBefore(pos.longValue());
454                 }
455                 if (index < imageIndex) {
456                     skipImage();
457                 } // Otherwise we are where we want to be
458
}
459         }
460
461         if (seekForwardOnly) {
462             minIndex = imageIndex;
463         }
464
465         haveSeeked = true; // No way is native buffer still valid
466
}
467
468     /**
469      * Skip over a complete image in the stream, leaving the stream
470      * positioned such that the next byte to be read is the first
471      * byte of the next image. For JPEG, this means that we read
472      * until we encounter an EOI marker or until the end of the stream.
473      * If the stream ends before an EOI marker is encountered, an
474      * IndexOutOfBoundsException is thrown.
475      */

476     private void skipImage() throws IOException JavaDoc {
477         if (debug) {
478             System.out.println("skipImage called");
479         }
480         boolean foundFF = false;
481         for (int byteval = iis.read();
482              byteval != -1;
483              byteval = iis.read()) {
484
485             if (foundFF == true) {
486                 if (byteval == JPEG.EOI) {
487                     return;
488                 }
489             }
490             foundFF = (byteval == 0xff) ? true : false;
491         }
492         throw new IndexOutOfBoundsException JavaDoc();
493     }
494
495     /**
496      * Returns <code>true</code> if there is an image beyond
497      * the current stream position. Does not disturb the
498      * stream position.
499      */

500     private boolean hasNextImage() throws IOException JavaDoc {
501         if (debug) {
502             System.out.print("hasNextImage called; returning ");
503         }
504         iis.mark();
505         boolean foundFF = false;
506         for (int byteval = iis.read();
507              byteval != -1;
508              byteval = iis.read()) {
509
510             if (foundFF == true) {
511                 if (byteval == JPEG.SOI) {
512                     iis.reset();
513                     if (debug) {
514                         System.out.println("true");
515                     }
516                     return true;
517                 }
518             }
519             foundFF = (byteval == 0xff) ? true : false;
520         }
521         // We hit the end of the stream before we hit an SOI, so no image
522
iis.reset();
523         if (debug) {
524             System.out.println("false");
525         }
526         return false;
527     }
528
529     /**
530      * Push back the given number of bytes to the input stream.
531      * Called by the native code at the end of each image so
532      * that the next one can be identified from Java.
533      */

534     private void pushBack(int num) throws IOException JavaDoc {
535         if (debug) {
536             System.out.println("pushing back " + num + " bytes");
537         }
538         iis.seek(iis.getStreamPosition()-num);
539         // The buffer is clear after this, so no need to set haveSeeked.
540
}
541
542     /**
543      * Reads header information for the given image, if possible.
544      */

545     private void readHeader(int imageIndex, boolean reset)
546         throws IOException JavaDoc {
547         gotoImage(imageIndex);
548         readNativeHeader(reset); // Ignore return
549
currentImage = imageIndex;
550     }
551
552     private boolean readNativeHeader(boolean reset) throws IOException JavaDoc {
553         boolean retval = false;
554         retval = readImageHeader(structPointer, haveSeeked, reset);
555         haveSeeked = false;
556         return retval;
557     }
558
559     /**
560      * Read in the header information starting from the current
561      * stream position, returning <code>true</code> if the
562      * header was a tables-only image. After this call, the
563      * native IJG decompression struct will contain the image
564      * information required by most query calls below
565      * (e.g. getWidth, getHeight, etc.), if the header was not
566      * a tables-only image.
567      * If reset is <code>true</code>, the state of the IJG
568      * object is reset so that it can read a header again.
569      * This happens automatically if the header was a tables-only
570      * image.
571      */

572     private native boolean readImageHeader(long structPointer,
573                                            boolean clearBuffer,
574                                            boolean reset)
575         throws IOException JavaDoc;
576
577     /*
578      * Called by the native code whenever an image header has been
579      * read. Whether we read metadata or not, we always need this
580      * information, so it is passed back independently of
581      * metadata, which may never be read.
582      */

583     private void setImageData(int width,
584                               int height,
585                               int colorSpaceCode,
586                               int outColorSpaceCode,
587                               int numComponents,
588                               byte [] iccData) {
589         this.width = width;
590         this.height = height;
591         this.colorSpaceCode = colorSpaceCode;
592         this.outColorSpaceCode = outColorSpaceCode;
593         this.numComponents = numComponents;
594         iccCS = null;
595         if (iccData != null) {
596             iccCS = new ICC_ColorSpace JavaDoc(ICC_Profile.getInstance(iccData));
597         }
598     }
599
600     public int getWidth(int imageIndex) throws IOException JavaDoc {
601         if (currentImage != imageIndex) {
602             readHeader(imageIndex, true);
603         }
604         return width;
605     }
606
607     public int getHeight(int imageIndex) throws IOException JavaDoc {
608         if (currentImage != imageIndex) {
609             readHeader(imageIndex, true);
610         }
611         return height;
612     }
613
614     /////////// Color Conversion and Image Types
615

616     /**
617      * Return an ImageTypeSpecifier corresponding to the given
618      * color space code, or null if the color space is unsupported.
619      */

620     private ImageTypeSpecifier JavaDoc getImageType(int code) {
621         ImageTypeSpecifier JavaDoc ret = null;
622
623         if ((code > 0) && (code < JPEG.NUM_JCS_CODES)) {
624             ret = defaultTypes[code];
625         }
626         return ret;
627     }
628
629     public ImageTypeSpecifier JavaDoc getRawImageType(int imageIndex)
630         throws IOException JavaDoc {
631         if (currentImage != imageIndex) {
632             readHeader(imageIndex, true);
633         }
634         // Returns null if it can't be represented
635
return getImageType(colorSpaceCode);
636     }
637
638     public Iterator JavaDoc getImageTypes(int imageIndex)
639         throws IOException JavaDoc {
640         if (currentImage != imageIndex) {
641             readHeader(imageIndex, true);
642         }
643
644         // We return an iterator containing the default, any
645
// conversions that the library provides, and
646
// all the other default types with the same number
647
// of components, as we can do these as a post-process.
648
// As we convert Rasters rather than images, images
649
// with alpha cannot be converted in a post-process.
650

651         // If this image can't be interpreted, this method
652
// returns an empty Iterator.
653

654         // Get the raw ITS, if there is one. Note that this
655
// won't always be the same as the default.
656
ImageTypeSpecifier JavaDoc raw = getImageType(colorSpaceCode);
657         
658         // Given the encoded colorspace, build a list of ITS's
659
// representing outputs you could handle starting
660
// with the default.
661

662         ArrayList JavaDoc list = new ArrayList JavaDoc(1);
663
664         switch (colorSpaceCode) {
665         case JPEG.JCS_GRAYSCALE:
666             list.add(raw);
667             list.add(getImageType(JPEG.JCS_RGB));
668             break;
669         case JPEG.JCS_RGB:
670             list.add(raw);
671             list.add(getImageType(JPEG.JCS_GRAYSCALE));
672             if (JPEG.YCC != null) {
673                 list.add(getImageType(JPEG.JCS_YCC));
674             }
675             break;
676         case JPEG.JCS_RGBA:
677             list.add(raw);
678             break;
679         case JPEG.JCS_YCC:
680             if (raw != null) { // Might be null if PYCC.pf not installed
681
list.add(raw);
682                 list.add(getImageType(JPEG.JCS_RGB));
683             }
684             break;
685         case JPEG.JCS_YCCA:
686             if (raw != null) { // Might be null if PYCC.pf not installed
687
list.add(raw);
688             }
689             break;
690         case JPEG.JCS_YCbCr:
691             // As there is no YCbCr ColorSpace, we can't support
692
// the raw type.
693
// If there is an ICC Profile, use that as the default
694
if (iccCS != null) {
695                 list.add(ImageTypeSpecifier.createInterleaved
696                          (iccCS,
697                           JPEG.bOffsRGB, // Assume it's for RGB
698
DataBuffer.TYPE_BYTE,
699                           false,
700                           false));
701
702             }
703             list.add(getImageType(JPEG.JCS_RGB));
704             list.add(getImageType(JPEG.JCS_GRAYSCALE));
705             if (JPEG.YCC != null) { // Might be null if PYCC.pf not installed
706
list.add(getImageType(JPEG.JCS_YCC));
707             }
708             break;
709         case JPEG.JCS_YCbCrA: // Default is to convert to RGBA
710
// As there is no YCbCr ColorSpace, we can't support
711
// the raw type.
712
list.add(getImageType(JPEG.JCS_RGBA));
713             break;
714         }
715         
716         return list.iterator();
717     }
718
719     /**
720      * Checks the implied color conversion between the stream and
721      * the target image, altering the IJG output color space if necessary.
722      * If a java color conversion is required, then this sets up
723      * <code>convert</code>.
724      * If bands are being rearranged at all (either source or destination
725      * bands are specified in the param), then the default color
726      * conversions are assumed to be correct.
727      * Throws an IIOException if there is no conversion available.
728      */

729     private void checkColorConversion(BufferedImage JavaDoc image,
730                                       ImageReadParam JavaDoc param)
731         throws IIOException JavaDoc {
732
733         // If we are rearranging channels at all, the default
734
// conversions remain in place. If the user wants
735
// raw channels then he should do this while reading
736
// a Raster.
737
if (param != null) {
738             if ((param.getSourceBands() != null) ||
739                 (param.getDestinationBands() != null)) {
740                 // Accept default conversions out of decoder, silently
741
return;
742             }
743         }
744
745         // XXX - We do not currently support any indexed color models,
746
// though we could, as IJG will quantize for us.
747
// This is a performance and memory-use issue, as
748
// users can read RGB and then convert to indexed in Java.
749

750         ColorModel JavaDoc cm = image.getColorModel();
751
752         if (cm instanceof IndexColorModel JavaDoc) {
753             throw new IIOException JavaDoc("IndexColorModel not supported");
754         }
755         
756         // Now check the ColorSpace type against outColorSpaceCode
757
// We may want to tweak the default
758
ColorSpace JavaDoc cs = cm.getColorSpace();
759         int csType = cs.getType();
760         convert = null;
761         switch (outColorSpaceCode) {
762         case JPEG.JCS_GRAYSCALE: // Its gray in the file
763
if (csType == ColorSpace.TYPE_RGB) { // We want RGB
764
// IJG can do this for us more efficiently
765
setOutColorSpace(structPointer, JPEG.JCS_RGB);
766             } else if (csType != ColorSpace.TYPE_GRAY) {
767                 throw new IIOException JavaDoc("Incompatible color conversion");
768             }
769             break;
770         case JPEG.JCS_RGB: // IJG wants to go to RGB
771
if (csType == ColorSpace.TYPE_GRAY) { // We want gray
772
if (colorSpaceCode == JPEG.JCS_YCbCr) {
773                     // If the jpeg space is YCbCr, IJG can do it
774
setOutColorSpace(structPointer, JPEG.JCS_GRAYSCALE);
775                 }
776             } else if ((iccCS != null) &&
777                        (cm.getNumComponents() == numComponents) &&
778                        (cs != iccCS)) {
779                 // We have an ICC profile but it isn't used in the dest
780
// image. So convert from the profile cs to the target cs
781
convert = new ColorConvertOp JavaDoc(iccCS, cs, null);
782                 // Leave IJG conversion in place; we still need it
783
} else if ((!cs.isCS_sRGB()) &&
784                        (cm.getNumComponents() == numComponents)) {
785                 // Target isn't sRGB, so convert from sRGB to the target
786
convert = new ColorConvertOp JavaDoc(JPEG.sRGB, cs, null);
787             } else if (csType != ColorSpace.TYPE_RGB) {
788                 throw new IIOException JavaDoc("Incompatible color conversion");
789             }
790             break;
791         case JPEG.JCS_RGBA:
792             // No conversions available; image must be RGBA
793
if ((csType != ColorSpace.TYPE_RGB) ||
794                 (cm.getNumComponents() != numComponents)) {
795                 throw new IIOException JavaDoc("Incompatible color conversion");
796             }
797             break;
798         case JPEG.JCS_YCC:
799             if (JPEG.YCC == null) { // We can't do YCC at all
800
throw new IIOException JavaDoc("Incompatible color conversion");
801             }
802             if ((cs != JPEG.YCC) &&
803                 (cm.getNumComponents() == numComponents)) {
804                 convert = new ColorConvertOp JavaDoc(JPEG.YCC, cs, null);
805             }
806             break;
807         case JPEG.JCS_YCCA:
808             // No conversions available; image must be YCCA
809
if ((JPEG.YCC == null) || // We can't do YCC at all
810
(cs != JPEG.YCC) ||
811                 (cm.getNumComponents() != numComponents)) {
812                 throw new IIOException JavaDoc("Incompatible color conversion");
813             }
814             break;
815         default:
816             // Anything else we can't handle at all
817
throw new IIOException JavaDoc("Incompatible color conversion");
818         }
819     }
820
821     /**
822      * Set the IJG output space to the given value. The library will
823      * perform the appropriate colorspace conversions.
824      */

825     private native void setOutColorSpace(long structPointer, int id);
826
827     /////// End of Color Conversion & Image Types
828

829     public ImageReadParam JavaDoc getDefaultReadParam() {
830         return new JPEGImageReadParam JavaDoc();
831     }
832
833     public IIOMetadata JavaDoc getStreamMetadata() throws IOException JavaDoc {
834         if (!tablesOnlyChecked) {
835             checkTablesOnly();
836         }
837         return streamMetadata;
838     }
839
840     public IIOMetadata JavaDoc getImageMetadata(int imageIndex)
841         throws IOException JavaDoc {
842
843         // imageMetadataIndex will always be either a valid index or
844
// -1, in which case imageMetadata will not be null.
845
// So we can leave checking imageIndex for gotoImage.
846
if ((imageMetadataIndex == imageIndex)
847             && (imageMetadata != null)) {
848             return imageMetadata;
849         }
850
851         gotoImage(imageIndex);
852         
853         imageMetadata = new JPEGMetadata(false, false, iis, this);
854         
855         imageMetadataIndex = imageIndex;
856
857         return imageMetadata;
858         
859     }
860
861     public BufferedImage JavaDoc read(int imageIndex, ImageReadParam JavaDoc param)
862         throws IOException JavaDoc {
863         try {
864             readInternal(imageIndex, param, false);
865         } catch (RuntimeException JavaDoc e) {
866             resetLibraryState(structPointer);
867             throw e;
868         } catch (IOException JavaDoc e) {
869             resetLibraryState(structPointer);
870             throw e;
871         }
872         BufferedImage JavaDoc ret = image;
873         image = null; // don't keep a reference here
874
return ret;
875     }
876
877     private Raster JavaDoc readInternal(int imageIndex,
878                                 ImageReadParam JavaDoc param,
879                                 boolean wantRaster) throws IOException JavaDoc {
880         readHeader(imageIndex, false);
881         
882         WritableRaster JavaDoc imRas = null;
883         int numImageBands = 0;
884
885         if (!wantRaster){
886             // Can we read this image?
887
Iterator JavaDoc imageTypes = getImageTypes(imageIndex);
888             if (imageTypes.hasNext() == false) {
889                 throw new IIOException JavaDoc("Unsupported Image Type");
890             }
891
892             image = getDestination(param, imageTypes, width, height);
893             imRas = image.getRaster();
894
895             // The destination may still be incompatible.
896

897             numImageBands = image.getSampleModel().getNumBands();
898
899             // Check whether we can handle any implied color conversion
900

901             // Throws IIOException if the stream and the image are
902
// incompatible, and sets convert if a java conversion
903
// is necessary
904
checkColorConversion(image, param);
905
906             // Check the source and destination bands in the param
907
checkReadParamBandSettings(param, numComponents, numImageBands);
908         } else {
909             // Set the output color space equal to the input colorspace
910
// This disables all conversions
911
setOutColorSpace(structPointer, colorSpaceCode);
912             image = null;
913         }
914
915         // Create an intermediate 1-line Raster that will hold the decoded,
916
// subsampled, clipped, band-selected image data in a single
917
// byte-interleaved buffer. The above transformations
918
// will occur in C for performance. Every time this Raster
919
// is filled we will call back to acceptPixels below to copy
920
// this to whatever kind of buffer our image has.
921

922         int [] srcBands = JPEG.bandOffsets[numComponents-1];
923         int numRasterBands = (wantRaster ? numComponents : numImageBands);
924         destinationBands = null;
925
926         Rectangle JavaDoc srcROI = new Rectangle JavaDoc(0, 0, 0, 0);
927         destROI = new Rectangle JavaDoc(0, 0, 0, 0);
928         computeRegions(param, width, height, image, srcROI, destROI);
929
930         int periodX = 1;
931         int periodY = 1;
932
933         minProgressivePass = 0;
934         maxProgressivePass = Integer.MAX_VALUE;
935
936         if (param != null) {
937             periodX = param.getSourceXSubsampling();
938             periodY = param.getSourceYSubsampling();
939
940             int[] sBands = param.getSourceBands();
941             if (sBands != null) {
942                 srcBands = sBands;
943                 numRasterBands = srcBands.length;
944             }
945             if (!wantRaster) { // ignore dest bands for Raster
946
destinationBands = param.getDestinationBands();
947             }
948
949             minProgressivePass = param.getSourceMinProgressivePass();
950             maxProgressivePass = param.getSourceMaxProgressivePass();
951
952             if (param instanceof JPEGImageReadParam JavaDoc) {
953                 JPEGImageReadParam JavaDoc jparam = (JPEGImageReadParam JavaDoc) param;
954                 if (jparam.areTablesSet()) {
955                     abbrevQTables = jparam.getQTables();
956                     abbrevDCHuffmanTables = jparam.getDCHuffmanTables();
957                     abbrevACHuffmanTables = jparam.getACHuffmanTables();
958                 }
959             }
960         }
961
962         int lineSize = destROI.width*numRasterBands;
963         
964         buffer = new DataBufferByte JavaDoc(lineSize);
965
966         int [] bandOffs = JPEG.bandOffsets[numRasterBands-1];
967         
968         raster = Raster.createInterleavedRaster(buffer,
969                                                 destROI.width, 1,
970                                                 lineSize,
971                                                 numRasterBands,
972                                                 bandOffs,
973                                                 null);
974         
975         // Now that we have the Raster we'll decode to, get a view of the
976
// target Raster that will permit a simple setRect for each scanline
977
if (wantRaster) {
978             target = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
979                                                      destROI.width,
980                                                      destROI.height,
981                                                      lineSize,
982                                                      numRasterBands,
983                                                      bandOffs,
984                                                      null);
985         } else {
986         target = imRas;
987         }
988         int [] bandSizes = target.getSampleModel().getSampleSize();
989
990         /*
991          * If the process is sequential, and we have restart markers,
992          * we could skip to the correct restart marker, if the library
993          * lets us. That's an optimization to investigate later.
994          */

995
996         // Check for update listeners (don't call back if none)
997
boolean callbackUpdates = ((updateListeners != null)
998                                    || (progressListeners != null));
999
1000        // Set up progression data
1001
initProgressData();
1002        // if we have a metadata object, we can count the scans
1003
// and set knownPassCount
1004
if (imageIndex == imageMetadataIndex) { // We have metadata
1005
knownPassCount = 0;
1006            for (Iterator JavaDoc iter = imageMetadata.markerSequence.iterator();
1007                 iter.hasNext();) {
1008                if (iter.next() instanceof SOSMarkerSegment) {
1009                    knownPassCount++;
1010                }
1011            }
1012        }
1013        progInterval = Math.max((target.getHeight()-1) / 20, 1);
1014        if (knownPassCount > 0) {
1015            progInterval *= knownPassCount;
1016        } else if (maxProgressivePass != Integer.MAX_VALUE) {
1017            progInterval *= (maxProgressivePass - minProgressivePass + 1);
1018        }
1019
1020        if (debug) {
1021            System.out.println("**** Read Data *****");
1022            System.out.println("numRasterBands is " + numRasterBands);
1023            System.out.print("srcBands:");
1024            for (int i = 0; i<srcBands.length;i++)
1025                System.out.print(" " + srcBands[i]);
1026            System.out.println();
1027            System.out.println("destination bands is " + destinationBands);
1028            if (destinationBands != null) {
1029                for (int i = 0; i < destinationBands.length; i++) {
1030                    System.out.print(" " + destinationBands[i]);
1031                }
1032                System.out.println();
1033            }
1034            System.out.println("sourceROI is " + srcROI);
1035            System.out.println("destROI is " + destROI);
1036            System.out.println("periodX is " + periodX);
1037            System.out.println("periodY is " + periodY);
1038            System.out.println("minProgressivePass is " + minProgressivePass);
1039            System.out.println("maxProgressivePass is " + maxProgressivePass);
1040            System.out.println("callbackUpdates is " + callbackUpdates);
1041        }
1042
1043        // Finally, we are ready to read
1044

1045        processImageStarted(currentImage);
1046
1047        boolean aborted = false;
1048
1049        aborted = readImage(structPointer,
1050                            buffer.getData(),
1051                            numRasterBands,
1052                            srcBands,
1053                            bandSizes,
1054                            srcROI.x, srcROI.y,
1055                            srcROI.width, srcROI.height,
1056                            periodX, periodY,
1057                            abbrevQTables,
1058                            abbrevDCHuffmanTables,
1059                            abbrevACHuffmanTables,
1060                            minProgressivePass, maxProgressivePass,
1061                            callbackUpdates);
1062
1063        if (aborted) {
1064            processReadAborted();
1065        } else {
1066            processImageComplete();
1067        }
1068
1069        return target;
1070
1071    }
1072
1073    /**
1074     * This method is called back from C when the intermediate Raster
1075     * is full. The parameter indicates the scanline in the target
1076     * Raster to which the intermediate Raster should be copied.
1077     * After the copy, we notify update listeners.
1078     */

1079    private void acceptPixels(int y, boolean progressive) {
1080        if (convert != null) {
1081            convert.filter(raster, raster);
1082        }
1083        target.setRect(destROI.x, destROI.y + y, raster);
1084        
1085        processImageUpdate(image,
1086                           destROI.x, destROI.y+y,
1087                           raster.getWidth(), 1,
1088                           1, 1,
1089                           destinationBands);
1090        if ((y > 0) && (y%progInterval == 0)) {
1091            int height = target.getHeight()-1;
1092            float percentOfPass = ((float)y)/height;
1093            if (progressive) {
1094                if (knownPassCount != UNKNOWN) {
1095                    processImageProgress((pass + percentOfPass)*100.0F
1096                                         / knownPassCount);
1097                } else if (maxProgressivePass != Integer.MAX_VALUE) {
1098                    // Use the range of allowed progressive passes
1099
processImageProgress((pass + percentOfPass)*100.0F
1100                        / (maxProgressivePass - minProgressivePass + 1));
1101                } else {
1102                    // Assume there are a minimum of MIN_ESTIMATED_PASSES
1103
// and that there is always one more pass
1104
// Compute the percentage as the percentage at the end
1105
// of the previous pass, plus the percentage of this
1106
// pass scaled to be the percentage of the total remaining,
1107
// assuming a minimum of MIN_ESTIMATED_PASSES passes and
1108
// that there is always one more pass. This is monotonic
1109
// and asymptotic to 1.0, which is what we need.
1110
int remainingPasses = // including this one
1111
Math.max(2, MIN_ESTIMATED_PASSES-pass);
1112                    int totalPasses = pass + remainingPasses-1;
1113                    progInterval = Math.max(height/20*totalPasses,
1114                                            totalPasses);
1115                    if (y%progInterval == 0) {
1116                        percentToDate = previousPassPercentage +
1117                            (1.0F - previousPassPercentage)
1118                            * (percentOfPass)/remainingPasses;
1119                        if (debug) {
1120                            System.out.print("pass= " + pass);
1121                            System.out.print(", y= " + y);
1122                            System.out.print(", progInt= " + progInterval);
1123                            System.out.print(", % of pass: " + percentOfPass);
1124                            System.out.print(", rem. passes: "
1125                                             + remainingPasses);
1126                            System.out.print(", prev%: "
1127                                             + previousPassPercentage);
1128                            System.out.print(", %ToDate: " + percentToDate);
1129                            System.out.print(" ");
1130                        }
1131                        processImageProgress(percentToDate*100.0F);
1132                    }
1133                }
1134            } else {
1135                processImageProgress(percentOfPass * 100.0F);
1136            }
1137        }
1138    }
1139
1140    private void initProgressData() {
1141        knownPassCount = UNKNOWN;
1142        pass = 0;
1143        percentToDate = 0.0F;
1144        previousPassPercentage = 0.0F;
1145        progInterval = 0;
1146    }
1147
1148    private void passStarted (int pass) {
1149        this.pass = pass;
1150        previousPassPercentage = percentToDate;
1151        processPassStarted(image,
1152                           pass,
1153                           minProgressivePass,
1154                           maxProgressivePass,
1155                           0, 0,
1156                           1,1,
1157                           destinationBands);
1158    }
1159
1160    private void passComplete () {
1161        processPassComplete(image);
1162    }
1163
1164    void thumbnailStarted(int thumbnailIndex) {
1165        processThumbnailStarted(currentImage, thumbnailIndex);
1166    }
1167
1168    // Provide access to protected superclass method
1169
void thumbnailProgress(float percentageDone) {
1170        processThumbnailProgress(percentageDone);
1171    }
1172
1173    // Provide access to protected superclass method
1174
void thumbnailComplete() {
1175        processThumbnailComplete();
1176    }
1177
1178    /**
1179     * Returns <code>true</code> if the read was aborted.
1180     */

1181    private native boolean readImage(long structPointer,
1182                                     byte [] buffer,
1183                                     int numRasterBands,
1184                                     int [] srcBands,
1185                                     int [] bandSizes,
1186                                     int sourceXOffset, int sourceYOffset,
1187                                     int sourceWidth, int sourceHeight,
1188                                     int periodX, int periodY,
1189                                     JPEGQTable JavaDoc [] abbrevQTables,
1190                                     JPEGHuffmanTable JavaDoc [] abbrevDCHuffmanTables,
1191                                     JPEGHuffmanTable JavaDoc [] abbrevACHuffmanTables,
1192                                     int minProgressivePass,
1193                                     int maxProgressivePass,
1194                                     boolean wantUpdates);
1195    
1196    public void abort() {
1197        super.abort();
1198        abortRead(structPointer);
1199    }
1200
1201    /** Set the C level abort flag. Keep it atomic for thread safety. */
1202    private native void abortRead(long structPointer);
1203
1204    /** Resets library state when an exception occurred during a read. */
1205    private native void resetLibraryState(long structPointer);
1206
1207    public boolean canReadRaster() {
1208        return true;
1209    }
1210
1211    public Raster JavaDoc readRaster(int imageIndex, ImageReadParam JavaDoc param)
1212        throws IOException JavaDoc {
1213        Raster JavaDoc retval = null;
1214        try {
1215        /*
1216         * This could be further optimized by not resetting the dest.
1217         * offset and creating a translated raster in readInternal()
1218         * (see bug 4994702 for more info).
1219         */

1220
1221        // For Rasters, destination offset is logical, not physical, so
1222
// set it to 0 before calling computeRegions, so that the destination
1223
// region is not clipped.
1224
Point JavaDoc saveDestOffset = null;
1225        if (param != null) {
1226        saveDestOffset = param.getDestinationOffset();
1227        param.setDestinationOffset(new Point JavaDoc(0, 0));
1228        }
1229            retval = readInternal(imageIndex, param, true);
1230        // Apply the destination offset, if any, as a logical offset
1231
if (saveDestOffset != null) {
1232        target = target.createWritableTranslatedChild(saveDestOffset.x,
1233                                  saveDestOffset.y);
1234        }
1235        } catch (RuntimeException JavaDoc e) {
1236            resetLibraryState(structPointer);
1237            throw e;
1238        } catch (IOException JavaDoc e) {
1239            resetLibraryState(structPointer);
1240            throw e;
1241        }
1242        return retval;
1243    }
1244
1245    public boolean readerSupportsThumbnails() {
1246        return true;
1247    }
1248
1249    public int getNumThumbnails(int imageIndex) throws IOException JavaDoc {
1250        getImageMetadata(imageIndex); // checks iis state for us
1251
// Now check the jfif segments
1252
JFIFMarkerSegment jfif =
1253            (JFIFMarkerSegment) imageMetadata.findMarkerSegment
1254            (JFIFMarkerSegment.class, true);
1255        int retval = 0;
1256        if (jfif != null) {
1257            retval = (jfif.thumb == null) ? 0 : 1;
1258            retval += jfif.extSegments.size();
1259        }
1260        return retval;
1261    }
1262
1263    public int getThumbnailWidth(int imageIndex, int thumbnailIndex)
1264        throws IOException JavaDoc {
1265        if ((thumbnailIndex < 0)
1266            || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
1267            throw new IndexOutOfBoundsException JavaDoc("No such thumbnail");
1268        }
1269        // Now we know that there is a jfif segment
1270
JFIFMarkerSegment jfif =
1271            (JFIFMarkerSegment) imageMetadata.findMarkerSegment
1272            (JFIFMarkerSegment.class, true);
1273        return jfif.getThumbnailWidth(thumbnailIndex);
1274    }
1275
1276    public int getThumbnailHeight(int imageIndex, int thumbnailIndex)
1277        throws IOException JavaDoc {
1278        if ((thumbnailIndex < 0)
1279            || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
1280            throw new IndexOutOfBoundsException JavaDoc("No such thumbnail");
1281        }
1282        // Now we know that there is a jfif segment
1283
JFIFMarkerSegment jfif =
1284            (JFIFMarkerSegment) imageMetadata.findMarkerSegment
1285            (JFIFMarkerSegment.class, true);
1286        return jfif.getThumbnailHeight(thumbnailIndex);
1287    }
1288
1289    public BufferedImage JavaDoc readThumbnail(int imageIndex,
1290                                       int thumbnailIndex)
1291        throws IOException JavaDoc {
1292        if ((thumbnailIndex < 0)
1293            || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
1294            throw new IndexOutOfBoundsException JavaDoc("No such thumbnail");
1295        }
1296        // Now we know that there is a jfif segment and that iis is good
1297
JFIFMarkerSegment jfif =
1298            (JFIFMarkerSegment) imageMetadata.findMarkerSegment
1299            (JFIFMarkerSegment.class, true);
1300        return jfif.getThumbnail(iis, thumbnailIndex, this);
1301    }
1302
1303    private void resetInternalState() {
1304        // reset C structures
1305
resetReader(structPointer);
1306
1307        // reset local Java structures
1308
numImages = 0;
1309        imagePositions = new ArrayList JavaDoc();
1310        currentImage = -1;
1311        image = null;
1312        raster = null;
1313        target = null;
1314        buffer = null;
1315        destROI = null;
1316        destinationBands = null;
1317        streamMetadata = null;
1318        imageMetadata = null;
1319        imageMetadataIndex = -1;
1320        haveSeeked = false;
1321        tablesOnlyChecked = false;
1322        iccCS = null;
1323        initProgressData();
1324    }
1325
1326    /**
1327     * Note that there is no need to override reset() here, as the default
1328     * implementation will call setInput(null, false, false), which will
1329     * invoke resetInternalState().
1330     */

1331
1332    private native void resetReader(long structPointer);
1333
1334    public void dispose() {
1335        if (structPointer != 0) {
1336            disposerRecord.dispose();
1337            structPointer = 0;
1338        }
1339    }
1340
1341    private static native void disposeReader(long structPointer);
1342
1343    private static class JPEGReaderDisposerRecord extends DisposerRecord {
1344        private long pData;
1345
1346        public JPEGReaderDisposerRecord(long pData) {
1347            this.pData = pData;
1348        }
1349
1350        public synchronized void dispose() {
1351            if (pData != 0) {
1352                disposeReader(pData);
1353                pData = 0;
1354            }
1355        }
1356    }
1357}
1358
Popular Tags