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