1 /*
   2  * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.imageio.plugins.jpeg;
  27 
  28 import javax.imageio.ImageTypeSpecifier;
  29 import javax.imageio.ImageWriteParam;
  30 import javax.imageio.IIOException;
  31 import javax.imageio.stream.ImageInputStream;
  32 import javax.imageio.stream.ImageOutputStream;
  33 import javax.imageio.metadata.IIOMetadata;
  34 import javax.imageio.metadata.IIOMetadataNode;
  35 import javax.imageio.metadata.IIOMetadataFormat;
  36 import javax.imageio.metadata.IIOMetadataFormatImpl;
  37 import javax.imageio.metadata.IIOInvalidTreeException;
  38 import javax.imageio.plugins.jpeg.JPEGQTable;
  39 import javax.imageio.plugins.jpeg.JPEGHuffmanTable;
  40 import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
  41 
  42 import org.w3c.dom.Node;
  43 import org.w3c.dom.NodeList;
  44 import org.w3c.dom.NamedNodeMap;
  45 
  46 import java.util.List;
  47 import java.util.ArrayList;
  48 import java.util.Arrays;
  49 import java.util.Iterator;
  50 import java.util.ListIterator;
  51 import java.io.IOException;
  52 import java.awt.color.ICC_Profile;
  53 import java.awt.color.ICC_ColorSpace;
  54 import java.awt.color.ColorSpace;
  55 import java.awt.image.ColorModel;
  56 import java.awt.Point;
  57 
  58 /**
  59  * Metadata for the JPEG plug-in.
  60  */
  61 public class JPEGMetadata extends IIOMetadata implements Cloneable {
  62 
  63     //////// Private variables
  64 
  65     private static final boolean debug = false;
  66 
  67     /**
  68      * A copy of <code>markerSequence</code>, created the first time the
  69      * <code>markerSequence</code> is modified.  This is used by reset
  70      * to restore the original state.
  71      */
  72     private List resetSequence = null;
  73 
  74     /**
  75      * Set to <code>true</code> when reading a thumbnail stored as
  76      * JPEG.  This is used to enforce the prohibition of JFIF thumbnails
  77      * containing any JFIF marker segments, and to ensure generation of
  78      * a correct native subtree during <code>getAsTree</code>.
  79      */
  80     private boolean inThumb = false;
  81 
  82     /**
  83      * Set by the chroma node construction method to signal the
  84      * presence or absence of an alpha channel to the transparency
  85      * node construction method.  Used only when constructing a
  86      * standard metadata tree.
  87      */
  88     private boolean hasAlpha;
  89 
  90     //////// end of private variables
  91 
  92     /////// Package-access variables
  93 
  94     /**
  95      * All data is a list of <code>MarkerSegment</code> objects.
  96      * When accessing the list, use the tag to identify the particular
  97      * subclass.  Any JFIF marker segment must be the first element
  98      * of the list if it is present, and any JFXX or APP2ICC marker
  99      * segments are subordinate to the JFIF marker segment.  This
 100      * list is package visible so that the writer can access it.
 101      * @see #MarkerSegment
 102      */
 103     List markerSequence = new ArrayList();
 104 
 105     /**
 106      * Indicates whether this object represents stream or image
 107      * metadata.  Package-visible so the writer can see it.
 108      */
 109     final boolean isStream;
 110 
 111     /////// End of package-access variables
 112 
 113     /////// Constructors
 114 
 115     /**
 116      * Constructor containing code shared by other constructors.
 117      */
 118     JPEGMetadata(boolean isStream, boolean inThumb) {
 119         super(true,  // Supports standard format
 120               JPEG.nativeImageMetadataFormatName,  // and a native format
 121               JPEG.nativeImageMetadataFormatClassName,
 122               null, null);  // No other formats
 123         this.inThumb = inThumb;
 124         // But if we are stream metadata, adjust the variables
 125         this.isStream = isStream;
 126         if (isStream) {
 127             nativeMetadataFormatName = JPEG.nativeStreamMetadataFormatName;
 128             nativeMetadataFormatClassName =
 129                 JPEG.nativeStreamMetadataFormatClassName;
 130         }
 131     }
 132 
 133     /*
 134      * Constructs a <code>JPEGMetadata</code> object by reading the
 135      * contents of an <code>ImageInputStream</code>.  Has package-only
 136      * access.
 137      *
 138      * @param isStream A boolean indicating whether this object will be
 139      * stream or image metadata.
 140      * @param isThumb A boolean indicating whether this metadata object
 141      * is for an image or for a thumbnail stored as JPEG.
 142      * @param iis An <code>ImageInputStream</code> from which to read
 143      * the metadata.
 144      * @param reader The <code>JPEGImageReader</code> calling this
 145      * constructor, to which warnings should be sent.
 146      */
 147     JPEGMetadata(boolean isStream,
 148                  boolean isThumb,
 149                  ImageInputStream iis,
 150                  JPEGImageReader reader) throws IOException {
 151         this(isStream, isThumb);
 152 
 153         JPEGBuffer buffer = new JPEGBuffer(iis);
 154 
 155         buffer.loadBuf(0);
 156 
 157         // The first three bytes should be FF, SOI, FF
 158         if (((buffer.buf[0] & 0xff) != 0xff)
 159             || ((buffer.buf[1] & 0xff) != JPEG.SOI)
 160             || ((buffer.buf[2] & 0xff) != 0xff)) {
 161             throw new IIOException ("Image format error");
 162         }
 163 
 164         boolean done = false;
 165         buffer.bufAvail -=2;  // Next byte should be the ff before a marker
 166         buffer.bufPtr = 2;
 167         MarkerSegment newGuy = null;
 168         while (!done) {
 169             byte [] buf;
 170             int ptr;
 171             buffer.loadBuf(1);
 172             if (debug) {
 173                 System.out.println("top of loop");
 174                 buffer.print(10);
 175             }
 176             buffer.scanForFF(reader);
 177             switch (buffer.buf[buffer.bufPtr] & 0xff) {
 178             case 0:
 179                 if (debug) {
 180                     System.out.println("Skipping 0");
 181                 }
 182                 buffer.bufAvail--;
 183                 buffer.bufPtr++;
 184                 break;
 185             case JPEG.SOF0:
 186             case JPEG.SOF1:
 187             case JPEG.SOF2:
 188                 if (isStream) {
 189                     throw new IIOException
 190                         ("SOF not permitted in stream metadata");
 191                 }
 192                 newGuy = new SOFMarkerSegment(buffer);
 193                 break;
 194             case JPEG.DQT:
 195                 newGuy = new DQTMarkerSegment(buffer);
 196                 break;
 197             case JPEG.DHT:
 198                 newGuy = new DHTMarkerSegment(buffer);
 199                 break;
 200             case JPEG.DRI:
 201                 newGuy = new DRIMarkerSegment(buffer);
 202                 break;
 203             case JPEG.APP0:
 204                 // Either JFIF, JFXX, or unknown APP0
 205                 buffer.loadBuf(8); // tag, length, id
 206                 buf = buffer.buf;
 207                 ptr = buffer.bufPtr;
 208                 if ((buf[ptr+3] == 'J')
 209                     && (buf[ptr+4] == 'F')
 210                     && (buf[ptr+5] == 'I')
 211                     && (buf[ptr+6] == 'F')
 212                     && (buf[ptr+7] == 0)) {
 213                     if (inThumb) {
 214                         reader.warningOccurred
 215                             (JPEGImageReader.WARNING_NO_JFIF_IN_THUMB);
 216                         // Leave newGuy null
 217                         // Read a dummy to skip the segment
 218                         JFIFMarkerSegment dummy =
 219                             new JFIFMarkerSegment(buffer);
 220                     } else if (isStream) {
 221                         throw new IIOException
 222                             ("JFIF not permitted in stream metadata");
 223                     } else if (markerSequence.isEmpty() == false) {
 224                         throw new IIOException
 225                             ("JFIF APP0 must be first marker after SOI");
 226                     } else {
 227                         newGuy = new JFIFMarkerSegment(buffer);
 228                     }
 229                 } else if ((buf[ptr+3] == 'J')
 230                            && (buf[ptr+4] == 'F')
 231                            && (buf[ptr+5] == 'X')
 232                            && (buf[ptr+6] == 'X')
 233                            && (buf[ptr+7] == 0)) {
 234                     if (isStream) {
 235                         throw new IIOException
 236                             ("JFXX not permitted in stream metadata");
 237                     }
 238                     if (inThumb) {
 239                         throw new IIOException
 240                           ("JFXX markers not allowed in JFIF JPEG thumbnail");
 241                     }
 242                     JFIFMarkerSegment jfif =
 243                         (JFIFMarkerSegment) findMarkerSegment
 244                                (JFIFMarkerSegment.class, true);
 245                     if (jfif == null) {
 246                         throw new IIOException
 247                             ("JFXX encountered without prior JFIF!");
 248                     }
 249                     jfif.addJFXX(buffer, reader);
 250                     // newGuy remains null
 251                 } else {
 252                     newGuy = new MarkerSegment(buffer);
 253                     newGuy.loadData(buffer);
 254                 }
 255                 break;
 256             case JPEG.APP2:
 257                 // Either an ICC profile or unknown APP2
 258                 buffer.loadBuf(15); // tag, length, id
 259                 if ((buffer.buf[buffer.bufPtr+3] == 'I')
 260                     && (buffer.buf[buffer.bufPtr+4] == 'C')
 261                     && (buffer.buf[buffer.bufPtr+5] == 'C')
 262                     && (buffer.buf[buffer.bufPtr+6] == '_')
 263                     && (buffer.buf[buffer.bufPtr+7] == 'P')
 264                     && (buffer.buf[buffer.bufPtr+8] == 'R')
 265                     && (buffer.buf[buffer.bufPtr+9] == 'O')
 266                     && (buffer.buf[buffer.bufPtr+10] == 'F')
 267                     && (buffer.buf[buffer.bufPtr+11] == 'I')
 268                     && (buffer.buf[buffer.bufPtr+12] == 'L')
 269                     && (buffer.buf[buffer.bufPtr+13] == 'E')
 270                     && (buffer.buf[buffer.bufPtr+14] == 0)
 271                     ) {
 272                     if (isStream) {
 273                         throw new IIOException
 274                             ("ICC profiles not permitted in stream metadata");
 275                     }
 276 
 277                     JFIFMarkerSegment jfif =
 278                         (JFIFMarkerSegment) findMarkerSegment
 279                         (JFIFMarkerSegment.class, true);
 280                     if (jfif == null) {
 281                         newGuy = new MarkerSegment(buffer);
 282                         newGuy.loadData(buffer);
 283                     } else {
 284                         jfif.addICC(buffer);
 285                     }
 286                     // newGuy remains null
 287                 } else {
 288                     newGuy = new MarkerSegment(buffer);
 289                     newGuy.loadData(buffer);
 290                 }
 291                 break;
 292             case JPEG.APP14:
 293                 // Either Adobe or unknown APP14
 294                 buffer.loadBuf(8); // tag, length, id
 295                 if ((buffer.buf[buffer.bufPtr+3] == 'A')
 296                     && (buffer.buf[buffer.bufPtr+4] == 'd')
 297                     && (buffer.buf[buffer.bufPtr+5] == 'o')
 298                     && (buffer.buf[buffer.bufPtr+6] == 'b')
 299                     && (buffer.buf[buffer.bufPtr+7] == 'e')) {
 300                     if (isStream) {
 301                         throw new IIOException
 302                       ("Adobe APP14 markers not permitted in stream metadata");
 303                     }
 304                     newGuy = new AdobeMarkerSegment(buffer);
 305                 } else {
 306                     newGuy = new MarkerSegment(buffer);
 307                     newGuy.loadData(buffer);
 308                 }
 309 
 310                 break;
 311             case JPEG.COM:
 312                 newGuy = new COMMarkerSegment(buffer);
 313                 break;
 314             case JPEG.SOS:
 315                 if (isStream) {
 316                     throw new IIOException
 317                         ("SOS not permitted in stream metadata");
 318                 }
 319                 newGuy = new SOSMarkerSegment(buffer);
 320                 break;
 321             case JPEG.RST0:
 322             case JPEG.RST1:
 323             case JPEG.RST2:
 324             case JPEG.RST3:
 325             case JPEG.RST4:
 326             case JPEG.RST5:
 327             case JPEG.RST6:
 328             case JPEG.RST7:
 329                 if (debug) {
 330                     System.out.println("Restart Marker");
 331                 }
 332                 buffer.bufPtr++; // Just skip it
 333                 buffer.bufAvail--;
 334                 break;
 335             case JPEG.EOI:
 336                 done = true;
 337                 buffer.bufPtr++;
 338                 buffer.bufAvail--;
 339                 break;
 340             default:
 341                 newGuy = new MarkerSegment(buffer);
 342                 newGuy.loadData(buffer);
 343                 newGuy.unknown = true;
 344                 break;
 345             }
 346             if (newGuy != null) {
 347                 markerSequence.add(newGuy);
 348                 if (debug) {
 349                     newGuy.print();
 350                 }
 351                 newGuy = null;
 352             }
 353         }
 354 
 355         // Now that we've read up to the EOI, we need to push back
 356         // whatever is left in the buffer, so that the next read
 357         // in the native code will work.
 358 
 359         buffer.pushBack();
 360 
 361         if (!isConsistent()) {
 362             throw new IIOException("Inconsistent metadata read from stream");
 363         }
 364     }
 365 
 366     /**
 367      * Constructs a default stream <code>JPEGMetadata</code> object appropriate
 368      * for the given write parameters.
 369      */
 370     JPEGMetadata(ImageWriteParam param, JPEGImageWriter writer) {
 371         this(true, false);
 372 
 373         JPEGImageWriteParam jparam = null;
 374 
 375         if ((param != null) && (param instanceof JPEGImageWriteParam)) {
 376             jparam = (JPEGImageWriteParam) param;
 377             if (!jparam.areTablesSet()) {
 378                 jparam = null;
 379             }
 380         }
 381         if (jparam != null) {
 382             markerSequence.add(new DQTMarkerSegment(jparam.getQTables()));
 383             markerSequence.add
 384                 (new DHTMarkerSegment(jparam.getDCHuffmanTables(),
 385                                       jparam.getACHuffmanTables()));
 386         } else {
 387             // default tables.
 388             markerSequence.add(new DQTMarkerSegment(JPEG.getDefaultQTables()));
 389             markerSequence.add(new DHTMarkerSegment(JPEG.getDefaultHuffmanTables(true),
 390                                                     JPEG.getDefaultHuffmanTables(false)));
 391         }
 392 
 393         // Defensive programming
 394         if (!isConsistent()) {
 395             throw new InternalError("Default stream metadata is inconsistent");
 396         }
 397     }
 398 
 399     /**
 400      * Constructs a default image <code>JPEGMetadata</code> object appropriate
 401      * for the given image type and write parameters.
 402      */
 403     JPEGMetadata(ImageTypeSpecifier imageType,
 404                  ImageWriteParam param,
 405                  JPEGImageWriter writer) {
 406         this(false, false);
 407 
 408         boolean wantJFIF = true;
 409         boolean wantAdobe = false;
 410         int transform = JPEG.ADOBE_UNKNOWN;
 411         boolean willSubsample = true;
 412         boolean wantICC = false;
 413         boolean wantProg = false;
 414         boolean wantOptimized = false;
 415         boolean wantExtended = false;
 416         boolean wantQTables = true;
 417         boolean wantHTables = true;
 418         float quality = JPEG.DEFAULT_QUALITY;
 419         byte[] componentIDs = { 1, 2, 3, 4};
 420         int numComponents = 0;
 421 
 422         ImageTypeSpecifier destType = null;
 423 
 424         if (param != null) {
 425             destType = param.getDestinationType();
 426             if (destType != null) {
 427                 if (imageType != null) {
 428                     // Ignore the destination type.
 429                     writer.warningOccurred
 430                         (JPEGImageWriter.WARNING_DEST_IGNORED);
 431                     destType = null;
 432                 }
 433             }
 434             // The only progressive mode that makes sense here is MODE_DEFAULT
 435             if (param.canWriteProgressive()) {
 436                 // the param may not be one of ours, so it may return false.
 437                 // If so, the following would throw an exception
 438                 if (param.getProgressiveMode() == ImageWriteParam.MODE_DEFAULT) {
 439                     wantProg = true;
 440                     wantOptimized = true;
 441                     wantHTables = false;
 442                 }
 443             }
 444 
 445             if (param instanceof JPEGImageWriteParam) {
 446                 JPEGImageWriteParam jparam = (JPEGImageWriteParam) param;
 447                 if (jparam.areTablesSet()) {
 448                     wantQTables = false;  // If the param has them, metadata shouldn't
 449                     wantHTables = false;
 450                     if ((jparam.getDCHuffmanTables().length > 2)
 451                             || (jparam.getACHuffmanTables().length > 2)) {
 452                         wantExtended = true;
 453                     }
 454                 }
 455                 // Progressive forces optimized, regardless of param setting
 456                 // so consult the param re optimized only if not progressive
 457                 if (!wantProg) {
 458                     wantOptimized = jparam.getOptimizeHuffmanTables();
 459                     if (wantOptimized) {
 460                         wantHTables = false;
 461                     }
 462                 }
 463             }
 464 
 465             // compression quality should determine the q tables.  Note that this
 466             // will be ignored if we already decided not to create any.
 467             // Again, the param may not be one of ours, so we must check that it
 468             // supports compression settings
 469             if (param.canWriteCompressed()) {
 470                 if (param.getCompressionMode() == ImageWriteParam.MODE_EXPLICIT) {
 471                     quality = param.getCompressionQuality();
 472                 }
 473             }
 474         }
 475 
 476         // We are done with the param, now for the image types
 477 
 478         ColorSpace cs = null;
 479         if (destType != null) {
 480             ColorModel cm = destType.getColorModel();
 481             numComponents = cm.getNumComponents();
 482             boolean hasExtraComponents = (cm.getNumColorComponents() != numComponents);
 483             boolean hasAlpha = cm.hasAlpha();
 484             cs = cm.getColorSpace();
 485             int type = cs.getType();
 486             switch(type) {
 487             case ColorSpace.TYPE_GRAY:
 488                 willSubsample = false;
 489                 if (hasExtraComponents) {  // e.g. alpha
 490                     wantJFIF = false;
 491                 }
 492                 break;
 493             case ColorSpace.TYPE_3CLR:
 494                 if (cs == JPEG.JCS.getYCC()) {
 495                     wantJFIF = false;
 496                     componentIDs[0] = (byte) 'Y';
 497                     componentIDs[1] = (byte) 'C';
 498                     componentIDs[2] = (byte) 'c';
 499                     if (hasAlpha) {
 500                         componentIDs[3] = (byte) 'A';
 501                     }
 502                 }
 503                 break;
 504             case ColorSpace.TYPE_YCbCr:
 505                 if (hasExtraComponents) { // e.g. K or alpha
 506                     wantJFIF = false;
 507                     if (!hasAlpha) { // Not alpha, so must be K
 508                         wantAdobe = true;
 509                         transform = JPEG.ADOBE_YCCK;
 510                     }
 511                 }
 512                 break;
 513             case ColorSpace.TYPE_RGB:  // with or without alpha
 514                 wantJFIF = false;
 515                 wantAdobe = true;
 516                 willSubsample = false;
 517                 componentIDs[0] = (byte) 'R';
 518                 componentIDs[1] = (byte) 'G';
 519                 componentIDs[2] = (byte) 'B';
 520                 if (hasAlpha) {
 521                     componentIDs[3] = (byte) 'A';
 522                 }
 523                 break;
 524             default:
 525                 // Everything else is not subsampled, gets no special marker,
 526                 // and component ids are 1 - N
 527                 wantJFIF = false;
 528                 willSubsample = false;
 529             }
 530         } else if (imageType != null) {
 531             ColorModel cm = imageType.getColorModel();
 532             numComponents = cm.getNumComponents();
 533             boolean hasExtraComponents = (cm.getNumColorComponents() != numComponents);
 534             boolean hasAlpha = cm.hasAlpha();
 535             cs = cm.getColorSpace();
 536             int type = cs.getType();
 537             switch(type) {
 538             case ColorSpace.TYPE_GRAY:
 539                 willSubsample = false;
 540                 if (hasExtraComponents) {  // e.g. alpha
 541                     wantJFIF = false;
 542                 }
 543                 break;
 544             case ColorSpace.TYPE_RGB:  // with or without alpha
 545                 // without alpha we just accept the JFIF defaults
 546                 if (hasAlpha) {
 547                     wantJFIF = false;
 548                 }
 549                 break;
 550             case ColorSpace.TYPE_3CLR:
 551                 wantJFIF = false;
 552                 willSubsample = false;
 553                 if (cs.equals(ColorSpace.getInstance(ColorSpace.CS_PYCC))) {
 554                     willSubsample = true;
 555                     wantAdobe = true;
 556                     componentIDs[0] = (byte) 'Y';
 557                     componentIDs[1] = (byte) 'C';
 558                     componentIDs[2] = (byte) 'c';
 559                     if (hasAlpha) {
 560                         componentIDs[3] = (byte) 'A';
 561                     }
 562                 }
 563                 break;
 564             case ColorSpace.TYPE_YCbCr:
 565                 if (hasExtraComponents) { // e.g. K or alpha
 566                     wantJFIF = false;
 567                     if (!hasAlpha) {  // then it must be K
 568                         wantAdobe = true;
 569                         transform = JPEG.ADOBE_YCCK;
 570                     }
 571                 }
 572                 break;
 573             case ColorSpace.TYPE_CMYK:
 574                 wantJFIF = false;
 575                 wantAdobe = true;
 576                 transform = JPEG.ADOBE_YCCK;
 577                 break;
 578 
 579             default:
 580                 // Everything else is not subsampled, gets no special marker,
 581                 // and component ids are 0 - N
 582                 wantJFIF = false;
 583                 willSubsample = false;
 584             }
 585 
 586         }
 587 
 588         // do we want an ICC profile?
 589         if (wantJFIF && JPEG.isNonStandardICC(cs)) {
 590             wantICC = true;
 591         }
 592 
 593         // Now step through the markers, consulting our variables.
 594         if (wantJFIF) {
 595             JFIFMarkerSegment jfif = new JFIFMarkerSegment();
 596             markerSequence.add(jfif);
 597             if (wantICC) {
 598                 try {
 599                     jfif.addICC((ICC_ColorSpace)cs);
 600                 } catch (IOException e) {} // Can't happen here
 601             }
 602         }
 603         // Adobe
 604         if (wantAdobe) {
 605             markerSequence.add(new AdobeMarkerSegment(transform));
 606         }
 607 
 608         // dqt
 609         if (wantQTables) {
 610             markerSequence.add(new DQTMarkerSegment(quality, willSubsample));
 611         }
 612 
 613         // dht
 614         if (wantHTables) {
 615             markerSequence.add(new DHTMarkerSegment(willSubsample));
 616         }
 617 
 618         // sof
 619         markerSequence.add(new SOFMarkerSegment(wantProg,
 620                                                 wantExtended,
 621                                                 willSubsample,
 622                                                 componentIDs,
 623                                                 numComponents));
 624 
 625         // sos
 626         if (!wantProg) {  // Default progression scans are done in the writer
 627             markerSequence.add(new SOSMarkerSegment(willSubsample,
 628                                                     componentIDs,
 629                                                     numComponents));
 630         }
 631 
 632         // Defensive programming
 633         if (!isConsistent()) {
 634             throw new InternalError("Default image metadata is inconsistent");
 635         }
 636     }
 637 
 638     ////// End of constructors
 639 
 640     // Utilities for dealing with the marker sequence.
 641     // The first ones have package access for access from the writer.
 642 
 643     /**
 644      * Returns the first MarkerSegment object in the list
 645      * with the given tag, or null if none is found.
 646      */
 647     MarkerSegment findMarkerSegment(int tag) {
 648         Iterator iter = markerSequence.iterator();
 649         while (iter.hasNext()) {
 650             MarkerSegment seg = (MarkerSegment)iter.next();
 651             if (seg.tag == tag) {
 652                 return seg;
 653             }
 654         }
 655         return null;
 656     }
 657 
 658     /**
 659      * Returns the first or last MarkerSegment object in the list
 660      * of the given class, or null if none is found.
 661      */
 662     MarkerSegment findMarkerSegment(Class cls, boolean first) {
 663         if (first) {
 664             Iterator iter = markerSequence.iterator();
 665             while (iter.hasNext()) {
 666                 MarkerSegment seg = (MarkerSegment)iter.next();
 667                 if (cls.isInstance(seg)) {
 668                     return seg;
 669                 }
 670             }
 671         } else {
 672             ListIterator iter = markerSequence.listIterator(markerSequence.size());
 673             while (iter.hasPrevious()) {
 674                 MarkerSegment seg = (MarkerSegment)iter.previous();
 675                 if (cls.isInstance(seg)) {
 676                     return seg;
 677                 }
 678             }
 679         }
 680         return null;
 681     }
 682 
 683     /**
 684      * Returns the index of the first or last MarkerSegment in the list
 685      * of the given class, or -1 if none is found.
 686      */
 687     private int findMarkerSegmentPosition(Class cls, boolean first) {
 688         if (first) {
 689             ListIterator iter = markerSequence.listIterator();
 690             for (int i = 0; iter.hasNext(); i++) {
 691                 MarkerSegment seg = (MarkerSegment)iter.next();
 692                 if (cls.isInstance(seg)) {
 693                     return i;
 694                 }
 695             }
 696         } else {
 697             ListIterator iter = markerSequence.listIterator(markerSequence.size());
 698             for (int i = markerSequence.size()-1; iter.hasPrevious(); i--) {
 699                 MarkerSegment seg = (MarkerSegment)iter.previous();
 700                 if (cls.isInstance(seg)) {
 701                     return i;
 702                 }
 703             }
 704         }
 705         return -1;
 706     }
 707 
 708     private int findLastUnknownMarkerSegmentPosition() {
 709         ListIterator iter = markerSequence.listIterator(markerSequence.size());
 710         for (int i = markerSequence.size()-1; iter.hasPrevious(); i--) {
 711             MarkerSegment seg = (MarkerSegment)iter.previous();
 712             if (seg.unknown == true) {
 713                 return i;
 714             }
 715         }
 716         return -1;
 717     }
 718 
 719     // Implement Cloneable, but restrict access
 720 
 721     protected Object clone() {
 722         JPEGMetadata newGuy = null;
 723         try {
 724             newGuy = (JPEGMetadata) super.clone();
 725         } catch (CloneNotSupportedException e) {} // won't happen
 726         if (markerSequence != null) {
 727             newGuy.markerSequence = (List) cloneSequence();
 728         }
 729         newGuy.resetSequence = null;
 730         return newGuy;
 731     }
 732 
 733     /**
 734      * Returns a deep copy of the current marker sequence.
 735      */
 736     private List cloneSequence() {
 737         if (markerSequence == null) {
 738             return null;
 739         }
 740         List retval = new ArrayList(markerSequence.size());
 741         Iterator iter = markerSequence.iterator();
 742         while(iter.hasNext()) {
 743             MarkerSegment seg = (MarkerSegment)iter.next();
 744             retval.add(seg.clone());
 745         }
 746 
 747         return retval;
 748     }
 749 
 750 
 751     // Tree methods
 752 
 753     public Node getAsTree(String formatName) {
 754         if (formatName == null) {
 755             throw new IllegalArgumentException("null formatName!");
 756         }
 757         if (isStream) {
 758             if (formatName.equals(JPEG.nativeStreamMetadataFormatName)) {
 759                 return getNativeTree();
 760             }
 761         } else {
 762             if (formatName.equals(JPEG.nativeImageMetadataFormatName)) {
 763                 return getNativeTree();
 764             }
 765             if (formatName.equals
 766                     (IIOMetadataFormatImpl.standardMetadataFormatName)) {
 767                 return getStandardTree();
 768             }
 769         }
 770         throw  new IllegalArgumentException("Unsupported format name: "
 771                                                 + formatName);
 772     }
 773 
 774     IIOMetadataNode getNativeTree() {
 775         IIOMetadataNode root;
 776         IIOMetadataNode top;
 777         Iterator iter = markerSequence.iterator();
 778         if (isStream) {
 779             root = new IIOMetadataNode(JPEG.nativeStreamMetadataFormatName);
 780             top = root;
 781         } else {
 782             IIOMetadataNode sequence = new IIOMetadataNode("markerSequence");
 783             if (!inThumb) {
 784                 root = new IIOMetadataNode(JPEG.nativeImageMetadataFormatName);
 785                 IIOMetadataNode header = new IIOMetadataNode("JPEGvariety");
 786                 root.appendChild(header);
 787                 JFIFMarkerSegment jfif = (JFIFMarkerSegment)
 788                     findMarkerSegment(JFIFMarkerSegment.class, true);
 789                 if (jfif != null) {
 790                     iter.next();  // JFIF must be first, so this skips it
 791                     header.appendChild(jfif.getNativeNode());
 792                 }
 793                 root.appendChild(sequence);
 794             } else {
 795                 root = sequence;
 796             }
 797             top = sequence;
 798         }
 799         while(iter.hasNext()) {
 800             MarkerSegment seg = (MarkerSegment) iter.next();
 801             top.appendChild(seg.getNativeNode());
 802         }
 803         return root;
 804     }
 805 
 806     // Standard tree node methods
 807 
 808     protected IIOMetadataNode getStandardChromaNode() {
 809         hasAlpha = false;  // Unless we find otherwise
 810 
 811         // Colorspace type - follow the rules in the spec
 812         // First get the SOF marker segment, if there is one
 813         SOFMarkerSegment sof = (SOFMarkerSegment)
 814             findMarkerSegment(SOFMarkerSegment.class, true);
 815         if (sof == null) {
 816             // No image, so no chroma
 817             return null;
 818         }
 819 
 820         IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
 821         IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
 822         chroma.appendChild(csType);
 823 
 824         // get the number of channels
 825         int numChannels = sof.componentSpecs.length;
 826 
 827         IIOMetadataNode numChanNode = new IIOMetadataNode("NumChannels");
 828         chroma.appendChild(numChanNode);
 829         numChanNode.setAttribute("value", Integer.toString(numChannels));
 830 
 831         // is there a JFIF marker segment?
 832         if (findMarkerSegment(JFIFMarkerSegment.class, true) != null) {
 833             if (numChannels == 1) {
 834                 csType.setAttribute("name", "GRAY");
 835             } else {
 836                 csType.setAttribute("name", "YCbCr");
 837             }
 838             return chroma;
 839         }
 840 
 841         // How about an Adobe marker segment?
 842         AdobeMarkerSegment adobe =
 843             (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class, true);
 844         if (adobe != null){
 845             switch (adobe.transform) {
 846             case JPEG.ADOBE_YCCK:
 847                 csType.setAttribute("name", "YCCK");
 848                 break;
 849             case JPEG.ADOBE_YCC:
 850                 csType.setAttribute("name", "YCbCr");
 851                 break;
 852             case JPEG.ADOBE_UNKNOWN:
 853                 if (numChannels == 3) {
 854                     csType.setAttribute("name", "RGB");
 855                 } else if (numChannels == 4) {
 856                     csType.setAttribute("name", "CMYK");
 857                 }
 858                 break;
 859             }
 860             return chroma;
 861         }
 862 
 863         // Neither marker.  Check components
 864         if (numChannels < 3) {
 865             csType.setAttribute("name", "GRAY");
 866             if (numChannels == 2) {
 867                 hasAlpha = true;
 868             }
 869             return chroma;
 870         }
 871 
 872         boolean idsAreJFIF = true;
 873 
 874         for (int i = 0; i < sof.componentSpecs.length; i++) {
 875             int id = sof.componentSpecs[i].componentId;
 876             if ((id < 1) || (id >= sof.componentSpecs.length)) {
 877                 idsAreJFIF = false;
 878             }
 879         }
 880 
 881         if (idsAreJFIF) {
 882             csType.setAttribute("name", "YCbCr");
 883             if (numChannels == 4) {
 884                 hasAlpha = true;
 885             }
 886             return chroma;
 887         }
 888 
 889         // Check against the letters
 890         if ((sof.componentSpecs[0].componentId == 'R')
 891             && (sof.componentSpecs[1].componentId == 'G')
 892             && (sof.componentSpecs[2].componentId == 'B')){
 893 
 894             csType.setAttribute("name", "RGB");
 895             if ((numChannels == 4)
 896                 && (sof.componentSpecs[3].componentId == 'A')) {
 897                 hasAlpha = true;
 898             }
 899             return chroma;
 900         }
 901 
 902         if ((sof.componentSpecs[0].componentId == 'Y')
 903             && (sof.componentSpecs[1].componentId == 'C')
 904             && (sof.componentSpecs[2].componentId == 'c')){
 905 
 906             csType.setAttribute("name", "PhotoYCC");
 907             if ((numChannels == 4)
 908                 && (sof.componentSpecs[3].componentId == 'A')) {
 909                 hasAlpha = true;
 910             }
 911             return chroma;
 912         }
 913 
 914         // Finally, 3-channel subsampled are YCbCr, unsubsampled are RGB
 915         // 4-channel subsampled are YCbCrA, unsubsampled are CMYK
 916 
 917         boolean subsampled = false;
 918 
 919         int hfactor = sof.componentSpecs[0].HsamplingFactor;
 920         int vfactor = sof.componentSpecs[0].VsamplingFactor;
 921 
 922         for (int i = 1; i<sof.componentSpecs.length; i++) {
 923             if ((sof.componentSpecs[i].HsamplingFactor != hfactor)
 924                 || (sof.componentSpecs[i].VsamplingFactor != vfactor)){
 925                 subsampled = true;
 926                 break;
 927             }
 928         }
 929 
 930         if (subsampled) {
 931             csType.setAttribute("name", "YCbCr");
 932             if (numChannels == 4) {
 933                 hasAlpha = true;
 934             }
 935             return chroma;
 936         }
 937 
 938         // Not subsampled.  numChannels < 3 is taken care of above
 939         if (numChannels == 3) {
 940             csType.setAttribute("name", "RGB");
 941         } else {
 942             csType.setAttribute("name", "CMYK");
 943         }
 944 
 945         return chroma;
 946     }
 947 
 948     protected IIOMetadataNode getStandardCompressionNode() {
 949 
 950         IIOMetadataNode compression = new IIOMetadataNode("Compression");
 951 
 952         // CompressionTypeName
 953         IIOMetadataNode name = new IIOMetadataNode("CompressionTypeName");
 954         name.setAttribute("value", "JPEG");
 955         compression.appendChild(name);
 956 
 957         // Lossless - false
 958         IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
 959         lossless.setAttribute("value", "FALSE");
 960         compression.appendChild(lossless);
 961 
 962         // NumProgressiveScans - count sos segments
 963         int sosCount = 0;
 964         Iterator iter = markerSequence.iterator();
 965         while (iter.hasNext()) {
 966             MarkerSegment ms = (MarkerSegment) iter.next();
 967             if (ms.tag == JPEG.SOS) {
 968                 sosCount++;
 969             }
 970         }
 971         if (sosCount != 0) {
 972             IIOMetadataNode prog = new IIOMetadataNode("NumProgressiveScans");
 973             prog.setAttribute("value", Integer.toString(sosCount));
 974             compression.appendChild(prog);
 975         }
 976 
 977         return compression;
 978     }
 979 
 980     protected IIOMetadataNode getStandardDimensionNode() {
 981         // If we have a JFIF marker segment, we know a little
 982         // otherwise all we know is the orientation, which is always normal
 983         IIOMetadataNode dim = new IIOMetadataNode("Dimension");
 984         IIOMetadataNode orient = new IIOMetadataNode("ImageOrientation");
 985         orient.setAttribute("value", "normal");
 986         dim.appendChild(orient);
 987 
 988         JFIFMarkerSegment jfif =
 989             (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class, true);
 990         if (jfif != null) {
 991 
 992             // Aspect Ratio is width of pixel / height of pixel
 993             float aspectRatio;
 994             if (jfif.resUnits == 0) {
 995                 // In this case they just encode aspect ratio directly
 996                 aspectRatio = ((float) jfif.Xdensity)/jfif.Ydensity;
 997             } else {
 998                 // They are true densities (e.g. dpi) and must be inverted
 999                 aspectRatio = ((float) jfif.Ydensity)/jfif.Xdensity;
1000             }
1001             IIOMetadataNode aspect = new IIOMetadataNode("PixelAspectRatio");
1002             aspect.setAttribute("value", Float.toString(aspectRatio));
1003             dim.insertBefore(aspect, orient);
1004 
1005             // Pixel size
1006             if (jfif.resUnits != 0) {
1007                 // 1 == dpi, 2 == dpc
1008                 float scale = (jfif.resUnits == 1) ? 25.4F : 10.0F;
1009 
1010                 IIOMetadataNode horiz =
1011                     new IIOMetadataNode("HorizontalPixelSize");
1012                 horiz.setAttribute("value",
1013                                    Float.toString(scale/jfif.Xdensity));
1014                 dim.appendChild(horiz);
1015 
1016                 IIOMetadataNode vert =
1017                     new IIOMetadataNode("VerticalPixelSize");
1018                 vert.setAttribute("value",
1019                                   Float.toString(scale/jfif.Ydensity));
1020                 dim.appendChild(vert);
1021             }
1022         }
1023         return dim;
1024     }
1025 
1026     protected IIOMetadataNode getStandardTextNode() {
1027         IIOMetadataNode text = null;
1028         // Add a text entry for each COM Marker Segment
1029         if (findMarkerSegment(JPEG.COM) != null) {
1030             text = new IIOMetadataNode("Text");
1031             Iterator iter = markerSequence.iterator();
1032             while (iter.hasNext()) {
1033                 MarkerSegment seg = (MarkerSegment) iter.next();
1034                 if (seg.tag == JPEG.COM) {
1035                     COMMarkerSegment com = (COMMarkerSegment) seg;
1036                     IIOMetadataNode entry = new IIOMetadataNode("TextEntry");
1037                     entry.setAttribute("keyword", "comment");
1038                     entry.setAttribute("value", com.getComment());
1039                 text.appendChild(entry);
1040                 }
1041             }
1042         }
1043         return text;
1044     }
1045 
1046     protected IIOMetadataNode getStandardTransparencyNode() {
1047         IIOMetadataNode trans = null;
1048         if (hasAlpha == true) {
1049             trans = new IIOMetadataNode("Transparency");
1050             IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
1051             alpha.setAttribute("value", "nonpremultiplied"); // Always assume
1052             trans.appendChild(alpha);
1053         }
1054         return trans;
1055     }
1056 
1057     // Editing
1058 
1059     public boolean isReadOnly() {
1060         return false;
1061     }
1062 
1063     public void mergeTree(String formatName, Node root)
1064         throws IIOInvalidTreeException {
1065         if (formatName == null) {
1066             throw new IllegalArgumentException("null formatName!");
1067         }
1068         if (root == null) {
1069             throw new IllegalArgumentException("null root!");
1070         }
1071         List copy = null;
1072         if (resetSequence == null) {
1073             resetSequence = cloneSequence();  // Deep copy
1074             copy = resetSequence;  // Avoid cloning twice
1075         } else {
1076             copy = cloneSequence();
1077         }
1078         if (isStream &&
1079             (formatName.equals(JPEG.nativeStreamMetadataFormatName))) {
1080                 mergeNativeTree(root);
1081         } else if (!isStream &&
1082                    (formatName.equals(JPEG.nativeImageMetadataFormatName))) {
1083             mergeNativeTree(root);
1084         } else if (!isStream &&
1085                    (formatName.equals
1086                     (IIOMetadataFormatImpl.standardMetadataFormatName))) {
1087             mergeStandardTree(root);
1088         } else {
1089             throw  new IllegalArgumentException("Unsupported format name: "
1090                                                 + formatName);
1091         }
1092         if (!isConsistent()) {
1093             markerSequence = copy;
1094             throw new IIOInvalidTreeException
1095                 ("Merged tree is invalid; original restored", root);
1096         }
1097     }
1098 
1099     private void mergeNativeTree(Node root) throws IIOInvalidTreeException {
1100         String name = root.getNodeName();
1101         if (name != ((isStream) ? JPEG.nativeStreamMetadataFormatName
1102                                 : JPEG.nativeImageMetadataFormatName)) {
1103             throw new IIOInvalidTreeException("Invalid root node name: " + name,
1104                                               root);
1105         }
1106         if (root.getChildNodes().getLength() != 2) { // JPEGvariety and markerSequence
1107             throw new IIOInvalidTreeException(
1108                 "JPEGvariety and markerSequence nodes must be present", root);
1109         }
1110         mergeJFIFsubtree(root.getFirstChild());
1111         mergeSequenceSubtree(root.getLastChild());
1112     }
1113 
1114     /**
1115      * Merge a JFIF subtree into the marker sequence, if the subtree
1116      * is non-empty.
1117      * If a JFIF marker exists, update it from the subtree.
1118      * If none exists, create one from the subtree and insert it at the
1119      * beginning of the marker sequence.
1120      */
1121     private void mergeJFIFsubtree(Node JPEGvariety)
1122         throws IIOInvalidTreeException {
1123         if (JPEGvariety.getChildNodes().getLength() != 0) {
1124             Node jfifNode = JPEGvariety.getFirstChild();
1125             // is there already a jfif marker segment?
1126             JFIFMarkerSegment jfifSeg =
1127                 (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class, true);
1128             if (jfifSeg != null) {
1129                 jfifSeg.updateFromNativeNode(jfifNode, false);
1130             } else {
1131                 // Add it as the first element in the list.
1132                 markerSequence.add(0, new JFIFMarkerSegment(jfifNode));
1133             }
1134         }
1135     }
1136 
1137     private void mergeSequenceSubtree(Node sequenceTree)
1138         throws IIOInvalidTreeException {
1139         NodeList children = sequenceTree.getChildNodes();
1140         for (int i = 0; i < children.getLength(); i++) {
1141             Node node = children.item(i);
1142             String name = node.getNodeName();
1143             if (name.equals("dqt")) {
1144                 mergeDQTNode(node);
1145             } else if (name.equals("dht")) {
1146                 mergeDHTNode(node);
1147             } else if (name.equals("dri")) {
1148                 mergeDRINode(node);
1149             } else if (name.equals("com")) {
1150                 mergeCOMNode(node);
1151             } else if (name.equals("app14Adobe")) {
1152                 mergeAdobeNode(node);
1153             } else if (name.equals("unknown")) {
1154                 mergeUnknownNode(node);
1155             } else if (name.equals("sof")) {
1156                 mergeSOFNode(node);
1157             } else if (name.equals("sos")) {
1158                 mergeSOSNode(node);
1159             } else {
1160                 throw new IIOInvalidTreeException("Invalid node: " + name, node);
1161             }
1162         }
1163     }
1164 
1165     /**
1166      * Merge the given DQT node into the marker sequence.  If there already
1167      * exist DQT marker segments in the sequence, then each table in the
1168      * node replaces the first table, in any DQT segment, with the same
1169      * table id.  If none of the existing DQT segments contain a table with
1170      * the same id, then the table is added to the last existing DQT segment.
1171      * If there are no DQT segments, then a new one is created and added
1172      * as follows:
1173      * If there are DHT segments, the new DQT segment is inserted before the
1174      * first one.
1175      * If there are no DHT segments, the new DQT segment is inserted before
1176      * an SOF segment, if there is one.
1177      * If there is no SOF segment, the new DQT segment is inserted before
1178      * the first SOS segment, if there is one.
1179      * If there is no SOS segment, the new DQT segment is added to the end
1180      * of the sequence.
1181      */
1182     private void mergeDQTNode(Node node) throws IIOInvalidTreeException {
1183         // First collect any existing DQT nodes into a local list
1184         ArrayList oldDQTs = new ArrayList();
1185         Iterator iter = markerSequence.iterator();
1186         while (iter.hasNext()) {
1187             MarkerSegment seg = (MarkerSegment) iter.next();
1188             if (seg instanceof DQTMarkerSegment) {
1189                 oldDQTs.add(seg);
1190             }
1191         }
1192         if (!oldDQTs.isEmpty()) {
1193             NodeList children = node.getChildNodes();
1194             for (int i = 0; i < children.getLength(); i++) {
1195                 Node child = children.item(i);
1196                 int childID = MarkerSegment.getAttributeValue(child,
1197                                                               null,
1198                                                               "qtableId",
1199                                                               0, 3,
1200                                                               true);
1201                 DQTMarkerSegment dqt = null;
1202                 int tableIndex = -1;
1203                 for (int j = 0; j < oldDQTs.size(); j++) {
1204                     DQTMarkerSegment testDQT = (DQTMarkerSegment) oldDQTs.get(j);
1205                     for (int k = 0; k < testDQT.tables.size(); k++) {
1206                         DQTMarkerSegment.Qtable testTable =
1207                             (DQTMarkerSegment.Qtable) testDQT.tables.get(k);
1208                         if (childID == testTable.tableID) {
1209                             dqt = testDQT;
1210                             tableIndex = k;
1211                             break;
1212                         }
1213                     }
1214                     if (dqt != null) break;
1215                 }
1216                 if (dqt != null) {
1217                     dqt.tables.set(tableIndex, dqt.getQtableFromNode(child));
1218                 } else {
1219                     dqt = (DQTMarkerSegment) oldDQTs.get(oldDQTs.size()-1);
1220                     dqt.tables.add(dqt.getQtableFromNode(child));
1221                 }
1222             }
1223         } else {
1224             DQTMarkerSegment newGuy = new DQTMarkerSegment(node);
1225             int firstDHT = findMarkerSegmentPosition(DHTMarkerSegment.class, true);
1226             int firstSOF = findMarkerSegmentPosition(SOFMarkerSegment.class, true);
1227             int firstSOS = findMarkerSegmentPosition(SOSMarkerSegment.class, true);
1228             if (firstDHT != -1) {
1229                 markerSequence.add(firstDHT, newGuy);
1230             } else if (firstSOF != -1) {
1231                 markerSequence.add(firstSOF, newGuy);
1232             } else if (firstSOS != -1) {
1233                 markerSequence.add(firstSOS, newGuy);
1234             } else {
1235                 markerSequence.add(newGuy);
1236             }
1237         }
1238     }
1239 
1240     /**
1241      * Merge the given DHT node into the marker sequence.  If there already
1242      * exist DHT marker segments in the sequence, then each table in the
1243      * node replaces the first table, in any DHT segment, with the same
1244      * table class and table id.  If none of the existing DHT segments contain
1245      * a table with the same class and id, then the table is added to the last
1246      * existing DHT segment.
1247      * If there are no DHT segments, then a new one is created and added
1248      * as follows:
1249      * If there are DQT segments, the new DHT segment is inserted immediately
1250      * following the last DQT segment.
1251      * If there are no DQT segments, the new DHT segment is inserted before
1252      * an SOF segment, if there is one.
1253      * If there is no SOF segment, the new DHT segment is inserted before
1254      * the first SOS segment, if there is one.
1255      * If there is no SOS segment, the new DHT segment is added to the end
1256      * of the sequence.
1257      */
1258     private void mergeDHTNode(Node node) throws IIOInvalidTreeException {
1259         // First collect any existing DQT nodes into a local list
1260         ArrayList oldDHTs = new ArrayList();
1261         Iterator iter = markerSequence.iterator();
1262         while (iter.hasNext()) {
1263             MarkerSegment seg = (MarkerSegment) iter.next();
1264             if (seg instanceof DHTMarkerSegment) {
1265                 oldDHTs.add(seg);
1266             }
1267         }
1268         if (!oldDHTs.isEmpty()) {
1269             NodeList children = node.getChildNodes();
1270             for (int i = 0; i < children.getLength(); i++) {
1271                 Node child = children.item(i);
1272                 NamedNodeMap attrs = child.getAttributes();
1273                 int childID = MarkerSegment.getAttributeValue(child,
1274                                                               attrs,
1275                                                               "htableId",
1276                                                               0, 3,
1277                                                               true);
1278                 int childClass = MarkerSegment.getAttributeValue(child,
1279                                                                  attrs,
1280                                                                  "class",
1281                                                                  0, 1,
1282                                                                  true);
1283                 DHTMarkerSegment dht = null;
1284                 int tableIndex = -1;
1285                 for (int j = 0; j < oldDHTs.size(); j++) {
1286                     DHTMarkerSegment testDHT = (DHTMarkerSegment) oldDHTs.get(j);
1287                     for (int k = 0; k < testDHT.tables.size(); k++) {
1288                         DHTMarkerSegment.Htable testTable =
1289                             (DHTMarkerSegment.Htable) testDHT.tables.get(k);
1290                         if ((childID == testTable.tableID) &&
1291                             (childClass == testTable.tableClass)) {
1292                             dht = testDHT;
1293                             tableIndex = k;
1294                             break;
1295                         }
1296                     }
1297                     if (dht != null) break;
1298                 }
1299                 if (dht != null) {
1300                     dht.tables.set(tableIndex, dht.getHtableFromNode(child));
1301                 } else {
1302                     dht = (DHTMarkerSegment) oldDHTs.get(oldDHTs.size()-1);
1303                     dht.tables.add(dht.getHtableFromNode(child));
1304                 }
1305             }
1306         } else {
1307             DHTMarkerSegment newGuy = new DHTMarkerSegment(node);
1308             int lastDQT = findMarkerSegmentPosition(DQTMarkerSegment.class, false);
1309             int firstSOF = findMarkerSegmentPosition(SOFMarkerSegment.class, true);
1310             int firstSOS = findMarkerSegmentPosition(SOSMarkerSegment.class, true);
1311             if (lastDQT != -1) {
1312                 markerSequence.add(lastDQT+1, newGuy);
1313             } else if (firstSOF != -1) {
1314                 markerSequence.add(firstSOF, newGuy);
1315             } else if (firstSOS != -1) {
1316                 markerSequence.add(firstSOS, newGuy);
1317             } else {
1318                 markerSequence.add(newGuy);
1319             }
1320         }
1321     }
1322 
1323     /**
1324      * Merge the given DRI node into the marker sequence.
1325      * If there already exists a DRI marker segment, the restart interval
1326      * value is updated.
1327      * If there is no DRI segment, then a new one is created and added as
1328      * follows:
1329      * If there is an SOF segment, the new DRI segment is inserted before
1330      * it.
1331      * If there is no SOF segment, the new DRI segment is inserted before
1332      * the first SOS segment, if there is one.
1333      * If there is no SOS segment, the new DRI segment is added to the end
1334      * of the sequence.
1335      */
1336     private void mergeDRINode(Node node) throws IIOInvalidTreeException {
1337         DRIMarkerSegment dri =
1338             (DRIMarkerSegment) findMarkerSegment(DRIMarkerSegment.class, true);
1339         if (dri != null) {
1340             dri.updateFromNativeNode(node, false);
1341         } else {
1342             DRIMarkerSegment newGuy = new DRIMarkerSegment(node);
1343             int firstSOF = findMarkerSegmentPosition(SOFMarkerSegment.class, true);
1344             int firstSOS = findMarkerSegmentPosition(SOSMarkerSegment.class, true);
1345             if (firstSOF != -1) {
1346                 markerSequence.add(firstSOF, newGuy);
1347             } else if (firstSOS != -1) {
1348                 markerSequence.add(firstSOS, newGuy);
1349             } else {
1350                 markerSequence.add(newGuy);
1351             }
1352         }
1353     }
1354 
1355     /**
1356      * Merge the given COM node into the marker sequence.
1357      * A new COM marker segment is created and added to the sequence
1358      * using insertCOMMarkerSegment.
1359      */
1360     private void mergeCOMNode(Node node) throws IIOInvalidTreeException {
1361         COMMarkerSegment newGuy = new COMMarkerSegment(node);
1362         insertCOMMarkerSegment(newGuy);
1363     }
1364 
1365      /**
1366       * Insert a new COM marker segment into an appropriate place in the
1367       * marker sequence, as follows:
1368       * If there already exist COM marker segments, the new one is inserted
1369       * after the last one.
1370       * If there are no COM segments, the new COM segment is inserted after the
1371       * JFIF segment, if there is one.
1372       * If there is no JFIF segment, the new COM segment is inserted after the
1373       * Adobe marker segment, if there is one.
1374       * If there is no Adobe segment, the new COM segment is inserted
1375       * at the beginning of the sequence.
1376       */
1377     private void insertCOMMarkerSegment(COMMarkerSegment newGuy) {
1378         int lastCOM = findMarkerSegmentPosition(COMMarkerSegment.class, false);
1379         boolean hasJFIF = (findMarkerSegment(JFIFMarkerSegment.class, true) != null);
1380         int firstAdobe = findMarkerSegmentPosition(AdobeMarkerSegment.class, true);
1381         if (lastCOM != -1) {
1382             markerSequence.add(lastCOM+1, newGuy);
1383         } else if (hasJFIF) {
1384             markerSequence.add(1, newGuy);  // JFIF is always 0
1385         } else if (firstAdobe != -1) {
1386             markerSequence.add(firstAdobe+1, newGuy);
1387         } else {
1388             markerSequence.add(0, newGuy);
1389         }
1390     }
1391 
1392     /**
1393      * Merge the given Adobe APP14 node into the marker sequence.
1394      * If there already exists an Adobe marker segment, then its attributes
1395      * are updated from the node.
1396      * If there is no Adobe segment, then a new one is created and added
1397      * using insertAdobeMarkerSegment.
1398      */
1399     private void mergeAdobeNode(Node node) throws IIOInvalidTreeException {
1400         AdobeMarkerSegment adobe =
1401             (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class, true);
1402         if (adobe != null) {
1403             adobe.updateFromNativeNode(node, false);
1404         } else {
1405             AdobeMarkerSegment newGuy = new AdobeMarkerSegment(node);
1406             insertAdobeMarkerSegment(newGuy);
1407         }
1408     }
1409 
1410     /**
1411      * Insert the given AdobeMarkerSegment into the marker sequence, as
1412      * follows (we assume there is no Adobe segment yet):
1413      * If there is a JFIF segment, then the new Adobe segment is inserted
1414      * after it.
1415      * If there is no JFIF segment, the new Adobe segment is inserted after the
1416      * last Unknown segment, if there are any.
1417      * If there are no Unknown segments, the new Adobe segment is inserted
1418      * at the beginning of the sequence.
1419      */
1420     private void insertAdobeMarkerSegment(AdobeMarkerSegment newGuy) {
1421         boolean hasJFIF =
1422             (findMarkerSegment(JFIFMarkerSegment.class, true) != null);
1423         int lastUnknown = findLastUnknownMarkerSegmentPosition();
1424         if (hasJFIF) {
1425             markerSequence.add(1, newGuy);  // JFIF is always 0
1426         } else if (lastUnknown != -1) {
1427             markerSequence.add(lastUnknown+1, newGuy);
1428         } else {
1429             markerSequence.add(0, newGuy);
1430         }
1431     }
1432 
1433     /**
1434      * Merge the given Unknown node into the marker sequence.
1435      * A new Unknown marker segment is created and added to the sequence as
1436      * follows:
1437      * If there already exist Unknown marker segments, the new one is inserted
1438      * after the last one.
1439      * If there are no Unknown marker segments, the new Unknown marker segment
1440      * is inserted after the JFIF segment, if there is one.
1441      * If there is no JFIF segment, the new Unknown segment is inserted before
1442      * the Adobe marker segment, if there is one.
1443      * If there is no Adobe segment, the new Unknown segment is inserted
1444      * at the beginning of the sequence.
1445      */
1446     private void mergeUnknownNode(Node node) throws IIOInvalidTreeException {
1447         MarkerSegment newGuy = new MarkerSegment(node);
1448         int lastUnknown = findLastUnknownMarkerSegmentPosition();
1449         boolean hasJFIF = (findMarkerSegment(JFIFMarkerSegment.class, true) != null);
1450         int firstAdobe = findMarkerSegmentPosition(AdobeMarkerSegment.class, true);
1451         if (lastUnknown != -1) {
1452             markerSequence.add(lastUnknown+1, newGuy);
1453         } else if (hasJFIF) {
1454             markerSequence.add(1, newGuy);  // JFIF is always 0
1455         } if (firstAdobe != -1) {
1456             markerSequence.add(firstAdobe, newGuy);
1457         } else {
1458             markerSequence.add(0, newGuy);
1459         }
1460     }
1461 
1462     /**
1463      * Merge the given SOF node into the marker sequence.
1464      * If there already exists an SOF marker segment in the sequence, then
1465      * its values are updated from the node.
1466      * If there is no SOF segment, then a new one is created and added as
1467      * follows:
1468      * If there are any SOS segments, the new SOF segment is inserted before
1469      * the first one.
1470      * If there is no SOS segment, the new SOF segment is added to the end
1471      * of the sequence.
1472      *
1473      */
1474     private void mergeSOFNode(Node node) throws IIOInvalidTreeException {
1475         SOFMarkerSegment sof =
1476             (SOFMarkerSegment) findMarkerSegment(SOFMarkerSegment.class, true);
1477         if (sof != null) {
1478             sof.updateFromNativeNode(node, false);
1479         } else {
1480             SOFMarkerSegment newGuy = new SOFMarkerSegment(node);
1481             int firstSOS = findMarkerSegmentPosition(SOSMarkerSegment.class, true);
1482             if (firstSOS != -1) {
1483                 markerSequence.add(firstSOS, newGuy);
1484             } else {
1485                 markerSequence.add(newGuy);
1486             }
1487         }
1488     }
1489 
1490     /**
1491      * Merge the given SOS node into the marker sequence.
1492      * If there already exists a single SOS marker segment, then the values
1493      * are updated from the node.
1494      * If there are more than one existing SOS marker segments, then an
1495      * IIOInvalidTreeException is thrown, as SOS segments cannot be merged
1496      * into a set of progressive scans.
1497      * If there are no SOS marker segments, a new one is created and added
1498      * to the end of the sequence.
1499      */
1500     private void mergeSOSNode(Node node) throws IIOInvalidTreeException {
1501         SOSMarkerSegment firstSOS =
1502             (SOSMarkerSegment) findMarkerSegment(SOSMarkerSegment.class, true);
1503         SOSMarkerSegment lastSOS =
1504             (SOSMarkerSegment) findMarkerSegment(SOSMarkerSegment.class, false);
1505         if (firstSOS != null) {
1506             if (firstSOS != lastSOS) {
1507                 throw new IIOInvalidTreeException
1508                     ("Can't merge SOS node into a tree with > 1 SOS node", node);
1509             }
1510             firstSOS.updateFromNativeNode(node, false);
1511         } else {
1512             markerSequence.add(new SOSMarkerSegment(node));
1513         }
1514     }
1515 
1516     private boolean transparencyDone;
1517 
1518     private void mergeStandardTree(Node root) throws IIOInvalidTreeException {
1519         transparencyDone = false;
1520         NodeList children = root.getChildNodes();
1521         for (int i = 0; i < children.getLength(); i++) {
1522             Node node = children.item(i);
1523             String name = node.getNodeName();
1524             if (name.equals("Chroma")) {
1525                 mergeStandardChromaNode(node, children);
1526             } else if (name.equals("Compression")) {
1527                 mergeStandardCompressionNode(node);
1528             } else if (name.equals("Data")) {
1529                 mergeStandardDataNode(node);
1530             } else if (name.equals("Dimension")) {
1531                 mergeStandardDimensionNode(node);
1532             } else if (name.equals("Document")) {
1533                 mergeStandardDocumentNode(node);
1534             } else if (name.equals("Text")) {
1535                 mergeStandardTextNode(node);
1536             } else if (name.equals("Transparency")) {
1537                 mergeStandardTransparencyNode(node);
1538             } else {
1539                 throw new IIOInvalidTreeException("Invalid node: " + name, node);
1540             }
1541         }
1542     }
1543 
1544     /*
1545      * In general, it could be possible to convert all non-pixel data to some
1546      * textual form and include it in comments, but then this would create the
1547      * expectation that these comment forms be recognized by the reader, thus
1548      * creating a defacto extension to JPEG metadata capabilities.  This is
1549      * probably best avoided, so the following convert only text nodes to
1550      * comments, and lose the keywords as well.
1551      */
1552 
1553     private void mergeStandardChromaNode(Node node, NodeList siblings)
1554         throws IIOInvalidTreeException {
1555         // ColorSpaceType can change the target colorspace for compression
1556         // This must take any transparency node into account as well, as
1557         // that affects the number of channels (if alpha is present).  If
1558         // a transparency node is dealt with here, set a flag to indicate
1559         // this to the transparency processor below.  If we discover that
1560         // the nodes are not in order, throw an exception as the tree is
1561         // invalid.
1562 
1563         if (transparencyDone) {
1564             throw new IIOInvalidTreeException
1565                 ("Transparency node must follow Chroma node", node);
1566         }
1567 
1568         Node csType = node.getFirstChild();
1569         if ((csType == null) || !csType.getNodeName().equals("ColorSpaceType")) {
1570             // If there is no ColorSpaceType node, we have nothing to do
1571             return;
1572         }
1573 
1574         String csName = csType.getAttributes().getNamedItem("name").getNodeValue();
1575 
1576         int numChannels = 0;
1577         boolean wantJFIF = false;
1578         boolean wantAdobe = false;
1579         int transform = 0;
1580         boolean willSubsample = false;
1581         byte [] ids = {1, 2, 3, 4};  // JFIF compatible
1582         if (csName.equals("GRAY")) {
1583             numChannels = 1;
1584             wantJFIF = true;
1585         } else if (csName.equals("YCbCr")) {
1586             numChannels = 3;
1587             wantJFIF = true;
1588             willSubsample = true;
1589         } else if (csName.equals("PhotoYCC")) {
1590             numChannels = 3;
1591             wantAdobe = true;
1592             transform = JPEG.ADOBE_YCC;
1593             ids[0] = (byte) 'Y';
1594             ids[1] = (byte) 'C';
1595             ids[2] = (byte) 'c';
1596         } else if (csName.equals("RGB")) {
1597             numChannels = 3;
1598             wantAdobe = true;
1599             transform = JPEG.ADOBE_UNKNOWN;
1600             ids[0] = (byte) 'R';
1601             ids[1] = (byte) 'G';
1602             ids[2] = (byte) 'B';
1603         } else if ((csName.equals("XYZ"))
1604                    || (csName.equals("Lab"))
1605                    || (csName.equals("Luv"))
1606                    || (csName.equals("YxY"))
1607                    || (csName.equals("HSV"))
1608                    || (csName.equals("HLS"))
1609                    || (csName.equals("CMY"))
1610                    || (csName.equals("3CLR"))) {
1611             numChannels = 3;
1612         } else if (csName.equals("YCCK")) {
1613             numChannels = 4;
1614             wantAdobe = true;
1615             transform = JPEG.ADOBE_YCCK;
1616             willSubsample = true;
1617         } else if (csName.equals("CMYK")) {
1618             numChannels = 4;
1619             wantAdobe = true;
1620             transform = JPEG.ADOBE_UNKNOWN;
1621         } else if (csName.equals("4CLR")) {
1622             numChannels = 4;
1623         } else { // We can't handle them, so don't modify any metadata
1624             return;
1625         }
1626 
1627         boolean wantAlpha = false;
1628         for (int i = 0; i < siblings.getLength(); i++) {
1629             Node trans = siblings.item(i);
1630             if (trans.getNodeName().equals("Transparency")) {
1631                 wantAlpha = wantAlpha(trans);
1632                 break;  // out of for
1633             }
1634         }
1635 
1636         if (wantAlpha) {
1637             numChannels++;
1638             wantJFIF = false;
1639             if (ids[0] == (byte) 'R') {
1640                 ids[3] = (byte) 'A';
1641                 wantAdobe = false;
1642             }
1643         }
1644 
1645         JFIFMarkerSegment jfif =
1646             (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class, true);
1647         AdobeMarkerSegment adobe =
1648             (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class, true);
1649         SOFMarkerSegment sof =
1650             (SOFMarkerSegment) findMarkerSegment(SOFMarkerSegment.class, true);
1651         SOSMarkerSegment sos =
1652             (SOSMarkerSegment) findMarkerSegment(SOSMarkerSegment.class, true);
1653 
1654         // If the metadata specifies progressive, then the number of channels
1655         // must match, so that we can modify all the existing SOS marker segments.
1656         // If they don't match, we don't know what to do with SOS so we can't do
1657         // the merge.  We then just return silently.
1658         // An exception would not be appropriate.  A warning might, but we have
1659         // nowhere to send it to.
1660         if ((sof != null) && (sof.tag == JPEG.SOF2)) { // Progressive
1661             if ((sof.componentSpecs.length != numChannels) && (sos != null)) {
1662                 return;
1663             }
1664         }
1665 
1666         // JFIF header might be removed
1667         if (!wantJFIF && (jfif != null)) {
1668             markerSequence.remove(jfif);
1669         }
1670 
1671         // Now add a JFIF if we do want one, but only if it isn't stream metadata
1672         if (wantJFIF && !isStream) {
1673             markerSequence.add(0, new JFIFMarkerSegment());
1674         }
1675 
1676         // Adobe header might be removed or the transform modified, if it isn't
1677         // stream metadata
1678         if (wantAdobe) {
1679             if ((adobe == null) && !isStream) {
1680                 adobe = new AdobeMarkerSegment(transform);
1681                 insertAdobeMarkerSegment(adobe);
1682             } else {
1683                 adobe.transform = transform;
1684             }
1685         } else if (adobe != null) {
1686             markerSequence.remove(adobe);
1687         }
1688 
1689         boolean updateQtables = false;
1690         boolean updateHtables = false;
1691 
1692         boolean progressive = false;
1693 
1694         int [] subsampledSelectors = {0, 1, 1, 0 } ;
1695         int [] nonSubsampledSelectors = { 0, 0, 0, 0};
1696 
1697         int [] newTableSelectors = willSubsample
1698                                    ? subsampledSelectors
1699                                    : nonSubsampledSelectors;
1700 
1701         // Keep the old componentSpecs array
1702         SOFMarkerSegment.ComponentSpec [] oldCompSpecs = null;
1703         // SOF might be modified
1704         if (sof != null) {
1705             oldCompSpecs = sof.componentSpecs;
1706             progressive = (sof.tag == JPEG.SOF2);
1707             // Now replace the SOF with a new one; it might be the same, but
1708             // this is easier.
1709             markerSequence.set(markerSequence.indexOf(sof),
1710                                new SOFMarkerSegment(progressive,
1711                                                     false, // we never need extended
1712                                                     willSubsample,
1713                                                     ids,
1714                                                     numChannels));
1715 
1716             // Now suss out if subsampling changed and set the boolean for
1717             // updating the q tables
1718             // if the old componentSpec q table selectors don't match
1719             // the new ones, update the qtables.  The new selectors are already
1720             // in place in the new SOF segment above.
1721             for (int i = 0; i < oldCompSpecs.length; i++) {
1722                 if (oldCompSpecs[i].QtableSelector != newTableSelectors[i]) {
1723                     updateQtables = true;
1724                 }
1725             }
1726 
1727             if (progressive) {
1728                 // if the component ids are different, update all the existing scans
1729                 // ignore Huffman tables
1730                 boolean idsDiffer = false;
1731                 for (int i = 0; i < oldCompSpecs.length; i++) {
1732                     if (ids[i] != oldCompSpecs[i].componentId) {
1733                         idsDiffer = true;
1734                     }
1735                 }
1736                 if (idsDiffer) {
1737                     // update the ids in each SOS marker segment
1738                     for (Iterator iter = markerSequence.iterator(); iter.hasNext();) {
1739                         MarkerSegment seg = (MarkerSegment) iter.next();
1740                         if (seg instanceof SOSMarkerSegment) {
1741                             SOSMarkerSegment target = (SOSMarkerSegment) seg;
1742                             for (int i = 0; i < target.componentSpecs.length; i++) {
1743                                 int oldSelector =
1744                                     target.componentSpecs[i].componentSelector;
1745                                 // Find the position in the old componentSpecs array
1746                                 // of the old component with the old selector
1747                                 // and replace the component selector with the
1748                                 // new id at the same position, as these match
1749                                 // the new component specs array in the SOF created
1750                                 // above.
1751                                 for (int j = 0; j < oldCompSpecs.length; j++) {
1752                                     if (oldCompSpecs[j].componentId == oldSelector) {
1753                                         target.componentSpecs[i].componentSelector =
1754                                             ids[j];
1755                                     }
1756                                 }
1757                             }
1758                         }
1759                     }
1760                 }
1761             } else {
1762                 if (sos != null) {
1763                     // htables - if the old htable selectors don't match the new ones,
1764                     // update the tables.
1765                     for (int i = 0; i < sos.componentSpecs.length; i++) {
1766                         if ((sos.componentSpecs[i].dcHuffTable
1767                              != newTableSelectors[i])
1768                             || (sos.componentSpecs[i].acHuffTable
1769                                 != newTableSelectors[i])) {
1770                             updateHtables = true;
1771                         }
1772                     }
1773 
1774                     // Might be the same as the old one, but this is easier.
1775                     markerSequence.set(markerSequence.indexOf(sos),
1776                                new SOSMarkerSegment(willSubsample,
1777                                                     ids,
1778                                                     numChannels));
1779                 }
1780             }
1781         } else {
1782             // should be stream metadata if there isn't an SOF, but check it anyway
1783             if (isStream) {
1784                 // update tables - routines below check if it's really necessary
1785                 updateQtables = true;
1786                 updateHtables = true;
1787             }
1788         }
1789 
1790         if (updateQtables) {
1791             List tableSegments = new ArrayList();
1792             for (Iterator iter = markerSequence.iterator(); iter.hasNext();) {
1793                 MarkerSegment seg = (MarkerSegment) iter.next();
1794                 if (seg instanceof DQTMarkerSegment) {
1795                     tableSegments.add(seg);
1796                 }
1797             }
1798             // If there are no tables, don't add them, as the metadata encodes an
1799             // abbreviated stream.
1800             // If we are not subsampling, we just need one, so don't do anything
1801             if (!tableSegments.isEmpty() && willSubsample) {
1802                 // Is it really necessary?  There should be at least 2 tables.
1803                 // If there is only one, assume it's a scaled "standard"
1804                 // luminance table, extract the scaling factor, and generate a
1805                 // scaled "standard" chrominance table.
1806 
1807                 // Find the table with selector 1.
1808                 boolean found = false;
1809                 for (Iterator iter = tableSegments.iterator(); iter.hasNext();) {
1810                     DQTMarkerSegment testdqt = (DQTMarkerSegment) iter.next();
1811                     for (Iterator tabiter = testdqt.tables.iterator();
1812                          tabiter.hasNext();) {
1813                         DQTMarkerSegment.Qtable tab =
1814                             (DQTMarkerSegment.Qtable) tabiter.next();
1815                         if (tab.tableID == 1) {
1816                             found = true;
1817                         }
1818                     }
1819                 }
1820                 if (!found) {
1821                     //    find the table with selector 0.  There should be one.
1822                     DQTMarkerSegment.Qtable table0 = null;
1823                     for (Iterator iter = tableSegments.iterator(); iter.hasNext();) {
1824                         DQTMarkerSegment testdqt = (DQTMarkerSegment) iter.next();
1825                         for (Iterator tabiter = testdqt.tables.iterator();
1826                              tabiter.hasNext();) {
1827                             DQTMarkerSegment.Qtable tab =
1828                                 (DQTMarkerSegment.Qtable) tabiter.next();
1829                             if (tab.tableID == 0) {
1830                                 table0 = tab;
1831                             }
1832                         }
1833                     }
1834 
1835                     // Assuming that the table with id 0 is a luminance table,
1836                     // compute a new chrominance table of the same quality and
1837                     // add it to the last DQT segment
1838                     DQTMarkerSegment dqt =
1839                         (DQTMarkerSegment) tableSegments.get(tableSegments.size()-1);
1840                     dqt.tables.add(dqt.getChromaForLuma(table0));
1841                 }
1842             }
1843         }
1844 
1845         if (updateHtables) {
1846             List tableSegments = new ArrayList();
1847             for (Iterator iter = markerSequence.iterator(); iter.hasNext();) {
1848                 MarkerSegment seg = (MarkerSegment) iter.next();
1849                 if (seg instanceof DHTMarkerSegment) {
1850                     tableSegments.add(seg);
1851                 }
1852             }
1853             // If there are no tables, don't add them, as the metadata encodes an
1854             // abbreviated stream.
1855             // If we are not subsampling, we just need one, so don't do anything
1856             if (!tableSegments.isEmpty() && willSubsample) {
1857                 // Is it really necessary?  There should be at least 2 dc and 2 ac
1858                 // tables.  If there is only one, add a
1859                 // "standard " chrominance table.
1860 
1861                 // find a table with selector 1. AC/DC is irrelevant
1862                 boolean found = false;
1863                 for (Iterator iter = tableSegments.iterator(); iter.hasNext();) {
1864                     DHTMarkerSegment testdht = (DHTMarkerSegment) iter.next();
1865                     for (Iterator tabiter = testdht.tables.iterator();
1866                          tabiter.hasNext();) {
1867                         DHTMarkerSegment.Htable tab =
1868                             (DHTMarkerSegment.Htable) tabiter.next();
1869                         if (tab.tableID == 1) {
1870                             found = true;
1871                         }
1872                     }
1873                 }
1874                 if (!found) {
1875                     // Create new standard dc and ac chrominance tables and add them
1876                     // to the last DHT segment
1877                     DHTMarkerSegment lastDHT =
1878                         (DHTMarkerSegment) tableSegments.get(tableSegments.size()-1);
1879                     lastDHT.addHtable(JPEGHuffmanTable.StdDCLuminance, true, 1);
1880                     lastDHT.addHtable(JPEGHuffmanTable.StdACLuminance, true, 1);
1881                 }
1882             }
1883         }
1884     }
1885 
1886     private boolean wantAlpha(Node transparency) {
1887         boolean returnValue = false;
1888         Node alpha = transparency.getFirstChild();  // Alpha must be first if present
1889         if (alpha.getNodeName().equals("Alpha")) {
1890             if (alpha.hasAttributes()) {
1891                 String value =
1892                     alpha.getAttributes().getNamedItem("value").getNodeValue();
1893                 if (!value.equals("none")) {
1894                     returnValue = true;
1895                 }
1896             }
1897         }
1898         transparencyDone = true;
1899         return returnValue;
1900     }
1901 
1902     private void mergeStandardCompressionNode(Node node)
1903         throws IIOInvalidTreeException {
1904         // NumProgressiveScans is ignored.  Progression must be enabled on the
1905         // ImageWriteParam.
1906         // No-op
1907     }
1908 
1909     private void mergeStandardDataNode(Node node)
1910         throws IIOInvalidTreeException {
1911         // No-op
1912     }
1913 
1914     private void mergeStandardDimensionNode(Node node)
1915         throws IIOInvalidTreeException {
1916         // Pixel Aspect Ratio or pixel size can be incorporated if there is,
1917         // or can be, a JFIF segment
1918         JFIFMarkerSegment jfif =
1919             (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class, true);
1920         if (jfif == null) {
1921             // Can there be one?
1922             // Criteria:
1923             // SOF must be present with 1 or 3 channels, (stream metadata fails this)
1924             //     Component ids must be JFIF compatible.
1925             boolean canHaveJFIF = false;
1926             SOFMarkerSegment sof =
1927                 (SOFMarkerSegment) findMarkerSegment(SOFMarkerSegment.class, true);
1928             if (sof != null) {
1929                 int numChannels = sof.componentSpecs.length;
1930                 if ((numChannels == 1) || (numChannels == 3)) {
1931                     canHaveJFIF = true; // remaining tests are negative
1932                     for (int i = 0; i < sof.componentSpecs.length; i++) {
1933                         if (sof.componentSpecs[i].componentId != i+1)
1934                             canHaveJFIF = false;
1935                     }
1936                     // if Adobe present, transform = ADOBE_UNKNOWN for 1-channel,
1937                     //     ADOBE_YCC for 3-channel.
1938                     AdobeMarkerSegment adobe =
1939                         (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class,
1940                                                                true);
1941                     if (adobe != null) {
1942                         if (adobe.transform != ((numChannels == 1)
1943                                                 ? JPEG.ADOBE_UNKNOWN
1944                                                 : JPEG.ADOBE_YCC)) {
1945                             canHaveJFIF = false;
1946                         }
1947                     }
1948                 }
1949             }
1950             // If so, create one and insert it into the sequence.  Note that
1951             // default is just pixel ratio at 1:1
1952             if (canHaveJFIF) {
1953                 jfif = new JFIFMarkerSegment();
1954                 markerSequence.add(0, jfif);
1955             }
1956         }
1957         if (jfif != null) {
1958             NodeList children = node.getChildNodes();
1959             for (int i = 0; i < children.getLength(); i++) {
1960                 Node child = children.item(i);
1961                 NamedNodeMap attrs = child.getAttributes();
1962                 String name = child.getNodeName();
1963                 if (name.equals("PixelAspectRatio")) {
1964                     String valueString = attrs.getNamedItem("value").getNodeValue();
1965                     float value = Float.parseFloat(valueString);
1966                     Point p = findIntegerRatio(value);
1967                     jfif.resUnits = JPEG.DENSITY_UNIT_ASPECT_RATIO;
1968                     jfif.Xdensity = p.x;
1969                     jfif.Xdensity = p.y;
1970                 } else if (name.equals("HorizontalPixelSize")) {
1971                     String valueString = attrs.getNamedItem("value").getNodeValue();
1972                     float value = Float.parseFloat(valueString);
1973                     // Convert from mm/dot to dots/cm
1974                     int dpcm = (int) Math.round(1.0/(value*10.0));
1975                     jfif.resUnits = JPEG.DENSITY_UNIT_DOTS_CM;
1976                     jfif.Xdensity = dpcm;
1977                 } else if (name.equals("VerticalPixelSize")) {
1978                     String valueString = attrs.getNamedItem("value").getNodeValue();
1979                     float value = Float.parseFloat(valueString);
1980                     // Convert from mm/dot to dots/cm
1981                     int dpcm = (int) Math.round(1.0/(value*10.0));
1982                     jfif.resUnits = JPEG.DENSITY_UNIT_DOTS_CM;
1983                     jfif.Ydensity = dpcm;
1984                 }
1985 
1986             }
1987         }
1988     }
1989 
1990     /*
1991      * Return a pair of integers whose ratio (x/y) approximates the given
1992      * float value.
1993      */
1994     private static Point findIntegerRatio(float value) {
1995         float epsilon = 0.005F;
1996 
1997         // Normalize
1998         value = Math.abs(value);
1999 
2000         // Deal with min case
2001         if (value <= epsilon) {
2002             return new Point(1, 255);
2003         }
2004 
2005         // Deal with max case
2006         if (value >= 255) {
2007             return new Point(255, 1);
2008         }
2009 
2010         // Remember if we invert
2011         boolean inverted = false;
2012         if (value < 1.0) {
2013             value = 1.0F/value;
2014             inverted = true;
2015         }
2016 
2017         // First approximation
2018         int y = 1;
2019         int x = (int) Math.round(value);
2020 
2021         float ratio = (float) x;
2022         float delta = Math.abs(value - ratio);
2023         while (delta > epsilon) { // not close enough
2024             // Increment y and compute a new x
2025             y++;
2026             x = (int) Math.round(y*value);
2027             ratio = (float)x/(float)y;
2028             delta = Math.abs(value - ratio);
2029         }
2030         return inverted ? new Point(y, x) : new Point(x, y);
2031     }
2032 
2033     private void mergeStandardDocumentNode(Node node)
2034         throws IIOInvalidTreeException {
2035         // No-op
2036     }
2037 
2038     private void mergeStandardTextNode(Node node)
2039         throws IIOInvalidTreeException {
2040         // Convert to comments.  For the moment ignore the encoding issue.
2041         // Ignore keywords, language, and encoding (for the moment).
2042         // If compression tag is present, use only entries with "none".
2043         NodeList children = node.getChildNodes();
2044         for (int i = 0; i < children.getLength(); i++) {
2045             Node child = children.item(i);
2046             NamedNodeMap attrs = child.getAttributes();
2047             Node comp = attrs.getNamedItem("compression");
2048             boolean copyIt = true;
2049             if (comp != null) {
2050                 String compString = comp.getNodeValue();
2051                 if (!compString.equals("none")) {
2052                     copyIt = false;
2053                 }
2054             }
2055             if (copyIt) {
2056                 String value = attrs.getNamedItem("value").getNodeValue();
2057                 COMMarkerSegment com = new COMMarkerSegment(value);
2058                 insertCOMMarkerSegment(com);
2059             }
2060         }
2061     }
2062 
2063     private void mergeStandardTransparencyNode(Node node)
2064         throws IIOInvalidTreeException {
2065         // This might indicate that an alpha channel is being added or removed.
2066         // The nodes must appear in order, and a Chroma node will process any
2067         // transparency, so process it here only if there was no Chroma node
2068         // Do nothing for stream metadata
2069         if (!transparencyDone && !isStream) {
2070             boolean wantAlpha = wantAlpha(node);
2071             // do we have alpha already?  If the number of channels is 2 or 4,
2072             // we do, as we don't support CMYK, nor can we add alpha to it
2073             // The number of channels can be determined from the SOF
2074             JFIFMarkerSegment jfif = (JFIFMarkerSegment) findMarkerSegment
2075                 (JFIFMarkerSegment.class, true);
2076             AdobeMarkerSegment adobe = (AdobeMarkerSegment) findMarkerSegment
2077                 (AdobeMarkerSegment.class, true);
2078             SOFMarkerSegment sof = (SOFMarkerSegment) findMarkerSegment
2079                 (SOFMarkerSegment.class, true);
2080             SOSMarkerSegment sos = (SOSMarkerSegment) findMarkerSegment
2081                 (SOSMarkerSegment.class, true);
2082 
2083             // We can do nothing for progressive, as we don't know how to
2084             // modify the scans.
2085             if ((sof != null) && (sof.tag == JPEG.SOF2)) { // Progressive
2086                 return;
2087             }
2088 
2089             // Do we already have alpha?  We can tell by the number of channels
2090             // We must have an sof, or we can't do anything further
2091             if (sof != null) {
2092                 int numChannels = sof.componentSpecs.length;
2093                 boolean hadAlpha = (numChannels == 2) || (numChannels == 4);
2094                 // proceed only if the old state and the new state differ
2095                 if (hadAlpha != wantAlpha) {
2096                     if (wantAlpha) {  // Adding alpha
2097                         numChannels++;
2098                         if (jfif != null) {
2099                             markerSequence.remove(jfif);
2100                         }
2101 
2102                         // If an adobe marker is present, transform must be UNKNOWN
2103                         if (adobe != null) {
2104                             adobe.transform = JPEG.ADOBE_UNKNOWN;
2105                         }
2106 
2107                         // Add a component spec with appropriate parameters to SOF
2108                         SOFMarkerSegment.ComponentSpec [] newSpecs =
2109                             new SOFMarkerSegment.ComponentSpec[numChannels];
2110                         for (int i = 0; i < sof.componentSpecs.length; i++) {
2111                             newSpecs[i] = sof.componentSpecs[i];
2112                         }
2113                         byte oldFirstID = (byte) sof.componentSpecs[0].componentId;
2114                         byte newID = (byte) ((oldFirstID > 1) ? 'A' : 4);
2115                         newSpecs[numChannels-1] =
2116                             sof.getComponentSpec(newID,
2117                                 sof.componentSpecs[0].HsamplingFactor,
2118                                 sof.componentSpecs[0].QtableSelector);
2119 
2120                         sof.componentSpecs = newSpecs;
2121 
2122                         // Add a component spec with appropriate parameters to SOS
2123                         SOSMarkerSegment.ScanComponentSpec [] newScanSpecs =
2124                             new SOSMarkerSegment.ScanComponentSpec [numChannels];
2125                         for (int i = 0; i < sos.componentSpecs.length; i++) {
2126                             newScanSpecs[i] = sos.componentSpecs[i];
2127                         }
2128                         newScanSpecs[numChannels-1] =
2129                             sos.getScanComponentSpec (newID, 0);
2130                         sos.componentSpecs = newScanSpecs;
2131                     } else {  // Removing alpha
2132                         numChannels--;
2133                         // Remove a component spec from SOF
2134                         SOFMarkerSegment.ComponentSpec [] newSpecs =
2135                             new SOFMarkerSegment.ComponentSpec[numChannels];
2136                         for (int i = 0; i < numChannels; i++) {
2137                             newSpecs[i] = sof.componentSpecs[i];
2138                         }
2139                         sof.componentSpecs = newSpecs;
2140 
2141                         // Remove a component spec from SOS
2142                         SOSMarkerSegment.ScanComponentSpec [] newScanSpecs =
2143                             new SOSMarkerSegment.ScanComponentSpec [numChannels];
2144                         for (int i = 0; i < numChannels; i++) {
2145                             newScanSpecs[i] = sos.componentSpecs[i];
2146                         }
2147                         sos.componentSpecs = newScanSpecs;
2148                     }
2149                 }
2150             }
2151         }
2152     }
2153 
2154 
2155     public void setFromTree(String formatName, Node root)
2156         throws IIOInvalidTreeException {
2157         if (formatName == null) {
2158             throw new IllegalArgumentException("null formatName!");
2159         }
2160         if (root == null) {
2161             throw new IllegalArgumentException("null root!");
2162         }
2163         if (isStream &&
2164             (formatName.equals(JPEG.nativeStreamMetadataFormatName))) {
2165             setFromNativeTree(root);
2166         } else if (!isStream &&
2167                    (formatName.equals(JPEG.nativeImageMetadataFormatName))) {
2168             setFromNativeTree(root);
2169         } else if (!isStream &&
2170                    (formatName.equals
2171                     (IIOMetadataFormatImpl.standardMetadataFormatName))) {
2172             // In this case a reset followed by a merge is correct
2173             super.setFromTree(formatName, root);
2174         } else {
2175             throw  new IllegalArgumentException("Unsupported format name: "
2176                                                 + formatName);
2177         }
2178     }
2179 
2180     private void setFromNativeTree(Node root) throws IIOInvalidTreeException {
2181         if (resetSequence == null) {
2182             resetSequence = markerSequence;
2183         }
2184         markerSequence = new ArrayList();
2185 
2186         // Build a whole new marker sequence from the tree
2187 
2188         String name = root.getNodeName();
2189         if (name != ((isStream) ? JPEG.nativeStreamMetadataFormatName
2190                                 : JPEG.nativeImageMetadataFormatName)) {
2191             throw new IIOInvalidTreeException("Invalid root node name: " + name,
2192                                               root);
2193         }
2194         if (!isStream) {
2195             if (root.getChildNodes().getLength() != 2) { // JPEGvariety and markerSequence
2196                 throw new IIOInvalidTreeException(
2197                     "JPEGvariety and markerSequence nodes must be present", root);
2198             }
2199 
2200             Node JPEGvariety = root.getFirstChild();
2201 
2202             if (JPEGvariety.getChildNodes().getLength() != 0) {
2203                 markerSequence.add(new JFIFMarkerSegment(JPEGvariety.getFirstChild()));
2204             }
2205         }
2206 
2207         Node markerSequenceNode = isStream ? root : root.getLastChild();
2208         setFromMarkerSequenceNode(markerSequenceNode);
2209 
2210     }
2211 
2212     void setFromMarkerSequenceNode(Node markerSequenceNode)
2213         throws IIOInvalidTreeException{
2214 
2215         NodeList children = markerSequenceNode.getChildNodes();
2216         // for all the children, add a marker segment
2217         for (int i = 0; i < children.getLength(); i++) {
2218             Node node = children.item(i);
2219             String childName = node.getNodeName();
2220             if (childName.equals("dqt")) {
2221                 markerSequence.add(new DQTMarkerSegment(node));
2222             } else if (childName.equals("dht")) {
2223                 markerSequence.add(new DHTMarkerSegment(node));
2224             } else if (childName.equals("dri")) {
2225                 markerSequence.add(new DRIMarkerSegment(node));
2226             } else if (childName.equals("com")) {
2227                 markerSequence.add(new COMMarkerSegment(node));
2228             } else if (childName.equals("app14Adobe")) {
2229                 markerSequence.add(new AdobeMarkerSegment(node));
2230             } else if (childName.equals("unknown")) {
2231                 markerSequence.add(new MarkerSegment(node));
2232             } else if (childName.equals("sof")) {
2233                 markerSequence.add(new SOFMarkerSegment(node));
2234             } else if (childName.equals("sos")) {
2235                 markerSequence.add(new SOSMarkerSegment(node));
2236             } else {
2237                 throw new IIOInvalidTreeException("Invalid "
2238                     + (isStream ? "stream " : "image ") + "child: "
2239                     + childName, node);
2240             }
2241         }
2242     }
2243 
2244     /**
2245      * Check that this metadata object is in a consistent state and
2246      * return <code>true</code> if it is or <code>false</code>
2247      * otherwise.  All the constructors and modifiers should call
2248      * this method at the end to guarantee that the data is always
2249      * consistent, as the writer relies on this.
2250      */
2251     private boolean isConsistent() {
2252         SOFMarkerSegment sof =
2253             (SOFMarkerSegment) findMarkerSegment(SOFMarkerSegment.class,
2254                                                  true);
2255         JFIFMarkerSegment jfif =
2256             (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class,
2257                                                   true);
2258         AdobeMarkerSegment adobe =
2259             (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class,
2260                                                    true);
2261         boolean retval = true;
2262         if (!isStream) {
2263             if (sof != null) {
2264                 // SOF numBands = total scan bands
2265                 int numSOFBands = sof.componentSpecs.length;
2266                 int numScanBands = countScanBands();
2267                 if (numScanBands != 0) {  // No SOS is OK
2268                     if (numScanBands != numSOFBands) {
2269                         retval = false;
2270                     }
2271                 }
2272                 // If JFIF is present, component ids are 1-3, bands are 1 or 3
2273                 if (jfif != null) {
2274                     if ((numSOFBands != 1) && (numSOFBands != 3)) {
2275                         retval = false;
2276                     }
2277                     for (int i = 0; i < numSOFBands; i++) {
2278                         if (sof.componentSpecs[i].componentId != i+1) {
2279                             retval = false;
2280                         }
2281                     }
2282 
2283                     // If both JFIF and Adobe are present,
2284                     // Adobe transform == unknown for gray,
2285                     // YCC for 3-chan.
2286                     if ((adobe != null)
2287                         && (((numSOFBands == 1)
2288                              && (adobe.transform != JPEG.ADOBE_UNKNOWN))
2289                             || ((numSOFBands == 3)
2290                                 && (adobe.transform != JPEG.ADOBE_YCC)))) {
2291                         retval = false;
2292                     }
2293                 }
2294             } else {
2295                 // stream can't have jfif, adobe, sof, or sos
2296                 SOSMarkerSegment sos =
2297                     (SOSMarkerSegment) findMarkerSegment(SOSMarkerSegment.class,
2298                                                          true);
2299                 if ((jfif != null) || (adobe != null)
2300                     || (sof != null) || (sos != null)) {
2301                     retval = false;
2302                 }
2303             }
2304         }
2305         return retval;
2306     }
2307 
2308     /**
2309      * Returns the total number of bands referenced in all SOS marker
2310      * segments, including 0 if there are no SOS marker segments.
2311      */
2312     private int countScanBands() {
2313         List ids = new ArrayList();
2314         Iterator iter = markerSequence.iterator();
2315         while(iter.hasNext()) {
2316             MarkerSegment seg = (MarkerSegment)iter.next();
2317             if (seg instanceof SOSMarkerSegment) {
2318                 SOSMarkerSegment sos = (SOSMarkerSegment) seg;
2319                 SOSMarkerSegment.ScanComponentSpec [] specs = sos.componentSpecs;
2320                 for (int i = 0; i < specs.length; i++) {
2321                     Integer id = new Integer(specs[i].componentSelector);
2322                     if (!ids.contains(id)) {
2323                         ids.add(id);
2324                     }
2325                 }
2326             }
2327         }
2328 
2329         return ids.size();
2330     }
2331 
2332     ///// Writer support
2333 
2334     void writeToStream(ImageOutputStream ios,
2335                        boolean ignoreJFIF,
2336                        boolean forceJFIF,
2337                        List thumbnails,
2338                        ICC_Profile iccProfile,
2339                        boolean ignoreAdobe,
2340                        int newAdobeTransform,
2341                        JPEGImageWriter writer)
2342         throws IOException {
2343         if (forceJFIF) {
2344             // Write a default JFIF segment, including thumbnails
2345             // This won't be duplicated below because forceJFIF will be
2346             // set only if there is no JFIF present already.
2347             JFIFMarkerSegment.writeDefaultJFIF(ios,
2348                                                thumbnails,
2349                                                iccProfile,
2350                                                writer);
2351             if ((ignoreAdobe == false)
2352                 && (newAdobeTransform != JPEG.ADOBE_IMPOSSIBLE)) {
2353                 if ((newAdobeTransform != JPEG.ADOBE_UNKNOWN)
2354                     && (newAdobeTransform != JPEG.ADOBE_YCC)) {
2355                     // Not compatible, so ignore Adobe.
2356                     ignoreAdobe = true;
2357                     writer.warningOccurred
2358                         (JPEGImageWriter.WARNING_METADATA_ADJUSTED_FOR_THUMB);
2359                 }
2360             }
2361         }
2362         // Iterate over each MarkerSegment
2363         Iterator iter = markerSequence.iterator();
2364         while(iter.hasNext()) {
2365             MarkerSegment seg = (MarkerSegment)iter.next();
2366             if (seg instanceof JFIFMarkerSegment) {
2367                 if (ignoreJFIF == false) {
2368                     JFIFMarkerSegment jfif = (JFIFMarkerSegment) seg;
2369                     jfif.writeWithThumbs(ios, thumbnails, writer);
2370                     if (iccProfile != null) {
2371                         JFIFMarkerSegment.writeICC(iccProfile, ios);
2372                     }
2373                 } // Otherwise ignore it, as requested
2374             } else if (seg instanceof AdobeMarkerSegment) {
2375                 if (ignoreAdobe == false) {
2376                     if (newAdobeTransform != JPEG.ADOBE_IMPOSSIBLE) {
2377                         AdobeMarkerSegment newAdobe =
2378                             (AdobeMarkerSegment) seg.clone();
2379                         newAdobe.transform = newAdobeTransform;
2380                         newAdobe.write(ios);
2381                     } else if (forceJFIF) {
2382                         // If adobe isn't JFIF compatible, ignore it
2383                         AdobeMarkerSegment adobe = (AdobeMarkerSegment) seg;
2384                         if ((adobe.transform == JPEG.ADOBE_UNKNOWN)
2385                             || (adobe.transform == JPEG.ADOBE_YCC)) {
2386                             adobe.write(ios);
2387                         } else {
2388                             writer.warningOccurred
2389                          (JPEGImageWriter.WARNING_METADATA_ADJUSTED_FOR_THUMB);
2390                         }
2391                     } else {
2392                         seg.write(ios);
2393                     }
2394                 } // Otherwise ignore it, as requested
2395             } else {
2396                 seg.write(ios);
2397             }
2398         }
2399     }
2400 
2401     //// End of writer support
2402 
2403     public void reset() {
2404         if (resetSequence != null) {  // Otherwise no need to reset
2405             markerSequence = resetSequence;
2406             resetSequence = null;
2407         }
2408     }
2409 
2410     public void print() {
2411         for (int i = 0; i < markerSequence.size(); i++) {
2412             MarkerSegment seg = (MarkerSegment) markerSequence.get(i);
2413             seg.print();
2414         }
2415     }
2416 
2417 }