src/share/classes/com/sun/imageio/plugins/jpeg/JPEGMetadata.java

Print this page
rev 9230 : imported patch 8033716


  35 import javax.imageio.metadata.IIOMetadataFormat;
  36 import javax.imageio.metadata.IIOMetadataFormatImpl;
  37 import javax.imageio.metadata.IIOInvalidTreeException;
  38 import javax.imageio.plugins.jpeg.JPEGQTable;
  39 import javax.imageio.plugins.jpeg.JPEGHuffmanTable;
  40 import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
  41 
  42 import org.w3c.dom.Node;
  43 import org.w3c.dom.NodeList;
  44 import org.w3c.dom.NamedNodeMap;
  45 
  46 import java.util.List;
  47 import java.util.ArrayList;
  48 import java.util.Arrays;
  49 import java.util.Iterator;
  50 import java.util.ListIterator;
  51 import java.io.IOException;
  52 import java.awt.color.ICC_Profile;
  53 import java.awt.color.ICC_ColorSpace;
  54 import java.awt.color.ColorSpace;

  55 import java.awt.image.ColorModel;
  56 import java.awt.Point;
  57 
  58 /**
  59  * Metadata for the JPEG plug-in.
  60  */
  61 public class JPEGMetadata extends IIOMetadata implements Cloneable {
  62 
  63     //////// Private variables
  64 
  65     private static final boolean debug = false;
  66 
  67     /**
  68      * A copy of <code>markerSequence</code>, created the first time the
  69      * <code>markerSequence</code> is modified.  This is used by reset
  70      * to restore the original state.
  71      */
  72     private List resetSequence = null;
  73 
  74     /**
  75      * Set to <code>true</code> when reading a thumbnail stored as
  76      * JPEG.  This is used to enforce the prohibition of JFIF thumbnails
  77      * containing any JFIF marker segments, and to ensure generation of
  78      * a correct native subtree during <code>getAsTree</code>.
  79      */
  80     private boolean inThumb = false;
  81 
  82     /**
  83      * Set by the chroma node construction method to signal the
  84      * presence or absence of an alpha channel to the transparency
  85      * node construction method.  Used only when constructing a
  86      * standard metadata tree.
  87      */
  88     private boolean hasAlpha;
  89 
  90     //////// end of private variables
  91 
  92     /////// Package-access variables
  93 
  94     /**
  95      * All data is a list of <code>MarkerSegment</code> objects.
  96      * When accessing the list, use the tag to identify the particular
  97      * subclass.  Any JFIF marker segment must be the first element
  98      * of the list if it is present, and any JFXX or APP2ICC marker
  99      * segments are subordinate to the JFIF marker segment.  This
 100      * list is package visible so that the writer can access it.
 101      * @see #MarkerSegment
 102      */
 103     List markerSequence = new ArrayList();
 104 
 105     /**
 106      * Indicates whether this object represents stream or image
 107      * metadata.  Package-visible so the writer can see it.
 108      */
 109     final boolean isStream;
 110 
 111     /////// End of package-access variables
 112 
 113     /////// Constructors
 114 
 115     /**
 116      * Constructor containing code shared by other constructors.
 117      */
 118     JPEGMetadata(boolean isStream, boolean inThumb) {
 119         super(true,  // Supports standard format
 120               JPEG.nativeImageMetadataFormatName,  // and a native format
 121               JPEG.nativeImageMetadataFormatClassName,
 122               null, null);  // No other formats
 123         this.inThumb = inThumb;


 628                                                     componentIDs,
 629                                                     numComponents));
 630         }
 631 
 632         // Defensive programming
 633         if (!isConsistent()) {
 634             throw new InternalError("Default image metadata is inconsistent");
 635         }
 636     }
 637 
 638     ////// End of constructors
 639 
 640     // Utilities for dealing with the marker sequence.
 641     // The first ones have package access for access from the writer.
 642 
 643     /**
 644      * Returns the first MarkerSegment object in the list
 645      * with the given tag, or null if none is found.
 646      */
 647     MarkerSegment findMarkerSegment(int tag) {
 648         Iterator iter = markerSequence.iterator();
 649         while (iter.hasNext()) {
 650             MarkerSegment seg = (MarkerSegment)iter.next();
 651             if (seg.tag == tag) {
 652                 return seg;
 653             }
 654         }
 655         return null;
 656     }
 657 
 658     /**
 659      * Returns the first or last MarkerSegment object in the list
 660      * of the given class, or null if none is found.
 661      */
 662     MarkerSegment findMarkerSegment(Class cls, boolean first) {
 663         if (first) {
 664             Iterator iter = markerSequence.iterator();
 665             while (iter.hasNext()) {
 666                 MarkerSegment seg = (MarkerSegment)iter.next();
 667                 if (cls.isInstance(seg)) {
 668                     return seg;
 669                 }
 670             }
 671         } else {
 672             ListIterator iter = markerSequence.listIterator(markerSequence.size());
 673             while (iter.hasPrevious()) {
 674                 MarkerSegment seg = (MarkerSegment)iter.previous();
 675                 if (cls.isInstance(seg)) {
 676                     return seg;
 677                 }
 678             }
 679         }
 680         return null;
 681     }
 682 
 683     /**
 684      * Returns the index of the first or last MarkerSegment in the list
 685      * of the given class, or -1 if none is found.
 686      */
 687     private int findMarkerSegmentPosition(Class cls, boolean first) {
 688         if (first) {
 689             ListIterator iter = markerSequence.listIterator();
 690             for (int i = 0; iter.hasNext(); i++) {
 691                 MarkerSegment seg = (MarkerSegment)iter.next();
 692                 if (cls.isInstance(seg)) {
 693                     return i;
 694                 }
 695             }
 696         } else {
 697             ListIterator iter = markerSequence.listIterator(markerSequence.size());
 698             for (int i = markerSequence.size()-1; iter.hasPrevious(); i--) {
 699                 MarkerSegment seg = (MarkerSegment)iter.previous();
 700                 if (cls.isInstance(seg)) {
 701                     return i;
 702                 }
 703             }
 704         }
 705         return -1;
 706     }
 707 
 708     private int findLastUnknownMarkerSegmentPosition() {
 709         ListIterator iter = markerSequence.listIterator(markerSequence.size());
 710         for (int i = markerSequence.size()-1; iter.hasPrevious(); i--) {
 711             MarkerSegment seg = (MarkerSegment)iter.previous();
 712             if (seg.unknown == true) {
 713                 return i;
 714             }
 715         }
 716         return -1;
 717     }
 718 
 719     // Implement Cloneable, but restrict access
 720 
 721     protected Object clone() {
 722         JPEGMetadata newGuy = null;
 723         try {
 724             newGuy = (JPEGMetadata) super.clone();
 725         } catch (CloneNotSupportedException e) {} // won't happen
 726         if (markerSequence != null) {
 727             newGuy.markerSequence = cloneSequence();
 728         }
 729         newGuy.resetSequence = null;
 730         return newGuy;
 731     }
 732 
 733     /**
 734      * Returns a deep copy of the current marker sequence.
 735      */
 736     private List cloneSequence() {
 737         if (markerSequence == null) {
 738             return null;
 739         }
 740         List retval = new ArrayList(markerSequence.size());
 741         Iterator iter = markerSequence.iterator();
 742         while(iter.hasNext()) {
 743             MarkerSegment seg = (MarkerSegment)iter.next();
 744             retval.add(seg.clone());
 745         }
 746 
 747         return retval;
 748     }
 749 
 750 
 751     // Tree methods
 752 
 753     public Node getAsTree(String formatName) {
 754         if (formatName == null) {
 755             throw new IllegalArgumentException("null formatName!");
 756         }
 757         if (isStream) {
 758             if (formatName.equals(JPEG.nativeStreamMetadataFormatName)) {
 759                 return getNativeTree();
 760             }
 761         } else {
 762             if (formatName.equals(JPEG.nativeImageMetadataFormatName)) {
 763                 return getNativeTree();
 764             }
 765             if (formatName.equals
 766                     (IIOMetadataFormatImpl.standardMetadataFormatName)) {
 767                 return getStandardTree();
 768             }
 769         }
 770         throw  new IllegalArgumentException("Unsupported format name: "
 771                                                 + formatName);
 772     }
 773 
 774     IIOMetadataNode getNativeTree() {
 775         IIOMetadataNode root;
 776         IIOMetadataNode top;
 777         Iterator iter = markerSequence.iterator();
 778         if (isStream) {
 779             root = new IIOMetadataNode(JPEG.nativeStreamMetadataFormatName);
 780             top = root;
 781         } else {
 782             IIOMetadataNode sequence = new IIOMetadataNode("markerSequence");
 783             if (!inThumb) {
 784                 root = new IIOMetadataNode(JPEG.nativeImageMetadataFormatName);
 785                 IIOMetadataNode header = new IIOMetadataNode("JPEGvariety");
 786                 root.appendChild(header);
 787                 JFIFMarkerSegment jfif = (JFIFMarkerSegment)
 788                     findMarkerSegment(JFIFMarkerSegment.class, true);
 789                 if (jfif != null) {
 790                     iter.next();  // JFIF must be first, so this skips it
 791                     header.appendChild(jfif.getNativeNode());
 792                 }
 793                 root.appendChild(sequence);
 794             } else {
 795                 root = sequence;
 796             }
 797             top = sequence;
 798         }
 799         while(iter.hasNext()) {
 800             MarkerSegment seg = (MarkerSegment) iter.next();
 801             top.appendChild(seg.getNativeNode());
 802         }
 803         return root;
 804     }
 805 
 806     // Standard tree node methods
 807 
 808     protected IIOMetadataNode getStandardChromaNode() {
 809         hasAlpha = false;  // Unless we find otherwise
 810 
 811         // Colorspace type - follow the rules in the spec
 812         // First get the SOF marker segment, if there is one
 813         SOFMarkerSegment sof = (SOFMarkerSegment)
 814             findMarkerSegment(SOFMarkerSegment.class, true);
 815         if (sof == null) {
 816             // No image, so no chroma
 817             return null;
 818         }
 819 
 820         IIOMetadataNode chroma = new IIOMetadataNode("Chroma");


 944 
 945         return chroma;
 946     }
 947 
 948     protected IIOMetadataNode getStandardCompressionNode() {
 949 
 950         IIOMetadataNode compression = new IIOMetadataNode("Compression");
 951 
 952         // CompressionTypeName
 953         IIOMetadataNode name = new IIOMetadataNode("CompressionTypeName");
 954         name.setAttribute("value", "JPEG");
 955         compression.appendChild(name);
 956 
 957         // Lossless - false
 958         IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
 959         lossless.setAttribute("value", "FALSE");
 960         compression.appendChild(lossless);
 961 
 962         // NumProgressiveScans - count sos segments
 963         int sosCount = 0;
 964         Iterator iter = markerSequence.iterator();
 965         while (iter.hasNext()) {
 966             MarkerSegment ms = (MarkerSegment) iter.next();
 967             if (ms.tag == JPEG.SOS) {
 968                 sosCount++;
 969             }
 970         }
 971         if (sosCount != 0) {
 972             IIOMetadataNode prog = new IIOMetadataNode("NumProgressiveScans");
 973             prog.setAttribute("value", Integer.toString(sosCount));
 974             compression.appendChild(prog);
 975         }
 976 
 977         return compression;
 978     }
 979 
 980     protected IIOMetadataNode getStandardDimensionNode() {
 981         // If we have a JFIF marker segment, we know a little
 982         // otherwise all we know is the orientation, which is always normal
 983         IIOMetadataNode dim = new IIOMetadataNode("Dimension");
 984         IIOMetadataNode orient = new IIOMetadataNode("ImageOrientation");
 985         orient.setAttribute("value", "normal");
 986         dim.appendChild(orient);


1011                     new IIOMetadataNode("HorizontalPixelSize");
1012                 horiz.setAttribute("value",
1013                                    Float.toString(scale/jfif.Xdensity));
1014                 dim.appendChild(horiz);
1015 
1016                 IIOMetadataNode vert =
1017                     new IIOMetadataNode("VerticalPixelSize");
1018                 vert.setAttribute("value",
1019                                   Float.toString(scale/jfif.Ydensity));
1020                 dim.appendChild(vert);
1021             }
1022         }
1023         return dim;
1024     }
1025 
1026     protected IIOMetadataNode getStandardTextNode() {
1027         IIOMetadataNode text = null;
1028         // Add a text entry for each COM Marker Segment
1029         if (findMarkerSegment(JPEG.COM) != null) {
1030             text = new IIOMetadataNode("Text");
1031             Iterator iter = markerSequence.iterator();
1032             while (iter.hasNext()) {
1033                 MarkerSegment seg = (MarkerSegment) iter.next();
1034                 if (seg.tag == JPEG.COM) {
1035                     COMMarkerSegment com = (COMMarkerSegment) seg;
1036                     IIOMetadataNode entry = new IIOMetadataNode("TextEntry");
1037                     entry.setAttribute("keyword", "comment");
1038                     entry.setAttribute("value", com.getComment());
1039                 text.appendChild(entry);
1040                 }
1041             }
1042         }
1043         return text;
1044     }
1045 
1046     protected IIOMetadataNode getStandardTransparencyNode() {
1047         IIOMetadataNode trans = null;
1048         if (hasAlpha == true) {
1049             trans = new IIOMetadataNode("Transparency");
1050             IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
1051             alpha.setAttribute("value", "nonpremultiplied"); // Always assume
1052             trans.appendChild(alpha);
1053         }
1054         return trans;
1055     }
1056 
1057     // Editing
1058 
1059     public boolean isReadOnly() {
1060         return false;
1061     }
1062 
1063     public void mergeTree(String formatName, Node root)
1064         throws IIOInvalidTreeException {
1065         if (formatName == null) {
1066             throw new IllegalArgumentException("null formatName!");
1067         }
1068         if (root == null) {
1069             throw new IllegalArgumentException("null root!");
1070         }
1071         List copy = null;
1072         if (resetSequence == null) {
1073             resetSequence = cloneSequence();  // Deep copy
1074             copy = resetSequence;  // Avoid cloning twice
1075         } else {
1076             copy = cloneSequence();
1077         }
1078         if (isStream &&
1079             (formatName.equals(JPEG.nativeStreamMetadataFormatName))) {
1080                 mergeNativeTree(root);
1081         } else if (!isStream &&
1082                    (formatName.equals(JPEG.nativeImageMetadataFormatName))) {
1083             mergeNativeTree(root);
1084         } else if (!isStream &&
1085                    (formatName.equals
1086                     (IIOMetadataFormatImpl.standardMetadataFormatName))) {
1087             mergeStandardTree(root);
1088         } else {
1089             throw  new IllegalArgumentException("Unsupported format name: "
1090                                                 + formatName);
1091         }


1164 
1165     /**
1166      * Merge the given DQT node into the marker sequence.  If there already
1167      * exist DQT marker segments in the sequence, then each table in the
1168      * node replaces the first table, in any DQT segment, with the same
1169      * table id.  If none of the existing DQT segments contain a table with
1170      * the same id, then the table is added to the last existing DQT segment.
1171      * If there are no DQT segments, then a new one is created and added
1172      * as follows:
1173      * If there are DHT segments, the new DQT segment is inserted before the
1174      * first one.
1175      * If there are no DHT segments, the new DQT segment is inserted before
1176      * an SOF segment, if there is one.
1177      * If there is no SOF segment, the new DQT segment is inserted before
1178      * the first SOS segment, if there is one.
1179      * If there is no SOS segment, the new DQT segment is added to the end
1180      * of the sequence.
1181      */
1182     private void mergeDQTNode(Node node) throws IIOInvalidTreeException {
1183         // First collect any existing DQT nodes into a local list
1184         ArrayList oldDQTs = new ArrayList();
1185         Iterator iter = markerSequence.iterator();
1186         while (iter.hasNext()) {
1187             MarkerSegment seg = (MarkerSegment) iter.next();
1188             if (seg instanceof DQTMarkerSegment) {
1189                 oldDQTs.add(seg);
1190             }
1191         }
1192         if (!oldDQTs.isEmpty()) {
1193             NodeList children = node.getChildNodes();
1194             for (int i = 0; i < children.getLength(); i++) {
1195                 Node child = children.item(i);
1196                 int childID = MarkerSegment.getAttributeValue(child,
1197                                                               null,
1198                                                               "qtableId",
1199                                                               0, 3,
1200                                                               true);
1201                 DQTMarkerSegment dqt = null;
1202                 int tableIndex = -1;
1203                 for (int j = 0; j < oldDQTs.size(); j++) {
1204                     DQTMarkerSegment testDQT = (DQTMarkerSegment) oldDQTs.get(j);
1205                     for (int k = 0; k < testDQT.tables.size(); k++) {
1206                         DQTMarkerSegment.Qtable testTable =
1207                             (DQTMarkerSegment.Qtable) testDQT.tables.get(k);
1208                         if (childID == testTable.tableID) {
1209                             dqt = testDQT;
1210                             tableIndex = k;
1211                             break;
1212                         }
1213                     }
1214                     if (dqt != null) break;
1215                 }
1216                 if (dqt != null) {
1217                     dqt.tables.set(tableIndex, dqt.getQtableFromNode(child));
1218                 } else {
1219                     dqt = (DQTMarkerSegment) oldDQTs.get(oldDQTs.size()-1);
1220                     dqt.tables.add(dqt.getQtableFromNode(child));
1221                 }
1222             }
1223         } else {
1224             DQTMarkerSegment newGuy = new DQTMarkerSegment(node);
1225             int firstDHT = findMarkerSegmentPosition(DHTMarkerSegment.class, true);
1226             int firstSOF = findMarkerSegmentPosition(SOFMarkerSegment.class, true);
1227             int firstSOS = findMarkerSegmentPosition(SOSMarkerSegment.class, true);
1228             if (firstDHT != -1) {
1229                 markerSequence.add(firstDHT, newGuy);
1230             } else if (firstSOF != -1) {
1231                 markerSequence.add(firstSOF, newGuy);
1232             } else if (firstSOS != -1) {
1233                 markerSequence.add(firstSOS, newGuy);
1234             } else {
1235                 markerSequence.add(newGuy);
1236             }
1237         }
1238     }
1239 
1240     /**
1241      * Merge the given DHT node into the marker sequence.  If there already
1242      * exist DHT marker segments in the sequence, then each table in the
1243      * node replaces the first table, in any DHT segment, with the same
1244      * table class and table id.  If none of the existing DHT segments contain
1245      * a table with the same class and id, then the table is added to the last
1246      * existing DHT segment.
1247      * If there are no DHT segments, then a new one is created and added
1248      * as follows:
1249      * If there are DQT segments, the new DHT segment is inserted immediately
1250      * following the last DQT segment.
1251      * If there are no DQT segments, the new DHT segment is inserted before
1252      * an SOF segment, if there is one.
1253      * If there is no SOF segment, the new DHT segment is inserted before
1254      * the first SOS segment, if there is one.
1255      * If there is no SOS segment, the new DHT segment is added to the end
1256      * of the sequence.
1257      */
1258     private void mergeDHTNode(Node node) throws IIOInvalidTreeException {
1259         // First collect any existing DQT nodes into a local list
1260         ArrayList oldDHTs = new ArrayList();
1261         Iterator iter = markerSequence.iterator();
1262         while (iter.hasNext()) {
1263             MarkerSegment seg = (MarkerSegment) iter.next();
1264             if (seg instanceof DHTMarkerSegment) {
1265                 oldDHTs.add(seg);
1266             }
1267         }
1268         if (!oldDHTs.isEmpty()) {
1269             NodeList children = node.getChildNodes();
1270             for (int i = 0; i < children.getLength(); i++) {
1271                 Node child = children.item(i);
1272                 NamedNodeMap attrs = child.getAttributes();
1273                 int childID = MarkerSegment.getAttributeValue(child,
1274                                                               attrs,
1275                                                               "htableId",
1276                                                               0, 3,
1277                                                               true);
1278                 int childClass = MarkerSegment.getAttributeValue(child,
1279                                                                  attrs,
1280                                                                  "class",
1281                                                                  0, 1,
1282                                                                  true);
1283                 DHTMarkerSegment dht = null;
1284                 int tableIndex = -1;
1285                 for (int j = 0; j < oldDHTs.size(); j++) {
1286                     DHTMarkerSegment testDHT = (DHTMarkerSegment) oldDHTs.get(j);
1287                     for (int k = 0; k < testDHT.tables.size(); k++) {
1288                         DHTMarkerSegment.Htable testTable =
1289                             (DHTMarkerSegment.Htable) testDHT.tables.get(k);
1290                         if ((childID == testTable.tableID) &&
1291                             (childClass == testTable.tableClass)) {
1292                             dht = testDHT;
1293                             tableIndex = k;
1294                             break;
1295                         }
1296                     }
1297                     if (dht != null) break;
1298                 }
1299                 if (dht != null) {
1300                     dht.tables.set(tableIndex, dht.getHtableFromNode(child));
1301                 } else {
1302                     dht = (DHTMarkerSegment) oldDHTs.get(oldDHTs.size()-1);
1303                     dht.tables.add(dht.getHtableFromNode(child));
1304                 }
1305             }
1306         } else {
1307             DHTMarkerSegment newGuy = new DHTMarkerSegment(node);
1308             int lastDQT = findMarkerSegmentPosition(DQTMarkerSegment.class, false);
1309             int firstSOF = findMarkerSegmentPosition(SOFMarkerSegment.class, true);
1310             int firstSOS = findMarkerSegmentPosition(SOSMarkerSegment.class, true);
1311             if (lastDQT != -1) {
1312                 markerSequence.add(lastDQT+1, newGuy);
1313             } else if (firstSOF != -1) {
1314                 markerSequence.add(firstSOF, newGuy);
1315             } else if (firstSOS != -1) {
1316                 markerSequence.add(firstSOS, newGuy);
1317             } else {
1318                 markerSequence.add(newGuy);
1319             }
1320         }
1321     }
1322 


1718             // if the old componentSpec q table selectors don't match
1719             // the new ones, update the qtables.  The new selectors are already
1720             // in place in the new SOF segment above.
1721             for (int i = 0; i < oldCompSpecs.length; i++) {
1722                 if (oldCompSpecs[i].QtableSelector != newTableSelectors[i]) {
1723                     updateQtables = true;
1724                 }
1725             }
1726 
1727             if (progressive) {
1728                 // if the component ids are different, update all the existing scans
1729                 // ignore Huffman tables
1730                 boolean idsDiffer = false;
1731                 for (int i = 0; i < oldCompSpecs.length; i++) {
1732                     if (ids[i] != oldCompSpecs[i].componentId) {
1733                         idsDiffer = true;
1734                     }
1735                 }
1736                 if (idsDiffer) {
1737                     // update the ids in each SOS marker segment
1738                     for (Iterator iter = markerSequence.iterator(); iter.hasNext();) {
1739                         MarkerSegment seg = (MarkerSegment) iter.next();
1740                         if (seg instanceof SOSMarkerSegment) {
1741                             SOSMarkerSegment target = (SOSMarkerSegment) seg;
1742                             for (int i = 0; i < target.componentSpecs.length; i++) {
1743                                 int oldSelector =
1744                                     target.componentSpecs[i].componentSelector;
1745                                 // Find the position in the old componentSpecs array
1746                                 // of the old component with the old selector
1747                                 // and replace the component selector with the
1748                                 // new id at the same position, as these match
1749                                 // the new component specs array in the SOF created
1750                                 // above.
1751                                 for (int j = 0; j < oldCompSpecs.length; j++) {
1752                                     if (oldCompSpecs[j].componentId == oldSelector) {
1753                                         target.componentSpecs[i].componentSelector =
1754                                             ids[j];
1755                                     }
1756                                 }
1757                             }
1758                         }
1759                     }


1771                         }
1772                     }
1773 
1774                     // Might be the same as the old one, but this is easier.
1775                     markerSequence.set(markerSequence.indexOf(sos),
1776                                new SOSMarkerSegment(willSubsample,
1777                                                     ids,
1778                                                     numChannels));
1779                 }
1780             }
1781         } else {
1782             // should be stream metadata if there isn't an SOF, but check it anyway
1783             if (isStream) {
1784                 // update tables - routines below check if it's really necessary
1785                 updateQtables = true;
1786                 updateHtables = true;
1787             }
1788         }
1789 
1790         if (updateQtables) {
1791             List tableSegments = new ArrayList();
1792             for (Iterator iter = markerSequence.iterator(); iter.hasNext();) {
1793                 MarkerSegment seg = (MarkerSegment) iter.next();
1794                 if (seg instanceof DQTMarkerSegment) {
1795                     tableSegments.add(seg);
1796                 }
1797             }
1798             // If there are no tables, don't add them, as the metadata encodes an
1799             // abbreviated stream.
1800             // If we are not subsampling, we just need one, so don't do anything
1801             if (!tableSegments.isEmpty() && willSubsample) {
1802                 // Is it really necessary?  There should be at least 2 tables.
1803                 // If there is only one, assume it's a scaled "standard"
1804                 // luminance table, extract the scaling factor, and generate a
1805                 // scaled "standard" chrominance table.
1806 
1807                 // Find the table with selector 1.
1808                 boolean found = false;
1809                 for (Iterator iter = tableSegments.iterator(); iter.hasNext();) {
1810                     DQTMarkerSegment testdqt = (DQTMarkerSegment) iter.next();
1811                     for (Iterator tabiter = testdqt.tables.iterator();
1812                          tabiter.hasNext();) {
1813                         DQTMarkerSegment.Qtable tab =
1814                             (DQTMarkerSegment.Qtable) tabiter.next();
1815                         if (tab.tableID == 1) {
1816                             found = true;
1817                         }
1818                     }
1819                 }
1820                 if (!found) {
1821                     //    find the table with selector 0.  There should be one.
1822                     DQTMarkerSegment.Qtable table0 = null;
1823                     for (Iterator iter = tableSegments.iterator(); iter.hasNext();) {
1824                         DQTMarkerSegment testdqt = (DQTMarkerSegment) iter.next();
1825                         for (Iterator tabiter = testdqt.tables.iterator();
1826                              tabiter.hasNext();) {
1827                             DQTMarkerSegment.Qtable tab =
1828                                 (DQTMarkerSegment.Qtable) tabiter.next();
1829                             if (tab.tableID == 0) {
1830                                 table0 = tab;
1831                             }
1832                         }
1833                     }
1834 
1835                     // Assuming that the table with id 0 is a luminance table,
1836                     // compute a new chrominance table of the same quality and
1837                     // add it to the last DQT segment
1838                     DQTMarkerSegment dqt =
1839                         (DQTMarkerSegment) tableSegments.get(tableSegments.size()-1);
1840                     dqt.tables.add(dqt.getChromaForLuma(table0));
1841                 }
1842             }
1843         }
1844 
1845         if (updateHtables) {
1846             List tableSegments = new ArrayList();
1847             for (Iterator iter = markerSequence.iterator(); iter.hasNext();) {
1848                 MarkerSegment seg = (MarkerSegment) iter.next();
1849                 if (seg instanceof DHTMarkerSegment) {
1850                     tableSegments.add(seg);
1851                 }
1852             }
1853             // If there are no tables, don't add them, as the metadata encodes an
1854             // abbreviated stream.
1855             // If we are not subsampling, we just need one, so don't do anything
1856             if (!tableSegments.isEmpty() && willSubsample) {
1857                 // Is it really necessary?  There should be at least 2 dc and 2 ac
1858                 // tables.  If there is only one, add a
1859                 // "standard " chrominance table.
1860 
1861                 // find a table with selector 1. AC/DC is irrelevant
1862                 boolean found = false;
1863                 for (Iterator iter = tableSegments.iterator(); iter.hasNext();) {
1864                     DHTMarkerSegment testdht = (DHTMarkerSegment) iter.next();
1865                     for (Iterator tabiter = testdht.tables.iterator();
1866                          tabiter.hasNext();) {
1867                         DHTMarkerSegment.Htable tab =
1868                             (DHTMarkerSegment.Htable) tabiter.next();
1869                         if (tab.tableID == 1) {
1870                             found = true;
1871                         }
1872                     }
1873                 }
1874                 if (!found) {
1875                     // Create new standard dc and ac chrominance tables and add them
1876                     // to the last DHT segment
1877                     DHTMarkerSegment lastDHT =
1878                         (DHTMarkerSegment) tableSegments.get(tableSegments.size()-1);
1879                     lastDHT.addHtable(JPEGHuffmanTable.StdDCLuminance, true, 1);
1880                     lastDHT.addHtable(JPEGHuffmanTable.StdACLuminance, true, 1);
1881                 }
1882             }
1883         }
1884     }
1885 
1886     private boolean wantAlpha(Node transparency) {
1887         boolean returnValue = false;
1888         Node alpha = transparency.getFirstChild();  // Alpha must be first if present
1889         if (alpha.getNodeName().equals("Alpha")) {
1890             if (alpha.hasAttributes()) {
1891                 String value =
1892                     alpha.getAttributes().getNamedItem("value").getNodeValue();
1893                 if (!value.equals("none")) {
1894                     returnValue = true;
1895                 }
1896             }
1897         }
1898         transparencyDone = true;


2164             (formatName.equals(JPEG.nativeStreamMetadataFormatName))) {
2165             setFromNativeTree(root);
2166         } else if (!isStream &&
2167                    (formatName.equals(JPEG.nativeImageMetadataFormatName))) {
2168             setFromNativeTree(root);
2169         } else if (!isStream &&
2170                    (formatName.equals
2171                     (IIOMetadataFormatImpl.standardMetadataFormatName))) {
2172             // In this case a reset followed by a merge is correct
2173             super.setFromTree(formatName, root);
2174         } else {
2175             throw  new IllegalArgumentException("Unsupported format name: "
2176                                                 + formatName);
2177         }
2178     }
2179 
2180     private void setFromNativeTree(Node root) throws IIOInvalidTreeException {
2181         if (resetSequence == null) {
2182             resetSequence = markerSequence;
2183         }
2184         markerSequence = new ArrayList();
2185 
2186         // Build a whole new marker sequence from the tree
2187 
2188         String name = root.getNodeName();
2189         if (name != ((isStream) ? JPEG.nativeStreamMetadataFormatName
2190                                 : JPEG.nativeImageMetadataFormatName)) {
2191             throw new IIOInvalidTreeException("Invalid root node name: " + name,
2192                                               root);
2193         }
2194         if (!isStream) {
2195             if (root.getChildNodes().getLength() != 2) { // JPEGvariety and markerSequence
2196                 throw new IIOInvalidTreeException(
2197                     "JPEGvariety and markerSequence nodes must be present", root);
2198             }
2199 
2200             Node JPEGvariety = root.getFirstChild();
2201 
2202             if (JPEGvariety.getChildNodes().getLength() != 0) {
2203                 markerSequence.add(new JFIFMarkerSegment(JPEGvariety.getFirstChild()));
2204             }


2293                 }
2294             } else {
2295                 // stream can't have jfif, adobe, sof, or sos
2296                 SOSMarkerSegment sos =
2297                     (SOSMarkerSegment) findMarkerSegment(SOSMarkerSegment.class,
2298                                                          true);
2299                 if ((jfif != null) || (adobe != null)
2300                     || (sof != null) || (sos != null)) {
2301                     retval = false;
2302                 }
2303             }
2304         }
2305         return retval;
2306     }
2307 
2308     /**
2309      * Returns the total number of bands referenced in all SOS marker
2310      * segments, including 0 if there are no SOS marker segments.
2311      */
2312     private int countScanBands() {
2313         List ids = new ArrayList();
2314         Iterator iter = markerSequence.iterator();
2315         while(iter.hasNext()) {
2316             MarkerSegment seg = (MarkerSegment)iter.next();
2317             if (seg instanceof SOSMarkerSegment) {
2318                 SOSMarkerSegment sos = (SOSMarkerSegment) seg;
2319                 SOSMarkerSegment.ScanComponentSpec [] specs = sos.componentSpecs;
2320                 for (int i = 0; i < specs.length; i++) {
2321                     Integer id = new Integer(specs[i].componentSelector);
2322                     if (!ids.contains(id)) {
2323                         ids.add(id);
2324                     }
2325                 }
2326             }
2327         }
2328 
2329         return ids.size();
2330     }
2331 
2332     ///// Writer support
2333 
2334     void writeToStream(ImageOutputStream ios,
2335                        boolean ignoreJFIF,
2336                        boolean forceJFIF,
2337                        List thumbnails,
2338                        ICC_Profile iccProfile,
2339                        boolean ignoreAdobe,
2340                        int newAdobeTransform,
2341                        JPEGImageWriter writer)
2342         throws IOException {
2343         if (forceJFIF) {
2344             // Write a default JFIF segment, including thumbnails
2345             // This won't be duplicated below because forceJFIF will be
2346             // set only if there is no JFIF present already.
2347             JFIFMarkerSegment.writeDefaultJFIF(ios,
2348                                                thumbnails,
2349                                                iccProfile,
2350                                                writer);
2351             if ((ignoreAdobe == false)
2352                 && (newAdobeTransform != JPEG.ADOBE_IMPOSSIBLE)) {
2353                 if ((newAdobeTransform != JPEG.ADOBE_UNKNOWN)
2354                     && (newAdobeTransform != JPEG.ADOBE_YCC)) {
2355                     // Not compatible, so ignore Adobe.
2356                     ignoreAdobe = true;
2357                     writer.warningOccurred
2358                         (JPEGImageWriter.WARNING_METADATA_ADJUSTED_FOR_THUMB);
2359                 }
2360             }
2361         }
2362         // Iterate over each MarkerSegment
2363         Iterator iter = markerSequence.iterator();
2364         while(iter.hasNext()) {
2365             MarkerSegment seg = (MarkerSegment)iter.next();
2366             if (seg instanceof JFIFMarkerSegment) {
2367                 if (ignoreJFIF == false) {
2368                     JFIFMarkerSegment jfif = (JFIFMarkerSegment) seg;
2369                     jfif.writeWithThumbs(ios, thumbnails, writer);
2370                     if (iccProfile != null) {
2371                         JFIFMarkerSegment.writeICC(iccProfile, ios);
2372                     }
2373                 } // Otherwise ignore it, as requested
2374             } else if (seg instanceof AdobeMarkerSegment) {
2375                 if (ignoreAdobe == false) {
2376                     if (newAdobeTransform != JPEG.ADOBE_IMPOSSIBLE) {
2377                         AdobeMarkerSegment newAdobe =
2378                             (AdobeMarkerSegment) seg.clone();
2379                         newAdobe.transform = newAdobeTransform;
2380                         newAdobe.write(ios);
2381                     } else if (forceJFIF) {
2382                         // If adobe isn't JFIF compatible, ignore it
2383                         AdobeMarkerSegment adobe = (AdobeMarkerSegment) seg;
2384                         if ((adobe.transform == JPEG.ADOBE_UNKNOWN)
2385                             || (adobe.transform == JPEG.ADOBE_YCC)) {


2392                         seg.write(ios);
2393                     }
2394                 } // Otherwise ignore it, as requested
2395             } else {
2396                 seg.write(ios);
2397             }
2398         }
2399     }
2400 
2401     //// End of writer support
2402 
2403     public void reset() {
2404         if (resetSequence != null) {  // Otherwise no need to reset
2405             markerSequence = resetSequence;
2406             resetSequence = null;
2407         }
2408     }
2409 
2410     public void print() {
2411         for (int i = 0; i < markerSequence.size(); i++) {
2412             MarkerSegment seg = (MarkerSegment) markerSequence.get(i);
2413             seg.print();
2414         }
2415     }
2416 
2417 }


  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;


 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");


 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);


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         }


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 


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                     }


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;


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             }


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)) {


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 }