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