KickJava   Java API By Example, From Geeks To Geeks.

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


1 /*
2  * @(#)JFIFMarkerSegment.java 1.9 03/12/19
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.IIOImage JavaDoc;
12 import javax.imageio.ImageTypeSpecifier JavaDoc;
13 import javax.imageio.ImageReader JavaDoc;
14 import javax.imageio.metadata.IIOInvalidTreeException JavaDoc;
15 import javax.imageio.metadata.IIOMetadataNode JavaDoc;
16 import javax.imageio.metadata.IIOMetadata JavaDoc;
17 import javax.imageio.stream.ImageInputStream JavaDoc;
18 import javax.imageio.stream.ImageOutputStream JavaDoc;
19 import javax.imageio.stream.MemoryCacheImageOutputStream JavaDoc;
20 import javax.imageio.event.IIOReadProgressListener JavaDoc;
21
22 import java.awt.Graphics JavaDoc;
23 import java.awt.color.ICC_Profile JavaDoc;
24 import java.awt.color.ICC_ColorSpace JavaDoc;
25 import java.awt.color.ColorSpace JavaDoc;
26 import java.awt.image.ColorModel JavaDoc;
27 import java.awt.image.SampleModel JavaDoc;
28 import java.awt.image.IndexColorModel JavaDoc;
29 import java.awt.image.ComponentColorModel JavaDoc;
30 import java.awt.image.BufferedImage JavaDoc;
31 import java.awt.image.DataBuffer JavaDoc;
32 import java.awt.image.DataBufferByte JavaDoc;
33 import java.awt.image.Raster JavaDoc;
34 import java.awt.image.WritableRaster JavaDoc;
35 import java.io.IOException JavaDoc;
36 import java.io.ByteArrayOutputStream JavaDoc;
37 import java.util.List JavaDoc;
38 import java.util.ArrayList JavaDoc;
39 import java.util.Iterator JavaDoc;
40
41 import org.w3c.dom.Node JavaDoc;
42 import org.w3c.dom.NodeList JavaDoc;
43 import org.w3c.dom.NamedNodeMap JavaDoc;
44
45 /**
46  * A JFIF (JPEG File Interchange Format) APP0 (Application-Specific)
47  * marker segment. Inner classes are included for JFXX extension
48  * marker segments, for different varieties of thumbnails, and for
49  * ICC Profile APP2 marker segments. Any of these secondary types
50  * that occur are kept as members of a single JFIFMarkerSegment object.
51  */

