59 import org.w3c.dom.Node;
60 import org.w3c.dom.NodeList;
61 import org.w3c.dom.NamedNodeMap;
62
63 /**
64 * A JFIF (JPEG File Interchange Format) APP0 (Application-Specific)
65 * marker segment. Inner classes are included for JFXX extension
66 * marker segments, for different varieties of thumbnails, and for
67 * ICC Profile APP2 marker segments. Any of these secondary types
68 * that occur are kept as members of a single JFIFMarkerSegment object.
69 */
70 class JFIFMarkerSegment extends MarkerSegment {
71 int majorVersion;
72 int minorVersion;
73 int resUnits;
74 int Xdensity;
75 int Ydensity;
76 int thumbWidth;
77 int thumbHeight;
78 JFIFThumbRGB thumb = null; // If present
79 ArrayList extSegments = new ArrayList();
80 ICCMarkerSegment iccSegment = null; // optional ICC
81 private static final int THUMB_JPEG = 0x10;
82 private static final int THUMB_PALETTE = 0x11;
83 private static final int THUMB_UNASSIGNED = 0x12;
84 private static final int THUMB_RGB = 0x13;
85 private static final int DATA_SIZE = 14;
86 private static final int ID_SIZE = 5;
87 private final int MAX_THUMB_WIDTH = 255;
88 private final int MAX_THUMB_HEIGHT = 255;
89
90 private final boolean debug = false;
91
92 /**
93 * Set to <code>true</code> when reading the chunks of an
94 * ICC profile. All chunks are consolidated to create a single
95 * "segment" containing all the chunks. This flag is a state
96 * variable identifying whether to construct a new segment or
97 * append to an old one.
98 */
99 private boolean inICC = false;
137 Ydensity |= buffer.buf[buffer.bufPtr++] & 0xff;
138 thumbWidth = buffer.buf[buffer.bufPtr++] & 0xff;
139 thumbHeight = buffer.buf[buffer.bufPtr++] & 0xff;
140 buffer.bufAvail -= DATA_SIZE;
141 if (thumbWidth > 0) {
142 thumb = new JFIFThumbRGB(buffer, thumbWidth, thumbHeight);
143 }
144 }
145
146 /**
147 * Constructs a JFIF header from a DOM Node.
148 */
149 JFIFMarkerSegment(Node node) throws IIOInvalidTreeException {
150 this();
151 updateFromNativeNode(node, true);
152 }
153
154 /**
155 * Returns a deep-copy clone of this object.
156 */
157 protected Object clone() {
158 JFIFMarkerSegment newGuy = (JFIFMarkerSegment) super.clone();
159 if (!extSegments.isEmpty()) { // Clone the list with a deep copy
160 newGuy.extSegments = new ArrayList();
161 for (Iterator iter = extSegments.iterator(); iter.hasNext();) {
162 JFIFExtensionMarkerSegment jfxx =
163 (JFIFExtensionMarkerSegment) iter.next();
164 newGuy.extSegments.add(jfxx.clone());
165 }
166 }
167 if (iccSegment != null) {
168 newGuy.iccSegment = (ICCMarkerSegment) iccSegment.clone();
169 }
170 return newGuy;
171 }
172
173 /**
174 * Add an JFXX extension marker segment from the stream wrapped
175 * in the JPEGBuffer to the list of extension segments.
176 */
177 void addJFXX(JPEGBuffer buffer, JPEGImageReader reader)
178 throws IOException {
179 extSegments.add(new JFIFExtensionMarkerSegment(buffer, reader));
180 }
181
182 /**
183 * Adds an ICC Profile APP2 segment from the stream wrapped
184 * in the JPEGBuffer.
185 */
186 void addICC(JPEGBuffer buffer) throws IOException {
187 if (inICC == false) {
188 if (iccSegment != null) {
213 }
214 iccSegment = new ICCMarkerSegment(cs);
215 }
216
217 /**
218 * Returns a tree of DOM nodes representing this object and any
219 * subordinate JFXX extension or ICC Profile segments.
220 */
221 IIOMetadataNode getNativeNode() {
222 IIOMetadataNode node = new IIOMetadataNode("app0JFIF");
223 node.setAttribute("majorVersion", Integer.toString(majorVersion));
224 node.setAttribute("minorVersion", Integer.toString(minorVersion));
225 node.setAttribute("resUnits", Integer.toString(resUnits));
226 node.setAttribute("Xdensity", Integer.toString(Xdensity));
227 node.setAttribute("Ydensity", Integer.toString(Ydensity));
228 node.setAttribute("thumbWidth", Integer.toString(thumbWidth));
229 node.setAttribute("thumbHeight", Integer.toString(thumbHeight));
230 if (!extSegments.isEmpty()) {
231 IIOMetadataNode JFXXnode = new IIOMetadataNode("JFXX");
232 node.appendChild(JFXXnode);
233 for (Iterator iter = extSegments.iterator(); iter.hasNext();) {
234 JFIFExtensionMarkerSegment seg =
235 (JFIFExtensionMarkerSegment) iter.next();
236 JFXXnode.appendChild(seg.getNativeNode());
237 }
238 }
239 if (iccSegment != null) {
240 node.appendChild(iccSegment.getNativeNode());
241 }
242
243 return node;
244 }
245
246 /**
247 * Updates the data in this object from the given DOM Node tree.
248 * If fromScratch is true, this object is being constructed.
249 * Otherwise an existing object is being modified.
250 * Throws an IIOInvalidTreeException if the tree is invalid in
251 * any way.
252 */
253 void updateFromNativeNode(Node node, boolean fromScratch)
254 throws IIOInvalidTreeException {
255 // none of the attributes are required
295 }
296 }
297 if (name.equals("app2ICC")) {
298 if ((iccSegment != null) && fromScratch) {
299 throw new IIOInvalidTreeException
300 ("> 1 ICC APP2 Marker Segment not supported", node);
301 }
302 iccSegment = new ICCMarkerSegment(child);
303 }
304 }
305 }
306 }
307
308 int getThumbnailWidth(int index) {
309 if (thumb != null) {
310 if (index == 0) {
311 return thumb.getWidth();
312 }
313 index--;
314 }
315 JFIFExtensionMarkerSegment jfxx =
316 (JFIFExtensionMarkerSegment) extSegments.get(index);
317 return jfxx.thumb.getWidth();
318 }
319
320 int getThumbnailHeight(int index) {
321 if (thumb != null) {
322 if (index == 0) {
323 return thumb.getHeight();
324 }
325 index--;
326 }
327 JFIFExtensionMarkerSegment jfxx =
328 (JFIFExtensionMarkerSegment) extSegments.get(index);
329 return jfxx.thumb.getHeight();
330 }
331
332 BufferedImage getThumbnail(ImageInputStream iis,
333 int index,
334 JPEGImageReader reader) throws IOException {
335 reader.thumbnailStarted(index);
336 BufferedImage ret = null;
337 if ((thumb != null) && (index == 0)) {
338 ret = thumb.getThumbnail(iis, reader);
339 } else {
340 if (thumb != null) {
341 index--;
342 }
343 JFIFExtensionMarkerSegment jfxx =
344 (JFIFExtensionMarkerSegment) extSegments.get(index);
345 ret = jfxx.thumb.getThumbnail(iis, reader);
346 }
347 reader.thumbnailComplete();
348 return ret;
349 }
350
351
352 /**
353 * Writes the data for this segment to the stream in
354 * valid JPEG format. Assumes that there will be no thumbnail.
355 */
356 void write(ImageOutputStream ios,
357 JPEGImageWriter writer) throws IOException {
358 // No thumbnail
359 write(ios, null, writer);
360 }
361
362 /**
363 * Writes the data for this segment to the stream in
364 * valid JPEG format. The length written takes the thumbnail
419 }
420 for (int i = 0; i < thumbData.length; i++) {
421 ios.write(thumbData[i]);
422 if ((i > progInterval) && (i % progInterval == 0)) {
423 writer.thumbnailProgress
424 (((float) i * 100) / ((float) thumbData.length));
425 }
426 }
427 }
428
429 /**
430 * Write out this JFIF Marker Segment, including a thumbnail or
431 * appending a series of JFXX Marker Segments, as appropriate.
432 * Warnings and progress reports are sent to the writer argument.
433 * The list of thumbnails is matched to the list of JFXX extension
434 * segments, if any, in order to determine how to encode the
435 * thumbnails. If there are more thumbnails than metadata segments,
436 * default encoding is used for the extra thumbnails.
437 */
438 void writeWithThumbs(ImageOutputStream ios,
439 List thumbnails,
440 JPEGImageWriter writer) throws IOException {
441 if (thumbnails != null) {
442 JFIFExtensionMarkerSegment jfxx = null;
443 if (thumbnails.size() == 1) {
444 if (!extSegments.isEmpty()) {
445 jfxx = (JFIFExtensionMarkerSegment) extSegments.get(0);
446 }
447 writeThumb(ios,
448 (BufferedImage) thumbnails.get(0),
449 jfxx,
450 0,
451 true,
452 writer);
453 } else {
454 // All others write as separate JFXX segments
455 write(ios, writer); // Just the header without any thumbnail
456 for (int i = 0; i < thumbnails.size(); i++) {
457 jfxx = null;
458 if (i < extSegments.size()) {
459 jfxx = (JFIFExtensionMarkerSegment) extSegments.get(i);
460 }
461 writeThumb(ios,
462 (BufferedImage) thumbnails.get(i),
463 jfxx,
464 i,
465 false,
466 writer);
467 }
468 }
469 } else { // No thumbnails
470 write(ios, writer);
471 }
472
473 }
474
475 private void writeThumb(ImageOutputStream ios,
476 BufferedImage thumb,
477 JFIFExtensionMarkerSegment jfxx,
478 int index,
479 boolean onlyOne,
588 */
589 private static BufferedImage expandGrayThumb(BufferedImage thumb) {
590 BufferedImage ret = new BufferedImage(thumb.getWidth(),
591 thumb.getHeight(),
592 BufferedImage.TYPE_INT_RGB);
593 Graphics g = ret.getGraphics();
594 g.drawImage(thumb, 0, 0, null);
595 return ret;
596 }
597
598 /**
599 * Writes out a default JFIF marker segment to the given
600 * output stream. If <code>thumbnails</code> is not <code>null</code>,
601 * writes out the set of thumbnail images as JFXX marker segments, or
602 * incorporated into the JFIF segment if appropriate.
603 * If <code>iccProfile</code> is not <code>null</code>,
604 * writes out the profile after the JFIF segment using as many APP2
605 * marker segments as necessary.
606 */
607 static void writeDefaultJFIF(ImageOutputStream ios,
608 List thumbnails,
609 ICC_Profile iccProfile,
610 JPEGImageWriter writer)
611 throws IOException {
612
613 JFIFMarkerSegment jfif = new JFIFMarkerSegment();
614 jfif.writeWithThumbs(ios, thumbnails, writer);
615 if (iccProfile != null) {
616 writeICC(iccProfile, ios);
617 }
618 }
619
620 /**
621 * Prints out the contents of this object to System.out for debugging.
622 */
623 void print() {
624 printTag("JFIF");
625 System.out.print("Version ");
626 System.out.print(majorVersion);
627 System.out.println(".0"
628 + Integer.toString(minorVersion));
629 System.out.print("Resolution units: ");
630 System.out.println(resUnits);
631 System.out.print("X density: ");
632 System.out.println(Xdensity);
633 System.out.print("Y density: ");
634 System.out.println(Ydensity);
635 System.out.print("Thumbnail Width: ");
636 System.out.println(thumbWidth);
637 System.out.print("Thumbnail Height: ");
638 System.out.println(thumbHeight);
639 if (!extSegments.isEmpty()) {
640 for (Iterator iter = extSegments.iterator(); iter.hasNext();) {
641 JFIFExtensionMarkerSegment extSegment =
642 (JFIFExtensionMarkerSegment) iter.next();
643 extSegment.print();
644 }
645 }
646 if (iccSegment != null) {
647 iccSegment.print();
648 }
649 }
650
651 /**
652 * A JFIF extension APP0 marker segment.
653 */
654 class JFIFExtensionMarkerSegment extends MarkerSegment {
655 int code;
656 JFIFThumb thumb;
657 private static final int DATA_SIZE = 6;
658 private static final int ID_SIZE = 5;
659
660 JFIFExtensionMarkerSegment(JPEGBuffer buffer, JPEGImageReader reader)
661 throws IOException {
662
751
752 void setThumbnail(BufferedImage thumbnail) {
753 try {
754 switch (code) {
755 case THUMB_PALETTE:
756 thumb = new JFIFThumbPalette(thumbnail);
757 break;
758 case THUMB_RGB:
759 thumb = new JFIFThumbRGB(thumbnail);
760 break;
761 case THUMB_JPEG:
762 thumb = new JFIFThumbJPEG(thumbnail);
763 break;
764 }
765 } catch (IllegalThumbException e) {
766 // Should never happen
767 throw new InternalError("Illegal thumb in setThumbnail!", e);
768 }
769 }
770
771 protected Object clone() {
772 JFIFExtensionMarkerSegment newGuy =
773 (JFIFExtensionMarkerSegment) super.clone();
774 if (thumb != null) {
775 newGuy.thumb = (JFIFThumb) thumb.clone();
776 }
777 return newGuy;
778 }
779
780 IIOMetadataNode getNativeNode() {
781 IIOMetadataNode node = new IIOMetadataNode("app0JFXX");
782 node.setAttribute("extensionCode", Integer.toString(code));
783 node.appendChild(thumb.getNativeNode());
784 return node;
785 }
786
787 void write(ImageOutputStream ios,
788 JPEGImageWriter writer) throws IOException {
789 length = LENGTH_SIZE + DATA_SIZE + thumb.getLength();
790 writeTag(ios);
791 byte [] id = {0x4A, 0x46, 0x58, 0x58, 0x00};
1356 ios.write(0xff);
1357 ios.write(JPEG.APP2);
1358 MarkerSegment.write2bytes(ios, segLength);
1359 byte [] id = ID.getBytes("US-ASCII");
1360 ios.write(id);
1361 ios.write(0); // Null-terminate the string
1362 ios.write(chunkNum++);
1363 ios.write(numChunks);
1364 ios.write(data, offset, dataLength);
1365 offset += dataLength;
1366 }
1367 }
1368
1369 /**
1370 * An APP2 marker segment containing an ICC profile. In the stream
1371 * a profile larger than 64K is broken up into a series of chunks.
1372 * This inner class represents the complete profile as a single object,
1373 * combining chunks as necessary.
1374 */
1375 class ICCMarkerSegment extends MarkerSegment {
1376 ArrayList chunks = null;
1377 byte [] profile = null; // The complete profile when it's fully read
1378 // May remain null when writing
1379 private static final int ID_SIZE = 12;
1380 int chunksRead;
1381 int numChunks;
1382
1383 ICCMarkerSegment(ICC_ColorSpace cs) {
1384 super(JPEG.APP2);
1385 chunks = null;
1386 chunksRead = 0;
1387 numChunks = 0;
1388 profile = cs.getProfile().getData();
1389 }
1390
1391 ICCMarkerSegment(JPEGBuffer buffer) throws IOException {
1392 super(buffer); // gets whole segment or fills the buffer
1393 if (debug) {
1394 System.out.println("Creating new ICC segment");
1395 }
1396 buffer.bufPtr += ID_SIZE; // Skip the id
1411 throw new IIOException
1412 ("Image format Error; chunk num > num chunks");
1413 }
1414
1415 // if there are no more chunks, set up the data
1416 if (numChunks == 1) {
1417 // reduce the stored length by the two chunk numbering bytes
1418 length -= 2;
1419 profile = new byte[length];
1420 buffer.bufPtr += 2;
1421 buffer.bufAvail-=2;
1422 buffer.readData(profile);
1423 inICC = false;
1424 } else {
1425 // If we store them away, include the chunk numbering bytes
1426 byte [] profileData = new byte[length];
1427 // Now reduce the stored length by the
1428 // two chunk numbering bytes
1429 length -= 2;
1430 buffer.readData(profileData);
1431 chunks = new ArrayList();
1432 chunks.add(profileData);
1433 chunksRead = 1;
1434 inICC = true;
1435 }
1436 }
1437
1438 ICCMarkerSegment(Node node) throws IIOInvalidTreeException {
1439 super(JPEG.APP2);
1440 if (node instanceof IIOMetadataNode) {
1441 IIOMetadataNode ourNode = (IIOMetadataNode) node;
1442 ICC_Profile prof = (ICC_Profile) ourNode.getUserObject();
1443 if (prof != null) { // May be null
1444 profile = prof.getData();
1445 }
1446 }
1447 }
1448
1449 protected Object clone () {
1450 ICCMarkerSegment newGuy = (ICCMarkerSegment) super.clone();
1451 if (profile != null) {
1452 newGuy.profile = profile.clone();
1453 }
1454 return newGuy;
1455 }
1456
1457 boolean addData(JPEGBuffer buffer) throws IOException {
1458 if (debug) {
1459 System.out.println("Adding to ICC segment");
1460 }
1461 // skip the tag
1462 buffer.bufPtr++;
1463 buffer.bufAvail--;
1464 // Get the length, but not in length
1465 int dataLen = (buffer.buf[buffer.bufPtr++] & 0xff) << 8;
1466 dataLen |= buffer.buf[buffer.bufPtr++] & 0xff;
1467 buffer.bufAvail -= 2;
1468 // Don't include length itself
1469 dataLen -= 2;
1501 buffer.readData(profileData);
1502 chunks.add(profileData);
1503 length += dataLen;
1504 chunksRead++;
1505 if (chunksRead < numChunks) {
1506 inICC = true;
1507 } else {
1508 if (debug) {
1509 System.out.println("Completing profile; total length is "
1510 + length);
1511 }
1512 // create an array for the whole thing
1513 profile = new byte[length];
1514 // copy the existing chunks, releasing them
1515 // Note that they may be out of order
1516
1517 int index = 0;
1518 for (int i = 1; i <= numChunks; i++) {
1519 boolean foundIt = false;
1520 for (int chunk = 0; chunk < chunks.size(); chunk++) {
1521 byte [] chunkData = (byte []) chunks.get(chunk);
1522 if (chunkData[0] == i) { // Right one
1523 System.arraycopy(chunkData, 2,
1524 profile, index,
1525 chunkData.length-2);
1526 index += chunkData.length-2;
1527 foundIt = true;
1528 }
1529 }
1530 if (foundIt == false) {
1531 throw new IIOException
1532 ("Image Format Error: Missing ICC chunk num " + i);
1533 }
1534 }
1535
1536 chunks = null;
1537 chunksRead = 0;
1538 numChunks = 0;
1539 inICC = false;
1540 retval = true;
1541 }
|
59 import org.w3c.dom.Node;
60 import org.w3c.dom.NodeList;
61 import org.w3c.dom.NamedNodeMap;
62
63 /**
64 * A JFIF (JPEG File Interchange Format) APP0 (Application-Specific)
65 * marker segment. Inner classes are included for JFXX extension
66 * marker segments, for different varieties of thumbnails, and for
67 * ICC Profile APP2 marker segments. Any of these secondary types
68 * that occur are kept as members of a single JFIFMarkerSegment object.
69 */
70 class JFIFMarkerSegment extends MarkerSegment {
71 int majorVersion;
72 int minorVersion;
73 int resUnits;
74 int Xdensity;
75 int Ydensity;
76 int thumbWidth;
77 int thumbHeight;
78 JFIFThumbRGB thumb = null; // If present
79 ArrayList<JFIFExtensionMarkerSegment> extSegments = new ArrayList<>();
80 ICCMarkerSegment iccSegment = null; // optional ICC
81 private static final int THUMB_JPEG = 0x10;
82 private static final int THUMB_PALETTE = 0x11;
83 private static final int THUMB_UNASSIGNED = 0x12;
84 private static final int THUMB_RGB = 0x13;
85 private static final int DATA_SIZE = 14;
86 private static final int ID_SIZE = 5;
87 private final int MAX_THUMB_WIDTH = 255;
88 private final int MAX_THUMB_HEIGHT = 255;
89
90 private final boolean debug = false;
91
92 /**
93 * Set to <code>true</code> when reading the chunks of an
94 * ICC profile. All chunks are consolidated to create a single
95 * "segment" containing all the chunks. This flag is a state
96 * variable identifying whether to construct a new segment or
97 * append to an old one.
98 */
99 private boolean inICC = false;
137 Ydensity |= buffer.buf[buffer.bufPtr++] & 0xff;
138 thumbWidth = buffer.buf[buffer.bufPtr++] & 0xff;
139 thumbHeight = buffer.buf[buffer.bufPtr++] & 0xff;
140 buffer.bufAvail -= DATA_SIZE;
141 if (thumbWidth > 0) {
142 thumb = new JFIFThumbRGB(buffer, thumbWidth, thumbHeight);
143 }
144 }
145
146 /**
147 * Constructs a JFIF header from a DOM Node.
148 */
149 JFIFMarkerSegment(Node node) throws IIOInvalidTreeException {
150 this();
151 updateFromNativeNode(node, true);
152 }
153
154 /**
155 * Returns a deep-copy clone of this object.
156 */
157 protected JFIFMarkerSegment clone() {
158 JFIFMarkerSegment newGuy = (JFIFMarkerSegment) super.clone();
159 if (!extSegments.isEmpty()) { // Clone the list with a deep copy
160 newGuy.extSegments = new ArrayList<>();
161 for (Iterator<JFIFExtensionMarkerSegment> iter = extSegments.iterator(); iter.hasNext();) {
162 JFIFExtensionMarkerSegment jfxx = iter.next();
163 newGuy.extSegments.add(jfxx.clone());
164 }
165 }
166 if (iccSegment != null) {
167 newGuy.iccSegment = iccSegment.clone();
168 }
169 return newGuy;
170 }
171
172 /**
173 * Add an JFXX extension marker segment from the stream wrapped
174 * in the JPEGBuffer to the list of extension segments.
175 */
176 void addJFXX(JPEGBuffer buffer, JPEGImageReader reader)
177 throws IOException {
178 extSegments.add(new JFIFExtensionMarkerSegment(buffer, reader));
179 }
180
181 /**
182 * Adds an ICC Profile APP2 segment from the stream wrapped
183 * in the JPEGBuffer.
184 */
185 void addICC(JPEGBuffer buffer) throws IOException {
186 if (inICC == false) {
187 if (iccSegment != null) {
212 }
213 iccSegment = new ICCMarkerSegment(cs);
214 }
215
216 /**
217 * Returns a tree of DOM nodes representing this object and any
218 * subordinate JFXX extension or ICC Profile segments.
219 */
220 IIOMetadataNode getNativeNode() {
221 IIOMetadataNode node = new IIOMetadataNode("app0JFIF");
222 node.setAttribute("majorVersion", Integer.toString(majorVersion));
223 node.setAttribute("minorVersion", Integer.toString(minorVersion));
224 node.setAttribute("resUnits", Integer.toString(resUnits));
225 node.setAttribute("Xdensity", Integer.toString(Xdensity));
226 node.setAttribute("Ydensity", Integer.toString(Ydensity));
227 node.setAttribute("thumbWidth", Integer.toString(thumbWidth));
228 node.setAttribute("thumbHeight", Integer.toString(thumbHeight));
229 if (!extSegments.isEmpty()) {
230 IIOMetadataNode JFXXnode = new IIOMetadataNode("JFXX");
231 node.appendChild(JFXXnode);
232 for (Iterator<JFIFExtensionMarkerSegment> iter = extSegments.iterator(); iter.hasNext();) {
233 JFIFExtensionMarkerSegment seg = iter.next();
234 JFXXnode.appendChild(seg.getNativeNode());
235 }
236 }
237 if (iccSegment != null) {
238 node.appendChild(iccSegment.getNativeNode());
239 }
240
241 return node;
242 }
243
244 /**
245 * Updates the data in this object from the given DOM Node tree.
246 * If fromScratch is true, this object is being constructed.
247 * Otherwise an existing object is being modified.
248 * Throws an IIOInvalidTreeException if the tree is invalid in
249 * any way.
250 */
251 void updateFromNativeNode(Node node, boolean fromScratch)
252 throws IIOInvalidTreeException {
253 // none of the attributes are required
293 }
294 }
295 if (name.equals("app2ICC")) {
296 if ((iccSegment != null) && fromScratch) {
297 throw new IIOInvalidTreeException
298 ("> 1 ICC APP2 Marker Segment not supported", node);
299 }
300 iccSegment = new ICCMarkerSegment(child);
301 }
302 }
303 }
304 }
305
306 int getThumbnailWidth(int index) {
307 if (thumb != null) {
308 if (index == 0) {
309 return thumb.getWidth();
310 }
311 index--;
312 }
313 JFIFExtensionMarkerSegment jfxx = extSegments.get(index);
314 return jfxx.thumb.getWidth();
315 }
316
317 int getThumbnailHeight(int index) {
318 if (thumb != null) {
319 if (index == 0) {
320 return thumb.getHeight();
321 }
322 index--;
323 }
324 JFIFExtensionMarkerSegment jfxx = extSegments.get(index);
325 return jfxx.thumb.getHeight();
326 }
327
328 BufferedImage getThumbnail(ImageInputStream iis,
329 int index,
330 JPEGImageReader reader) throws IOException {
331 reader.thumbnailStarted(index);
332 BufferedImage ret = null;
333 if ((thumb != null) && (index == 0)) {
334 ret = thumb.getThumbnail(iis, reader);
335 } else {
336 if (thumb != null) {
337 index--;
338 }
339 JFIFExtensionMarkerSegment jfxx = extSegments.get(index);
340 ret = jfxx.thumb.getThumbnail(iis, reader);
341 }
342 reader.thumbnailComplete();
343 return ret;
344 }
345
346
347 /**
348 * Writes the data for this segment to the stream in
349 * valid JPEG format. Assumes that there will be no thumbnail.
350 */
351 void write(ImageOutputStream ios,
352 JPEGImageWriter writer) throws IOException {
353 // No thumbnail
354 write(ios, null, writer);
355 }
356
357 /**
358 * Writes the data for this segment to the stream in
359 * valid JPEG format. The length written takes the thumbnail
414 }
415 for (int i = 0; i < thumbData.length; i++) {
416 ios.write(thumbData[i]);
417 if ((i > progInterval) && (i % progInterval == 0)) {
418 writer.thumbnailProgress
419 (((float) i * 100) / ((float) thumbData.length));
420 }
421 }
422 }
423
424 /**
425 * Write out this JFIF Marker Segment, including a thumbnail or
426 * appending a series of JFXX Marker Segments, as appropriate.
427 * Warnings and progress reports are sent to the writer argument.
428 * The list of thumbnails is matched to the list of JFXX extension
429 * segments, if any, in order to determine how to encode the
430 * thumbnails. If there are more thumbnails than metadata segments,
431 * default encoding is used for the extra thumbnails.
432 */
433 void writeWithThumbs(ImageOutputStream ios,
434 List<? extends BufferedImage> thumbnails,
435 JPEGImageWriter writer) throws IOException {
436 if (thumbnails != null) {
437 JFIFExtensionMarkerSegment jfxx = null;
438 if (thumbnails.size() == 1) {
439 if (!extSegments.isEmpty()) {
440 jfxx = extSegments.get(0);
441 }
442 writeThumb(ios,
443 (BufferedImage) thumbnails.get(0),
444 jfxx,
445 0,
446 true,
447 writer);
448 } else {
449 // All others write as separate JFXX segments
450 write(ios, writer); // Just the header without any thumbnail
451 for (int i = 0; i < thumbnails.size(); i++) {
452 jfxx = null;
453 if (i < extSegments.size()) {
454 jfxx = extSegments.get(i);
455 }
456 writeThumb(ios,
457 (BufferedImage) thumbnails.get(i),
458 jfxx,
459 i,
460 false,
461 writer);
462 }
463 }
464 } else { // No thumbnails
465 write(ios, writer);
466 }
467
468 }
469
470 private void writeThumb(ImageOutputStream ios,
471 BufferedImage thumb,
472 JFIFExtensionMarkerSegment jfxx,
473 int index,
474 boolean onlyOne,
583 */
584 private static BufferedImage expandGrayThumb(BufferedImage thumb) {
585 BufferedImage ret = new BufferedImage(thumb.getWidth(),
586 thumb.getHeight(),
587 BufferedImage.TYPE_INT_RGB);
588 Graphics g = ret.getGraphics();
589 g.drawImage(thumb, 0, 0, null);
590 return ret;
591 }
592
593 /**
594 * Writes out a default JFIF marker segment to the given
595 * output stream. If <code>thumbnails</code> is not <code>null</code>,
596 * writes out the set of thumbnail images as JFXX marker segments, or
597 * incorporated into the JFIF segment if appropriate.
598 * If <code>iccProfile</code> is not <code>null</code>,
599 * writes out the profile after the JFIF segment using as many APP2
600 * marker segments as necessary.
601 */
602 static void writeDefaultJFIF(ImageOutputStream ios,
603 List<? extends BufferedImage> thumbnails,
604 ICC_Profile iccProfile,
605 JPEGImageWriter writer)
606 throws IOException {
607
608 JFIFMarkerSegment jfif = new JFIFMarkerSegment();
609 jfif.writeWithThumbs(ios, thumbnails, writer);
610 if (iccProfile != null) {
611 writeICC(iccProfile, ios);
612 }
613 }
614
615 /**
616 * Prints out the contents of this object to System.out for debugging.
617 */
618 void print() {
619 printTag("JFIF");
620 System.out.print("Version ");
621 System.out.print(majorVersion);
622 System.out.println(".0"
623 + Integer.toString(minorVersion));
624 System.out.print("Resolution units: ");
625 System.out.println(resUnits);
626 System.out.print("X density: ");
627 System.out.println(Xdensity);
628 System.out.print("Y density: ");
629 System.out.println(Ydensity);
630 System.out.print("Thumbnail Width: ");
631 System.out.println(thumbWidth);
632 System.out.print("Thumbnail Height: ");
633 System.out.println(thumbHeight);
634 if (!extSegments.isEmpty()) {
635 for (Iterator<JFIFExtensionMarkerSegment> iter = extSegments.iterator(); iter.hasNext();) {
636 JFIFExtensionMarkerSegment extSegment = iter.next();
637 extSegment.print();
638 }
639 }
640 if (iccSegment != null) {
641 iccSegment.print();
642 }
643 }
644
645 /**
646 * A JFIF extension APP0 marker segment.
647 */
648 class JFIFExtensionMarkerSegment extends MarkerSegment {
649 int code;
650 JFIFThumb thumb;
651 private static final int DATA_SIZE = 6;
652 private static final int ID_SIZE = 5;
653
654 JFIFExtensionMarkerSegment(JPEGBuffer buffer, JPEGImageReader reader)
655 throws IOException {
656
745
746 void setThumbnail(BufferedImage thumbnail) {
747 try {
748 switch (code) {
749 case THUMB_PALETTE:
750 thumb = new JFIFThumbPalette(thumbnail);
751 break;
752 case THUMB_RGB:
753 thumb = new JFIFThumbRGB(thumbnail);
754 break;
755 case THUMB_JPEG:
756 thumb = new JFIFThumbJPEG(thumbnail);
757 break;
758 }
759 } catch (IllegalThumbException e) {
760 // Should never happen
761 throw new InternalError("Illegal thumb in setThumbnail!", e);
762 }
763 }
764
765 protected JFIFExtensionMarkerSegment clone() {
766 JFIFExtensionMarkerSegment newGuy =
767 (JFIFExtensionMarkerSegment) super.clone();
768 if (thumb != null) {
769 newGuy.thumb = (JFIFThumb) thumb.clone();
770 }
771 return newGuy;
772 }
773
774 IIOMetadataNode getNativeNode() {
775 IIOMetadataNode node = new IIOMetadataNode("app0JFXX");
776 node.setAttribute("extensionCode", Integer.toString(code));
777 node.appendChild(thumb.getNativeNode());
778 return node;
779 }
780
781 void write(ImageOutputStream ios,
782 JPEGImageWriter writer) throws IOException {
783 length = LENGTH_SIZE + DATA_SIZE + thumb.getLength();
784 writeTag(ios);
785 byte [] id = {0x4A, 0x46, 0x58, 0x58, 0x00};
1350 ios.write(0xff);
1351 ios.write(JPEG.APP2);
1352 MarkerSegment.write2bytes(ios, segLength);
1353 byte [] id = ID.getBytes("US-ASCII");
1354 ios.write(id);
1355 ios.write(0); // Null-terminate the string
1356 ios.write(chunkNum++);
1357 ios.write(numChunks);
1358 ios.write(data, offset, dataLength);
1359 offset += dataLength;
1360 }
1361 }
1362
1363 /**
1364 * An APP2 marker segment containing an ICC profile. In the stream
1365 * a profile larger than 64K is broken up into a series of chunks.
1366 * This inner class represents the complete profile as a single object,
1367 * combining chunks as necessary.
1368 */
1369 class ICCMarkerSegment extends MarkerSegment {
1370 ArrayList<byte[]> chunks = null;
1371 byte [] profile = null; // The complete profile when it's fully read
1372 // May remain null when writing
1373 private static final int ID_SIZE = 12;
1374 int chunksRead;
1375 int numChunks;
1376
1377 ICCMarkerSegment(ICC_ColorSpace cs) {
1378 super(JPEG.APP2);
1379 chunks = null;
1380 chunksRead = 0;
1381 numChunks = 0;
1382 profile = cs.getProfile().getData();
1383 }
1384
1385 ICCMarkerSegment(JPEGBuffer buffer) throws IOException {
1386 super(buffer); // gets whole segment or fills the buffer
1387 if (debug) {
1388 System.out.println("Creating new ICC segment");
1389 }
1390 buffer.bufPtr += ID_SIZE; // Skip the id
1405 throw new IIOException
1406 ("Image format Error; chunk num > num chunks");
1407 }
1408
1409 // if there are no more chunks, set up the data
1410 if (numChunks == 1) {
1411 // reduce the stored length by the two chunk numbering bytes
1412 length -= 2;
1413 profile = new byte[length];
1414 buffer.bufPtr += 2;
1415 buffer.bufAvail-=2;
1416 buffer.readData(profile);
1417 inICC = false;
1418 } else {
1419 // If we store them away, include the chunk numbering bytes
1420 byte [] profileData = new byte[length];
1421 // Now reduce the stored length by the
1422 // two chunk numbering bytes
1423 length -= 2;
1424 buffer.readData(profileData);
1425 chunks = new ArrayList<>();
1426 chunks.add(profileData);
1427 chunksRead = 1;
1428 inICC = true;
1429 }
1430 }
1431
1432 ICCMarkerSegment(Node node) throws IIOInvalidTreeException {
1433 super(JPEG.APP2);
1434 if (node instanceof IIOMetadataNode) {
1435 IIOMetadataNode ourNode = (IIOMetadataNode) node;
1436 ICC_Profile prof = (ICC_Profile) ourNode.getUserObject();
1437 if (prof != null) { // May be null
1438 profile = prof.getData();
1439 }
1440 }
1441 }
1442
1443 protected ICCMarkerSegment clone () {
1444 ICCMarkerSegment newGuy = (ICCMarkerSegment) super.clone();
1445 if (profile != null) {
1446 newGuy.profile = profile.clone();
1447 }
1448 return newGuy;
1449 }
1450
1451 boolean addData(JPEGBuffer buffer) throws IOException {
1452 if (debug) {
1453 System.out.println("Adding to ICC segment");
1454 }
1455 // skip the tag
1456 buffer.bufPtr++;
1457 buffer.bufAvail--;
1458 // Get the length, but not in length
1459 int dataLen = (buffer.buf[buffer.bufPtr++] & 0xff) << 8;
1460 dataLen |= buffer.buf[buffer.bufPtr++] & 0xff;
1461 buffer.bufAvail -= 2;
1462 // Don't include length itself
1463 dataLen -= 2;
1495 buffer.readData(profileData);
1496 chunks.add(profileData);
1497 length += dataLen;
1498 chunksRead++;
1499 if (chunksRead < numChunks) {
1500 inICC = true;
1501 } else {
1502 if (debug) {
1503 System.out.println("Completing profile; total length is "
1504 + length);
1505 }
1506 // create an array for the whole thing
1507 profile = new byte[length];
1508 // copy the existing chunks, releasing them
1509 // Note that they may be out of order
1510
1511 int index = 0;
1512 for (int i = 1; i <= numChunks; i++) {
1513 boolean foundIt = false;
1514 for (int chunk = 0; chunk < chunks.size(); chunk++) {
1515 byte [] chunkData = chunks.get(chunk);
1516 if (chunkData[0] == i) { // Right one
1517 System.arraycopy(chunkData, 2,
1518 profile, index,
1519 chunkData.length-2);
1520 index += chunkData.length-2;
1521 foundIt = true;
1522 }
1523 }
1524 if (foundIt == false) {
1525 throw new IIOException
1526 ("Image Format Error: Missing ICC chunk num " + i);
1527 }
1528 }
1529
1530 chunks = null;
1531 chunksRead = 0;
1532 numChunks = 0;
1533 inICC = false;
1534 retval = true;
1535 }
|