KickJava   Java API By Example, From Geeks To Geeks.

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


1 /*
2  * @(#)JPEGMetadata.java 1.27 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.ImageTypeSpecifier JavaDoc;
11 import javax.imageio.ImageWriteParam JavaDoc;
12 import javax.imageio.IIOException JavaDoc;
13 import javax.imageio.stream.ImageInputStream JavaDoc;
14 import javax.imageio.stream.ImageOutputStream JavaDoc;
15 import javax.imageio.metadata.IIOMetadata JavaDoc;
16 import javax.imageio.metadata.IIOMetadataNode JavaDoc;
17 import javax.imageio.metadata.IIOMetadataFormat JavaDoc;
18 import javax.imageio.metadata.IIOMetadataFormatImpl JavaDoc;
19 import javax.imageio.metadata.IIOInvalidTreeException JavaDoc;
20 import javax.imageio.plugins.jpeg.JPEGQTable JavaDoc;
21 import javax.imageio.plugins.jpeg.JPEGHuffmanTable JavaDoc;
22 import javax.imageio.plugins.jpeg.JPEGImageWriteParam JavaDoc;
23
24 import org.w3c.dom.Node JavaDoc;
25 import org.w3c.dom.NodeList JavaDoc;
26 import org.w3c.dom.NamedNodeMap JavaDoc;
27
28 import java.util.List JavaDoc;
29 import java.util.ArrayList JavaDoc;
30 import java.util.Arrays JavaDoc;
31 import java.util.Iterator JavaDoc;
32 import java.util.ListIterator JavaDoc;
33 import java.io.IOException JavaDoc;
34 import java.awt.color.ICC_Profile JavaDoc;
35 import java.awt.color.ICC_ColorSpace JavaDoc;
36 import java.awt.color.ColorSpace JavaDoc;
37 import java.awt.image.ColorModel JavaDoc;
38 import java.awt.Point JavaDoc;
39
40 /**
41  * Metadata for the JPEG plug-in.
42  */