52 class JFIFMarkerSegment extends MarkerSegment {
53     int majorVersion;
54     int minorVersion;
55     int resUnits;
56     int Xdensity;
57     int Ydensity;
58     int thumbWidth;
59     int thumbHeight;
60     JFIFThumbRGB thumb = null; // If present
61
ArrayList JavaDoc extSegments = new ArrayList JavaDoc();
62     ICCMarkerSegment iccSegment = null; // optional ICC
63
private static final int THUMB_JPEG = 0x10;
64     private static final int THUMB_PALETTE = 0x11;
65     private static final int THUMB_UNASSIGNED = 0x12;
66     private static final int THUMB_RGB = 0x13;
67     private static final int DATA_SIZE = 14;
68     private static final int ID_SIZE = 5;
69     private final int MAX_THUMB_WIDTH = 255;
70     private final int MAX_THUMB_HEIGHT = 255;
71
72     private final boolean debug = false;
73
74     /**
75      * Set to <code>true</code> when reading the chunks of an
76      * ICC profile. All chunks are consolidated to create a single
77      * "segment" containing all the chunks. This flag is a state
78      * variable identifying whether to construct a new segment or
79      * append to an old one.
80      */

81     private boolean inICC = false;
82
83     /**
84      * A placeholder for an ICC profile marker segment under
85      * construction. The segment is not added to the list
86      * until all chunks have been read.
87      */

88     private ICCMarkerSegment tempICCSegment = null;
89
90
91     /**
92      * Default constructor. Used to create a default JFIF header
93      */

94     JFIFMarkerSegment() {
95         super(JPEG.APP0);
96         majorVersion = 1;
97         minorVersion = 2;
98         resUnits = JPEG.DENSITY_UNIT_ASPECT_RATIO;
99         Xdensity = 1;
100         Ydensity = 1;
101         thumbWidth = 0;
102         thumbHeight = 0;
103     }
104
105     /**
106      * Constructs a JFIF header by reading from a stream wrapped
107      * in a JPEGBuffer.
108      */

109     JFIFMarkerSegment(JPEGBuffer buffer) throws IOException JavaDoc {
110         super(buffer);
111         buffer.bufPtr += ID_SIZE; // skip the id, we already checked it
112

113         majorVersion = buffer.buf[buffer.bufPtr++];
114         minorVersion = buffer.buf[buffer.bufPtr++];
115         resUnits = buffer.buf[buffer.bufPtr++];
116         Xdensity = (buffer.buf[buffer.bufPtr++] & 0xff) << 8;
117         Xdensity |= buffer.buf[buffer.bufPtr++] & 0xff;
118         Ydensity = (buffer.buf[buffer.bufPtr++] & 0xff) << 8;
119         Ydensity |= buffer.buf[buffer.bufPtr++] & 0xff;
120         thumbWidth = buffer.buf[buffer.bufPtr++] & 0xff;
121         thumbHeight = buffer.buf[buffer.bufPtr++] & 0xff;
122         buffer.bufAvail -= DATA_SIZE;
123         if (thumbWidth > 0) {
124             thumb = new JFIFThumbRGB(buffer, thumbWidth, thumbHeight);
125         }
126     }
127
128     /**
129      * Constructs a JFIF header from a DOM Node.
130      */

131     JFIFMarkerSegment(Node JavaDoc node) throws IIOInvalidTreeException JavaDoc {
132         this();
133         updateFromNativeNode(node, true);
134     }
135
136     /**
137      * Returns a deep-copy clone of this object.
138      */

139     protected Object JavaDoc clone() {
140         JFIFMarkerSegment newGuy = (JFIFMarkerSegment) super.clone();
141         if (!extSegments.isEmpty()) { // Clone the list with a deep copy
142
newGuy.extSegments = new ArrayList JavaDoc();
143             for (Iterator JavaDoc iter = extSegments.iterator(); iter.hasNext();) {
144                 JFIFExtensionMarkerSegment jfxx =
145                     (JFIFExtensionMarkerSegment) iter.next();
146                 newGuy.extSegments.add(jfxx.clone());
147             }
148         }
149         if (iccSegment != null) {
150             newGuy.iccSegment = (ICCMarkerSegment) iccSegment.clone();
151         }
152         return newGuy;
153     }
154
155     /**
156      * Add an JFXX extension marker segment from the stream wrapped
157      * in the JPEGBuffer to the list of extension segments.
158      */

159     void addJFXX(JPEGBuffer buffer, JPEGImageReader reader)
160         throws IOException JavaDoc {
161         extSegments.add(new JFIFExtensionMarkerSegment(buffer, reader));
162     }
163
164     /**
165      * Adds an ICC Profile APP2 segment from the stream wrapped
166      * in the JPEGBuffer.
167      */

168     void addICC(JPEGBuffer buffer) throws IOException JavaDoc {
169         if (inICC == false) {
170             if (iccSegment != null) {
171                 throw new IIOException JavaDoc
172                     ("> 1 ICC APP2 Marker Segment not supported");
173             }
174             tempICCSegment = new ICCMarkerSegment(buffer);
175             if (inICC == false) { // Just one chunk
176
iccSegment = tempICCSegment;
177                 tempICCSegment = null;
178             }
179         } else {
180             if (tempICCSegment.addData(buffer) == true) {
181                 iccSegment = tempICCSegment;
182                 tempICCSegment = null;
183             }
184         }
185     }
186
187     /**
188      * Add an ICC Profile APP2 segment by constructing it from
189      * the given ICC_ColorSpace object.
190      */

191     void addICC(ICC_ColorSpace JavaDoc cs) throws IOException JavaDoc {
192         if (iccSegment != null) {
193             throw new IIOException JavaDoc
194                 ("> 1 ICC APP2 Marker Segment not supported");
195         }
196         iccSegment = new ICCMarkerSegment(cs);
197     }
198
199     /**
200      * Returns a tree of DOM nodes representing this object and any
201      * subordinate JFXX extension or ICC Profile segments.
202      */

203     IIOMetadataNode JavaDoc getNativeNode() {
204         IIOMetadataNode JavaDoc node = new IIOMetadataNode JavaDoc("app0JFIF");
205         node.setAttribute("majorVersion", Integer.toString(majorVersion));
206         node.setAttribute("minorVersion", Integer.toString(minorVersion));
207         node.setAttribute("resUnits", Integer.toString(resUnits));
208         node.setAttribute("Xdensity", Integer.toString(Xdensity));
209         node.setAttribute("Ydensity", Integer.toString(Ydensity));
210         node.setAttribute("thumbWidth", Integer.toString(thumbWidth));
211         node.setAttribute("thumbHeight", Integer.toString(thumbHeight));
212         if (!extSegments.isEmpty()) {
213             IIOMetadataNode JavaDoc JFXXnode = new IIOMetadataNode JavaDoc("JFXX");
214             node.appendChild(JFXXnode);
215             for (Iterator JavaDoc iter = extSegments.iterator(); iter.hasNext();) {
216                 JFIFExtensionMarkerSegment seg =
217                     (JFIFExtensionMarkerSegment) iter.next();
218                 JFXXnode.appendChild(seg.getNativeNode());
219             }
220         }
221         if (iccSegment != null) {
222             node.appendChild(iccSegment.getNativeNode());
223         }
224
225         return node;
226     }
227
228     /**
229      * Updates the data in this object from the given DOM Node tree.
230      * If fromScratch is true, this object is being constructed.
231      * Otherwise an existing object is being modified.
232      * Throws an IIOInvalidTreeException if the tree is invalid in
233      * any way.
234      */

235     void updateFromNativeNode(Node JavaDoc node, boolean fromScratch)
236         throws IIOInvalidTreeException JavaDoc {
237         // none of the attributes are required
238
NamedNodeMap JavaDoc attrs = node.getAttributes();
239         if (attrs.getLength() > 0) {
240             int value = getAttributeValue(node, attrs, "majorVersion",
241                                           0, 255, false);
242             majorVersion = (value != -1) ? value : majorVersion;
243             value = getAttributeValue(node, attrs, "minorVersion",
244                                       0, 255, false);
245             minorVersion = (value != -1) ? value : minorVersion;
246             value = getAttributeValue(node, attrs, "resUnits", 0, 2, false);
247             resUnits = (value != -1) ? value : resUnits;
248             value = getAttributeValue(node, attrs, "Xdensity", 1, 65535, false);
249             Xdensity = (value != -1) ? value : Xdensity;
250             value = getAttributeValue(node, attrs, "Ydensity", 1, 65535, false);
251             Ydensity = (value != -1) ? value : Ydensity;
252             value = getAttributeValue(node, attrs, "thumbWidth", 0, 255, false);
253             thumbWidth = (value != -1) ? value : thumbWidth;
254             value = getAttributeValue(node, attrs, "thumbHeight", 0, 255, false);
255             thumbHeight = (value != -1) ? value : thumbHeight;
256         }
257         if (node.hasChildNodes()) {
258             NodeList JavaDoc children = node.getChildNodes();
259             int count = children.getLength();
260             if (count > 2) {
261                 throw new IIOInvalidTreeException JavaDoc
262                     ("app0JFIF node cannot have > 2 children", node);
263             }
264             for (int i = 0; i < count; i++) {
265                 Node JavaDoc child = children.item(i);
266                 String JavaDoc name = child.getNodeName();
267                 if (name.equals("JFXX")) {
268                     if ((!extSegments.isEmpty()) && fromScratch) {
269                         throw new IIOInvalidTreeException JavaDoc
270                             ("app0JFIF node cannot have > 1 JFXX node", node);
271                     }
272                     NodeList JavaDoc exts = child.getChildNodes();
273                     int extCount = exts.getLength();
274                     for (int j = 0; j < extCount; j++) {
275                         Node JavaDoc ext = exts.item(j);
276                         extSegments.add(new JFIFExtensionMarkerSegment(ext));
277                     }
278                 }
279                 if (name.equals("app2ICC")) {
280                     if ((iccSegment != null) && fromScratch) {
281                         throw new IIOInvalidTreeException JavaDoc
282                             ("> 1 ICC APP2 Marker Segment not supported", node);
283                     }
284                     iccSegment = new ICCMarkerSegment(child);
285                 }
286             }
287         }
288     }
289
290     int getThumbnailWidth(int index) {
291         if (thumb != null) {
292             if (index == 0) {
293                 return thumb.getWidth();
294             }
295             index--;
296         }
297         JFIFExtensionMarkerSegment jfxx =
298             (JFIFExtensionMarkerSegment) extSegments.get(index);
299         return jfxx.thumb.getWidth();
300     }
301    
302     int getThumbnailHeight(int index) {
303         if (thumb != null) {
304             if (index == 0) {
305                 return thumb.getHeight();
306             }
307             index--;
308         }
309         JFIFExtensionMarkerSegment jfxx =
310             (JFIFExtensionMarkerSegment) extSegments.get(index);
311         return jfxx.thumb.getHeight();
312     }
313    
314     BufferedImage JavaDoc getThumbnail(ImageInputStream JavaDoc iis,
315                                int index,
316                                JPEGImageReader reader) throws IOException JavaDoc {
317         reader.thumbnailStarted(index);
318         BufferedImage JavaDoc ret = null;
319         if ((thumb != null) && (index == 0)) {
320                 ret = thumb.getThumbnail(iis, reader);
321         } else {
322             if (thumb != null) {
323                 index--;
324             }
325             JFIFExtensionMarkerSegment jfxx =
326                 (JFIFExtensionMarkerSegment) extSegments.get(index);
327             ret = jfxx.thumb.getThumbnail(iis, reader);
328         }
329         reader.thumbnailComplete();
330         return ret;
331     }
332        
333
334     /**
335      * Writes the data for this segment to the stream in
336      * valid JPEG format. Assumes that there will be no thumbnail.
337      */

338     void write(ImageOutputStream JavaDoc ios,
339                JPEGImageWriter writer) throws IOException JavaDoc {
340         // No thumbnail
341
write(ios, null, writer);
342     }
343
344     /**
345      * Writes the data for this segment to the stream in
346      * valid JPEG format. The length written takes the thumbnail
347      * width and height into account. If necessary, the thumbnail
348      * is clipped to 255 x 255 and a warning is sent to the writer
349      * argument. Progress updates are sent to the writer argument.
350      */

351     void write(ImageOutputStream JavaDoc ios,
352                BufferedImage JavaDoc thumb,
353                JPEGImageWriter writer) throws IOException JavaDoc {
354         int thumbWidth = 0;
355         int thumbHeight = 0;
356         int thumbLength = 0;
357         int [] thumbData = null;
358         if (thumb != null) {
359             // Clip if necessary and get the data in thumbData
360
thumbWidth = thumb.getWidth();
361             thumbHeight = thumb.getHeight();
362             if ((thumbWidth > MAX_THUMB_WIDTH)
363                 || (thumbHeight > MAX_THUMB_HEIGHT)) {
364                 writer.warningOccurred(JPEGImageWriter.WARNING_THUMB_CLIPPED);
365             }
366             thumbWidth = Math.min(thumbWidth, MAX_THUMB_WIDTH);
367             thumbHeight = Math.min(thumbHeight, MAX_THUMB_HEIGHT);
368             thumbData = thumb.getRaster().getPixels(0, 0,
369                                                     thumbWidth, thumbHeight,
370                                                     (int []) null);
371             thumbLength = thumbData.length;
372         }
373         length = DATA_SIZE + LENGTH_SIZE + thumbLength;
374         writeTag(ios);
375         byte [] id = {0x4A, 0x46, 0x49, 0x46, 0x00};
376         ios.write(id);
377         ios.write(majorVersion);
378         ios.write(minorVersion);
379         ios.write(resUnits);
380         write2bytes(ios, Xdensity);
381         write2bytes(ios, Ydensity);
382         ios.write(thumbWidth);
383         ios.write(thumbHeight);
384         if (thumbData != null) {
385             writer.thumbnailStarted(0);
386             writeThumbnailData(ios, thumbData, writer);
387             writer.thumbnailComplete();
388         }
389     }
390
391     /*
392      * Write out the values in the integer array as a sequence of bytes,
393      * reporting progress to the writer argument.
394      */

395     void writeThumbnailData(ImageOutputStream JavaDoc ios,
396                             int [] thumbData,
397                             JPEGImageWriter writer) throws IOException JavaDoc {
398         int progInterval = thumbData.length / 20; // approx. every 5%
399
if (progInterval == 0) {
400             progInterval = 1;
401         }
402         for (int i = 0; i < thumbData.length; i++) {
403             ios.write(thumbData[i]);
404             if ((i > progInterval) && (i % progInterval == 0)) {
405                 writer.thumbnailProgress
406                     (((float) i * 100) / ((float) thumbData.length));
407             }
408         }
409     }
410
411     /**
412      * Write out this JFIF Marker Segment, including a thumbnail or
413      * appending a series of JFXX Marker Segments, as appropriate.
414      * Warnings and progress reports are sent to the writer argument.
415      * The list of thumbnails is matched to the list of JFXX extension
416      * segments, if any, in order to determine how to encode the
417      * thumbnails. If there are more thumbnails than metadata segments,
418      * default encoding is used for the extra thumbnails.
419      */

420     void writeWithThumbs(ImageOutputStream JavaDoc ios,
421                          List JavaDoc thumbnails,
422                          JPEGImageWriter writer) throws IOException JavaDoc {
423         if (thumbnails != null) {
424             JFIFExtensionMarkerSegment jfxx = null;
425             if (thumbnails.size() == 1) {
426                 if (!extSegments.isEmpty()) {
427                     jfxx = (JFIFExtensionMarkerSegment) extSegments.get(0);
428                 }
429                 writeThumb(ios,
430                            (BufferedImage JavaDoc) thumbnails.get(0),
431                            jfxx,
432                            0,
433                            true,
434                            writer);
435             } else {
436                 // All others write as separate JFXX segments
437
write(ios, writer); // Just the header without any thumbnail
438
for (int i = 0; i < thumbnails.size(); i++) {
439                     jfxx = null;
440                     if (i < extSegments.size()) {
441                         jfxx = (JFIFExtensionMarkerSegment) extSegments.get(i);
442                     }
443                     writeThumb(ios,
444                                (BufferedImage JavaDoc) thumbnails.get(i),
445                                jfxx,
446                                i,
447                                false,
448                                writer);
449                 }
450             }
451         } else { // No thumbnails
452
write(ios, writer);
453         }
454
455     }
456
457     private void writeThumb(ImageOutputStream JavaDoc ios,
458                             BufferedImage JavaDoc thumb,
459                             JFIFExtensionMarkerSegment jfxx,
460                             int index,
461                             boolean onlyOne,
462                             JPEGImageWriter writer) throws IOException JavaDoc {
463         ColorModel JavaDoc cm = thumb.getColorModel();
464         ColorSpace JavaDoc cs = cm.getColorSpace();
465         
466         if (cm instanceof IndexColorModel JavaDoc) {
467             // We never write a palette image into the header
468
// So if it's the only one, we need to write the header first
469
if (onlyOne) {
470                 write(ios, writer);
471             }
472             if ((jfxx == null)
473                 || (jfxx.code == THUMB_PALETTE)) {
474                 writeJFXXSegment(index, thumb, ios, writer); // default
475
} else {
476                 // Expand to RGB
477
BufferedImage JavaDoc thumbRGB =
478                     ((IndexColorModel JavaDoc) cm).convertToIntDiscrete
479                     (thumb.getRaster(), false);
480                 jfxx.setThumbnail(thumbRGB);
481                 writer.thumbnailStarted(index);
482                 jfxx.write(ios, writer); // Handles clipping if needed
483
writer.thumbnailComplete();
484             }
485         } else if (cs.getType() == ColorSpace.TYPE_RGB) {
486             if (jfxx == null) {
487                 if (onlyOne) {
488                     write(ios, thumb, writer); // As part of the header
489
} else {
490                     writeJFXXSegment(index, thumb, ios, writer); // default
491
}
492             } else {
493                 // If this is the only one, write the header first
494
if (onlyOne) {
495                     write(ios, writer);
496                 }
497                 if (jfxx.code == THUMB_PALETTE) {
498                     writeJFXXSegment(index, thumb, ios, writer); // default
499
writer.warningOccurred
500                         (JPEGImageWriter.WARNING_NO_RGB_THUMB_AS_INDEXED);
501                 } else {
502                     jfxx.setThumbnail(thumb);
503                     writer.thumbnailStarted(index);
504                     jfxx.write(ios, writer); // Handles clipping if needed
505
writer.thumbnailComplete();
506                 }
507             }
508         } else if (cs.getType() == ColorSpace.TYPE_GRAY) {
509             if (jfxx == null) {
510                 if (onlyOne) {
511                     BufferedImage JavaDoc thumbRGB = expandGrayThumb(thumb);
512                     write(ios, thumbRGB, writer); // As part of the header
513
} else {
514                     writeJFXXSegment(index, thumb, ios, writer); // default
515
}
516             } else {
517                 // If this is the only one, write the header first
518
if (onlyOne) {
519                     write(ios, writer);
520                 }
521                 if (jfxx.code == THUMB_RGB) {
522                     BufferedImage JavaDoc thumbRGB = expandGrayThumb(thumb);
523                     writeJFXXSegment(index, thumbRGB, ios, writer);
524                 } else if (jfxx.code == THUMB_JPEG) {
525                     jfxx.setThumbnail(thumb);
526                     writer.thumbnailStarted(index);
527                     jfxx.write(ios, writer); // Handles clipping if needed
528
writer.thumbnailComplete();
529                 } else if (jfxx.code == THUMB_PALETTE) {
530                     writeJFXXSegment(index, thumb, ios, writer); // default
531
writer.warningOccurred
532                         (JPEGImageWriter.WARNING_NO_GRAY_THUMB_AS_INDEXED);
533                 }
534             }
535         } else {
536             writer.warningOccurred
537                 (JPEGImageWriter.WARNING_ILLEGAL_THUMBNAIL);
538         }
539     }
540
541     // Could put reason codes in here to be parsed in writeJFXXSegment
542
// in order to provide more meaningful warnings.
543
private class IllegalThumbException extends Exception JavaDoc {}
544
545     /**
546      * Writes out a new JFXX extension segment, without saving it.
547      */

548     private void writeJFXXSegment(int index,
549                                   BufferedImage JavaDoc thumbnail,
550                                   ImageOutputStream JavaDoc ios,
551                                   JPEGImageWriter writer) throws IOException JavaDoc {
552         JFIFExtensionMarkerSegment jfxx = null;
553         try {
554              jfxx = new JFIFExtensionMarkerSegment(thumbnail);
555         } catch (IllegalThumbException e) {
556             writer.warningOccurred
557                 (JPEGImageWriter.WARNING_ILLEGAL_THUMBNAIL);
558             return;
559         }
560         writer.thumbnailStarted(index);
561         jfxx.write(ios, writer);
562         writer.thumbnailComplete();
563     }
564
565
566     /**
567      * Return an RGB image that is the expansion of the given grayscale
568      * image.
569      */

570     private static BufferedImage JavaDoc expandGrayThumb(BufferedImage JavaDoc thumb) {
571         BufferedImage JavaDoc ret = new BufferedImage JavaDoc(thumb.getWidth(),
572                                               thumb.getHeight(),
573                                               BufferedImage.TYPE_INT_RGB);
574         Graphics JavaDoc g = ret.getGraphics();
575         g.drawImage(thumb, 0, 0, null);
576         return ret;
577     }
578
579     /**
580      * Writes out a default JFIF marker segment to the given
581      * output stream. If <code>thumbnails</code> is not <code>null</code>,
582      * writes out the set of thumbnail images as JFXX marker segments, or
583      * incorporated into the JFIF segment if appropriate.
584      * If <code>iccProfile</code> is not <code>null</code>,
585      * writes out the profile after the JFIF segment using as many APP2
586      * marker segments as necessary.
587      */

588     static void writeDefaultJFIF(ImageOutputStream JavaDoc ios,
589                                  List JavaDoc thumbnails,
590                                  ICC_Profile JavaDoc iccProfile,
591                                  JPEGImageWriter writer)
592         throws IOException JavaDoc {
593
594         JFIFMarkerSegment jfif = new JFIFMarkerSegment();
595         jfif.writeWithThumbs(ios, thumbnails, writer);
596         if (iccProfile != null) {
597             writeICC(iccProfile, ios);
598         }
599     }
600
601     /**
602      * Prints out the contents of this object to System.out for debugging.
603      */

604     void print() {
605         printTag("JFIF");
606         System.out.print("Version ");
607         System.out.print(majorVersion);
608         System.out.println(".0"
609                            + Integer.toString(minorVersion));
610         System.out.print("Resolution units: ");
611         System.out.println(resUnits);
612         System.out.print("X density: ");
613         System.out.println(Xdensity);
614         System.out.print("Y density: ");
615         System.out.println(Ydensity);
616         System.out.print("Thumbnail Width: ");
617         System.out.println(thumbWidth);
618         System.out.print("Thumbnail Height: ");
619         System.out.println(thumbHeight);
620         if (!extSegments.isEmpty()) {
621             for (Iterator JavaDoc iter = extSegments.iterator(); iter.hasNext();) {
622                 JFIFExtensionMarkerSegment extSegment =
623                     (JFIFExtensionMarkerSegment) iter.next();
624                 extSegment.print();
625             }
626         }
627         if (iccSegment != null) {
628             iccSegment.print();
629         }
630     }
631
632     /**
633      * A JFIF extension APP0 marker segment.
634      */

635     class JFIFExtensionMarkerSegment extends MarkerSegment {
636         int code;
637         JFIFThumb thumb;
638         private static final int DATA_SIZE = 6;
639         private static final int ID_SIZE = 5;
640
641         JFIFExtensionMarkerSegment(JPEGBuffer buffer, JPEGImageReader reader)
642             throws IOException JavaDoc {
643             
644             super(buffer);
645             buffer.bufPtr += ID_SIZE; // skip the id, we already checked it
646

647             code = buffer.buf[buffer.bufPtr++] & 0xff;
648             buffer.bufAvail -= DATA_SIZE;
649             if (code == THUMB_JPEG) {
650                 thumb = new JFIFThumbJPEG(buffer, length, reader);
651             } else {
652                 buffer.loadBuf(2);
653                 int thumbX = buffer.buf[buffer.bufPtr++] & 0xff;
654                 int thumbY = buffer.buf[buffer.bufPtr++] & 0xff;
655                 buffer.bufAvail -= 2;
656                 // following constructors handle bufAvail
657
if (code == THUMB_PALETTE) {
658                     thumb = new JFIFThumbPalette(buffer, thumbX, thumbY);
659                 } else {
660                     thumb = new JFIFThumbRGB(buffer, thumbX, thumbY);
661                 }
662             }
663         }
664
665         JFIFExtensionMarkerSegment(Node JavaDoc node) throws IIOInvalidTreeException JavaDoc {
666             super(JPEG.APP0);
667             NamedNodeMap JavaDoc attrs = node.getAttributes();
668             if (attrs.getLength() > 0) {
669                 code = getAttributeValue(node,
670                                          attrs,
671                                          "extensionCode",
672                                          THUMB_JPEG,
673                                          THUMB_RGB,
674                                          false);
675                 if (code == THUMB_UNASSIGNED) {
676                 throw new IIOInvalidTreeException JavaDoc
677                     ("invalid extensionCode attribute value", node);
678                 }
679             } else {
680                 code = THUMB_UNASSIGNED;
681             }
682             // Now the child
683
if (node.getChildNodes().getLength() != 1) {
684                 throw new IIOInvalidTreeException JavaDoc
685                     ("app0JFXX node must have exactly 1 child", node);
686             }
687             Node JavaDoc child = node.getFirstChild();
688             String JavaDoc name = child.getNodeName();
689             if (name.equals("JFIFthumbJPEG")) {
690                 if (code == THUMB_UNASSIGNED) {
691                     code = THUMB_JPEG;
692                 }
693                 thumb = new JFIFThumbJPEG(child);
694             } else if (name.equals("JFIFthumbPalette")) {
695                 if (code == THUMB_UNASSIGNED) {
696                     code = THUMB_PALETTE;
697                 }
698                 thumb = new JFIFThumbPalette(child);
699             } else if (name.equals("JFIFthumbRGB")) {
700                 if (code == THUMB_UNASSIGNED) {
701                     code = THUMB_RGB;
702                 }
703                 thumb = new JFIFThumbRGB(child);
704             } else {
705                 throw new IIOInvalidTreeException JavaDoc
706                     ("unrecognized app0JFXX child node", node);
707             }
708         }
709
710         JFIFExtensionMarkerSegment(BufferedImage JavaDoc thumbnail)
711             throws IllegalThumbException {
712
713             super(JPEG.APP0);
714             ColorModel JavaDoc cm = thumbnail.getColorModel();
715             int csType = cm.getColorSpace().getType();
716             if (cm.hasAlpha()) {
717                 throw new IllegalThumbException();
718             }
719             if (cm instanceof IndexColorModel JavaDoc) {
720                 code = THUMB_PALETTE;
721                 thumb = new JFIFThumbPalette(thumbnail);
722             } else if (csType == ColorSpace.TYPE_RGB) {
723                 code = THUMB_RGB;
724                 thumb = new JFIFThumbRGB(thumbnail);
725             } else if (csType == ColorSpace.TYPE_GRAY) {
726                 code = THUMB_JPEG;
727                 thumb = new JFIFThumbJPEG(thumbnail);
728             } else {
729                 throw new IllegalThumbException();
730             }
731         }
732
733         void setThumbnail(BufferedImage JavaDoc thumbnail) {
734             try {
735                 switch (code) {
736                 case THUMB_PALETTE:
737                     thumb = new JFIFThumbPalette(thumbnail);
738                     break;
739                 case THUMB_RGB:
740                     thumb = new JFIFThumbRGB(thumbnail);
741                     break;
742                 case THUMB_JPEG:
743                     thumb = new JFIFThumbJPEG(thumbnail);
744                     break;
745                 }
746             } catch (IllegalThumbException e) {
747                 // Should never happen
748
throw new InternalError JavaDoc("Illegal thumb in setThumbnail!");
749             }
750         }
751
752         protected Object JavaDoc clone() {
753             JFIFExtensionMarkerSegment newGuy =
754                 (JFIFExtensionMarkerSegment) super.clone();
755             if (thumb != null) {
756                 newGuy.thumb = (JFIFThumb) thumb.clone();
757             }
758             return newGuy;
759         }
760
761         IIOMetadataNode JavaDoc getNativeNode() {
762             IIOMetadataNode JavaDoc node = new IIOMetadataNode JavaDoc("app0JFXX");
763             node.setAttribute("extensionCode", Integer.toString(code));
764             node.appendChild(thumb.getNativeNode());
765             return node;
766         }
767
768         void write(ImageOutputStream JavaDoc ios,
769                    JPEGImageWriter writer) throws IOException JavaDoc {
770             length = LENGTH_SIZE + DATA_SIZE + thumb.getLength();
771             writeTag(ios);
772             byte [] id = {0x4A, 0x46, 0x58, 0x58, 0x00};
773             ios.write(id);
774             ios.write(code);
775             thumb.write(ios, writer);
776         }
777
778         void print() {
779             printTag("JFXX");
780             thumb.print();
781         }
782     }
783
784     /**
785      * A superclass for the varieties of thumbnails that can
786      * be stored in a JFIF extension marker segment.
787      */

788     abstract class JFIFThumb implements Cloneable JavaDoc {
789         long streamPos = -1L; // Save the thumbnail pos when reading
790
abstract int getLength(); // When writing
791
abstract int getWidth();
792         abstract int getHeight();
793         abstract BufferedImage JavaDoc getThumbnail(ImageInputStream JavaDoc iis,
794                                             JPEGImageReader reader)
795             throws IOException JavaDoc;
796         
797         protected JFIFThumb() {}
798
799         protected JFIFThumb(JPEGBuffer buffer) throws IOException JavaDoc{
800             // Save the stream position for reading the thumbnail later
801
streamPos = buffer.getStreamPosition();
802         }
803
804         abstract void print();
805
806         abstract IIOMetadataNode JavaDoc getNativeNode();
807
808         abstract void write(ImageOutputStream JavaDoc ios,
809                             JPEGImageWriter writer) throws IOException JavaDoc;
810
811         protected Object JavaDoc clone() {
812             try {
813                 return super.clone();
814             } catch (CloneNotSupportedException JavaDoc e) {} // won't happen
815
return null;
816         }
817
818     }
819
820     abstract class JFIFThumbUncompressed extends JFIFThumb {
821         BufferedImage JavaDoc thumbnail = null;
822         int thumbWidth;
823         int thumbHeight;
824         String JavaDoc name;
825
826         JFIFThumbUncompressed(JPEGBuffer buffer,
827                               int width,
828                               int height,
829                               int skip,
830                               String JavaDoc name)
831             throws IOException JavaDoc {
832             super(buffer);
833             thumbWidth = width;
834             thumbHeight = height;
835             // Now skip the thumbnail data
836
buffer.skipData(skip);
837             this.name = name;
838         }
839
840         JFIFThumbUncompressed(Node JavaDoc node, String JavaDoc name)
841             throws IIOInvalidTreeException JavaDoc {
842
843             thumbWidth = 0;
844             thumbHeight = 0;
845             this.name = name;
846             NamedNodeMap JavaDoc attrs = node.getAttributes();
847             int count = attrs.getLength();
848             if (count > 2) {
849                 throw new IIOInvalidTreeException JavaDoc
850                     (name +" node cannot have > 2 attributes", node);
851             }
852             if (count != 0) {
853                 int value = getAttributeValue(node, attrs, "thumbWidth",
854                                               0, 255, false);
855                 thumbWidth = (value != -1) ? value : thumbWidth;
856                 value = getAttributeValue(node, attrs, "thumbHeight",
857                                           0, 255, false);
858                 thumbHeight = (value != -1) ? value : thumbHeight;
859             }
860         }
861
862         JFIFThumbUncompressed(BufferedImage JavaDoc thumb) {
863             thumbnail = thumb;
864             thumbWidth = thumb.getWidth();
865             thumbHeight = thumb.getHeight();
866             name = null; // not used when writing
867
}
868
869         void readByteBuffer(ImageInputStream JavaDoc iis,
870                             byte [] data,
871                             JPEGImageReader reader,
872                             float workPortion,
873                             float workOffset) throws IOException JavaDoc {
874             int progInterval = Math.max((int)(data.length/20/workPortion),
875                                         1);
876             for (int offset = 0;
877                  offset < data.length;) {
878                 int len = Math.min(progInterval, data.length-offset);
879                 iis.read(data, offset, len);
880                 offset += progInterval;
881                 float percentDone = ((float) offset* 100)
882                     / data.length
883                     * workPortion + workOffset;
884                 if (percentDone > 100.0F) {
885                     percentDone = 100.0F;
886                 }
887                 reader.thumbnailProgress (percentDone);
888             }
889         }
890                             
891
892         int getWidth() {
893             return thumbWidth;
894         }
895
896         int getHeight() {
897             return thumbHeight;
898         }
899
900         IIOMetadataNode JavaDoc getNativeNode() {
901             IIOMetadataNode JavaDoc node = new IIOMetadataNode JavaDoc(name);
902             node.setAttribute("thumbWidth", Integer.toString(thumbWidth));
903             node.setAttribute("thumbHeight", Integer.toString(thumbHeight));
904             return node;
905         }
906
907         void write(ImageOutputStream JavaDoc ios,
908                    JPEGImageWriter writer) throws IOException JavaDoc {
909             if ((thumbWidth > MAX_THUMB_WIDTH)
910                 || (thumbHeight > MAX_THUMB_HEIGHT)) {
911                 writer.warningOccurred(JPEGImageWriter.WARNING_THUMB_CLIPPED);
912             }
913             thumbWidth = Math.min(thumbWidth, MAX_THUMB_WIDTH);
914             thumbHeight = Math.min(thumbHeight, MAX_THUMB_HEIGHT);
915             ios.write(thumbWidth);
916             ios.write(thumbHeight);
917         }
918
919         void writePixels(ImageOutputStream JavaDoc ios,
920                          JPEGImageWriter writer) throws IOException JavaDoc {
921             if ((thumbWidth > MAX_THUMB_WIDTH)
922                 || (thumbHeight > MAX_THUMB_HEIGHT)) {
923                 writer.warningOccurred(JPEGImageWriter.WARNING_THUMB_CLIPPED);
924             }
925             thumbWidth = Math.min(thumbWidth, MAX_THUMB_WIDTH);
926             thumbHeight = Math.min(thumbHeight, MAX_THUMB_HEIGHT);
927             int [] data = thumbnail.getRaster().getPixels(0, 0,
928                                                           thumbWidth,
929                                                           thumbHeight,
930                                                           (int []) null);
931             writeThumbnailData(ios, data, writer);
932         }
933
934         void print() {
935             System.out.print(name + " width: ");
936             System.out.println(thumbWidth);
937             System.out.print(name + " height: ");
938             System.out.println(thumbHeight);
939         }
940
941     }
942
943     /**
944      * A JFIF thumbnail stored as RGB, one byte per channel,
945      * interleaved.
946      */

947     class JFIFThumbRGB extends JFIFThumbUncompressed {
948
949         JFIFThumbRGB(JPEGBuffer buffer, int width, int height)
950             throws IOException JavaDoc {
951
952             super(buffer, width, height, width*height*3, "JFIFthumbRGB");
953         }
954
955         JFIFThumbRGB(Node JavaDoc node) throws IIOInvalidTreeException JavaDoc {
956             super(node, "JFIFthumbRGB");
957         }
958
959         JFIFThumbRGB(BufferedImage JavaDoc thumb) throws IllegalThumbException {
960             super(thumb);
961         }
962         
963         int getLength() {
964             return (thumbWidth*thumbHeight*3);
965         }
966
967         BufferedImage JavaDoc getThumbnail(ImageInputStream JavaDoc iis,
968                                    JPEGImageReader reader)
969             throws IOException JavaDoc {
970             iis.mark();
971             iis.seek(streamPos);
972             DataBufferByte JavaDoc buffer = new DataBufferByte JavaDoc(getLength());
973             readByteBuffer(iis,
974                            buffer.getData(),
975                            reader,
976                            1.0F,
977                            0.0F);
978             iis.reset();
979
980             WritableRaster JavaDoc raster =
981                 Raster.createInterleavedRaster(buffer,
982                                                thumbWidth,
983                                                thumbHeight,
984                                                thumbWidth*3,
985                                                3,
986                                                new int [] {0, 1, 2},
987                                                null);
988             ColorModel JavaDoc cm = new ComponentColorModel JavaDoc(JPEG.sRGB,
989                                                     false,
990                                                     false,
991                                                     ColorModel.OPAQUE,
992                                                     DataBuffer.TYPE_BYTE);
993             return new BufferedImage JavaDoc(cm,
994                                      raster,
995                                      false,
996                                      null);
997         }
998
999         void write(ImageOutputStream JavaDoc ios,
1000                   JPEGImageWriter writer) throws IOException JavaDoc {
1001            super.write(ios, writer); // width and height
1002
writePixels(ios, writer);
1003        }
1004        
1005    }
1006
1007    /**
1008     * A JFIF thumbnail stored as an indexed palette image
1009     * using an RGB palette.
1010     */

1011    class JFIFThumbPalette extends JFIFThumbUncompressed {
1012        private static final int PALETTE_SIZE = 768;
1013
1014        JFIFThumbPalette(JPEGBuffer buffer, int width, int height)
1015            throws IOException JavaDoc {
1016            super(buffer,
1017                  width,
1018                  height,
1019                  PALETTE_SIZE + width * height,
1020                  "JFIFThumbPalette");
1021        }
1022
1023        JFIFThumbPalette(Node JavaDoc node) throws IIOInvalidTreeException JavaDoc {
1024            super(node, "JFIFThumbPalette");
1025        }
1026
1027        JFIFThumbPalette(BufferedImage JavaDoc thumb) throws IllegalThumbException {
1028            super(thumb);
1029            IndexColorModel JavaDoc icm = (IndexColorModel JavaDoc) thumbnail.getColorModel();
1030            if (icm.getMapSize() > 256) {
1031                throw new IllegalThumbException();
1032            }
1033        }
1034
1035        int getLength() {
1036            return (thumbWidth*thumbHeight + PALETTE_SIZE);
1037        }
1038
1039        BufferedImage JavaDoc getThumbnail(ImageInputStream JavaDoc iis,
1040                                   JPEGImageReader reader)
1041            throws IOException JavaDoc {
1042            iis.mark();
1043            iis.seek(streamPos);
1044            // read the palette
1045
byte [] palette = new byte [PALETTE_SIZE];
1046            float palettePart = ((float) PALETTE_SIZE) / getLength();
1047            readByteBuffer(iis,
1048                           palette,
1049                           reader,
1050                           palettePart,
1051                           0.0F);
1052            DataBufferByte JavaDoc buffer = new DataBufferByte JavaDoc(thumbWidth*thumbHeight);
1053            readByteBuffer(iis,
1054                           buffer.getData(),
1055                           reader,
1056                           1.0F-palettePart,
1057                           palettePart);
1058            iis.read();
1059            iis.reset();
1060
1061            IndexColorModel JavaDoc cm = new IndexColorModel JavaDoc(8,
1062                                                     256,
1063                                                     palette,
1064                                                     0,
1065                                                     false);
1066            SampleModel JavaDoc sm = cm.createCompatibleSampleModel(thumbWidth,
1067                                                            thumbHeight);
1068            WritableRaster JavaDoc raster =
1069                Raster.createWritableRaster(sm, buffer, null);
1070            return new BufferedImage JavaDoc(cm,
1071                                     raster,
1072                                     false,
1073                                     null);
1074        }
1075
1076        void write(ImageOutputStream JavaDoc ios,
1077                   JPEGImageWriter writer) throws IOException JavaDoc {
1078            super.write(ios, writer); // width and height
1079
// Write the palette (must be 768 bytes)
1080
byte [] palette = new byte[768];
1081            IndexColorModel JavaDoc icm = (IndexColorModel JavaDoc) thumbnail.getColorModel();
1082            byte [] reds = new byte [256];
1083            byte [] greens = new byte [256];
1084            byte [] blues = new byte [256];
1085            icm.getReds(reds);
1086            icm.getGreens(greens);
1087            icm.getBlues(blues);
1088            for (int i = 0; i < 256; i++) {
1089                palette[i*3] = reds[i];
1090                palette[i*3+1] = greens[i];
1091                palette[i*3+2] = blues[i];
1092            }
1093            ios.write(palette);
1094            writePixels(ios, writer);
1095        }
1096    }
1097
1098
1099    /**
1100     * A JFIF thumbnail stored as a JPEG stream. No JFIF or
1101     * JFIF extension markers are permitted. There is no need
1102     * to clip these, but the entire image must fit into a
1103     * single JFXX marker segment.
1104     */

1105    class JFIFThumbJPEG extends JFIFThumb {
1106        JPEGMetadata thumbMetadata = null;
1107        byte [] data = null; // Compressed image data, for writing
1108
private static final int PREAMBLE_SIZE = 6;
1109        
1110        JFIFThumbJPEG(JPEGBuffer buffer,
1111                      int length,
1112                      JPEGImageReader reader) throws IOException JavaDoc {
1113            super(buffer);
1114            // Compute the final stream position
1115
long finalPos = streamPos + (length - PREAMBLE_SIZE);
1116            // Set the stream back to the start of the thumbnail
1117
// and read its metadata (but don't decode the image)
1118
buffer.iis.seek(streamPos);
1119            thumbMetadata = new JPEGMetadata(false, true, buffer.iis, reader);
1120            // Set the stream to the computed final position
1121
buffer.iis.seek(finalPos);
1122            // Clear the now invalid buffer
1123
buffer.bufAvail = 0;
1124            buffer.bufPtr = 0;
1125        }
1126
1127        JFIFThumbJPEG(Node JavaDoc node) throws IIOInvalidTreeException JavaDoc {
1128            if (node.getChildNodes().getLength() > 1) {
1129                throw new IIOInvalidTreeException JavaDoc
1130                    ("JFIFThumbJPEG node must have 0 or 1 child", node);
1131            }
1132            Node JavaDoc child = node.getFirstChild();
1133            if (child != null) {
1134                String JavaDoc name = child.getNodeName();
1135                if (!name.equals("markerSequence")) {
1136                    throw new IIOInvalidTreeException JavaDoc
1137                        ("JFIFThumbJPEG child must be a markerSequence node",
1138                         node);
1139                }
1140                thumbMetadata = new JPEGMetadata(false, true);
1141                thumbMetadata.setFromMarkerSequenceNode(child);
1142            }
1143        }
1144
1145        JFIFThumbJPEG(BufferedImage JavaDoc thumb) throws IllegalThumbException {
1146            int INITIAL_BUFSIZE = 4096;
1147            int MAZ_BUFSIZE = 65535 - 2 - PREAMBLE_SIZE;
1148            try {
1149                ByteArrayOutputStream JavaDoc baos =
1150                    new ByteArrayOutputStream JavaDoc(INITIAL_BUFSIZE);
1151                MemoryCacheImageOutputStream JavaDoc mos =
1152                    new MemoryCacheImageOutputStream JavaDoc(baos);
1153
1154                JPEGImageWriter thumbWriter = new JPEGImageWriter(null);
1155
1156                thumbWriter.setOutput(mos);
1157
1158                // get default metadata for the thumb
1159
JPEGMetadata metadata =
1160                    (JPEGMetadata) thumbWriter.getDefaultImageMetadata
1161                    (new ImageTypeSpecifier JavaDoc(thumb), null);
1162
1163                // Remove the jfif segment, which should be there.
1164
MarkerSegment jfif = metadata.findMarkerSegment
1165                    (JFIFMarkerSegment.class, true);
1166                if (jfif == null) {
1167                    throw new IllegalThumbException();
1168                }
1169
1170                metadata.markerSequence.remove(jfif);
1171
1172                /* Use this if removing leaves a hole and causes trouble
1173
1174                // Get the tree
1175                String format = metadata.getNativeMetadataFormatName();
1176                IIOMetadataNode tree =
1177                (IIOMetadataNode) metadata.getAsTree(format);
1178
1179                // If there is no app0jfif node, the image is bad
1180                NodeList jfifs = tree.getElementsByTagName("app0JFIF");
1181                if (jfifs.getLength() == 0) {
1182                throw new IllegalThumbException();
1183                }
1184
1185                // remove the app0jfif node
1186                Node jfif = jfifs.item(0);
1187                Node parent = jfif.getParentNode();
1188                parent.removeChild(jfif);
1189
1190                metadata.setFromTree(format, tree);
1191                */

1192
1193                thumbWriter.write(new IIOImage JavaDoc(thumb, null, metadata));
1194
1195                thumbWriter.dispose();
1196                // Now check that the size is OK
1197
if (baos.size() > MAZ_BUFSIZE) {
1198                    throw new IllegalThumbException();
1199                }
1200                data = baos.toByteArray();
1201            } catch (IOException JavaDoc e) {
1202                throw new IllegalThumbException();
1203            }
1204        }
1205
1206        int getWidth() {
1207            int retval = 0;
1208            SOFMarkerSegment sof =
1209                (SOFMarkerSegment) thumbMetadata.findMarkerSegment
1210                (SOFMarkerSegment.class, true);
1211            if (sof != null) {
1212                retval = sof.samplesPerLine;
1213            }
1214            return retval;
1215        }
1216
1217        int getHeight() {
1218            int retval = 0;
1219            SOFMarkerSegment sof =
1220                (SOFMarkerSegment) thumbMetadata.findMarkerSegment
1221                (SOFMarkerSegment.class, true);
1222            if (sof != null) {
1223                retval = sof.numLines;
1224            }
1225            return retval;
1226        }
1227
1228        private class ThumbnailReadListener
1229            implements IIOReadProgressListener JavaDoc {
1230            JPEGImageReader reader = null;
1231            ThumbnailReadListener (JPEGImageReader reader) {
1232                this.reader = reader;
1233            }
1234            public void sequenceStarted(ImageReader JavaDoc source, int minIndex) {}
1235            public void sequenceComplete(ImageReader JavaDoc source) {}
1236            public void imageStarted(ImageReader JavaDoc source, int imageIndex) {}
1237            public void imageProgress(ImageReader JavaDoc source,
1238                                      float percentageDone) {
1239                reader.thumbnailProgress(percentageDone);
1240            }
1241            public void imageComplete(ImageReader JavaDoc source) {}
1242            public void thumbnailStarted(ImageReader JavaDoc source,
1243                int imageIndex, int thumbnailIndex) {}
1244            public void thumbnailProgress(ImageReader JavaDoc source, float percentageDone) {}
1245            public void thumbnailComplete(ImageReader JavaDoc source) {}
1246            public void readAborted(ImageReader JavaDoc source) {}
1247        }
1248
1249        BufferedImage JavaDoc getThumbnail(ImageInputStream JavaDoc iis,
1250                                   JPEGImageReader reader)
1251            throws IOException JavaDoc {
1252            iis.mark();
1253            iis.seek(streamPos);
1254            JPEGImageReader thumbReader = new JPEGImageReader(null);
1255            thumbReader.setInput(iis);
1256            thumbReader.addIIOReadProgressListener
1257                (new ThumbnailReadListener(reader));
1258            BufferedImage JavaDoc ret = thumbReader.read(0, null);
1259            thumbReader.dispose();
1260            iis.reset();
1261            return ret;
1262        }
1263
1264        protected Object JavaDoc clone() {
1265            JFIFThumbJPEG newGuy = (JFIFThumbJPEG) super.clone();
1266            if (thumbMetadata != null) {
1267                newGuy.thumbMetadata = (JPEGMetadata) thumbMetadata.clone();
1268            }
1269            return newGuy;
1270        }
1271
1272        IIOMetadataNode JavaDoc getNativeNode() {
1273            IIOMetadataNode JavaDoc node = new IIOMetadataNode JavaDoc("JFIFthumbJPEG");
1274            if (thumbMetadata != null) {
1275                node.appendChild(thumbMetadata.getNativeTree());
1276            }
1277            return node;
1278        }
1279
1280        int getLength() {
1281            if (data == null) {
1282                return 0;
1283            } else {
1284                return data.length;
1285            }
1286        }
1287
1288        void write(ImageOutputStream JavaDoc ios,
1289                   JPEGImageWriter writer) throws IOException JavaDoc {
1290            int progInterval = data.length / 20; // approx. every 5%
1291
if (progInterval == 0) {
1292                progInterval = 1;
1293            }
1294            for (int offset = 0;
1295                 offset < data.length;) {
1296                int len = Math.min(progInterval, data.length-offset);
1297                ios.write(data, offset, len);
1298                offset += progInterval;
1299                float percentDone = ((float) offset * 100) / data.length;
1300                if (percentDone > 100.0F) {
1301                    percentDone = 100.0F;
1302                }
1303                writer.thumbnailProgress (percentDone);
1304            }
1305        }
1306            
1307        void print () {
1308            System.out.println("JFIF thumbnail stored as JPEG");
1309        }
1310    }
1311
1312    /**
1313     * Write out the given profile to the stream, embedded in
1314     * the necessary number of APP2 segments, per the ICC spec.
1315     * This is the only mechanism for writing an ICC profile
1316     * to a stream.
1317     */

1318    static void writeICC(ICC_Profile JavaDoc profile, ImageOutputStream JavaDoc ios)
1319        throws IOException JavaDoc {
1320        int LENGTH_LENGTH = 2;
1321        final String JavaDoc ID = "ICC_PROFILE";
1322        int ID_LENGTH = ID.length()+1; // spec says it's null-terminated
1323
int COUNTS_LENGTH = 2;
1324        int MAX_ICC_CHUNK_SIZE =
1325            65535 - LENGTH_LENGTH - ID_LENGTH - COUNTS_LENGTH;
1326
1327        byte [] data = profile.getData();
1328        int numChunks = data.length / MAX_ICC_CHUNK_SIZE;
1329        if ((data.length % MAX_ICC_CHUNK_SIZE) != 0) {
1330            numChunks++;
1331        }
1332        int chunkNum = 1;
1333        int offset = 0;
1334        for (int i = 0; i < numChunks; i++) {
1335            int dataLength = Math.min(data.length-offset, MAX_ICC_CHUNK_SIZE);
1336            int segLength = dataLength+COUNTS_LENGTH+ID_LENGTH+LENGTH_LENGTH;
1337            ios.write(0xff);
1338            ios.write(JPEG.APP2);
1339            MarkerSegment.write2bytes(ios, segLength);
1340            byte [] id = ID.getBytes("US-ASCII");
1341            ios.write(id);
1342            ios.write(0); // Null-terminate the string
1343
ios.write(chunkNum++);
1344            ios.write(numChunks);
1345            ios.write(data, offset, dataLength);
1346            offset += dataLength;
1347        }
1348    }
1349
1350    /**
1351     * An APP2 marker segment containing an ICC profile. In the stream
1352     * a profile larger than 64K is broken up into a series of chunks.
1353     * This inner class represents the complete profile as a single objec,
1354     * combining chunks as necessary.
1355     */

1356    class ICCMarkerSegment extends MarkerSegment {
1357        ArrayList JavaDoc chunks = null;
1358        byte [] profile = null; // The complete profile when it's fully read
1359
// May remain null when writing
1360
private static final int ID_SIZE = 12;
1361        int chunksRead;
1362        int numChunks;
1363        
1364        ICCMarkerSegment(ICC_ColorSpace JavaDoc cs) {
1365            super(JPEG.APP2);
1366            chunks = null;
1367            chunksRead = 0;
1368            numChunks = 0;
1369            profile = cs.getProfile().getData();
1370        }
1371
1372        ICCMarkerSegment(JPEGBuffer buffer) throws IOException JavaDoc {
1373            super(buffer); // gets whole segment or fills the buffer
1374
if (debug) {
1375                System.out.println("Creating new ICC segment");
1376            }
1377            buffer.bufPtr += ID_SIZE; // Skip the id
1378
buffer.bufAvail -= ID_SIZE;
1379            /*
1380             * Reduce the stored length by the id size. The stored
1381             * length is used to store the length of the profile
1382             * data only.
1383             */

1384            length -= ID_SIZE;
1385
1386            // get the chunk number
1387
int chunkNum = buffer.buf[buffer.bufPtr] & 0xff;
1388            // get the total number of chunks
1389
numChunks = buffer.buf[buffer.bufPtr+1] & 0xff;
1390
1391            if (chunkNum > numChunks) {
1392                throw new IIOException JavaDoc
1393                    ("Image format Error; chunk num > num chunks");
1394            }
1395
1396            // if there are no more chunks, set up the data
1397
if (numChunks == 1) {
1398                // reduce the stored length by the two chunk numbering bytes
1399
length -= 2;
1400                profile = new byte[length];
1401                buffer.bufPtr += 2;
1402                buffer.bufAvail-=2;
1403                buffer.readData(profile);
1404                inICC = false;
1405            } else {
1406                // If we store them away, include the chunk numbering bytes
1407
byte [] profileData = new byte[length];
1408                // Now reduce the stored length by the
1409
// two chunk numbering bytes
1410
length -= 2;
1411                buffer.readData(profileData);
1412                chunks = new ArrayList JavaDoc();
1413                chunks.add(profileData);
1414                chunksRead = 1;
1415                inICC = true;
1416            }
1417        }
1418
1419        ICCMarkerSegment(Node JavaDoc node) throws IIOInvalidTreeException JavaDoc {
1420            super(JPEG.APP2);
1421            if (node instanceof IIOMetadataNode JavaDoc) {
1422                IIOMetadataNode JavaDoc ourNode = (IIOMetadataNode JavaDoc) node;
1423                ICC_Profile JavaDoc prof = (ICC_Profile JavaDoc) ourNode.getUserObject();
1424                if (prof != null) { // May be null
1425
profile = prof.getData();
1426                }
1427            }
1428        }
1429
1430        protected Object JavaDoc clone () {
1431            ICCMarkerSegment newGuy = (ICCMarkerSegment) super.clone();
1432            if (profile != null) {
1433                newGuy.profile = (byte[]) profile.clone();
1434            }
1435            return newGuy;
1436        }
1437
1438        boolean addData(JPEGBuffer buffer) throws IOException JavaDoc {
1439            if (debug) {
1440                System.out.println("Adding to ICC segment");
1441            }
1442            // skip the tag
1443
buffer.bufPtr++;
1444            buffer.bufAvail--;
1445            // Get the length, but not in length
1446
int dataLen = (buffer.buf[buffer.bufPtr++] & 0xff) << 8;
1447            dataLen |= buffer.buf[buffer.bufPtr++] & 0xff;
1448            buffer.bufAvail -= 2;
1449            // Don't include length itself
1450
dataLen -= 2;
1451            // skip the id
1452
buffer.bufPtr += ID_SIZE; // Skip the id
1453
buffer.bufAvail -= ID_SIZE;
1454            /*
1455             * Reduce the stored length by the id size. The stored
1456             * length is used to store the length of the profile
1457             * data only.
1458             */

1459            dataLen -= ID_SIZE;
1460
1461            // get the chunk number
1462
int chunkNum = buffer.buf[buffer.bufPtr] & 0xff;
1463            if (chunkNum > numChunks) {
1464                throw new IIOException JavaDoc
1465                    ("Image format Error; chunk num > num chunks");
1466            }
1467
1468            // get the number of chunks, which should match
1469
int newNumChunks = buffer.buf[buffer.bufPtr+1] & 0xff;
1470            if (numChunks != newNumChunks) {
1471                throw new IIOException JavaDoc
1472                    ("Image format Error; icc num chunks mismatch");
1473            }
1474            dataLen -= 2;
1475            if (debug) {
1476                System.out.println("chunkNum: " + chunkNum
1477                                   + ", numChunks: " + numChunks
1478                                   + ", dataLen: " + dataLen);
1479            }
1480            boolean retval = false;
1481            byte [] profileData = new byte[dataLen];
1482            buffer.readData(profileData);
1483            chunks.add(profileData);
1484            length += dataLen;
1485            chunksRead++;
1486            if (chunksRead < numChunks) {
1487                inICC = true;
1488            } else {
1489                if (debug) {
1490                    System.out.println("Completing profile; total length is "
1491                                       + length);
1492                }
1493                // create an array for the whole thing
1494
profile = new byte[length];
1495                // copy the existing chunks, releasing them
1496
// Note that they may be out of order
1497

1498                int index = 0;
1499                for (int i = 1; i <= numChunks; i++) {
1500                    boolean foundIt = false;
1501                    for (int chunk = 0; chunk < chunks.size(); chunk++) {
1502                        byte [] chunkData = (byte []) chunks.get(chunk);
1503                        if (chunkData[0] == i) { // Right one
1504
System.arraycopy(chunkData, 2,
1505                                             profile, index,
1506                                             chunkData.length-2);
1507                            index += chunkData.length-2;
1508                            foundIt = true;
1509                        }
1510                    }
1511                    if (foundIt == false) {
1512                        throw new IIOException JavaDoc
1513                            ("Image Format Error: Missing ICC chunk num " + i);
1514                    }
1515                }
1516
1517                chunks = null;
1518                chunksRead = 0;
1519                numChunks = 0;
1520                inICC = false;
1521                retval = true;
1522            }
1523            return retval;
1524        }
1525
1526        IIOMetadataNode JavaDoc getNativeNode() {
1527            IIOMetadataNode JavaDoc node = new IIOMetadataNode JavaDoc("app2ICC");
1528            if (profile != null) {
1529                node.setUserObject(ICC_Profile.getInstance(profile));
1530            }
1531            return node;
1532        }
1533
1534        /**
1535         * No-op. Profiles are never written from metadata.
1536         * They are written from the ColorSpace of the image.
1537         */

1538        void write(ImageOutputStream JavaDoc ios) throws IOException JavaDoc {
1539            // No-op
1540
}
1541
1542        void print () {
1543            printTag("ICC Profile APP2");
1544        }
1545    }
1546}
1547    
1548
Popular Tags