49 import java.util.Iterator;
50 import java.util.ListIterator;
51 import java.io.IOException;
52 import java.awt.color.ICC_Profile;
53 import java.awt.color.ICC_ColorSpace;
54 import java.awt.color.ColorSpace;
55 import java.awt.image.BufferedImage;
56 import java.awt.image.ColorModel;
57 import java.awt.Point;
58
59 /**
60 * Metadata for the JPEG plug-in.
61 */
62 public class JPEGMetadata extends IIOMetadata implements Cloneable {
63
64 //////// Private variables
65
66 private static final boolean debug = false;
67
68 /**
69 * A copy of <code>markerSequence</code>, created the first time the
70 * <code>markerSequence</code> is modified. This is used by reset
71 * to restore the original state.
72 */
73 private List<MarkerSegment> resetSequence = null;
74
75 /**
76 * Set to <code>true</code> when reading a thumbnail stored as
77 * JPEG. This is used to enforce the prohibition of JFIF thumbnails
78 * containing any JFIF marker segments, and to ensure generation of
79 * a correct native subtree during <code>getAsTree</code>.
80 */
81 private boolean inThumb = false;
82
83 /**
84 * Set by the chroma node construction method to signal the
85 * presence or absence of an alpha channel to the transparency
86 * node construction method. Used only when constructing a
87 * standard metadata tree.
88 */
89 private boolean hasAlpha;
90
91 //////// end of private variables
92
93 /////// Package-access variables
94
95 /**
96 * All data is a list of <code>MarkerSegment</code> objects.
97 * When accessing the list, use the tag to identify the particular
98 * subclass. Any JFIF marker segment must be the first element
99 * of the list if it is present, and any JFXX or APP2ICC marker
100 * segments are subordinate to the JFIF marker segment. This
101 * list is package visible so that the writer can access it.
102 * @see #MarkerSegment
103 */
104 List<MarkerSegment> markerSequence = new ArrayList<>();
105
106 /**
107 * Indicates whether this object represents stream or image
108 * metadata. Package-visible so the writer can see it.
109 */
110 final boolean isStream;
111
112 /////// End of package-access variables
113
114 /////// Constructors
115
116 /**
117 * Constructor containing code shared by other constructors.
118 */
119 JPEGMetadata(boolean isStream, boolean inThumb) {
120 super(true, // Supports standard format
121 JPEG.nativeImageMetadataFormatName, // and a native format
122 JPEG.nativeImageMetadataFormatClassName,
123 null, null); // No other formats
124 this.inThumb = inThumb;
125 // But if we are stream metadata, adjust the variables
126 this.isStream = isStream;
127 if (isStream) {
128 nativeMetadataFormatName = JPEG.nativeStreamMetadataFormatName;
129 nativeMetadataFormatClassName =
130 JPEG.nativeStreamMetadataFormatClassName;
131 }
132 }
133
134 /*
135 * Constructs a <code>JPEGMetadata</code> object by reading the
136 * contents of an <code>ImageInputStream</code>. Has package-only
137 * access.
138 *
139 * @param isStream A boolean indicating whether this object will be
140 * stream or image metadata.
141 * @param isThumb A boolean indicating whether this metadata object
142 * is for an image or for a thumbnail stored as JPEG.
143 * @param iis An <code>ImageInputStream</code> from which to read
144 * the metadata.
145 * @param reader The <code>JPEGImageReader</code> calling this
146 * constructor, to which warnings should be sent.
147 */
148 JPEGMetadata(boolean isStream,
149 boolean isThumb,
150 ImageInputStream iis,
151 JPEGImageReader reader) throws IOException {
152 this(isStream, isThumb);
153
154 JPEGBuffer buffer = new JPEGBuffer(iis);
155
156 buffer.loadBuf(0);
157
158 // The first three bytes should be FF, SOI, FF
159 if (((buffer.buf[0] & 0xff) != 0xff)
160 || ((buffer.buf[1] & 0xff) != JPEG.SOI)
161 || ((buffer.buf[2] & 0xff) != 0xff)) {
162 throw new IIOException ("Image format error");
163 }
164
165 boolean done = false;
348 markerSequence.add(newGuy);
349 if (debug) {
350 newGuy.print();
351 }
352 newGuy = null;
353 }
354 }
355
356 // Now that we've read up to the EOI, we need to push back
357 // whatever is left in the buffer, so that the next read
358 // in the native code will work.
359
360 buffer.pushBack();
361
362 if (!isConsistent()) {
363 throw new IIOException("Inconsistent metadata read from stream");
364 }
365 }
366
367 /**
368 * Constructs a default stream <code>JPEGMetadata</code> object appropriate
369 * for the given write parameters.
370 */
371 JPEGMetadata(ImageWriteParam param, JPEGImageWriter writer) {
372 this(true, false);
373
374 JPEGImageWriteParam jparam = null;
375
376 if ((param != null) && (param instanceof JPEGImageWriteParam)) {
377 jparam = (JPEGImageWriteParam) param;
378 if (!jparam.areTablesSet()) {
379 jparam = null;
380 }
381 }
382 if (jparam != null) {
383 markerSequence.add(new DQTMarkerSegment(jparam.getQTables()));
384 markerSequence.add
385 (new DHTMarkerSegment(jparam.getDCHuffmanTables(),
386 jparam.getACHuffmanTables()));
387 } else {
388 // default tables.
389 markerSequence.add(new DQTMarkerSegment(JPEG.getDefaultQTables()));
390 markerSequence.add(new DHTMarkerSegment(JPEG.getDefaultHuffmanTables(true),
391 JPEG.getDefaultHuffmanTables(false)));
392 }
393
394 // Defensive programming
395 if (!isConsistent()) {
396 throw new InternalError("Default stream metadata is inconsistent");
397 }
398 }
399
400 /**
401 * Constructs a default image <code>JPEGMetadata</code> object appropriate
402 * for the given image type and write parameters.
403 */
404 JPEGMetadata(ImageTypeSpecifier imageType,
405 ImageWriteParam param,
406 JPEGImageWriter writer) {
407 this(false, false);
408
409 boolean wantJFIF = true;
410 boolean wantAdobe = false;
411 int transform = JPEG.ADOBE_UNKNOWN;
412 boolean willSubsample = true;
413 boolean wantICC = false;
414 boolean wantProg = false;
415 boolean wantOptimized = false;
416 boolean wantExtended = false;
417 boolean wantQTables = true;
418 boolean wantHTables = true;
419 float quality = JPEG.DEFAULT_QUALITY;
420 byte[] componentIDs = { 1, 2, 3, 4};
421 int numComponents = 0;
2231 } else if (childName.equals("com")) {
2232 markerSequence.add(new COMMarkerSegment(node));
2233 } else if (childName.equals("app14Adobe")) {
2234 markerSequence.add(new AdobeMarkerSegment(node));
2235 } else if (childName.equals("unknown")) {
2236 markerSequence.add(new MarkerSegment(node));
2237 } else if (childName.equals("sof")) {
2238 markerSequence.add(new SOFMarkerSegment(node));
2239 } else if (childName.equals("sos")) {
2240 markerSequence.add(new SOSMarkerSegment(node));
2241 } else {
2242 throw new IIOInvalidTreeException("Invalid "
2243 + (isStream ? "stream " : "image ") + "child: "
2244 + childName, node);
2245 }
2246 }
2247 }
2248
2249 /**
2250 * Check that this metadata object is in a consistent state and
2251 * return <code>true</code> if it is or <code>false</code>
2252 * otherwise. All the constructors and modifiers should call
2253 * this method at the end to guarantee that the data is always
2254 * consistent, as the writer relies on this.
2255 */
2256 private boolean isConsistent() {
2257 SOFMarkerSegment sof =
2258 (SOFMarkerSegment) findMarkerSegment(SOFMarkerSegment.class,
2259 true);
2260 JFIFMarkerSegment jfif =
2261 (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class,
2262 true);
2263 AdobeMarkerSegment adobe =
2264 (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class,
2265 true);
2266 boolean retval = true;
2267 if (!isStream) {
2268 if (sof != null) {
2269 // SOF numBands = total scan bands
2270 int numSOFBands = sof.componentSpecs.length;
2271 int numScanBands = countScanBands();
|
49 import java.util.Iterator;
50 import java.util.ListIterator;
51 import java.io.IOException;
52 import java.awt.color.ICC_Profile;
53 import java.awt.color.ICC_ColorSpace;
54 import java.awt.color.ColorSpace;
55 import java.awt.image.BufferedImage;
56 import java.awt.image.ColorModel;
57 import java.awt.Point;
58
59 /**
60 * Metadata for the JPEG plug-in.
61 */
62 public class JPEGMetadata extends IIOMetadata implements Cloneable {
63
64 //////// Private variables
65
66 private static final boolean debug = false;
67
68 /**
69 * A copy of {@code markerSequence}, created the first time the
70 * {@code markerSequence} is modified. This is used by reset
71 * to restore the original state.
72 */
73 private List<MarkerSegment> resetSequence = null;
74
75 /**
76 * Set to {@code true} when reading a thumbnail stored as
77 * JPEG. This is used to enforce the prohibition of JFIF thumbnails
78 * containing any JFIF marker segments, and to ensure generation of
79 * a correct native subtree during {@code getAsTree}.
80 */
81 private boolean inThumb = false;
82
83 /**
84 * Set by the chroma node construction method to signal the
85 * presence or absence of an alpha channel to the transparency
86 * node construction method. Used only when constructing a
87 * standard metadata tree.
88 */
89 private boolean hasAlpha;
90
91 //////// end of private variables
92
93 /////// Package-access variables
94
95 /**
96 * All data is a list of {@code MarkerSegment} objects.
97 * When accessing the list, use the tag to identify the particular
98 * subclass. Any JFIF marker segment must be the first element
99 * of the list if it is present, and any JFXX or APP2ICC marker
100 * segments are subordinate to the JFIF marker segment. This
101 * list is package visible so that the writer can access it.
102 * @see #MarkerSegment
103 */
104 List<MarkerSegment> markerSequence = new ArrayList<>();
105
106 /**
107 * Indicates whether this object represents stream or image
108 * metadata. Package-visible so the writer can see it.
109 */
110 final boolean isStream;
111
112 /////// End of package-access variables
113
114 /////// Constructors
115
116 /**
117 * Constructor containing code shared by other constructors.
118 */
119 JPEGMetadata(boolean isStream, boolean inThumb) {
120 super(true, // Supports standard format
121 JPEG.nativeImageMetadataFormatName, // and a native format
122 JPEG.nativeImageMetadataFormatClassName,
123 null, null); // No other formats
124 this.inThumb = inThumb;
125 // But if we are stream metadata, adjust the variables
126 this.isStream = isStream;
127 if (isStream) {
128 nativeMetadataFormatName = JPEG.nativeStreamMetadataFormatName;
129 nativeMetadataFormatClassName =
130 JPEG.nativeStreamMetadataFormatClassName;
131 }
132 }
133
134 /*
135 * Constructs a {@code JPEGMetadata} object by reading the
136 * contents of an {@code ImageInputStream}. Has package-only
137 * access.
138 *
139 * @param isStream A boolean indicating whether this object will be
140 * stream or image metadata.
141 * @param isThumb A boolean indicating whether this metadata object
142 * is for an image or for a thumbnail stored as JPEG.
143 * @param iis An {@code ImageInputStream} from which to read
144 * the metadata.
145 * @param reader The {@code JPEGImageReader} calling this
146 * constructor, to which warnings should be sent.
147 */
148 JPEGMetadata(boolean isStream,
149 boolean isThumb,
150 ImageInputStream iis,
151 JPEGImageReader reader) throws IOException {
152 this(isStream, isThumb);
153
154 JPEGBuffer buffer = new JPEGBuffer(iis);
155
156 buffer.loadBuf(0);
157
158 // The first three bytes should be FF, SOI, FF
159 if (((buffer.buf[0] & 0xff) != 0xff)
160 || ((buffer.buf[1] & 0xff) != JPEG.SOI)
161 || ((buffer.buf[2] & 0xff) != 0xff)) {
162 throw new IIOException ("Image format error");
163 }
164
165 boolean done = false;
348 markerSequence.add(newGuy);
349 if (debug) {
350 newGuy.print();
351 }
352 newGuy = null;
353 }
354 }
355
356 // Now that we've read up to the EOI, we need to push back
357 // whatever is left in the buffer, so that the next read
358 // in the native code will work.
359
360 buffer.pushBack();
361
362 if (!isConsistent()) {
363 throw new IIOException("Inconsistent metadata read from stream");
364 }
365 }
366
367 /**
368 * Constructs a default stream {@code JPEGMetadata} object appropriate
369 * for the given write parameters.
370 */
371 JPEGMetadata(ImageWriteParam param, JPEGImageWriter writer) {
372 this(true, false);
373
374 JPEGImageWriteParam jparam = null;
375
376 if ((param != null) && (param instanceof JPEGImageWriteParam)) {
377 jparam = (JPEGImageWriteParam) param;
378 if (!jparam.areTablesSet()) {
379 jparam = null;
380 }
381 }
382 if (jparam != null) {
383 markerSequence.add(new DQTMarkerSegment(jparam.getQTables()));
384 markerSequence.add
385 (new DHTMarkerSegment(jparam.getDCHuffmanTables(),
386 jparam.getACHuffmanTables()));
387 } else {
388 // default tables.
389 markerSequence.add(new DQTMarkerSegment(JPEG.getDefaultQTables()));
390 markerSequence.add(new DHTMarkerSegment(JPEG.getDefaultHuffmanTables(true),
391 JPEG.getDefaultHuffmanTables(false)));
392 }
393
394 // Defensive programming
395 if (!isConsistent()) {
396 throw new InternalError("Default stream metadata is inconsistent");
397 }
398 }
399
400 /**
401 * Constructs a default image {@code JPEGMetadata} object appropriate
402 * for the given image type and write parameters.
403 */
404 JPEGMetadata(ImageTypeSpecifier imageType,
405 ImageWriteParam param,
406 JPEGImageWriter writer) {
407 this(false, false);
408
409 boolean wantJFIF = true;
410 boolean wantAdobe = false;
411 int transform = JPEG.ADOBE_UNKNOWN;
412 boolean willSubsample = true;
413 boolean wantICC = false;
414 boolean wantProg = false;
415 boolean wantOptimized = false;
416 boolean wantExtended = false;
417 boolean wantQTables = true;
418 boolean wantHTables = true;
419 float quality = JPEG.DEFAULT_QUALITY;
420 byte[] componentIDs = { 1, 2, 3, 4};
421 int numComponents = 0;
2231 } else if (childName.equals("com")) {
2232 markerSequence.add(new COMMarkerSegment(node));
2233 } else if (childName.equals("app14Adobe")) {
2234 markerSequence.add(new AdobeMarkerSegment(node));
2235 } else if (childName.equals("unknown")) {
2236 markerSequence.add(new MarkerSegment(node));
2237 } else if (childName.equals("sof")) {
2238 markerSequence.add(new SOFMarkerSegment(node));
2239 } else if (childName.equals("sos")) {
2240 markerSequence.add(new SOSMarkerSegment(node));
2241 } else {
2242 throw new IIOInvalidTreeException("Invalid "
2243 + (isStream ? "stream " : "image ") + "child: "
2244 + childName, node);
2245 }
2246 }
2247 }
2248
2249 /**
2250 * Check that this metadata object is in a consistent state and
2251 * return {@code true} if it is or {@code false}
2252 * otherwise. All the constructors and modifiers should call
2253 * this method at the end to guarantee that the data is always
2254 * consistent, as the writer relies on this.
2255 */
2256 private boolean isConsistent() {
2257 SOFMarkerSegment sof =
2258 (SOFMarkerSegment) findMarkerSegment(SOFMarkerSegment.class,
2259 true);
2260 JFIFMarkerSegment jfif =
2261 (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class,
2262 true);
2263 AdobeMarkerSegment adobe =
2264 (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class,
2265 true);
2266 boolean retval = true;
2267 if (!isStream) {
2268 if (sof != null) {
2269 // SOF numBands = total scan bands
2270 int numSOFBands = sof.componentSpecs.length;
2271 int numScanBands = countScanBands();
|