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;
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.
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
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) {
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;
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<JFIFExtensionMarkerSegment> iter =
162 extSegments.iterator(); iter.hasNext();) {
163 JFIFExtensionMarkerSegment jfxx = iter.next();
164 newGuy.extSegments.add((JFIFExtensionMarkerSegment) 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.
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<JFIFExtensionMarkerSegment> iter =
234 extSegments.iterator(); iter.hasNext();) {
235 JFIFExtensionMarkerSegment seg = 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 = extSegments.get(index);
316 return jfxx.thumb.getWidth();
317 }
318
319 int getThumbnailHeight(int index) {
320 if (thumb != null) {
321 if (index == 0) {
322 return thumb.getHeight();
323 }
324 index--;
325 }
326 JFIFExtensionMarkerSegment jfxx = extSegments.get(index);
327 return jfxx.thumb.getHeight();
328 }
329
330 BufferedImage getThumbnail(ImageInputStream iis,
331 int index,
332 JPEGImageReader reader) throws IOException {
333 reader.thumbnailStarted(index);
334 BufferedImage ret = null;
335 if ((thumb != null) && (index == 0)) {
336 ret = thumb.getThumbnail(iis, reader);
337 } else {
338 if (thumb != null) {
339 index--;
340 }
341 JFIFExtensionMarkerSegment jfxx = extSegments.get(index);
342 ret = jfxx.thumb.getThumbnail(iis, reader);
343 }
344 reader.thumbnailComplete();
345 return ret;
346 }
347
348
349 /**
350 * Writes the data for this segment to the stream in
351 * valid JPEG format. Assumes that there will be no thumbnail.
352 */
353 void write(ImageOutputStream ios,
354 JPEGImageWriter writer) throws IOException {
355 // No thumbnail
356 write(ios, null, writer);
357 }
358
359 /**
360 * Writes the data for this segment to the stream in
361 * valid JPEG format. The length written takes the thumbnail
416 }
417 for (int i = 0; i < thumbData.length; i++) {
418 ios.write(thumbData[i]);
419 if ((i > progInterval) && (i % progInterval == 0)) {
420 writer.thumbnailProgress
421 (((float) i * 100) / ((float) thumbData.length));
422 }
423 }
424 }
425
426 /**
427 * Write out this JFIF Marker Segment, including a thumbnail or
428 * appending a series of JFXX Marker Segments, as appropriate.
429 * Warnings and progress reports are sent to the writer argument.
430 * The list of thumbnails is matched to the list of JFXX extension
431 * segments, if any, in order to determine how to encode the
432 * thumbnails. If there are more thumbnails than metadata segments,
433 * default encoding is used for the extra thumbnails.
434 */
435 void writeWithThumbs(ImageOutputStream ios,
436 List<? extends BufferedImage> thumbnails,
437 JPEGImageWriter writer) throws IOException {
438 if (thumbnails != null) {
439 JFIFExtensionMarkerSegment jfxx = null;
440 if (thumbnails.size() == 1) {
441 if (!extSegments.isEmpty()) {
442 jfxx = extSegments.get(0);
443 }
444 writeThumb(ios,
445 (BufferedImage) thumbnails.get(0),
446 jfxx,
447 0,
448 true,
449 writer);
450 } else {
451 // All others write as separate JFXX segments
452 write(ios, writer); // Just the header without any thumbnail
453 for (int i = 0; i < thumbnails.size(); i++) {
454 jfxx = null;
455 if (i < extSegments.size()) {
456 jfxx = extSegments.get(i);
457 }
458 writeThumb(ios,
459 (BufferedImage) thumbnails.get(i),
460 jfxx,
461 i,
462 false,
463 writer);
464 }
465 }
466 } else { // No thumbnails
467 write(ios, writer);
468 }
469
470 }
471
472 private void writeThumb(ImageOutputStream ios,
473 BufferedImage thumb,
474 JFIFExtensionMarkerSegment jfxx,
475 int index,
476 boolean onlyOne,
585 */
586 private static BufferedImage expandGrayThumb(BufferedImage thumb) {
587 BufferedImage ret = new BufferedImage(thumb.getWidth(),
588 thumb.getHeight(),
589 BufferedImage.TYPE_INT_RGB);
590 Graphics g = ret.getGraphics();
591 g.drawImage(thumb, 0, 0, null);
592 return ret;
593 }
594
595 /**
596 * Writes out a default JFIF marker segment to the given
597 * output stream. If <code>thumbnails</code> is not <code>null</code>,
598 * writes out the set of thumbnail images as JFXX marker segments, or
599 * incorporated into the JFIF segment if appropriate.
600 * If <code>iccProfile</code> is not <code>null</code>,
601 * writes out the profile after the JFIF segment using as many APP2
602 * marker segments as necessary.
603 */
604 static void writeDefaultJFIF(ImageOutputStream ios,
605 List<? extends BufferedImage> thumbnails,
606 ICC_Profile iccProfile,
607 JPEGImageWriter writer)
608 throws IOException {
609
610 JFIFMarkerSegment jfif = new JFIFMarkerSegment();
611 jfif.writeWithThumbs(ios, thumbnails, writer);
612 if (iccProfile != null) {
613 writeICC(iccProfile, ios);
614 }
615 }
616
617 /**
618 * Prints out the contents of this object to System.out for debugging.
619 */
620 void print() {
621 printTag("JFIF");
622 System.out.print("Version ");
623 System.out.print(majorVersion);
624 System.out.println(".0"
625 + Integer.toString(minorVersion));
626 System.out.print("Resolution units: ");
627 System.out.println(resUnits);
628 System.out.print("X density: ");
629 System.out.println(Xdensity);
630 System.out.print("Y density: ");
631 System.out.println(Ydensity);
632 System.out.print("Thumbnail Width: ");
633 System.out.println(thumbWidth);
634 System.out.print("Thumbnail Height: ");
635 System.out.println(thumbHeight);
636 if (!extSegments.isEmpty()) {
637 for (Iterator<JFIFExtensionMarkerSegment> iter =
638 extSegments.iterator(); iter.hasNext();) {
639 JFIFExtensionMarkerSegment extSegment = iter.next();
640 extSegment.print();
641 }
642 }
643 if (iccSegment != null) {
644 iccSegment.print();
645 }
646 }
647
648 /**
649 * A JFIF extension APP0 marker segment.
650 */
651 class JFIFExtensionMarkerSegment extends MarkerSegment {
652 int code;
653 JFIFThumb thumb;
654 private static final int DATA_SIZE = 6;
655 private static final int ID_SIZE = 5;
656
657 JFIFExtensionMarkerSegment(JPEGBuffer buffer, JPEGImageReader reader)
658 throws IOException {
659
1353 ios.write(0xff);
1354 ios.write(JPEG.APP2);
1355 MarkerSegment.write2bytes(ios, segLength);
1356 byte [] id = ID.getBytes("US-ASCII");
1357 ios.write(id);
1358 ios.write(0); // Null-terminate the string
1359 ios.write(chunkNum++);
1360 ios.write(numChunks);
1361 ios.write(data, offset, dataLength);
1362 offset += dataLength;
1363 }
1364 }
1365
1366 /**
1367 * An APP2 marker segment containing an ICC profile. In the stream
1368 * a profile larger than 64K is broken up into a series of chunks.
1369 * This inner class represents the complete profile as a single object,
1370 * combining chunks as necessary.
1371 */
1372 class ICCMarkerSegment extends MarkerSegment {
1373 ArrayList<byte[]> chunks = null;
1374 byte [] profile = null; // The complete profile when it's fully read
1375 // May remain null when writing
1376 private static final int ID_SIZE = 12;
1377 int chunksRead;
1378 int numChunks;
1379
1380 ICCMarkerSegment(ICC_ColorSpace cs) {
1381 super(JPEG.APP2);
1382 chunks = null;
1383 chunksRead = 0;
1384 numChunks = 0;
1385 profile = cs.getProfile().getData();
1386 }
1387
1388 ICCMarkerSegment(JPEGBuffer buffer) throws IOException {
1389 super(buffer); // gets whole segment or fills the buffer
1390 if (debug) {
1391 System.out.println("Creating new ICC segment");
1392 }
1393 buffer.bufPtr += ID_SIZE; // Skip the id
1408 throw new IIOException
1409 ("Image format Error; chunk num > num chunks");
1410 }
1411
1412 // if there are no more chunks, set up the data
1413 if (numChunks == 1) {
1414 // reduce the stored length by the two chunk numbering bytes
1415 length -= 2;
1416 profile = new byte[length];
1417 buffer.bufPtr += 2;
1418 buffer.bufAvail-=2;
1419 buffer.readData(profile);
1420 inICC = false;
1421 } else {
1422 // If we store them away, include the chunk numbering bytes
1423 byte [] profileData = new byte[length];
1424 // Now reduce the stored length by the
1425 // two chunk numbering bytes
1426 length -= 2;
1427 buffer.readData(profileData);
1428 chunks = new ArrayList<>();
1429 chunks.add(profileData);
1430 chunksRead = 1;
1431 inICC = true;
1432 }
1433 }
1434
1435 ICCMarkerSegment(Node node) throws IIOInvalidTreeException {
1436 super(JPEG.APP2);
1437 if (node instanceof IIOMetadataNode) {
1438 IIOMetadataNode ourNode = (IIOMetadataNode) node;
1439 ICC_Profile prof = (ICC_Profile) ourNode.getUserObject();
1440 if (prof != null) { // May be null
1441 profile = prof.getData();
1442 }
1443 }
1444 }
1445
1446 protected Object clone () {
1447 ICCMarkerSegment newGuy = (ICCMarkerSegment) super.clone();
1448 if (profile != null) {
1498 buffer.readData(profileData);
1499 chunks.add(profileData);
1500 length += dataLen;
1501 chunksRead++;
1502 if (chunksRead < numChunks) {
1503 inICC = true;
1504 } else {
1505 if (debug) {
1506 System.out.println("Completing profile; total length is "
1507 + length);
1508 }
1509 // create an array for the whole thing
1510 profile = new byte[length];
1511 // copy the existing chunks, releasing them
1512 // Note that they may be out of order
1513
1514 int index = 0;
1515 for (int i = 1; i <= numChunks; i++) {
1516 boolean foundIt = false;
1517 for (int chunk = 0; chunk < chunks.size(); chunk++) {
1518 byte [] chunkData = chunks.get(chunk);
1519 if (chunkData[0] == i) { // Right one
1520 System.arraycopy(chunkData, 2,
1521 profile, index,
1522 chunkData.length-2);
1523 index += chunkData.length-2;
1524 foundIt = true;
1525 }
1526 }
1527 if (foundIt == false) {
1528 throw new IIOException
1529 ("Image Format Error: Missing ICC chunk num " + i);
1530 }
1531 }
1532
1533 chunks = null;
1534 chunksRead = 0;
1535 numChunks = 0;
1536 inICC = false;
1537 retval = true;
1538 }
|