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