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 }
|