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