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 =
674 markerSequence.listIterator(markerSequence.size());
675 while (iter.hasPrevious()) {
676 MarkerSegment seg = iter.previous();
677 if (cls.isInstance(seg)) {
678 return seg;
679 }
680 }
681 }
682 return null;
683 }
684
685 /**
686 * Returns the index of the first or last MarkerSegment in the list
687 * of the given class, or -1 if none is found.
688 */
689 private int findMarkerSegmentPosition(Class<? extends MarkerSegment> cls,
690 boolean first) {
691 if (first) {
692 ListIterator<MarkerSegment> iter = markerSequence.listIterator();
693 for (int i = 0; iter.hasNext(); i++) {
694 MarkerSegment seg = iter.next();
695 if (cls.isInstance(seg)) {
696 return i;
697 }
698 }
699 } else {
700 ListIterator<MarkerSegment> iter =
701 markerSequence.listIterator(markerSequence.size());
702 for (int i = markerSequence.size()-1; iter.hasPrevious(); i--) {
703 MarkerSegment seg = iter.previous();
704 if (cls.isInstance(seg)) {
705 return i;
706 }
707 }
708 }
709 return -1;
710 }
711
712 private int findLastUnknownMarkerSegmentPosition() {
713 ListIterator<MarkerSegment> iter =
714 markerSequence.listIterator(markerSequence.size());
715 for (int i = markerSequence.size()-1; iter.hasPrevious(); i--) {
716 MarkerSegment seg = iter.previous();
717 if (seg.unknown == true) {
718 return i;
719 }
720 }
721 return -1;
722 }
723
724 // Implement Cloneable, but restrict access
725
726 protected Object clone() {
727 JPEGMetadata newGuy = null;
728 try {
729 newGuy = (JPEGMetadata) super.clone();
730 } catch (CloneNotSupportedException e) {} // won't happen
731 if (markerSequence != null) {
732 newGuy.markerSequence = cloneSequence();
733 }
734 newGuy.resetSequence = null;
735 return newGuy;
736 }
737
738 /**
739 * Returns a deep copy of the current marker sequence.
740 */
741 private List<MarkerSegment> cloneSequence() {
742 if (markerSequence == null) {
743 return null;
744 }
745 List<MarkerSegment> retval = new ArrayList<>(markerSequence.size());
746 Iterator<MarkerSegment> iter = markerSequence.iterator();
747 while(iter.hasNext()) {
748 MarkerSegment seg = iter.next();
749 retval.add((MarkerSegment) seg.clone());
750 }
751
752 return retval;
753 }
754
755
756 // Tree methods
757
758 public Node getAsTree(String formatName) {
759 if (formatName == null) {
760 throw new IllegalArgumentException("null formatName!");
761 }
762 if (isStream) {
763 if (formatName.equals(JPEG.nativeStreamMetadataFormatName)) {
764 return getNativeTree();
765 }
766 } else {
767 if (formatName.equals(JPEG.nativeImageMetadataFormatName)) {
768 return getNativeTree();
769 }
770 if (formatName.equals
771 (IIOMetadataFormatImpl.standardMetadataFormatName)) {
772 return getStandardTree();
773 }
774 }
775 throw new IllegalArgumentException("Unsupported format name: "
776 + formatName);
777 }
778
779 IIOMetadataNode getNativeTree() {
780 IIOMetadataNode root;
781 IIOMetadataNode top;
782 Iterator<MarkerSegment> iter = markerSequence.iterator();
783 if (isStream) {
784 root = new IIOMetadataNode(JPEG.nativeStreamMetadataFormatName);
785 top = root;
786 } else {
787 IIOMetadataNode sequence = new IIOMetadataNode("markerSequence");
788 if (!inThumb) {
789 root = new IIOMetadataNode(JPEG.nativeImageMetadataFormatName);
790 IIOMetadataNode header = new IIOMetadataNode("JPEGvariety");
791 root.appendChild(header);
792 JFIFMarkerSegment jfif = (JFIFMarkerSegment)
793 findMarkerSegment(JFIFMarkerSegment.class, true);
794 if (jfif != null) {
795 iter.next(); // JFIF must be first, so this skips it
796 header.appendChild(jfif.getNativeNode());
797 }
798 root.appendChild(sequence);
799 } else {
800 root = sequence;
801 }
802 top = sequence;
803 }
804 while(iter.hasNext()) {
805 MarkerSegment seg = iter.next();
806 top.appendChild(seg.getNativeNode());
807 }
808 return root;
809 }
810
811 // Standard tree node methods
812
813 protected IIOMetadataNode getStandardChromaNode() {
814 hasAlpha = false; // Unless we find otherwise
815
816 // Colorspace type - follow the rules in the spec
817 // First get the SOF marker segment, if there is one
818 SOFMarkerSegment sof = (SOFMarkerSegment)
819 findMarkerSegment(SOFMarkerSegment.class, true);
820 if (sof == null) {
821 // No image, so no chroma
822 return null;
823 }
824
825 IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
949
950 return chroma;
951 }
952
953 protected IIOMetadataNode getStandardCompressionNode() {
954
955 IIOMetadataNode compression = new IIOMetadataNode("Compression");
956
957 // CompressionTypeName
958 IIOMetadataNode name = new IIOMetadataNode("CompressionTypeName");
959 name.setAttribute("value", "JPEG");
960 compression.appendChild(name);
961
962 // Lossless - false
963 IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
964 lossless.setAttribute("value", "FALSE");
965 compression.appendChild(lossless);
966
967 // NumProgressiveScans - count sos segments
968 int sosCount = 0;
969 Iterator<MarkerSegment> iter = markerSequence.iterator();
970 while (iter.hasNext()) {
971 MarkerSegment ms = iter.next();
972 if (ms.tag == JPEG.SOS) {
973 sosCount++;
974 }
975 }
976 if (sosCount != 0) {
977 IIOMetadataNode prog = new IIOMetadataNode("NumProgressiveScans");
978 prog.setAttribute("value", Integer.toString(sosCount));
979 compression.appendChild(prog);
980 }
981
982 return compression;
983 }
984
985 protected IIOMetadataNode getStandardDimensionNode() {
986 // If we have a JFIF marker segment, we know a little
987 // otherwise all we know is the orientation, which is always normal
988 IIOMetadataNode dim = new IIOMetadataNode("Dimension");
989 IIOMetadataNode orient = new IIOMetadataNode("ImageOrientation");
990 orient.setAttribute("value", "normal");
991 dim.appendChild(orient);
1016 new IIOMetadataNode("HorizontalPixelSize");
1017 horiz.setAttribute("value",
1018 Float.toString(scale/jfif.Xdensity));
1019 dim.appendChild(horiz);
1020
1021 IIOMetadataNode vert =
1022 new IIOMetadataNode("VerticalPixelSize");
1023 vert.setAttribute("value",
1024 Float.toString(scale/jfif.Ydensity));
1025 dim.appendChild(vert);
1026 }
1027 }
1028 return dim;
1029 }
1030
1031 protected IIOMetadataNode getStandardTextNode() {
1032 IIOMetadataNode text = null;
1033 // Add a text entry for each COM Marker Segment
1034 if (findMarkerSegment(JPEG.COM) != null) {
1035 text = new IIOMetadataNode("Text");
1036 Iterator<MarkerSegment> iter = markerSequence.iterator();
1037 while (iter.hasNext()) {
1038 MarkerSegment seg = iter.next();
1039 if (seg.tag == JPEG.COM) {
1040 COMMarkerSegment com = (COMMarkerSegment) seg;
1041 IIOMetadataNode entry = new IIOMetadataNode("TextEntry");
1042 entry.setAttribute("keyword", "comment");
1043 entry.setAttribute("value", com.getComment());
1044 text.appendChild(entry);
1045 }
1046 }
1047 }
1048 return text;
1049 }
1050
1051 protected IIOMetadataNode getStandardTransparencyNode() {
1052 IIOMetadataNode trans = null;
1053 if (hasAlpha == true) {
1054 trans = new IIOMetadataNode("Transparency");
1055 IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
1056 alpha.setAttribute("value", "nonpremultiplied"); // Always assume
1057 trans.appendChild(alpha);
1058 }
1059 return trans;
1060 }
1061
1062 // Editing
1063
1064 public boolean isReadOnly() {
1065 return false;
1066 }
1067
1068 public void mergeTree(String formatName, Node root)
1069 throws IIOInvalidTreeException {
1070 if (formatName == null) {
1071 throw new IllegalArgumentException("null formatName!");
1072 }
1073 if (root == null) {
1074 throw new IllegalArgumentException("null root!");
1075 }
1076 List<MarkerSegment> copy = null;
1077 if (resetSequence == null) {
1078 resetSequence = cloneSequence(); // Deep copy
1079 copy = resetSequence; // Avoid cloning twice
1080 } else {
1081 copy = cloneSequence();
1082 }
1083 if (isStream &&
1084 (formatName.equals(JPEG.nativeStreamMetadataFormatName))) {
1085 mergeNativeTree(root);
1086 } else if (!isStream &&
1087 (formatName.equals(JPEG.nativeImageMetadataFormatName))) {
1088 mergeNativeTree(root);
1089 } else if (!isStream &&
1090 (formatName.equals
1091 (IIOMetadataFormatImpl.standardMetadataFormatName))) {
1092 mergeStandardTree(root);
1093 } else {
1094 throw new IllegalArgumentException("Unsupported format name: "
1095 + formatName);
1096 }
1169
1170 /**
1171 * Merge the given DQT node into the marker sequence. If there already
1172 * exist DQT marker segments in the sequence, then each table in the
1173 * node replaces the first table, in any DQT segment, with the same
1174 * table id. If none of the existing DQT segments contain a table with
1175 * the same id, then the table is added to the last existing DQT segment.
1176 * If there are no DQT segments, then a new one is created and added
1177 * as follows:
1178 * If there are DHT segments, the new DQT segment is inserted before the
1179 * first one.
1180 * If there are no DHT segments, the new DQT segment is inserted before
1181 * an SOF segment, if there is one.
1182 * If there is no SOF segment, the new DQT segment is inserted before
1183 * the first SOS segment, if there is one.
1184 * If there is no SOS segment, the new DQT segment is added to the end
1185 * of the sequence.
1186 */
1187 private void mergeDQTNode(Node node) throws IIOInvalidTreeException {
1188 // First collect any existing DQT nodes into a local list
1189 ArrayList<DQTMarkerSegment> oldDQTs = new ArrayList<>();
1190 Iterator<MarkerSegment> iter = markerSequence.iterator();
1191 while (iter.hasNext()) {
1192 MarkerSegment seg = iter.next();
1193 if (seg instanceof DQTMarkerSegment) {
1194 oldDQTs.add((DQTMarkerSegment) seg);
1195 }
1196 }
1197 if (!oldDQTs.isEmpty()) {
1198 NodeList children = node.getChildNodes();
1199 for (int i = 0; i < children.getLength(); i++) {
1200 Node child = children.item(i);
1201 int childID = MarkerSegment.getAttributeValue(child,
1202 null,
1203 "qtableId",
1204 0, 3,
1205 true);
1206 DQTMarkerSegment dqt = null;
1207 int tableIndex = -1;
1208 for (int j = 0; j < oldDQTs.size(); j++) {
1209 DQTMarkerSegment testDQT = oldDQTs.get(j);
1210 for (int k = 0; k < testDQT.tables.size(); k++) {
1211 DQTMarkerSegment.Qtable testTable = testDQT.tables.get(k);
1212 if (childID == testTable.tableID) {
1213 dqt = testDQT;
1214 tableIndex = k;
1215 break;
1216 }
1217 }
1218 if (dqt != null) break;
1219 }
1220 if (dqt != null) {
1221 dqt.tables.set(tableIndex, dqt.getQtableFromNode(child));
1222 } else {
1223 dqt = oldDQTs.get(oldDQTs.size()-1);
1224 dqt.tables.add(dqt.getQtableFromNode(child));
1225 }
1226 }
1227 } else {
1228 DQTMarkerSegment newGuy = new DQTMarkerSegment(node);
1229 int firstDHT = findMarkerSegmentPosition(DHTMarkerSegment.class, true);
1230 int firstSOF = findMarkerSegmentPosition(SOFMarkerSegment.class, true);
1231 int firstSOS = findMarkerSegmentPosition(SOSMarkerSegment.class, true);
1232 if (firstDHT != -1) {
1233 markerSequence.add(firstDHT, newGuy);
1234 } else if (firstSOF != -1) {
1235 markerSequence.add(firstSOF, newGuy);
1236 } else if (firstSOS != -1) {
1237 markerSequence.add(firstSOS, newGuy);
1238 } else {
1239 markerSequence.add(newGuy);
1240 }
1241 }
1242 }
1243
1244 /**
1245 * Merge the given DHT node into the marker sequence. If there already
1246 * exist DHT marker segments in the sequence, then each table in the
1247 * node replaces the first table, in any DHT segment, with the same
1248 * table class and table id. If none of the existing DHT segments contain
1249 * a table with the same class and id, then the table is added to the last
1250 * existing DHT segment.
1251 * If there are no DHT segments, then a new one is created and added
1252 * as follows:
1253 * If there are DQT segments, the new DHT segment is inserted immediately
1254 * following the last DQT segment.
1255 * If there are no DQT segments, the new DHT segment is inserted before
1256 * an SOF segment, if there is one.
1257 * If there is no SOF segment, the new DHT segment is inserted before
1258 * the first SOS segment, if there is one.
1259 * If there is no SOS segment, the new DHT segment is added to the end
1260 * of the sequence.
1261 */
1262 private void mergeDHTNode(Node node) throws IIOInvalidTreeException {
1263 // First collect any existing DQT nodes into a local list
1264 ArrayList<DHTMarkerSegment> oldDHTs = new ArrayList<>();
1265 Iterator<MarkerSegment> iter = markerSequence.iterator();
1266 while (iter.hasNext()) {
1267 MarkerSegment seg = iter.next();
1268 if (seg instanceof DHTMarkerSegment) {
1269 oldDHTs.add((DHTMarkerSegment) seg);
1270 }
1271 }
1272 if (!oldDHTs.isEmpty()) {
1273 NodeList children = node.getChildNodes();
1274 for (int i = 0; i < children.getLength(); i++) {
1275 Node child = children.item(i);
1276 NamedNodeMap attrs = child.getAttributes();
1277 int childID = MarkerSegment.getAttributeValue(child,
1278 attrs,
1279 "htableId",
1280 0, 3,
1281 true);
1282 int childClass = MarkerSegment.getAttributeValue(child,
1283 attrs,
1284 "class",
1285 0, 1,
1286 true);
1287 DHTMarkerSegment dht = null;
1288 int tableIndex = -1;
1289 for (int j = 0; j < oldDHTs.size(); j++) {
1290 DHTMarkerSegment testDHT = oldDHTs.get(j);
1291 for (int k = 0; k < testDHT.tables.size(); k++) {
1292 DHTMarkerSegment.Htable testTable = testDHT.tables.get(k);
1293 if ((childID == testTable.tableID) &&
1294 (childClass == testTable.tableClass)) {
1295 dht = testDHT;
1296 tableIndex = k;
1297 break;
1298 }
1299 }
1300 if (dht != null) break;
1301 }
1302 if (dht != null) {
1303 dht.tables.set(tableIndex, dht.getHtableFromNode(child));
1304 } else {
1305 dht = oldDHTs.get(oldDHTs.size()-1);
1306 dht.tables.add(dht.getHtableFromNode(child));
1307 }
1308 }
1309 } else {
1310 DHTMarkerSegment newGuy = new DHTMarkerSegment(node);
1311 int lastDQT = findMarkerSegmentPosition(DQTMarkerSegment.class, false);
1312 int firstSOF = findMarkerSegmentPosition(SOFMarkerSegment.class, true);
1313 int firstSOS = findMarkerSegmentPosition(SOSMarkerSegment.class, true);
1314 if (lastDQT != -1) {
1315 markerSequence.add(lastDQT+1, newGuy);
1316 } else if (firstSOF != -1) {
1317 markerSequence.add(firstSOF, newGuy);
1318 } else if (firstSOS != -1) {
1319 markerSequence.add(firstSOS, newGuy);
1320 } else {
1321 markerSequence.add(newGuy);
1322 }
1323 }
1324 }
1325
1721 // if the old componentSpec q table selectors don't match
1722 // the new ones, update the qtables. The new selectors are already
1723 // in place in the new SOF segment above.
1724 for (int i = 0; i < oldCompSpecs.length; i++) {
1725 if (oldCompSpecs[i].QtableSelector != newTableSelectors[i]) {
1726 updateQtables = true;
1727 }
1728 }
1729
1730 if (progressive) {
1731 // if the component ids are different, update all the existing scans
1732 // ignore Huffman tables
1733 boolean idsDiffer = false;
1734 for (int i = 0; i < oldCompSpecs.length; i++) {
1735 if (ids[i] != oldCompSpecs[i].componentId) {
1736 idsDiffer = true;
1737 }
1738 }
1739 if (idsDiffer) {
1740 // update the ids in each SOS marker segment
1741 for (Iterator<MarkerSegment> iter = markerSequence.iterator();
1742 iter.hasNext();) {
1743 MarkerSegment seg = iter.next();
1744 if (seg instanceof SOSMarkerSegment) {
1745 SOSMarkerSegment target = (SOSMarkerSegment) seg;
1746 for (int i = 0; i < target.componentSpecs.length; i++) {
1747 int oldSelector =
1748 target.componentSpecs[i].componentSelector;
1749 // Find the position in the old componentSpecs array
1750 // of the old component with the old selector
1751 // and replace the component selector with the
1752 // new id at the same position, as these match
1753 // the new component specs array in the SOF created
1754 // above.
1755 for (int j = 0; j < oldCompSpecs.length; j++) {
1756 if (oldCompSpecs[j].componentId == oldSelector) {
1757 target.componentSpecs[i].componentSelector =
1758 ids[j];
1759 }
1760 }
1761 }
1762 }
1763 }
1775 }
1776 }
1777
1778 // Might be the same as the old one, but this is easier.
1779 markerSequence.set(markerSequence.indexOf(sos),
1780 new SOSMarkerSegment(willSubsample,
1781 ids,
1782 numChannels));
1783 }
1784 }
1785 } else {
1786 // should be stream metadata if there isn't an SOF, but check it anyway
1787 if (isStream) {
1788 // update tables - routines below check if it's really necessary
1789 updateQtables = true;
1790 updateHtables = true;
1791 }
1792 }
1793
1794 if (updateQtables) {
1795 List<DQTMarkerSegment> tableSegments = new ArrayList<>();
1796 for (Iterator<MarkerSegment> iter = markerSequence.iterator();
1797 iter.hasNext();) {
1798 MarkerSegment seg = iter.next();
1799 if (seg instanceof DQTMarkerSegment) {
1800 tableSegments.add((DQTMarkerSegment) seg);
1801 }
1802 }
1803 // If there are no tables, don't add them, as the metadata encodes an
1804 // abbreviated stream.
1805 // If we are not subsampling, we just need one, so don't do anything
1806 if (!tableSegments.isEmpty() && willSubsample) {
1807 // Is it really necessary? There should be at least 2 tables.
1808 // If there is only one, assume it's a scaled "standard"
1809 // luminance table, extract the scaling factor, and generate a
1810 // scaled "standard" chrominance table.
1811
1812 // Find the table with selector 1.
1813 boolean found = false;
1814 for (Iterator<DQTMarkerSegment> iter = tableSegments.iterator();
1815 iter.hasNext();) {
1816 DQTMarkerSegment testdqt = iter.next();
1817 for (Iterator<DQTMarkerSegment.Qtable> tabiter =
1818 testdqt.tables.iterator(); tabiter.hasNext();) {
1819 DQTMarkerSegment.Qtable tab = tabiter.next();
1820 if (tab.tableID == 1) {
1821 found = true;
1822 }
1823 }
1824 }
1825 if (!found) {
1826 // find the table with selector 0. There should be one.
1827 DQTMarkerSegment.Qtable table0 = null;
1828 for (Iterator<DQTMarkerSegment> iter =
1829 tableSegments.iterator(); iter.hasNext();) {
1830 DQTMarkerSegment testdqt = iter.next();
1831 for (Iterator<DQTMarkerSegment.Qtable> tabiter =
1832 testdqt.tables.iterator(); tabiter.hasNext();) {
1833 DQTMarkerSegment.Qtable tab = tabiter.next();
1834 if (tab.tableID == 0) {
1835 table0 = tab;
1836 }
1837 }
1838 }
1839
1840 // Assuming that the table with id 0 is a luminance table,
1841 // compute a new chrominance table of the same quality and
1842 // add it to the last DQT segment
1843 DQTMarkerSegment dqt = tableSegments.get(tableSegments.size()-1);
1844 dqt.tables.add(dqt.getChromaForLuma(table0));
1845 }
1846 }
1847 }
1848
1849 if (updateHtables) {
1850 List<DHTMarkerSegment> tableSegments = new ArrayList<>();
1851 for (Iterator<MarkerSegment> iter = markerSequence.iterator();
1852 iter.hasNext();) {
1853 MarkerSegment seg = iter.next();
1854 if (seg instanceof DHTMarkerSegment) {
1855 tableSegments.add((DHTMarkerSegment) seg);
1856 }
1857 }
1858 // If there are no tables, don't add them, as the metadata encodes an
1859 // abbreviated stream.
1860 // If we are not subsampling, we just need one, so don't do anything
1861 if (!tableSegments.isEmpty() && willSubsample) {
1862 // Is it really necessary? There should be at least 2 dc and 2 ac
1863 // tables. If there is only one, add a
1864 // "standard " chrominance table.
1865
1866 // find a table with selector 1. AC/DC is irrelevant
1867 boolean found = false;
1868 for (Iterator<DHTMarkerSegment> iter = tableSegments.iterator();
1869 iter.hasNext();) {
1870 DHTMarkerSegment testdht = iter.next();
1871 for (Iterator<DHTMarkerSegment.Htable> tabiter =
1872 testdht.tables.iterator(); tabiter.hasNext();) {
1873 DHTMarkerSegment.Htable tab = tabiter.next();
1874 if (tab.tableID == 1) {
1875 found = true;
1876 }
1877 }
1878 }
1879 if (!found) {
1880 // Create new standard dc and ac chrominance tables and add them
1881 // to the last DHT segment
1882 DHTMarkerSegment lastDHT =
1883 tableSegments.get(tableSegments.size()-1);
1884 lastDHT.addHtable(JPEGHuffmanTable.StdDCLuminance, true, 1);
1885 lastDHT.addHtable(JPEGHuffmanTable.StdACLuminance, true, 1);
1886 }
1887 }
1888 }
1889 }
1890
1891 private boolean wantAlpha(Node transparency) {
1892 boolean returnValue = false;
1893 Node alpha = transparency.getFirstChild(); // Alpha must be first if present
1894 if (alpha.getNodeName().equals("Alpha")) {
1895 if (alpha.hasAttributes()) {
1896 String value =
1897 alpha.getAttributes().getNamedItem("value").getNodeValue();
1898 if (!value.equals("none")) {
1899 returnValue = true;
1900 }
1901 }
1902 }
1903 transparencyDone = true;
2169 (formatName.equals(JPEG.nativeStreamMetadataFormatName))) {
2170 setFromNativeTree(root);
2171 } else if (!isStream &&
2172 (formatName.equals(JPEG.nativeImageMetadataFormatName))) {
2173 setFromNativeTree(root);
2174 } else if (!isStream &&
2175 (formatName.equals
2176 (IIOMetadataFormatImpl.standardMetadataFormatName))) {
2177 // In this case a reset followed by a merge is correct
2178 super.setFromTree(formatName, root);
2179 } else {
2180 throw new IllegalArgumentException("Unsupported format name: "
2181 + formatName);
2182 }
2183 }
2184
2185 private void setFromNativeTree(Node root) throws IIOInvalidTreeException {
2186 if (resetSequence == null) {
2187 resetSequence = markerSequence;
2188 }
2189 markerSequence = new ArrayList<>();
2190
2191 // Build a whole new marker sequence from the tree
2192
2193 String name = root.getNodeName();
2194 if (name != ((isStream) ? JPEG.nativeStreamMetadataFormatName
2195 : JPEG.nativeImageMetadataFormatName)) {
2196 throw new IIOInvalidTreeException("Invalid root node name: " + name,
2197 root);
2198 }
2199 if (!isStream) {
2200 if (root.getChildNodes().getLength() != 2) { // JPEGvariety and markerSequence
2201 throw new IIOInvalidTreeException(
2202 "JPEGvariety and markerSequence nodes must be present", root);
2203 }
2204
2205 Node JPEGvariety = root.getFirstChild();
2206
2207 if (JPEGvariety.getChildNodes().getLength() != 0) {
2208 markerSequence.add(new JFIFMarkerSegment(JPEGvariety.getFirstChild()));
2209 }
2298 }
2299 } else {
2300 // stream can't have jfif, adobe, sof, or sos
2301 SOSMarkerSegment sos =
2302 (SOSMarkerSegment) findMarkerSegment(SOSMarkerSegment.class,
2303 true);
2304 if ((jfif != null) || (adobe != null)
2305 || (sof != null) || (sos != null)) {
2306 retval = false;
2307 }
2308 }
2309 }
2310 return retval;
2311 }
2312
2313 /**
2314 * Returns the total number of bands referenced in all SOS marker
2315 * segments, including 0 if there are no SOS marker segments.
2316 */
2317 private int countScanBands() {
2318 List<Integer> ids = new ArrayList<>();
2319 Iterator<MarkerSegment> iter = markerSequence.iterator();
2320 while(iter.hasNext()) {
2321 MarkerSegment seg = iter.next();
2322 if (seg instanceof SOSMarkerSegment) {
2323 SOSMarkerSegment sos = (SOSMarkerSegment) seg;
2324 SOSMarkerSegment.ScanComponentSpec [] specs = sos.componentSpecs;
2325 for (int i = 0; i < specs.length; i++) {
2326 Integer id = new Integer(specs[i].componentSelector);
2327 if (!ids.contains(id)) {
2328 ids.add(id);
2329 }
2330 }
2331 }
2332 }
2333
2334 return ids.size();
2335 }
2336
2337 ///// Writer support
2338
2339 void writeToStream(ImageOutputStream ios,
2340 boolean ignoreJFIF,
2341 boolean forceJFIF,
2342 List<? extends BufferedImage> thumbnails,
2343 ICC_Profile iccProfile,
2344 boolean ignoreAdobe,
2345 int newAdobeTransform,
2346 JPEGImageWriter writer)
2347 throws IOException {
2348 if (forceJFIF) {
2349 // Write a default JFIF segment, including thumbnails
2350 // This won't be duplicated below because forceJFIF will be
2351 // set only if there is no JFIF present already.
2352 JFIFMarkerSegment.writeDefaultJFIF(ios,
2353 thumbnails,
2354 iccProfile,
2355 writer);
2356 if ((ignoreAdobe == false)
2357 && (newAdobeTransform != JPEG.ADOBE_IMPOSSIBLE)) {
2358 if ((newAdobeTransform != JPEG.ADOBE_UNKNOWN)
2359 && (newAdobeTransform != JPEG.ADOBE_YCC)) {
2360 // Not compatible, so ignore Adobe.
2361 ignoreAdobe = true;
2362 writer.warningOccurred
2363 (JPEGImageWriter.WARNING_METADATA_ADJUSTED_FOR_THUMB);
2364 }
2365 }
2366 }
2367 // Iterate over each MarkerSegment
2368 Iterator<MarkerSegment> iter = markerSequence.iterator();
2369 while(iter.hasNext()) {
2370 MarkerSegment seg = iter.next();
2371 if (seg instanceof JFIFMarkerSegment) {
2372 if (ignoreJFIF == false) {
2373 JFIFMarkerSegment jfif = (JFIFMarkerSegment) seg;
2374 jfif.writeWithThumbs(ios, thumbnails, writer);
2375 if (iccProfile != null) {
2376 JFIFMarkerSegment.writeICC(iccProfile, ios);
2377 }
2378 } // Otherwise ignore it, as requested
2379 } else if (seg instanceof AdobeMarkerSegment) {
2380 if (ignoreAdobe == false) {
2381 if (newAdobeTransform != JPEG.ADOBE_IMPOSSIBLE) {
2382 AdobeMarkerSegment newAdobe =
2383 (AdobeMarkerSegment) seg.clone();
2384 newAdobe.transform = newAdobeTransform;
2385 newAdobe.write(ios);
2386 } else if (forceJFIF) {
2387 // If adobe isn't JFIF compatible, ignore it
2388 AdobeMarkerSegment adobe = (AdobeMarkerSegment) seg;
2389 if ((adobe.transform == JPEG.ADOBE_UNKNOWN)
2390 || (adobe.transform == JPEG.ADOBE_YCC)) {
2397 seg.write(ios);
2398 }
2399 } // Otherwise ignore it, as requested
2400 } else {
2401 seg.write(ios);
2402 }
2403 }
2404 }
2405
2406 //// End of writer support
2407
2408 public void reset() {
2409 if (resetSequence != null) { // Otherwise no need to reset
2410 markerSequence = resetSequence;
2411 resetSequence = null;
2412 }
2413 }
2414
2415 public void print() {
2416 for (int i = 0; i < markerSequence.size(); i++) {
2417 MarkerSegment seg = markerSequence.get(i);
2418 seg.print();
2419 }
2420 }
2421
2422 }
|