43 public class JPEGMetadata extends IIOMetadata JavaDoc implements Cloneable JavaDoc {
44
45     //////// Private variables
46

47     private static final boolean debug = false;
48
49     /**
50      * A copy of <code>markerSequence</code>, created the first time the
51      * <code>markerSequence</code> is modified. This is used by reset
52      * to restore the original state.
53      */

54     private List JavaDoc resetSequence = null;
55
56     /**
57      * Set to <code>true</code> when reading a thumbnail stored as
58      * JPEG. This is used to enforce the prohibition of JFIF thumbnails
59      * containing any JFIF marker segments, and to ensure generation of
60      * a correct native subtree during <code>getAsTree</code>.
61      */

62     private boolean inThumb = false;
63
64     /**
65      * Set by the chroma node construction method to signal the
66      * presence or absence of an alpha channel to the transparency
67      * node construction method. Used only when constructing a
68      * standard metadata tree.
69      */

70     private boolean hasAlpha;
71
72     //////// end of private variables
73

74     /////// Package-access variables
75

76     /**
77      * All data is a list of <code>MarkerSegment</code> objects.
78      * When accessing the list, use the tag to identify the particular
79      * subclass. Any JFIF marker segment must be the first element
80      * of the list if it is present, and any JFXX or APP2ICC marker
81      * segments are subordinate to the JFIF marker segment. This
82      * list is package visible so that the writer can access it.
83      * @see #MarkerSegment
84      */

85     List JavaDoc markerSequence = new ArrayList JavaDoc();
86
87     /**
88      * Indicates whether this object represents stream or image
89      * metadata. Package-visible so the writer can see it.
90      */

91     final boolean isStream;
92
93     /////// End of package-access variables
94

95     /////// Constructors
96

97     /**
98      * Constructor containing code shared by other constructors.
99      */

100     JPEGMetadata(boolean isStream, boolean inThumb) {
101         super(true, // Supports standard format
102
JPEG.nativeImageMetadataFormatName, // and a native format
103
JPEG.nativeImageMetadataFormatClassName,
104               null, null); // No other formats
105
this.inThumb = inThumb;
106         // But if we are stream metadata, adjust the variables
107
this.isStream = isStream;
108         if (isStream) {
109             nativeMetadataFormatName = JPEG.nativeStreamMetadataFormatName;
110             nativeMetadataFormatClassName =
111                 JPEG.nativeStreamMetadataFormatClassName;
112         }
113     }
114
115     /*
116      * Constructs a <code>JPEGMetadata</code> object by reading the
117      * contents of an <code>ImageInputStream</code>. Has package-only
118      * access.
119      *
120      * @param isStream A boolean indicating whether this object will be
121      * stream or image metadata.
122      * @param isThumb A boolean indicating whether this metadata object
123      * is for an image or for a thumbnail stored as JPEG.
124      * @param iis An <code>ImageInputStream</code> from which to read
125      * the metadata.
126      * @param reader The <code>JPEGImageReader</code> calling this
127      * constructor, to which warnings should be sent.
128      */

129     JPEGMetadata(boolean isStream,
130                  boolean isThumb,
131                  ImageInputStream JavaDoc iis,
132                  JPEGImageReader reader) throws IOException JavaDoc {
133         this(isStream, isThumb);
134
135         JPEGBuffer buffer = new JPEGBuffer(iis);
136
137         buffer.loadBuf(0);
138
139         // The first three bytes should be FF, SOI, FF
140
if (((buffer.buf[0] & 0xff) != 0xff)
141             || ((buffer.buf[1] & 0xff) != JPEG.SOI)
142             || ((buffer.buf[2] & 0xff) != 0xff)) {
143             throw new IIOException JavaDoc ("Image format error");
144         }
145
146         boolean done = false;
147         buffer.bufAvail -=2; // Next byte should be the ff before a marker
148
buffer.bufPtr = 2;
149         MarkerSegment newGuy = null;
150         while (!done) {
151             byte [] buf;
152             int ptr;
153             buffer.loadBuf(1);
154             if (debug) {
155                 System.out.println("top of loop");
156                 buffer.print(10);
157             }
158             buffer.scanForFF(reader);
159             switch (buffer.buf[buffer.bufPtr] & 0xff) {
160             case 0:
161                 if (debug) {
162                     System.out.println("Skipping 0");
163                 }
164                 buffer.bufAvail--;
165                 buffer.bufPtr++;
166                 break;
167             case JPEG.SOF0:
168             case JPEG.SOF1:
169             case JPEG.SOF2:
170                 if (isStream) {
171                     throw new IIOException JavaDoc
172                         ("SOF not permitted in stream metadata");
173                 }
174                 newGuy = new SOFMarkerSegment(buffer);
175                 break;
176             case JPEG.DQT:
177                 newGuy = new DQTMarkerSegment(buffer);
178                 break;
179             case JPEG.DHT:
180                 newGuy = new DHTMarkerSegment(buffer);
181                 break;
182             case JPEG.DRI:
183                 newGuy = new DRIMarkerSegment(buffer);
184                 break;
185             case JPEG.APP0:
186                 // Either JFIF, JFXX, or unknown APP0
187
buffer.loadBuf(8); // tag, length, id
188
buf = buffer.buf;
189                 ptr = buffer.bufPtr;
190                 if ((buf[ptr+3] == 'J')
191                     && (buf[ptr+4] == 'F')
192                     && (buf[ptr+5] == 'I')
193                     && (buf[ptr+6] == 'F')
194                     && (buf[ptr+7] == 0)) {
195                     if (inThumb) {
196                         reader.warningOccurred
197                             (JPEGImageReader.WARNING_NO_JFIF_IN_THUMB);
198                         // Leave newGuy null
199
// Read a dummy to skip the segment
200
JFIFMarkerSegment dummy =
201                             new JFIFMarkerSegment(buffer);
202                     } else if (isStream) {
203                         throw new IIOException JavaDoc
204                             ("JFIF not permitted in stream metadata");
205                     } else if (markerSequence.isEmpty() == false) {
206                         throw new IIOException JavaDoc
207                             ("JFIF APP0 must be first marker after SOI");
208                     } else {
209                         newGuy = new JFIFMarkerSegment(buffer);
210                     }
211                 } else if ((buf[ptr+3] == 'J')
212                            && (buf[ptr+4] == 'F')
213                            && (buf[ptr+5] == 'X')
214                            && (buf[ptr+6] == 'X')
215                            && (buf[ptr+7] == 0)) {
216                     if (isStream) {
217                         throw new IIOException JavaDoc
218                             ("JFXX not permitted in stream metadata");
219                     }
220                     if (inThumb) {
221                         throw new IIOException JavaDoc
222                           ("JFXX markers not allowed in JFIF JPEG thumbnail");
223                     }
224                     JFIFMarkerSegment jfif =
225                         (JFIFMarkerSegment) findMarkerSegment
226                                (JFIFMarkerSegment.class, true);
227                     if (jfif == null) {
228                         throw new IIOException JavaDoc
229                             ("JFXX encountered without prior JFIF!");
230                     }
231                     jfif.addJFXX(buffer, reader);
232                     // newGuy remains null
233
} else {
234                     newGuy = new MarkerSegment(buffer);
235                     newGuy.loadData(buffer);
236                 }
237                 break;
238             case JPEG.APP2:
239                 // Either an ICC profile or unknown APP2
240
buffer.loadBuf(15); // tag, length, id
241
if ((buffer.buf[buffer.bufPtr+3] == 'I')
242                     && (buffer.buf[buffer.bufPtr+4] == 'C')
243                     && (buffer.buf[buffer.bufPtr+5] == 'C')
244                     && (buffer.buf[buffer.bufPtr+6] == '_')
245                     && (buffer.buf[buffer.bufPtr+7] == 'P')
246                     && (buffer.buf[buffer.bufPtr+8] == 'R')
247                     && (buffer.buf[buffer.bufPtr+9] == 'O')
248                     && (buffer.buf[buffer.bufPtr+10] == 'F')
249                     && (buffer.buf[buffer.bufPtr+11] == 'I')
250                     && (buffer.buf[buffer.bufPtr+12] == 'L')
251                     && (buffer.buf[buffer.bufPtr+13] == 'E')
252                     && (buffer.buf[buffer.bufPtr+14] == 0)
253                     ) {
254                     if (isStream) {
255                         throw new IIOException JavaDoc
256                             ("ICC profiles not permitted in stream metadata");
257                     }
258
259                     JFIFMarkerSegment jfif =
260                         (JFIFMarkerSegment) findMarkerSegment
261                         (JFIFMarkerSegment.class, true);
262                     if (jfif == null) {
263                         throw new IIOException JavaDoc
264                             ("ICC APP2 encountered without prior JFIF!");
265                     }
266                     jfif.addICC(buffer);
267                     // newGuy remains null
268
} else {
269                     newGuy = new MarkerSegment(buffer);
270                     newGuy.loadData(buffer);
271                 }
272                 break;
273             case JPEG.APP14:
274                 // Either Adobe or unknown APP14
275
buffer.loadBuf(8); // tag, length, id
276
if ((buffer.buf[buffer.bufPtr+3] == 'A')
277                     && (buffer.buf[buffer.bufPtr+4] == 'd')
278                     && (buffer.buf[buffer.bufPtr+5] == 'o')
279                     && (buffer.buf[buffer.bufPtr+6] == 'b')
280                     && (buffer.buf[buffer.bufPtr+7] == 'e')) {
281                     if (isStream) {
282                         throw new IIOException JavaDoc
283                       ("Adobe APP14 markers not permitted in stream metadata");
284                     }
285                     newGuy = new AdobeMarkerSegment(buffer);
286                 } else {
287                     newGuy = new MarkerSegment(buffer);
288                     newGuy.loadData(buffer);
289                 }
290
291                 break;
292             case JPEG.COM:
293                 newGuy = new COMMarkerSegment(buffer);
294                 break;
295             case JPEG.SOS:
296                 if (isStream) {
297                     throw new IIOException JavaDoc
298                         ("SOS not permitted in stream metadata");
299                 }
300                 newGuy = new SOSMarkerSegment(buffer);
301                 break;
302             case JPEG.RST0:
303             case JPEG.RST1:
304             case JPEG.RST2:
305             case JPEG.RST3:
306             case JPEG.RST4:
307             case JPEG.RST5:
308             case JPEG.RST6:
309             case JPEG.RST7:
310                 if (debug) {
311                     System.out.println("Restart Marker");
312                 }
313                 buffer.bufPtr++; // Just skip it
314
buffer.bufAvail--;
315                 break;
316             case JPEG.EOI:
317                 done = true;
318                 buffer.bufPtr++;
319                 buffer.bufAvail--;
320                 break;
321             default:
322                 newGuy = new MarkerSegment(buffer);
323                 newGuy.loadData(buffer);
324                 newGuy.unknown = true;
325                 break;
326             }
327             if (newGuy != null) {
328                 markerSequence.add(newGuy);
329                 if (debug) {
330                     newGuy.print();
331                 }
332                 newGuy = null;
333             }
334         }
335
336         // Now that we've read up to the EOI, we need to push back
337
// whatever is left in the buffer, so that the next read
338
// in the native code will work.
339

340         buffer.pushBack();
341
342         if (!isConsistent()) {
343             throw new IIOException JavaDoc("Inconsistent metadata read from stream");
344         }
345     }
346
347     /**
348      * Constructs a default stream <code>JPEGMetadata</code> object appropriate
349      * for the given write parameters.
350      */

351     JPEGMetadata(ImageWriteParam JavaDoc param, JPEGImageWriter writer) {
352         this(true, false);
353         
354         JPEGImageWriteParam JavaDoc jparam = null;
355         
356         if ((param != null) && (param instanceof JPEGImageWriteParam JavaDoc)) {
357             jparam = (JPEGImageWriteParam JavaDoc) param;
358             if (!jparam.areTablesSet()) {
359                 jparam = null;
360             }
361         }
362         if (jparam != null) {
363             markerSequence.add(new DQTMarkerSegment(jparam.getQTables()));
364             markerSequence.add
365                 (new DHTMarkerSegment(jparam.getDCHuffmanTables(),
366                                       jparam.getACHuffmanTables()));
367         } else {
368             // default tables.
369
markerSequence.add(new DQTMarkerSegment(JPEG.getDefaultQTables()));
370             markerSequence.add(new DHTMarkerSegment(JPEG.getDefaultHuffmanTables(true),
371                                                     JPEG.getDefaultHuffmanTables(false)));
372         }
373
374         // Defensive programming
375
if (!isConsistent()) {
376             throw new InternalError JavaDoc("Default stream metadata is inconsistent");
377         }
378     }
379
380     /**
381      * Constructs a default image <code>JPEGMetadata</code> object appropriate
382      * for the given image type and write parameters.
383      */

384     JPEGMetadata(ImageTypeSpecifier JavaDoc imageType,
385                  ImageWriteParam JavaDoc param,
386                  JPEGImageWriter writer) {
387         this(false, false);
388
389         boolean wantJFIF = true;
390         boolean wantAdobe = false;
391         int transform = JPEG.ADOBE_UNKNOWN;
392         boolean willSubsample = true;
393         boolean wantICC = false;
394         boolean wantProg = false;
395         boolean wantOptimized = false;
396         boolean wantExtended = false;
397         boolean wantQTables = true;
398         boolean wantHTables = true;
399         float quality = JPEG.DEFAULT_QUALITY;
400         byte[] componentIDs = { 1, 2, 3, 4};
401         int numComponents = 0;
402
403         ImageTypeSpecifier JavaDoc destType = null;
404
405         if (param != null) {
406             destType = param.getDestinationType();
407             if (destType != null) {
408                 if (imageType != null) {
409                     // Ignore the destination type.
410
writer.warningOccurred
411                         (JPEGImageWriter.WARNING_DEST_IGNORED);
412                     destType = null;
413                 }
414             }
415             // The only progressive mode that makes sense here is MODE_DEFAULT
416
if (param.canWriteProgressive()) {
417                 // the param may not be one of ours, so it may return false.
418
// If so, the following would throw an exception
419
if (param.getProgressiveMode() == ImageWriteParam.MODE_DEFAULT) {
420                     wantProg = true;
421                     wantOptimized = true;
422                     wantHTables = false;
423                 }
424             }
425
426             if (param instanceof JPEGImageWriteParam JavaDoc) {
427                 JPEGImageWriteParam JavaDoc jparam = (JPEGImageWriteParam JavaDoc) param;
428                 if (jparam.areTablesSet()) {
429                     wantQTables = false; // If the param has them, metadata shouldn't
430
wantHTables = false;
431                     if ((jparam.getDCHuffmanTables().length > 2)
432                             || (jparam.getACHuffmanTables().length > 2)) {
433                         wantExtended = true;
434                     }
435                 }
436                 // Progressive forces optimized, regardless of param setting
437
// so consult the param re optimized only if not progressive
438
if (!wantProg) {
439                     wantOptimized = jparam.getOptimizeHuffmanTables();
440                     if (wantOptimized) {
441                         wantHTables = false;
442                     }
443                 }
444             }
445
446             // compression quality should determine the q tables. Note that this
447
// will be ignored if we already decided not to create any.
448
// Again, the param may not be one of ours, so we must check that it
449
// supports compression settings
450
if (param.canWriteCompressed()) {
451                 if (param.getCompressionMode() == ImageWriteParam.MODE_EXPLICIT) {
452                     quality = param.getCompressionQuality();
453                 }
454             }
455         }
456         
457         // We are done with the param, now for the image types
458

459         ColorSpace JavaDoc cs = null;
460         if (destType != null) {
461             ColorModel JavaDoc cm = destType.getColorModel();
462             numComponents = cm.getNumComponents();
463             boolean hasExtraComponents = (cm.getNumColorComponents() != numComponents);
464             boolean hasAlpha = cm.hasAlpha();
465             cs = cm.getColorSpace();
466             int type = cs.getType();
467             switch(type) {
468             case ColorSpace.TYPE_GRAY:
469                 willSubsample = false;
470                 if (hasExtraComponents) { // e.g. alpha
471
wantJFIF = false;
472                 }
473                 break;
474             case ColorSpace.TYPE_3CLR:
475                 if (cs == JPEG.YCC) {
476                     wantJFIF = false;
477                     componentIDs[0] = (byte) 'Y';
478                     componentIDs[1] = (byte) 'C';
479                     componentIDs[2] = (byte) 'c';
480                     if (hasAlpha) {
481                         componentIDs[3] = (byte) 'A';
482                     }
483                 }
484                 break;
485             case ColorSpace.TYPE_YCbCr:
486                 if (hasExtraComponents) { // e.g. K or alpha
487
wantJFIF = false;
488                     if (!hasAlpha) { // Not alpha, so must be K
489
wantAdobe = true;
490                         transform = JPEG.ADOBE_YCCK;
491                     }
492                 }
493                 break;
494             case ColorSpace.TYPE_RGB: // with or without alpha
495
wantJFIF = false;
496                 wantAdobe = true;
497                 willSubsample = false;
498                 componentIDs[0] = (byte) 'R';
499                 componentIDs[1] = (byte) 'G';
500                 componentIDs[2] = (byte) 'B';
501                 if (hasAlpha) {
502                     componentIDs[3] = (byte) 'A';
503                 }
504                 break;
505             default:
506                 // Everything else is not subsampled, gets no special marker,
507
// and component ids are 1 - N
508
wantJFIF = false;
509                 willSubsample = false;
510             }
511         } else if (imageType != null) {
512             ColorModel JavaDoc cm = imageType.getColorModel();
513             numComponents = cm.getNumComponents();
514             boolean hasExtraComponents = (cm.getNumColorComponents() != numComponents);
515             boolean hasAlpha = cm.hasAlpha();
516             cs = cm.getColorSpace();
517             int type = cs.getType();
518             switch(type) {
519             case ColorSpace.TYPE_GRAY:
520                 willSubsample = false;
521                 if (hasExtraComponents) { // e.g. alpha
522
wantJFIF = false;
523                 }
524                 break;
525             case ColorSpace.TYPE_RGB: // with or without alpha
526
// without alpha we just accept the JFIF defaults
527
if (hasAlpha) {
528                     wantJFIF = false;
529                 }
530                 break;
531             case ColorSpace.TYPE_3CLR:
532                 wantJFIF = false;
533                 willSubsample = false;
534                 if (cs.equals(ColorSpace.getInstance(ColorSpace.CS_PYCC))) {
535                     willSubsample = true;
536                     wantAdobe = true;
537                     componentIDs[0] = (byte) 'Y';
538                     componentIDs[1] = (byte) 'C';
539                     componentIDs[2] = (byte) 'c';
540                     if (hasAlpha) {
541                         componentIDs[3] = (byte) 'A';
542                     }
543                 }
544                 break;
545             case ColorSpace.TYPE_YCbCr:
546                 if (hasExtraComponents) { // e.g. K or alpha
547
wantJFIF = false;
548                     if (!hasAlpha) { // then it must be K
549
wantAdobe = true;
550                         transform = JPEG.ADOBE_YCCK;
551                     }
552                 }
553                 break;
554             case ColorSpace.TYPE_CMYK:
555                 wantJFIF = false;
556                 wantAdobe = true;
557                 transform = JPEG.ADOBE_YCCK;
558                 break;
559                 
560             default:
561                 // Everything else is not subsampled, gets no special marker,
562
// and component ids are 0 - N
563
wantJFIF = false;
564                 willSubsample = false;
565             }
566
567         }
568
569         // do we want an ICC profile?
570
if (wantJFIF && JPEG.isNonStandardICC(cs)) {
571             wantICC = true;
572         }
573
574         // Now step through the markers, consulting our variables.
575
if (wantJFIF) {
576             JFIFMarkerSegment jfif = new JFIFMarkerSegment();
577             markerSequence.add(jfif);
578             if (wantICC) {
579                 try {
580                     jfif.addICC((ICC_ColorSpace JavaDoc)cs);
581                 } catch (IOException JavaDoc e) {} // Can't happen here
582
}
583         }
584         // Adobe
585
if (wantAdobe) {
586             markerSequence.add(new AdobeMarkerSegment(transform));
587         }
588
589         // dqt
590
if (wantQTables) {
591             markerSequence.add(new DQTMarkerSegment(quality, willSubsample));
592         }
593
594         // dht
595
if (wantHTables) {
596             markerSequence.add(new DHTMarkerSegment(willSubsample));
597         }
598
599         // sof
600
markerSequence.add(new SOFMarkerSegment(wantProg,
601                                                 wantExtended,
602                                                 willSubsample,
603                                                 componentIDs,
604                                                 numComponents));
605
606         // sos
607
if (!wantProg) { // Default progression scans are done in the writer
608
markerSequence.add(new SOSMarkerSegment(willSubsample,
609                                                     componentIDs,
610                                                     numComponents));
611         }
612
613         // Defensive programming
614
if (!isConsistent()) {
615             throw new InternalError JavaDoc("Default image metadata is inconsistent");
616         }
617     }
618
619     ////// End of constructors
620

621     // Utilities for dealing with the marker sequence.
622
// The first ones have package access for access from the writer.
623

624     /**
625      * Returns the first MarkerSegment object in the list
626      * with the given tag, or null if none is found.
627      */

628     MarkerSegment findMarkerSegment(int tag) {
629         Iterator JavaDoc iter = markerSequence.iterator();
630         while (iter.hasNext()) {
631             MarkerSegment seg = (MarkerSegment)iter.next();
632             if (seg.tag == tag) {
633                 return seg;
634             }
635         }
636         return null;
637     }
638
639     /**
640      * Returns the first or last MarkerSegment object in the list
641      * of the given class, or null if none is found.
642      */

643     MarkerSegment findMarkerSegment(Class JavaDoc cls, boolean first) {
644         if (first) {
645             Iterator JavaDoc iter = markerSequence.iterator();
646             while (iter.hasNext()) {
647                 MarkerSegment seg = (MarkerSegment)iter.next();
648                 if (cls.isInstance(seg)) {
649                     return seg;
650                 }
651             }
652         } else {
653             ListIterator JavaDoc iter = markerSequence.listIterator(markerSequence.size());
654             while (iter.hasPrevious()) {
655                 MarkerSegment seg = (MarkerSegment)iter.previous();
656                 if (cls.isInstance(seg)) {
657                     return seg;
658                 }
659             }
660         }
661         return null;
662     }
663
664     /**
665      * Returns the index of the first or last MarkerSegment in the list
666      * of the given class, or -1 if none is found.
667      */

668     private int findMarkerSegmentPosition(Class JavaDoc cls, boolean first) {
669         if (first) {
670             ListIterator JavaDoc iter = markerSequence.listIterator();
671             for (int i = 0; iter.hasNext(); i++) {
672                 MarkerSegment seg = (MarkerSegment)iter.next();
673                 if (cls.isInstance(seg)) {
674                     return i;
675                 }
676             }
677         } else {
678             ListIterator JavaDoc iter = markerSequence.listIterator(markerSequence.size());
679             for (int i = markerSequence.size()-1; iter.hasPrevious(); i--) {
680                 MarkerSegment seg = (MarkerSegment)iter.previous();
681                 if (cls.isInstance(seg)) {
682                     return i;
683                 }
684             }
685         }
686         return -1;
687     }
688
689     private int findLastUnknownMarkerSegmentPosition() {
690         ListIterator JavaDoc iter = markerSequence.listIterator(markerSequence.size());
691         for (int i = markerSequence.size()-1; iter.hasPrevious(); i--) {
692             MarkerSegment seg = (MarkerSegment)iter.previous();
693             if (seg.unknown == true) {
694                 return i;
695             }
696         }
697         return -1;
698     }
699
700     // Implement Cloneable, but restrict access
701

702     protected Object JavaDoc clone() {
703         JPEGMetadata newGuy = null;
704         try {
705             newGuy = (JPEGMetadata) super.clone();
706         } catch (CloneNotSupportedException JavaDoc e) {} // won't happen
707
if (markerSequence != null) {
708             newGuy.markerSequence = (List JavaDoc) cloneSequence();
709         }
710         newGuy.resetSequence = null;
711         return newGuy;
712     }
713
714     /**
715      * Returns a deep copy of the current marker sequence.
716      */

717     private List JavaDoc cloneSequence() {
718         if (markerSequence == null) {
719             return null;
720         }
721         List JavaDoc retval = new ArrayList JavaDoc(markerSequence.size());
722         Iterator JavaDoc iter = markerSequence.iterator();
723         while(iter.hasNext()) {
724             MarkerSegment seg = (MarkerSegment)iter.next();
725             retval.add(seg.clone());
726         }
727
728         return retval;
729     }
730
731
732     // Tree methods
733

734     public Node JavaDoc getAsTree(String JavaDoc formatName) {
735         if (formatName == null) {
736             throw new IllegalArgumentException JavaDoc("null formatName!");
737         }
738         if (isStream) {
739             if (formatName.equals(JPEG.nativeStreamMetadataFormatName)) {
740                 return getNativeTree();
741             }
742         } else {
743             if (formatName.equals(JPEG.nativeImageMetadataFormatName)) {
744                 return getNativeTree();
745             }
746             if (formatName.equals
747                     (IIOMetadataFormatImpl.standardMetadataFormatName)) {
748                 return getStandardTree();
749             }
750         }
751         throw new IllegalArgumentException JavaDoc("Unsupported format name: "
752                                                 + formatName);
753     }
754
755     IIOMetadataNode JavaDoc getNativeTree() {
756         IIOMetadataNode JavaDoc root;
757         IIOMetadataNode JavaDoc top;
758         Iterator JavaDoc iter = markerSequence.iterator();
759         if (isStream) {
760             root = new IIOMetadataNode JavaDoc(JPEG.nativeStreamMetadataFormatName);
761             top = root;
762         } else {
763             IIOMetadataNode JavaDoc sequence = new IIOMetadataNode JavaDoc("markerSequence");
764             if (!inThumb) {
765                 root = new IIOMetadataNode JavaDoc(JPEG.nativeImageMetadataFormatName);
766                 IIOMetadataNode JavaDoc header = new IIOMetadataNode JavaDoc("JPEGvariety");
767                 root.appendChild(header);
768                 JFIFMarkerSegment jfif = (JFIFMarkerSegment)
769                     findMarkerSegment(JFIFMarkerSegment.class, true);
770                 if (jfif != null) {
771                     iter.next(); // JFIF must be first, so this skips it
772
header.appendChild(jfif.getNativeNode());
773                 }
774                 root.appendChild(sequence);
775             } else {
776                 root = sequence;
777             }
778             top = sequence;
779         }
780         while(iter.hasNext()) {
781             MarkerSegment seg = (MarkerSegment) iter.next();
782             top.appendChild(seg.getNativeNode());
783         }
784         return root;
785     }
786
787     // Standard tree node methods
788

789     protected IIOMetadataNode JavaDoc getStandardChromaNode() {
790         hasAlpha = false; // Unless we find otherwise
791

792         // Colorspace type - follow the rules in the spec
793
// First get the SOF marker segment, if there is one
794
SOFMarkerSegment sof = (SOFMarkerSegment)
795             findMarkerSegment(SOFMarkerSegment.class, true);
796         if (sof == null) {
797             // No image, so no chroma
798
return null;
799         }
800
801         IIOMetadataNode JavaDoc chroma = new IIOMetadataNode JavaDoc("Chroma");
802         IIOMetadataNode JavaDoc csType = new IIOMetadataNode JavaDoc("ColorSpaceType");
803         chroma.appendChild(csType);
804
805         // get the number of channels
806
int numChannels = sof.componentSpecs.length;
807
808         IIOMetadataNode JavaDoc numChanNode = new IIOMetadataNode JavaDoc("NumChannels");
809         chroma.appendChild(numChanNode);
810         numChanNode.setAttribute("value", Integer.toString(numChannels));
811
812         // is there a JFIF marker segment?
813
if (findMarkerSegment(JFIFMarkerSegment.class, true) != null) {
814             if (numChannels == 1) {
815                 csType.setAttribute("name", "GRAY");
816             } else {
817                 csType.setAttribute("name", "YCbCr");
818             }
819             return chroma;
820         }
821
822         // How about an Adobe marker segment?
823
AdobeMarkerSegment adobe =
824             (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class, true);
825         if (adobe != null){
826             switch (adobe.transform) {
827             case JPEG.ADOBE_YCCK:
828                 csType.setAttribute("name", "YCCK");
829                 break;
830             case JPEG.ADOBE_YCC:
831                 csType.setAttribute("name", "YCbCr");
832                 break;
833             case JPEG.ADOBE_UNKNOWN:
834                 if (numChannels == 3) {
835                     csType.setAttribute("name", "RGB");
836                 } else if (numChannels == 4) {
837                     csType.setAttribute("name", "CMYK");
838                 }
839                 break;
840             }
841             return chroma;
842         }
843
844         // Neither marker. Check components
845
if (numChannels < 3) {
846             csType.setAttribute("name", "GRAY");
847             if (numChannels == 2) {
848                 hasAlpha = true;
849             }
850             return chroma;
851         }
852
853         boolean idsAreJFIF = true;
854
855         for (int i = 0; i < sof.componentSpecs.length; i++) {
856             int id = sof.componentSpecs[i].componentId;
857             if ((id < 1) || (id >= sof.componentSpecs.length)) {
858                 idsAreJFIF = false;
859             }
860         }
861         
862         if (idsAreJFIF) {
863             csType.setAttribute("name", "YCbCr");
864             if (numChannels == 4) {
865                 hasAlpha = true;
866             }
867             return chroma;
868         }
869
870         // Check against the letters
871
if ((sof.componentSpecs[0].componentId == 'R')
872             && (sof.componentSpecs[1].componentId == 'G')
873             && (sof.componentSpecs[2].componentId == 'B')){
874
875             csType.setAttribute("name", "RGB");
876             if ((numChannels == 4)
877                 && (sof.componentSpecs[3].componentId == 'A')) {
878                 hasAlpha = true;
879             }
880             return chroma;
881         }
882         
883         if ((sof.componentSpecs[0].componentId == 'Y')
884             && (sof.componentSpecs[1].componentId == 'C')
885             && (sof.componentSpecs[2].componentId == 'c')){
886
887             csType.setAttribute("name", "PhotoYCC");
888             if ((numChannels == 4)
889                 && (sof.componentSpecs[3].componentId == 'A')) {
890                 hasAlpha = true;
891             }
892             return chroma;
893         }
894         
895         // Finally, 3-channel subsampled are YCbCr, unsubsampled are RGB
896
// 4-channel subsampled are YCbCrA, unsubsampled are CMYK
897

898         boolean subsampled = false;
899
900         int hfactor = sof.componentSpecs[0].HsamplingFactor;
901         int vfactor = sof.componentSpecs[0].VsamplingFactor;
902
903         for (int i = 1; i<sof.componentSpecs.length; i++) {
904             if ((sof.componentSpecs[i].HsamplingFactor != hfactor)
905                 || (sof.componentSpecs[i].VsamplingFactor != vfactor)){
906                 subsampled = true;
907                 break;
908             }
909         }
910
911         if (subsampled) {
912             csType.setAttribute("name", "YCbCr");
913             if (numChannels == 4) {
914                 hasAlpha = true;
915             }
916             return chroma;
917         }
918          
919         // Not subsampled. numChannels < 3 is taken care of above
920
if (numChannels == 3) {
921             csType.setAttribute("name", "RGB");
922         } else {
923             csType.setAttribute("name", "CMYK");
924         }
925
926         return chroma;
927     }
928
929     protected IIOMetadataNode JavaDoc getStandardCompressionNode() {
930
931         IIOMetadataNode JavaDoc compression = new IIOMetadataNode JavaDoc("Compression");
932
933         // CompressionTypeName
934
IIOMetadataNode JavaDoc name = new IIOMetadataNode JavaDoc("CompressionTypeName");
935         name.setAttribute("value", "JPEG");
936         compression.appendChild(name);
937
938         // Lossless - false
939
IIOMetadataNode JavaDoc lossless = new IIOMetadataNode JavaDoc("Lossless");
940         lossless.setAttribute("value", "false");
941         compression.appendChild(lossless);
942
943         // NumProgressiveScans - count sos segments
944
int sosCount = 0;
945         Iterator JavaDoc iter = markerSequence.iterator();
946         while (iter.hasNext()) {
947             MarkerSegment ms = (MarkerSegment) iter.next();
948             if (ms.tag == JPEG.SOS) {
949                 sosCount++;
950             }
951         }
952         if (sosCount != 0) {
953             IIOMetadataNode JavaDoc prog = new IIOMetadataNode JavaDoc("NumProgressiveScans");
954             prog.setAttribute("value", Integer.toString(sosCount));
955             compression.appendChild(prog);
956         }
957
958         return compression;
959     }
960
961     protected IIOMetadataNode JavaDoc getStandardDimensionNode() {
962         // If we have a JFIF marker segment, we know a little
963
// otherwise all we know is the orientation, which is always normal
964
IIOMetadataNode JavaDoc dim = new IIOMetadataNode JavaDoc("Dimension");
965         IIOMetadataNode JavaDoc orient = new IIOMetadataNode JavaDoc("ImageOrientation");
966         orient.setAttribute("value", "normal");
967         dim.appendChild(orient);
968
969         JFIFMarkerSegment jfif =
970             (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class, true);
971         if (jfif != null) {
972
973             // Aspect Ratio is width of pixel / height of pixel
974
float aspectRatio;
975             if (jfif.resUnits == 0) {
976                 // In this case they just encode aspect ratio directly
977
aspectRatio = ((float) jfif.Xdensity)/jfif.Ydensity;
978             } else {
979                 // They are true densities (e.g. dpi) and must be inverted
980
aspectRatio = ((float) jfif.Ydensity)/jfif.Xdensity;
981             }
982             IIOMetadataNode JavaDoc aspect = new IIOMetadataNode JavaDoc("PixelAspectRatio");
983             aspect.setAttribute("value", Float.toString(aspectRatio));
984             dim.insertBefore(aspect, orient);
985
986             // Pixel size
987
if (jfif.resUnits != 0) {
988                 // 1 == dpi, 2 == dpc
989
float scale = (jfif.resUnits == 1) ? 25.4F : 10.0F;
990
991                 IIOMetadataNode JavaDoc horiz =
992                     new IIOMetadataNode JavaDoc("HorizontalPixelSize");
993                 horiz.setAttribute("value",
994                                    Float.toString(scale/jfif.Xdensity));
995                 dim.appendChild(horiz);
996
997                 IIOMetadataNode JavaDoc vert =
998                     new IIOMetadataNode JavaDoc("VerticalPixelSize");
999                 vert.setAttribute("value",
1000                                  Float.toString(scale/jfif.Ydensity));
1001                dim.appendChild(vert);
1002            }
1003        }
1004        return dim;
1005    }
1006
1007    protected IIOMetadataNode JavaDoc getStandardTextNode() {
1008        IIOMetadataNode JavaDoc text = null;
1009        // Add a text entry for each COM Marker Segment
1010
if (findMarkerSegment(JPEG.COM) != null) {
1011            text = new IIOMetadataNode JavaDoc("Text");
1012            Iterator JavaDoc iter = markerSequence.iterator();
1013            while (iter.hasNext()) {
1014                MarkerSegment seg = (MarkerSegment) iter.next();
1015                if (seg.tag == JPEG.COM) {
1016                    COMMarkerSegment com = (COMMarkerSegment) seg;
1017                    IIOMetadataNode JavaDoc entry = new IIOMetadataNode JavaDoc("TextEntry");
1018                    entry.setAttribute("keyword", "comment");
1019                    entry.setAttribute("value", com.getComment());
1020                text.appendChild(entry);
1021                }
1022            }
1023        }
1024        return text;
1025    }
1026
1027    protected IIOMetadataNode JavaDoc getStandardTransparencyNode() {
1028        IIOMetadataNode JavaDoc trans = null;
1029        if (hasAlpha == true) {
1030            trans = new IIOMetadataNode JavaDoc("Transparency");
1031            IIOMetadataNode JavaDoc alpha = new IIOMetadataNode JavaDoc("Alpha");
1032            alpha.setAttribute("value", "nonpremultiplied"); // Always assume
1033
trans.appendChild(alpha);
1034        }
1035        return trans;
1036    }
1037
1038    // Editing
1039

1040    public boolean isReadOnly() {
1041        return false;
1042    }
1043
1044    public void mergeTree(String JavaDoc formatName, Node JavaDoc root)
1045        throws IIOInvalidTreeException JavaDoc {
1046        if (formatName == null) {
1047            throw new IllegalArgumentException JavaDoc("null formatName!");
1048        }
1049        if (root == null) {
1050            throw new IllegalArgumentException JavaDoc("null root!");
1051        }
1052        List JavaDoc copy = null;
1053        if (resetSequence == null) {
1054            resetSequence = cloneSequence(); // Deep copy
1055
copy = resetSequence; // Avoid cloning twice
1056
} else {
1057            copy = cloneSequence();
1058        }
1059        if (isStream &&
1060            (formatName.equals(JPEG.nativeStreamMetadataFormatName))) {
1061                mergeNativeTree(root);
1062        } else if (!isStream &&
1063                   (formatName.equals(JPEG.nativeImageMetadataFormatName))) {
1064            mergeNativeTree(root);
1065        } else if (!isStream &&
1066                   (formatName.equals
1067                    (IIOMetadataFormatImpl.standardMetadataFormatName))) {
1068            mergeStandardTree(root);
1069        } else {
1070            throw new IllegalArgumentException JavaDoc("Unsupported format name: "
1071                                                + formatName);
1072        }
1073        if (!isConsistent()) {
1074            markerSequence = copy;
1075            throw new IIOInvalidTreeException JavaDoc
1076                ("Merged tree is invalid; original restored", root);
1077        }
1078    }
1079
1080    private void mergeNativeTree(Node JavaDoc root) throws IIOInvalidTreeException JavaDoc {
1081        String JavaDoc name = root.getNodeName();
1082        if (name != ((isStream) ? JPEG.nativeStreamMetadataFormatName
1083                                : JPEG.nativeImageMetadataFormatName)) {
1084            throw new IIOInvalidTreeException JavaDoc("Invalid root node name: " + name,
1085                                              root);
1086        }
1087        if (root.getChildNodes().getLength() != 2) { // JPEGvariety and markerSequence
1088
throw new IIOInvalidTreeException JavaDoc(
1089                "JPEGvariety and markerSequence nodes must be present", root);
1090        }
1091        mergeJFIFsubtree(root.getFirstChild());
1092        mergeSequenceSubtree(root.getLastChild());
1093    }
1094
1095    /**
1096     * Merge a JFIF subtree into the marker sequence, if the subtree
1097     * is non-empty.
1098     * If a JFIF marker exists, update it from the subtree.
1099     * If none exists, create one from the subtree and insert it at the
1100     * beginning of the marker sequence.
1101     */

1102    private void mergeJFIFsubtree(Node JavaDoc JPEGvariety)
1103        throws IIOInvalidTreeException JavaDoc {
1104        if (JPEGvariety.getChildNodes().getLength() != 0) {
1105            Node JavaDoc jfifNode = JPEGvariety.getFirstChild();
1106            // is there already a jfif marker segment?
1107
JFIFMarkerSegment jfifSeg =
1108                (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class, true);
1109            if (jfifSeg != null) {
1110                jfifSeg.updateFromNativeNode(jfifNode, false);
1111            } else {
1112                // Add it as the first element in the list.
1113
markerSequence.add(0, new JFIFMarkerSegment(jfifNode));
1114            }
1115        }
1116    }
1117
1118    private void mergeSequenceSubtree(Node JavaDoc sequenceTree)
1119        throws IIOInvalidTreeException JavaDoc {
1120        NodeList JavaDoc children = sequenceTree.getChildNodes();
1121        for (int i = 0; i < children.getLength(); i++) {
1122            Node JavaDoc node = children.item(i);
1123            String JavaDoc name = node.getNodeName();
1124            if (name.equals("dqt")) {
1125                mergeDQTNode(node);
1126            } else if (name.equals("dht")) {
1127                mergeDHTNode(node);
1128            } else if (name.equals("dri")) {
1129                mergeDRINode(node);
1130            } else if (name.equals("com")) {
1131                mergeCOMNode(node);
1132            } else if (name.equals("app14Adobe")) {
1133                mergeAdobeNode(node);
1134            } else if (name.equals("unknown")) {
1135                mergeUnknownNode(node);
1136            } else if (name.equals("sof")) {
1137                mergeSOFNode(node);
1138            } else if (name.equals("sos")) {
1139                mergeSOSNode(node);
1140            } else {
1141                throw new IIOInvalidTreeException JavaDoc("Invalid node: " + name, node);
1142            }
1143        }
1144    }
1145
1146    /**
1147     * Merge the given DQT node into the marker sequence. If there already
1148     * exist DQT marker segments in the sequence, then each table in the
1149     * node replaces the first table, in any DQT segment, with the same
1150     * table id. If none of the existing DQT segments contain a table with
1151     * the same id, then the table is added to the last existing DQT segment.
1152     * If there are no DQT segments, then a new one is created and added
1153     * as follows:
1154     * If there are DHT segments, the new DQT segment is inserted before the
1155     * first one.
1156     * If there are no DHT segments, the new DQT segment is inserted before
1157     * an SOF segment, if there is one.
1158     * If there is no SOF segment, the new DQT segment is inserted before
1159     * the first SOS segment, if there is one.
1160     * If there is no SOS segment, the new DQT segment is added to the end
1161     * of the sequence.
1162     */

1163    private void mergeDQTNode(Node JavaDoc node) throws IIOInvalidTreeException JavaDoc {
1164        // First collect any existing DQT nodes into a local list
1165
ArrayList JavaDoc oldDQTs = new ArrayList JavaDoc();
1166        Iterator JavaDoc iter = markerSequence.iterator();
1167        while (iter.hasNext()) {
1168            MarkerSegment seg = (MarkerSegment) iter.next();
1169            if (seg instanceof DQTMarkerSegment) {
1170                oldDQTs.add(seg);
1171            }
1172        }
1173        if (!oldDQTs.isEmpty()) {
1174            NodeList JavaDoc children = node.getChildNodes();
1175            for (int i = 0; i < children.getLength(); i++) {
1176                Node JavaDoc child = children.item(i);
1177                int childID = MarkerSegment.getAttributeValue(child,
1178                                                              null,
1179                                                              "qtableId",
1180                                                              0, 3,
1181                                                              true);
1182                DQTMarkerSegment dqt = null;
1183                int tableIndex = -1;
1184                for (int j = 0; j < oldDQTs.size(); j++) {
1185                    DQTMarkerSegment testDQT = (DQTMarkerSegment) oldDQTs.get(j);
1186                    for (int k = 0; k < testDQT.tables.size(); k++) {
1187                        DQTMarkerSegment.Qtable testTable =
1188                            (DQTMarkerSegment.Qtable) testDQT.tables.get(k);
1189                        if (childID == testTable.tableID) {
1190                            dqt = testDQT;
1191                            tableIndex = k;
1192                            break;
1193                        }
1194                    }
1195                    if (dqt != null) break;
1196                }
1197                if (dqt != null) {
1198                    dqt.tables.set(tableIndex, dqt.getQtableFromNode(child));
1199                } else {
1200                    dqt = (DQTMarkerSegment) oldDQTs.get(oldDQTs.size()-1);
1201                    dqt.tables.add(dqt.getQtableFromNode(child));
1202                }
1203            }
1204        } else {
1205            DQTMarkerSegment newGuy = new DQTMarkerSegment(node);
1206            int firstDHT = findMarkerSegmentPosition(DHTMarkerSegment.class, true);
1207            int firstSOF = findMarkerSegmentPosition(SOFMarkerSegment.class, true);
1208            int firstSOS = findMarkerSegmentPosition(SOSMarkerSegment.class, true);
1209            if (firstDHT != -1) {
1210                markerSequence.add(firstDHT, newGuy);
1211            } else if (firstSOF != -1) {
1212                markerSequence.add(firstSOF, newGuy);
1213            } else if (firstSOS != -1) {
1214                markerSequence.add(firstSOS, newGuy);
1215            } else {
1216                markerSequence.add(newGuy);
1217            }
1218        }
1219    }
1220
1221    /**
1222     * Merge the given DHT node into the marker sequence. If there already
1223     * exist DHT marker segments in the sequence, then each table in the
1224     * node replaces the first table, in any DHT segment, with the same
1225     * table class and table id. If none of the existing DHT segments contain
1226     * a table with the same class and id, then the table is added to the last
1227     * existing DHT segment.
1228     * If there are no DHT segments, then a new one is created and added
1229     * as follows:
1230     * If there are DQT segments, the new DHT segment is inserted immediately
1231     * following the last DQT segment.
1232     * If there are no DQT segments, the new DHT segment is inserted before
1233     * an SOF segment, if there is one.
1234     * If there is no SOF segment, the new DHT segment is inserted before
1235     * the first SOS segment, if there is one.
1236     * If there is no SOS segment, the new DHT segment is added to the end
1237     * of the sequence.
1238     */

1239    private void mergeDHTNode(Node JavaDoc node) throws IIOInvalidTreeException JavaDoc {
1240        // First collect any existing DQT nodes into a local list
1241
ArrayList JavaDoc oldDHTs = new ArrayList JavaDoc();
1242        Iterator JavaDoc iter = markerSequence.iterator();
1243        while (iter.hasNext()) {
1244            MarkerSegment seg = (MarkerSegment) iter.next();
1245            if (seg instanceof DHTMarkerSegment) {
1246                oldDHTs.add(seg);
1247            }
1248        }
1249        if (!oldDHTs.isEmpty()) {
1250            NodeList JavaDoc children = node.getChildNodes();
1251            for (int i = 0; i < children.getLength(); i++) {
1252                Node JavaDoc child = children.item(i);
1253                NamedNodeMap JavaDoc attrs = node.getAttributes();
1254                int childID = MarkerSegment.getAttributeValue(child,
1255                                                              attrs,
1256                                                              "htableId",
1257                                                              0, 3,
1258                                                              true);
1259                int childClass = MarkerSegment.getAttributeValue(child,
1260                                                                 attrs,
1261                                                                 "class",
1262                                                                 0, 1,
1263                                                                 true);
1264                DHTMarkerSegment dht = null;
1265                int tableIndex = -1;
1266                for (int j = 0; j < oldDHTs.size(); j++) {
1267                    DHTMarkerSegment testDHT = (DHTMarkerSegment) oldDHTs.get(j);
1268                    for (int k = 0; k < testDHT.tables.size(); k++) {
1269                        DHTMarkerSegment.Htable testTable =
1270                            (DHTMarkerSegment.Htable) testDHT.tables.get(k);
1271                        if ((childID == testTable.tableID) &&
1272                            (childClass == testTable.tableClass)) {
1273                            dht = testDHT;
1274                            tableIndex = k;
1275                            break;
1276                        }
1277                    }
1278                    if (dht != null) break;
1279                }
1280                if (dht != null) {
1281                    dht.tables.set(tableIndex, dht.getHtableFromNode(child));
1282                } else {
1283                    dht = (DHTMarkerSegment) oldDHTs.get(oldDHTs.size()-1);
1284                    dht.tables.add(dht.getHtableFromNode(child));
1285                }
1286            }
1287        } else {
1288            DHTMarkerSegment newGuy = new DHTMarkerSegment(node);
1289            int lastDQT = findMarkerSegmentPosition(DQTMarkerSegment.class, false);
1290            int firstSOF = findMarkerSegmentPosition(SOFMarkerSegment.class, true);
1291            int firstSOS = findMarkerSegmentPosition(SOSMarkerSegment.class, true);
1292            if (lastDQT != -1) {
1293                markerSequence.add(lastDQT+1, newGuy);
1294            } else if (firstSOF != -1) {
1295                markerSequence.add(firstSOF, newGuy);
1296            } else if (firstSOS != -1) {
1297                markerSequence.add(firstSOS, newGuy);
1298            } else {
1299                markerSequence.add(newGuy);
1300            }
1301        }
1302    }
1303
1304    /**
1305     * Merge the given DRI node into the marker sequence.
1306     * If there already exists a DRI marker segment, the restart interval
1307     * value is updated.
1308     * If there is no DRI segment, then a new one is created and added as
1309     * follows:
1310     * If there is an SOF segment, the new DRI segment is inserted before
1311     * it.
1312     * If there is no SOF segment, the new DRI segment is inserted before
1313     * the first SOS segment, if there is one.
1314     * If there is no SOS segment, the new DRI segment is added to the end
1315     * of the sequence.
1316     */

1317    private void mergeDRINode(Node JavaDoc node) throws IIOInvalidTreeException JavaDoc {
1318        DRIMarkerSegment dri =
1319            (DRIMarkerSegment) findMarkerSegment(DRIMarkerSegment.class, true);
1320        if (dri != null) {
1321            dri.updateFromNativeNode(node, false);
1322        } else {
1323            DRIMarkerSegment newGuy = new DRIMarkerSegment(node);
1324            int firstSOF = findMarkerSegmentPosition(SOFMarkerSegment.class, true);
1325            int firstSOS = findMarkerSegmentPosition(SOSMarkerSegment.class, true);
1326            if (firstSOF != -1) {
1327                markerSequence.add(firstSOF, newGuy);
1328            } else if (firstSOS != -1) {
1329                markerSequence.add(firstSOS, newGuy);
1330            } else {
1331                markerSequence.add(newGuy);
1332            }
1333        }
1334    }
1335
1336    /**
1337     * Merge the given COM node into the marker sequence.
1338     * A new COM marker segment is created and added to the sequence
1339     * using insertCOMMarkerSegment.
1340     */

1341    private void mergeCOMNode(Node JavaDoc node) throws IIOInvalidTreeException JavaDoc {
1342        COMMarkerSegment newGuy = new COMMarkerSegment(node);
1343        insertCOMMarkerSegment(newGuy);
1344    }
1345
1346     /**
1347      * Insert a new COM marker segment into an appropriate place in the
1348      * marker sequence, as follows:
1349      * If there already exist COM marker segments, the new one is inserted
1350      * after the last one.
1351      * If there are no COM segments, the new COM segment is inserted after the
1352      * JFIF segment, if there is one.
1353      * If there is no JFIF segment, the new COM segment is inserted after the
1354      * Adobe marker segment, if there is one.
1355      * If there is no Adobe segment, the new COM segment is inserted
1356      * at the beginning of the sequence.
1357      */

1358    private void insertCOMMarkerSegment(COMMarkerSegment newGuy) {
1359        int lastCOM = findMarkerSegmentPosition(COMMarkerSegment.class, false);
1360        boolean hasJFIF = (findMarkerSegment(JFIFMarkerSegment.class, true) != null);
1361        int firstAdobe = findMarkerSegmentPosition(AdobeMarkerSegment.class, true);
1362        if (lastCOM != -1) {
1363            markerSequence.add(lastCOM+1, newGuy);
1364        } else if (hasJFIF) {
1365            markerSequence.add(1, newGuy); // JFIF is always 0
1366
} else if (firstAdobe != -1) {
1367            markerSequence.add(firstAdobe+1, newGuy);
1368        } else {
1369            markerSequence.add(0, newGuy);
1370        }
1371    }
1372
1373    /**
1374     * Merge the given Adobe APP14 node into the marker sequence.
1375     * If there already exists an Adobe marker segment, then its attributes
1376     * are updated from the node.
1377     * If there is no Adobe segment, then a new one is created and added
1378     * using insertAdobeMarkerSegment.
1379     */

1380    private void mergeAdobeNode(Node JavaDoc node) throws IIOInvalidTreeException JavaDoc {
1381        AdobeMarkerSegment adobe =
1382            (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class, true);
1383        if (adobe != null) {
1384            adobe.updateFromNativeNode(node, false);
1385        } else {
1386            AdobeMarkerSegment newGuy = new AdobeMarkerSegment(node);
1387            insertAdobeMarkerSegment(newGuy);
1388        }
1389    }
1390
1391    /**
1392     * Insert the given AdobeMarkerSegment into the marker sequence, as
1393     * follows (we assume there is no Adobe segment yet):
1394     * If there is a JFIF segment, then the new Adobe segment is inserted
1395     * after it.
1396     * If there is no JFIF segment, the new Adobe segment is inserted after the
1397     * last Unknown segment, if there are any.
1398     * If there are no Unknown segments, the new Adobe segment is inserted
1399     * at the beginning of the sequence.
1400     */

1401    private void insertAdobeMarkerSegment(AdobeMarkerSegment newGuy) {
1402        boolean hasJFIF =
1403            (findMarkerSegment(JFIFMarkerSegment.class, true) != null);
1404        int lastUnknown = findLastUnknownMarkerSegmentPosition();
1405        if (hasJFIF) {
1406            markerSequence.add(1, newGuy); // JFIF is always 0
1407
} else if (lastUnknown != -1) {
1408            markerSequence.add(lastUnknown+1, newGuy);
1409        } else {
1410            markerSequence.add(0, newGuy);
1411        }
1412    }
1413
1414    /**
1415     * Merge the given Unknown node into the marker sequence.
1416     * A new Unknown marker segment is created and added to the sequence as
1417     * follows:
1418     * If there already exist Unknown marker segments, the new one is inserted
1419     * after the last one.
1420     * If there are no Unknown marker segments, the new Unknown marker segment
1421     * is inserted after the JFIF segment, if there is one.
1422     * If there is no JFIF segment, the new Unknown segment is inserted before
1423     * the Adobe marker segment, if there is one.
1424     * If there is no Adobe segment, the new Unknown segment is inserted
1425     * at the beginning of the sequence.
1426     */

1427    private void mergeUnknownNode(Node JavaDoc node) throws IIOInvalidTreeException JavaDoc {
1428        MarkerSegment newGuy = new MarkerSegment(node);
1429        int lastUnknown = findLastUnknownMarkerSegmentPosition();
1430        boolean hasJFIF = (findMarkerSegment(JFIFMarkerSegment.class, true) != null);
1431        int firstAdobe = findMarkerSegmentPosition(AdobeMarkerSegment.class, true);
1432        if (lastUnknown != -1) {
1433            markerSequence.add(lastUnknown+1, newGuy);
1434        } else if (hasJFIF) {
1435            markerSequence.add(1, newGuy); // JFIF is always 0
1436
} if (firstAdobe != -1) {
1437            markerSequence.add(firstAdobe, newGuy);
1438        } else {
1439            markerSequence.add(0, newGuy);
1440        }
1441    }
1442
1443    /**
1444     * Merge the given SOF node into the marker sequence.
1445     * If there already exists an SOF marker segment in the sequence, then
1446     * its values are updated from the node.
1447     * If there is no SOF segment, then a new one is created and added as
1448     * follows:
1449     * If there are any SOS segments, the new SOF segment is inserted before
1450     * the first one.
1451     * If there is no SOS segment, the new SOF segment is added to the end
1452     * of the sequence.
1453     *
1454     */

1455    private void mergeSOFNode(Node JavaDoc node) throws IIOInvalidTreeException JavaDoc {
1456        SOFMarkerSegment sof =
1457            (SOFMarkerSegment) findMarkerSegment(SOFMarkerSegment.class, true);
1458        if (sof != null) {
1459            sof.updateFromNativeNode(node, false);
1460        } else {
1461            SOFMarkerSegment newGuy = new SOFMarkerSegment(node);
1462            int firstSOS = findMarkerSegmentPosition(SOSMarkerSegment.class, true);
1463            if (firstSOS != -1) {
1464                markerSequence.add(firstSOS, newGuy);
1465            } else {
1466                markerSequence.add(newGuy);
1467            }
1468        }
1469    }
1470
1471    /**
1472     * Merge the given SOS node into the marker sequence.
1473     * If there already exists a single SOS marker segment, then the values
1474     * are updated from the node.
1475     * If there are more than one existing SOS marker segments, then an
1476     * IIOInvalidTreeException is thrown, as SOS segments cannot be merged
1477     * into a set of progressive scans.
1478     * If there are no SOS marker segments, a new one is created and added
1479     * to the end of the sequence.
1480     */

1481    private void mergeSOSNode(Node JavaDoc node) throws IIOInvalidTreeException JavaDoc {
1482        SOSMarkerSegment firstSOS =
1483            (SOSMarkerSegment) findMarkerSegment(SOSMarkerSegment.class, true);
1484        SOSMarkerSegment lastSOS =
1485            (SOSMarkerSegment) findMarkerSegment(SOSMarkerSegment.class, false);
1486        if (firstSOS != null) {
1487            if (firstSOS != lastSOS) {
1488                throw new IIOInvalidTreeException JavaDoc
1489                    ("Can't merge SOS node into a tree with > 1 SOS node", node);
1490            }
1491            firstSOS.updateFromNativeNode(node, false);
1492        } else {
1493            markerSequence.add(new SOSMarkerSegment(node));
1494        }
1495    }
1496
1497    private boolean transparencyDone;
1498
1499    private void mergeStandardTree(Node JavaDoc root) throws IIOInvalidTreeException JavaDoc {
1500        transparencyDone = false;
1501        NodeList JavaDoc children = root.getChildNodes();
1502        for (int i = 0; i < children.getLength(); i++) {
1503            Node JavaDoc node = children.item(i);
1504            String JavaDoc name = node.getNodeName();
1505            if (name.equals("Chroma")) {
1506                mergeStandardChromaNode(node, children);
1507            } else if (name.equals("Compression")) {
1508                mergeStandardCompressionNode(node);
1509            } else if (name.equals("Data")) {
1510                mergeStandardDataNode(node);
1511            } else if (name.equals("Dimension")) {
1512                mergeStandardDimensionNode(node);
1513            } else if (name.equals("Document")) {
1514                mergeStandardDocumentNode(node);
1515            } else if (name.equals("Text")) {
1516                mergeStandardTextNode(node);
1517            } else if (name.equals("Transparency")) {
1518                mergeStandardTransparencyNode(node);
1519            } else {
1520                throw new IIOInvalidTreeException JavaDoc("Invalid node: " + name, node);
1521            }
1522        }
1523    }
1524
1525    /*
1526     * In general, it could be possible to convert all non-pixel data to some
1527     * textual form and include it in comments, but then this would create the
1528     * expectation that these comment forms be recognized by the reader, thus
1529     * creating a defacto extension to JPEG metadata capabilities. This is
1530     * probably best avoided, so the following convert only text nodes to
1531     * comments, and lose the keywords as well.
1532     */

1533
1534    private void mergeStandardChromaNode(Node JavaDoc node, NodeList JavaDoc siblings)
1535        throws IIOInvalidTreeException JavaDoc {
1536        // ColorSpaceType can change the target colorspace for compression
1537
// This must take any transparency node into account as well, as
1538
// that affects the number of channels (if alpha is present). If
1539
// a transparency node is dealt with here, set a flag to indicate
1540
// this to the transparency processor below. If we discover that
1541
// the nodes are not in order, throw an exception as the tree is
1542
// invalid.
1543

1544        if (transparencyDone) {
1545            throw new IIOInvalidTreeException JavaDoc
1546                ("Transparency node must follow Chroma node", node);
1547        }
1548
1549        Node JavaDoc csType = node.getFirstChild();
1550        if ((csType == null) || !csType.getNodeName().equals("ColorSpaceType")) {
1551            // If there is no ColorSpaceType node, we have nothing to do
1552
return;
1553        }
1554
1555        String JavaDoc csName = csType.getAttributes().getNamedItem("name").getNodeValue();
1556
1557        int numChannels = 0;
1558        boolean wantJFIF = false;
1559        boolean wantAdobe = false;
1560        int transform = 0;
1561        boolean willSubsample = false;
1562        byte [] ids = {1, 2, 3, 4}; // JFIF compatible
1563
if (csName.equals("GRAY")) {
1564            numChannels = 1;
1565            wantJFIF = true;
1566        } else if (csName.equals("YCbCr")) {
1567            numChannels = 3;
1568            wantJFIF = true;
1569            willSubsample = true;
1570        } else if (csName.equals("PhotoYCC")) {
1571            numChannels = 3;
1572            wantAdobe = true;
1573            transform = JPEG.ADOBE_YCC;
1574            ids[0] = (byte) 'Y';
1575            ids[1] = (byte) 'C';
1576            ids[2] = (byte) 'c';
1577        } else if (csName.equals("RGB")) {
1578            numChannels = 3;
1579            wantAdobe = true;
1580            transform = JPEG.ADOBE_UNKNOWN;
1581            ids[0] = (byte) 'R';
1582            ids[1] = (byte) 'G';
1583            ids[2] = (byte) 'B';
1584        } else if ((csName.equals("XYZ"))
1585                   || (csName.equals("Lab"))
1586                   || (csName.equals("Luv"))
1587                   || (csName.equals("YxY"))
1588                   || (csName.equals("HSV"))
1589                   || (csName.equals("HLS"))
1590                   || (csName.equals("CMY"))
1591                   || (csName.equals("3CLR"))) {
1592            numChannels = 3;
1593        } else if (csName.equals("YCCK")) {
1594            numChannels = 4;
1595            wantAdobe = true;
1596            transform = JPEG.ADOBE_YCCK;
1597            willSubsample = true;
1598        } else if (csName.equals("CMYK")) {
1599            numChannels = 4;
1600            wantAdobe = true;
1601            transform = JPEG.ADOBE_UNKNOWN;
1602        } else if (csName.equals("4CLR")) {
1603            numChannels = 4;
1604        } else { // We can't handle them, so don't modify any metadata
1605
return;
1606        }
1607
1608        boolean wantAlpha = false;
1609        for (int i = 0; i < siblings.getLength(); i++) {
1610            Node JavaDoc trans = siblings.item(i);
1611            if (trans.getNodeName().equals("Transparency")) {
1612                wantAlpha = wantAlpha(trans);
1613                break; // out of for
1614
}
1615        }
1616
1617        if (wantAlpha) {
1618            numChannels++;
1619            wantJFIF = false;
1620            if (ids[0] == (byte) 'R') {
1621                ids[3] = (byte) 'A';
1622                wantAdobe = false;
1623            }
1624        }
1625
1626        JFIFMarkerSegment jfif =
1627            (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class, true);
1628        AdobeMarkerSegment adobe =
1629            (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class, true);
1630        SOFMarkerSegment sof =
1631            (SOFMarkerSegment) findMarkerSegment(SOFMarkerSegment.class, true);
1632        SOSMarkerSegment sos =
1633            (SOSMarkerSegment) findMarkerSegment(SOSMarkerSegment.class, true);
1634
1635        // If the metadata specifies progressive, then the number of channels
1636
// must match, so that we can modify all the existing SOS marker segments.
1637
// If they don't match, we don't know what to do with SOS so we can't do
1638
// the merge. We then just return silently.
1639
// An exception would not be appropriate. A warning might, but we have
1640
// nowhere to send it to.
1641
if ((sof != null) && (sof.tag == JPEG.SOF2)) { // Progressive
1642
if ((sof.componentSpecs.length != numChannels) && (sos != null)) {
1643                return;
1644            }
1645        }
1646
1647        // JFIF header might be removed
1648
if (!wantJFIF && (jfif != null)) {
1649            markerSequence.remove(jfif);
1650        }
1651
1652        // Now add a JFIF if we do want one, but only if it isn't stream metadata
1653
if (wantJFIF && !isStream) {
1654            markerSequence.add(0, new JFIFMarkerSegment());
1655        }
1656
1657        // Adobe header might be removed or the transform modified, if it isn't
1658
// stream metadata
1659
if (wantAdobe) {
1660            if ((adobe == null) && !isStream) {
1661                adobe = new AdobeMarkerSegment(transform);
1662                insertAdobeMarkerSegment(adobe);
1663            } else {
1664                adobe.transform = transform;
1665            }
1666        } else if (adobe != null) {
1667            markerSequence.remove(adobe);
1668        }
1669
1670        boolean updateQtables = false;
1671        boolean updateHtables = false;
1672
1673        boolean progressive = false;
1674        
1675        int [] subsampledSelectors = {0, 1, 1, 0 } ;
1676        int [] nonSubsampledSelectors = { 0, 0, 0, 0};
1677
1678        int [] newTableSelectors = willSubsample
1679                                   ? subsampledSelectors
1680                                   : nonSubsampledSelectors;
1681
1682        // Keep the old componentSpecs array
1683
SOFMarkerSegment.ComponentSpec [] oldCompSpecs = null;
1684        // SOF might be modified
1685
if (sof != null) {
1686            oldCompSpecs = sof.componentSpecs;
1687            progressive = (sof.tag == JPEG.SOF2);
1688            // Now replace the SOF with a new one; it might be the same, but
1689
// this is easier.
1690
markerSequence.set(markerSequence.indexOf(sof),
1691                               new SOFMarkerSegment(progressive,
1692                                                    false, // we never need extended
1693
willSubsample,
1694                                                    ids,
1695                                                    numChannels));
1696
1697            // Now suss out if subsampling changed and set the boolean for
1698
// updating the q tables
1699
// if the old componentSpec q table selectors don't match
1700
// the new ones, update the qtables. The new selectors are already
1701
// in place in the new SOF segment above.
1702
for (int i = 0; i < oldCompSpecs.length; i++) {
1703                if (oldCompSpecs[i].QtableSelector != newTableSelectors[i]) {
1704                    updateQtables = true;
1705                }
1706            }
1707
1708            if (progressive) {
1709                // if the component ids are different, update all the existing scans
1710
// ignore Huffman tables
1711
boolean idsDiffer = false;
1712                for (int i = 0; i < oldCompSpecs.length; i++) {
1713                    if (ids[i] != oldCompSpecs[i].componentId) {
1714                        idsDiffer = true;
1715                    }
1716                }
1717                if (idsDiffer) {
1718                    // update the ids in each SOS marker segment
1719
for (Iterator JavaDoc iter = markerSequence.iterator(); iter.hasNext();) {
1720                        MarkerSegment seg = (MarkerSegment) iter.next();
1721                        if (seg instanceof SOSMarkerSegment) {
1722                            SOSMarkerSegment target = (SOSMarkerSegment) seg;
1723                            for (int i = 0; i < target.componentSpecs.length; i++) {
1724                                int oldSelector =
1725                                    target.componentSpecs[i].componentSelector;
1726                                // Find the position in the old componentSpecs array
1727
// of the old component with the old selector
1728
// and replace the component selector with the
1729
// new id at the same position, as these match
1730
// the new component specs array in the SOF created
1731
// above.
1732
for (int j = 0; j < oldCompSpecs.length; j++) {
1733                                    if (oldCompSpecs[j].componentId == oldSelector) {
1734                                        target.componentSpecs[i].componentSelector =
1735                                            ids[j];
1736                                    }
1737                                }
1738                            }
1739                        }
1740                    }
1741                }
1742            } else {
1743                if (sos != null) {
1744                    // htables - if the old htable selectors don't match the new ones,
1745
// update the tables.
1746
for (int i = 0; i < sos.componentSpecs.length; i++) {
1747                        if ((sos.componentSpecs[i].dcHuffTable
1748                             != newTableSelectors[i])
1749                            || (sos.componentSpecs[i].acHuffTable
1750                                != newTableSelectors[i])) {
1751                            updateHtables = true;
1752                        }
1753                    }
1754
1755                    // Might be the same as the old one, but this is easier.
1756
markerSequence.set(markerSequence.indexOf(sos),
1757                               new SOSMarkerSegment(willSubsample,
1758                                                    ids,
1759                                                    numChannels));
1760                }
1761            }
1762        } else {
1763            // should be stream metadata if there isn't an SOF, but check it anyway
1764
if (isStream) {
1765                // update tables - routines below check if it's really necessary
1766
updateQtables = true;
1767                updateHtables = true;
1768            }
1769        }
1770
1771        if (updateQtables) {
1772            List JavaDoc tableSegments = new ArrayList JavaDoc();
1773            for (Iterator JavaDoc iter = markerSequence.iterator(); iter.hasNext();) {
1774                MarkerSegment seg = (MarkerSegment) iter.next();
1775                if (seg instanceof DQTMarkerSegment) {
1776                    tableSegments.add(seg);
1777                }
1778            }
1779            // If there are no tables, don't add them, as the metadata encodes an
1780
// abbreviated stream.
1781
// If we are not subsampling, we just need one, so don't do anything
1782
if (!tableSegments.isEmpty() && willSubsample) {
1783                // Is it really necessary? There should be at least 2 tables.
1784
// If there is only one, assume it's a scaled "standard"
1785
// luminance table, extract the scaling factor, and generate a
1786
// scaled "standard" chrominance table.
1787

1788                // Find the table with selector 1.
1789
boolean found = false;
1790                for (Iterator JavaDoc iter = tableSegments.iterator(); iter.hasNext();) {
1791                    DQTMarkerSegment testdqt = (DQTMarkerSegment) iter.next();
1792                    for (Iterator JavaDoc tabiter = testdqt.tables.iterator();
1793                         tabiter.hasNext();) {
1794                        DQTMarkerSegment.Qtable tab =
1795                            (DQTMarkerSegment.Qtable) tabiter.next();
1796                        if (tab.tableID == 1) {
1797                            found = true;
1798                        }
1799                    }
1800                }
1801                if (!found) {
1802                    // find the table with selector 0. There should be one.
1803
DQTMarkerSegment.Qtable table0 = null;
1804                    for (Iterator JavaDoc iter = tableSegments.iterator(); iter.hasNext();) {
1805                        DQTMarkerSegment testdqt = (DQTMarkerSegment) iter.next();
1806                        for (Iterator JavaDoc tabiter = testdqt.tables.iterator();
1807                             tabiter.hasNext();) {
1808                            DQTMarkerSegment.Qtable tab =
1809                                (DQTMarkerSegment.Qtable) tabiter.next();
1810                            if (tab.tableID == 0) {
1811                                table0 = tab;
1812                            }
1813                        }
1814                    }
1815
1816                    // Assuming that the table with id 0 is a luminance table,
1817
// compute a new chrominance table of the same quality and
1818
// add it to the last DQT segment
1819
DQTMarkerSegment dqt =
1820                        (DQTMarkerSegment) tableSegments.get(tableSegments.size()-1);
1821                    dqt.tables.add(dqt.getChromaForLuma(table0));
1822                }
1823            }
1824        }
1825
1826        if (updateHtables) {
1827            List JavaDoc tableSegments = new ArrayList JavaDoc();
1828            for (Iterator JavaDoc iter = markerSequence.iterator(); iter.hasNext();) {
1829                MarkerSegment seg = (MarkerSegment) iter.next();
1830                if (seg instanceof DHTMarkerSegment) {
1831                    tableSegments.add(seg);
1832                }
1833            }
1834            // If there are no tables, don't add them, as the metadata encodes an
1835
// abbreviated stream.
1836
// If we are not subsampling, we just need one, so don't do anything
1837
if (!tableSegments.isEmpty() && willSubsample) {
1838                // Is it really necessary? There should be at least 2 dc and 2 ac
1839
// tables. If there is only one, add a
1840
// "standard " chrominance table.
1841

1842                // find a table with selector 1. AC/DC is irrelevant
1843
boolean found = false;
1844                for (Iterator JavaDoc iter = tableSegments.iterator(); iter.hasNext();) {
1845                    DHTMarkerSegment testdht = (DHTMarkerSegment) iter.next();
1846                    for (Iterator JavaDoc tabiter = testdht.tables.iterator();
1847                         tabiter.hasNext();) {
1848                        DHTMarkerSegment.Htable tab =
1849                            (DHTMarkerSegment.Htable) tabiter.next();
1850                        if (tab.tableID == 1) {
1851                            found = true;
1852                        }
1853                    }
1854                }
1855                if (!found) {
1856                    // Create new standard dc and ac chrominance tables and add them
1857
// to the last DHT segment
1858
DHTMarkerSegment lastDHT =
1859                        (DHTMarkerSegment) tableSegments.get(tableSegments.size()-1);
1860                    lastDHT.addHtable(JPEGHuffmanTable.StdDCLuminance, true, 1);
1861                    lastDHT.addHtable(JPEGHuffmanTable.StdACLuminance, true, 1);
1862                }
1863            }
1864        }
1865    }
1866
1867    private boolean wantAlpha(Node JavaDoc transparency) {
1868        boolean returnValue = false;
1869        Node JavaDoc alpha = transparency.getFirstChild(); // Alpha must be first if present
1870
if (alpha.getNodeName().equals("Alpha")) {
1871            if (alpha.hasAttributes()) {
1872                String JavaDoc value =
1873                    alpha.getAttributes().getNamedItem("value").getNodeValue();
1874                if (!value.equals("none")) {
1875                    returnValue = true;
1876                }
1877            }
1878        }
1879        transparencyDone = true;
1880        return returnValue;
1881    }
1882
1883    private void mergeStandardCompressionNode(Node JavaDoc node)
1884        throws IIOInvalidTreeException JavaDoc {
1885        // NumProgressiveScans is ignored. Progression must be enabled on the
1886
// ImageWriteParam.
1887
// No-op
1888
}
1889
1890    private void mergeStandardDataNode(Node JavaDoc node)
1891        throws IIOInvalidTreeException JavaDoc {
1892        // No-op
1893
}
1894
1895    private void mergeStandardDimensionNode(Node JavaDoc node)
1896        throws IIOInvalidTreeException JavaDoc {
1897        // Pixel Aspect Ratio or pixel size can be incorporated if there is,
1898
// or can be, a JFIF segment
1899
JFIFMarkerSegment jfif =
1900            (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class, true);
1901        if (jfif == null) {
1902            // Can there be one?
1903
// Criteria:
1904
// SOF must be present with 1 or 3 channels, (stream metadata fails this)
1905
// Component ids must be JFIF compatible.
1906
boolean canHaveJFIF = false;
1907            SOFMarkerSegment sof =
1908                (SOFMarkerSegment) findMarkerSegment(SOFMarkerSegment.class, true);
1909            if (sof != null) {
1910                int numChannels = sof.componentSpecs.length;
1911                if ((numChannels == 1) || (numChannels == 3)) {
1912                    canHaveJFIF = true; // remaining tests are negative
1913
for (int i = 0; i < sof.componentSpecs.length; i++) {
1914                        if (sof.componentSpecs[i].componentId != i+1)
1915                            canHaveJFIF = false;
1916                    }
1917                    // if Adobe present, transform = ADOBE_UNKNOWN for 1-channel,
1918
// ADOBE_YCC for 3-channel.
1919
AdobeMarkerSegment adobe =
1920                        (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class,
1921                                                               true);
1922                    if (adobe != null) {
1923                        if (adobe.transform != ((numChannels == 1)
1924                                                ? JPEG.ADOBE_UNKNOWN
1925                                                : JPEG.ADOBE_YCC)) {
1926                            canHaveJFIF = false;
1927                        }
1928                    }
1929                }
1930            }
1931            // If so, create one and insert it into the sequence. Note that
1932
// default is just pixel ratio at 1:1
1933
if (canHaveJFIF) {
1934                jfif = new JFIFMarkerSegment();
1935                markerSequence.add(0, jfif);
1936            }
1937        }
1938        if (jfif != null) {
1939            NodeList JavaDoc children = node.getChildNodes();
1940            for (int i = 0; i < children.getLength(); i++) {
1941                Node JavaDoc child = children.item(i);
1942                NamedNodeMap JavaDoc attrs = child.getAttributes();
1943                String JavaDoc name = child.getNodeName();
1944                if (name.equals("PixelAspectRatio")) {
1945                    String JavaDoc valueString = attrs.getNamedItem("value").getNodeValue();
1946                    float value = Float.parseFloat(valueString);
1947                    Point JavaDoc p = findIntegerRatio(value);
1948                    jfif.resUnits = JPEG.DENSITY_UNIT_ASPECT_RATIO;
1949                    jfif.Xdensity = p.x;
1950                    jfif.Xdensity = p.y;
1951                } else if (name.equals("HorizontalPixelSize")) {
1952                    String JavaDoc valueString = attrs.getNamedItem("value").getNodeValue();
1953                    float value = Float.parseFloat(valueString);
1954                    // Convert from mm/dot to dots/cm
1955
int dpcm = (int) Math.round(1.0/(value*10.0));
1956                    jfif.resUnits = JPEG.DENSITY_UNIT_DOTS_CM;
1957                    jfif.Xdensity = dpcm;
1958                } else if (name.equals("VerticalPixelSize")) {
1959                    String JavaDoc valueString = attrs.getNamedItem("value").getNodeValue();
1960                    float value = Float.parseFloat(valueString);
1961                    // Convert from mm/dot to dots/cm
1962
int dpcm = (int) Math.round(1.0/(value*10.0));
1963                    jfif.resUnits = JPEG.DENSITY_UNIT_DOTS_CM;
1964                    jfif.Ydensity = dpcm;
1965                }
1966                
1967            }
1968        }
1969    }
1970
1971    /*
1972     * Return a pair of integers whose ratio (x/y) approximates the given
1973     * float value.
1974     */

1975    private static Point JavaDoc findIntegerRatio(float value) {
1976        float epsilon = 0.005F;
1977
1978        // Normalize
1979
value = Math.abs(value);
1980
1981        // Deal with min case
1982
if (value <= epsilon) {
1983            return new Point JavaDoc(1, 255);
1984        }
1985
1986        // Deal with max case
1987
if (value >= 255) {
1988            return new Point JavaDoc(255, 1);
1989        }
1990
1991        // Remember if we invert
1992
boolean inverted = false;
1993        if (value < 1.0) {
1994            value = 1.0F/value;
1995            inverted = true;
1996        }
1997
1998        // First approximation
1999
int y = 1;
2000        int x = (int) Math.round(value);
2001
2002        float ratio = (float) x;
2003        float delta = Math.abs(value - ratio);
2004        while (delta > epsilon) { // not close enough
2005
// Increment y and compute a new x
2006
y++;
2007            x = (int) Math.round(y*value);
2008            ratio = (float)x/(float)y;
2009            delta = Math.abs(value - ratio);
2010        }
2011        return inverted ? new Point JavaDoc(y, x) : new Point JavaDoc(x, y);
2012    }
2013
2014    private void mergeStandardDocumentNode(Node JavaDoc node)
2015        throws IIOInvalidTreeException JavaDoc {
2016        // No-op
2017
}
2018
2019    private void mergeStandardTextNode(Node JavaDoc node)
2020        throws IIOInvalidTreeException JavaDoc {
2021        // Convert to comments. For the moment ignore the encoding issue.
2022
// Ignore keywords, language, and encoding (for the moment).
2023
// If compression tag is present, use only entries with "none".
2024
NodeList JavaDoc children = node.getChildNodes();
2025        for (int i = 0; i < children.getLength(); i++) {
2026            Node JavaDoc child = children.item(i);
2027            NamedNodeMap JavaDoc attrs = child.getAttributes();
2028            Node JavaDoc comp = attrs.getNamedItem("compression");
2029            boolean copyIt = true;
2030            if (comp != null) {
2031                String JavaDoc compString = comp.getNodeValue();
2032                if (!compString.equals("none")) {
2033                    copyIt = false;
2034                }
2035            }
2036            if (copyIt) {
2037                String JavaDoc value = attrs.getNamedItem("value").getNodeValue();
2038                COMMarkerSegment com = new COMMarkerSegment(value);
2039                insertCOMMarkerSegment(com);
2040            }
2041        }
2042    }
2043
2044    private void mergeStandardTransparencyNode(Node JavaDoc node)
2045        throws IIOInvalidTreeException JavaDoc {
2046        // This might indicate that an alpha channel is being added or removed.
2047
// The nodes must appear in order, and a Chroma node will process any
2048
// transparency, so process it here only if there was no Chroma node
2049
// Do nothing for stream metadata
2050
if (!transparencyDone && !isStream) {
2051            boolean wantAlpha = wantAlpha(node);
2052            // do we have alpha already? If the number of channels is 2 or 4,
2053
// we do, as we don't support CMYK, nor can we add alpha to it
2054
// The number of channels can be determined from the SOF
2055
JFIFMarkerSegment jfif = (JFIFMarkerSegment) findMarkerSegment
2056                (JFIFMarkerSegment.class, true);
2057            AdobeMarkerSegment adobe = (AdobeMarkerSegment) findMarkerSegment
2058                (AdobeMarkerSegment.class, true);
2059            SOFMarkerSegment sof = (SOFMarkerSegment) findMarkerSegment
2060                (SOFMarkerSegment.class, true);
2061            SOSMarkerSegment sos = (SOSMarkerSegment) findMarkerSegment
2062                (SOSMarkerSegment.class, true);
2063
2064            // We can do nothing for progressive, as we don't know how to
2065
// modify the scans.
2066
if ((sof != null) && (sof.tag == JPEG.SOF2)) { // Progressive
2067
return;
2068            }
2069            
2070            // Do we already have alpha? We can tell by the number of channels
2071
// We must have an sof, or we can't do anything further
2072
if (sof != null) {
2073                int numChannels = sof.componentSpecs.length;
2074                boolean hadAlpha = (numChannels == 2) || (numChannels == 4);
2075                // proceed only if the old state and the new state differ
2076
if (hadAlpha != wantAlpha) {
2077                    if (wantAlpha) { // Adding alpha
2078
numChannels++;
2079                        if (jfif != null) {
2080                            markerSequence.remove(jfif);
2081                        }
2082            
2083                        // If an adobe marker is present, transform must be UNKNOWN
2084
if (adobe != null) {
2085                            adobe.transform = JPEG.ADOBE_UNKNOWN;
2086                        }
2087                        
2088                        // Add a component spec with appropriate parameters to SOF
2089
SOFMarkerSegment.ComponentSpec [] newSpecs =
2090                            new SOFMarkerSegment.ComponentSpec[numChannels];
2091                        for (int i = 0; i < sof.componentSpecs.length; i++) {
2092                            newSpecs[i] = sof.componentSpecs[i];
2093                        }
2094                        byte oldFirstID = (byte) sof.componentSpecs[0].componentId;
2095                        byte newID = (byte) ((oldFirstID > 1) ? 'A' : 4);
2096                        newSpecs[numChannels-1] =
2097                            sof.getComponentSpec(newID,
2098                                sof.componentSpecs[0].HsamplingFactor,
2099                                sof.componentSpecs[0].QtableSelector);
2100
2101                        sof.componentSpecs = newSpecs;
2102
2103                        // Add a component spec with appropriate parameters to SOS
2104
SOSMarkerSegment.ScanComponentSpec [] newScanSpecs =
2105                            new SOSMarkerSegment.ScanComponentSpec [numChannels];
2106                        for (int i = 0; i < sos.componentSpecs.length; i++) {
2107                            newScanSpecs[i] = sos.componentSpecs[i];
2108                        }
2109                        newScanSpecs[numChannels-1] =
2110                            sos.getScanComponentSpec (newID, 0);
2111                        sos.componentSpecs = newScanSpecs;
2112                    } else { // Removing alpha
2113
numChannels--;
2114                        // Remove a component spec from SOF
2115
SOFMarkerSegment.ComponentSpec [] newSpecs =
2116                            new SOFMarkerSegment.ComponentSpec[numChannels];
2117                        for (int i = 0; i < numChannels; i++) {
2118                            newSpecs[i] = sof.componentSpecs[i];
2119                        }
2120                        sof.componentSpecs = newSpecs;
2121
2122                        // Remove a component spec from SOS
2123
SOSMarkerSegment.ScanComponentSpec [] newScanSpecs =
2124                            new SOSMarkerSegment.ScanComponentSpec [numChannels];
2125                        for (int i = 0; i < numChannels; i++) {
2126                            newScanSpecs[i] = sos.componentSpecs[i];
2127                        }
2128                        sos.componentSpecs = newScanSpecs;
2129                    }
2130                }
2131            }
2132        }
2133    }
2134
2135
2136    public void setFromTree(String JavaDoc formatName, Node JavaDoc root)
2137        throws IIOInvalidTreeException JavaDoc {
2138        if (formatName == null) {
2139            throw new IllegalArgumentException JavaDoc("null formatName!");
2140        }
2141        if (root == null) {
2142            throw new IllegalArgumentException JavaDoc("null root!");
2143        }
2144        if (isStream &&
2145            (formatName.equals(JPEG.nativeStreamMetadataFormatName))) {
2146            setFromNativeTree(root);
2147        } else if (!isStream &&
2148                   (formatName.equals(JPEG.nativeImageMetadataFormatName))) {
2149            setFromNativeTree(root);
2150        } else if (!isStream &&
2151                   (formatName.equals
2152                    (IIOMetadataFormatImpl.standardMetadataFormatName))) {
2153            // In this case a reset followed by a merge is correct
2154
super.setFromTree(formatName, root);
2155        } else {
2156            throw new IllegalArgumentException JavaDoc("Unsupported format name: "
2157                                                + formatName);
2158        }
2159    }
2160
2161    private void setFromNativeTree(Node JavaDoc root) throws IIOInvalidTreeException JavaDoc {
2162        if (resetSequence == null) {
2163            resetSequence = markerSequence;
2164        }
2165        markerSequence = new ArrayList JavaDoc();
2166
2167        // Build a whole new marker sequence from the tree
2168

2169        String JavaDoc name = root.getNodeName();
2170        if (name != ((isStream) ? JPEG.nativeStreamMetadataFormatName
2171                                : JPEG.nativeImageMetadataFormatName)) {
2172            throw new IIOInvalidTreeException JavaDoc("Invalid root node name: " + name,
2173                                              root);
2174        }
2175        if (!isStream) {
2176            if (root.getChildNodes().getLength() != 2) { // JPEGvariety and markerSequence
2177
throw new IIOInvalidTreeException JavaDoc(
2178                    "JPEGvariety and markerSequence nodes must be present", root);
2179            }
2180
2181            Node JavaDoc JPEGvariety = root.getFirstChild();
2182
2183            if (JPEGvariety.getChildNodes().getLength() != 0) {
2184                markerSequence.add(new JFIFMarkerSegment(JPEGvariety.getFirstChild()));
2185            }
2186        }
2187
2188        Node JavaDoc markerSequenceNode = isStream ? root : root.getLastChild();
2189        setFromMarkerSequenceNode(markerSequenceNode);
2190
2191    }
2192
2193    void setFromMarkerSequenceNode(Node JavaDoc markerSequenceNode)
2194        throws IIOInvalidTreeException JavaDoc{
2195
2196        NodeList JavaDoc children = markerSequenceNode.getChildNodes();
2197        // for all the children, add a marker segment
2198
for (int i = 0; i < children.getLength(); i++) {
2199            Node JavaDoc node = children.item(i);
2200            String JavaDoc childName = node.getNodeName();
2201            if (childName.equals("dqt")) {
2202                markerSequence.add(new DQTMarkerSegment(node));
2203            } else if (childName.equals("dht")) {
2204                markerSequence.add(new DHTMarkerSegment(node));
2205            } else if (childName.equals("dri")) {
2206                markerSequence.add(new DRIMarkerSegment(node));
2207            } else if (childName.equals("com")) {
2208                markerSequence.add(new COMMarkerSegment(node));
2209            } else if (childName.equals("app14Adobe")) {
2210                markerSequence.add(new AdobeMarkerSegment(node));
2211            } else if (childName.equals("unknown")) {
2212                markerSequence.add(new MarkerSegment(node));
2213            } else if (childName.equals("sof")) {
2214                markerSequence.add(new SOFMarkerSegment(node));
2215            } else if (childName.equals("sos")) {
2216                markerSequence.add(new SOSMarkerSegment(node));
2217            } else {
2218                throw new IIOInvalidTreeException JavaDoc("Invalid "
2219                    + (isStream ? "stream " : "image ") + "child: "
2220                    + childName, node);
2221            }
2222        }
2223    }
2224
2225    /**
2226     * Check that this metadata object is in a consistent state and
2227     * return <code>true</code> if it is or <code>false</code>
2228     * otherwise. All the constructors and modifiers should call
2229     * this method at the end to guarantee that the data is always
2230     * consistent, as the writer relies on this.
2231     */

2232    private boolean isConsistent() {
2233        SOFMarkerSegment sof =
2234            (SOFMarkerSegment) findMarkerSegment(SOFMarkerSegment.class,
2235                                                 true);
2236        JFIFMarkerSegment jfif =
2237            (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class,
2238                                                  true);
2239        AdobeMarkerSegment adobe =
2240            (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class,
2241                                                   true);
2242        boolean retval = true;
2243        if (!isStream) {
2244            if (sof != null) {
2245                // SOF numBands = total scan bands
2246
int numSOFBands = sof.componentSpecs.length;
2247                int numScanBands = countScanBands();
2248                if (numScanBands != 0) { // No SOS is OK
2249
if (numScanBands != numSOFBands) {
2250                        retval = false;
2251                    }
2252                }
2253                // If JFIF is present, component ids are 1-3, bands are 1 or 3
2254
if (jfif != null) {
2255                    if ((numSOFBands != 1) && (numSOFBands != 3)) {
2256                        retval = false;
2257                    }
2258                    for (int i = 0; i < numSOFBands; i++) {
2259                        if (sof.componentSpecs[i].componentId != i+1) {
2260                            retval = false;
2261                        }
2262                    }
2263                
2264                    // If both JFIF and Adobe are present,
2265
// Adobe transform == unknown for gray,
2266
// YCC for 3-chan.
2267
if ((adobe != null)
2268                        && (((numSOFBands == 1)
2269                             && (adobe.transform != JPEG.ADOBE_UNKNOWN))
2270                            || ((numSOFBands == 3)
2271                                && (adobe.transform != JPEG.ADOBE_YCC)))) {
2272                        retval = false;
2273                    }
2274                }
2275            } else {
2276                // stream can't have jfif, adobe, sof, or sos
2277
SOSMarkerSegment sos =
2278                    (SOSMarkerSegment) findMarkerSegment(SOSMarkerSegment.class,
2279                                                         true);
2280                if ((jfif != null) || (adobe != null)
2281                    || (sof != null) || (sos != null)) {
2282                    retval = false;
2283                }
2284            }
2285        }
2286        return retval;
2287    }
2288
2289    /**
2290     * Returns the total number of bands referenced in all SOS marker
2291     * segments, including 0 if there are no SOS marker segments.
2292     */

2293    private int countScanBands() {
2294        List JavaDoc ids = new ArrayList JavaDoc();
2295        Iterator JavaDoc iter = markerSequence.iterator();
2296        while(iter.hasNext()) {
2297            MarkerSegment seg = (MarkerSegment)iter.next();
2298            if (seg instanceof SOSMarkerSegment) {
2299                SOSMarkerSegment sos = (SOSMarkerSegment) seg;
2300                SOSMarkerSegment.ScanComponentSpec [] specs = sos.componentSpecs;
2301                for (int i = 0; i < specs.length; i++) {
2302                    Integer JavaDoc id = new Integer JavaDoc(specs[i].componentSelector);
2303                    if (!ids.contains(id)) {
2304                        ids.add(id);
2305                    }
2306                }
2307            }
2308        }
2309        
2310        return ids.size();
2311    }
2312
2313    ///// Writer support
2314

2315    void writeToStream(ImageOutputStream JavaDoc ios,
2316                       boolean ignoreJFIF,
2317                       boolean forceJFIF,
2318                       List JavaDoc thumbnails,
2319                       ICC_Profile JavaDoc iccProfile,
2320                       boolean ignoreAdobe,
2321                       int newAdobeTransform,
2322                       JPEGImageWriter writer)
2323        throws IOException JavaDoc {
2324        if (forceJFIF) {
2325            // Write a default JFIF segment, including thumbnails
2326
// This won't be duplicated below because forceJFIF will be
2327
// set only if there is no JFIF present already.
2328
JFIFMarkerSegment.writeDefaultJFIF(ios,
2329                                               thumbnails,
2330                                               iccProfile,
2331                                               writer);
2332            if ((ignoreAdobe == false)
2333                && (newAdobeTransform != JPEG.ADOBE_IMPOSSIBLE)) {
2334                if ((newAdobeTransform != JPEG.ADOBE_UNKNOWN)
2335                    && (newAdobeTransform != JPEG.ADOBE_YCC)) {
2336                    // Not compatible, so ignore Adobe.
2337
ignoreAdobe = true;
2338                    writer.warningOccurred
2339                        (JPEGImageWriter.WARNING_METADATA_ADJUSTED_FOR_THUMB);
2340                }
2341            }
2342        }
2343        // Iterate over each MarkerSegment
2344
Iterator JavaDoc iter = markerSequence.iterator();
2345        while(iter.hasNext()) {
2346            MarkerSegment seg = (MarkerSegment)iter.next();
2347            if (seg instanceof JFIFMarkerSegment) {
2348                if (ignoreJFIF == false) {
2349                    JFIFMarkerSegment jfif = (JFIFMarkerSegment) seg;
2350                    jfif.writeWithThumbs(ios, thumbnails, writer);
2351                    if (iccProfile != null) {
2352                        JFIFMarkerSegment.writeICC(iccProfile, ios);
2353                    }
2354                } // Otherwise ignore it, as requested
2355
} else if (seg instanceof AdobeMarkerSegment) {
2356                if (ignoreAdobe == false) {
2357                    if (newAdobeTransform != JPEG.ADOBE_IMPOSSIBLE) {
2358                        AdobeMarkerSegment newAdobe =
2359                            (AdobeMarkerSegment) seg.clone();
2360                        newAdobe.transform = newAdobeTransform;
2361                        newAdobe.write(ios);
2362                    } else if (forceJFIF) {
2363                        // If adobe isn't JFIF compatible, ignore it
2364
AdobeMarkerSegment adobe = (AdobeMarkerSegment) seg;
2365                        if ((adobe.transform == JPEG.ADOBE_UNKNOWN)
2366                            || (adobe.transform == JPEG.ADOBE_YCC)) {
2367                            adobe.write(ios);
2368                        } else {
2369                            writer.warningOccurred
2370                         (JPEGImageWriter.WARNING_METADATA_ADJUSTED_FOR_THUMB);
2371                        }
2372                    } else {
2373                        seg.write(ios);
2374                    }
2375                } // Otherwise ignore it, as requested
2376
} else {
2377                seg.write(ios);
2378            }
2379        }
2380    }
2381
2382    //// End of writer support
2383

2384    public void reset() {
2385        if (resetSequence != null) { // Otherwise no need to reset
2386
markerSequence = resetSequence;
2387            resetSequence = null;
2388        }
2389    }
2390
2391    public void print() {
2392        for (int i = 0; i < markerSequence.size(); i++) {
2393            MarkerSegment seg = (MarkerSegment) markerSequence.get(i);
2394            seg.print();
2395        }
2396    }
2397
2398}
2399
Popular Tags