1 7 8 package com.sun.imageio.plugins.jpeg; 9 10 import javax.imageio.IIOException ; 11 import javax.imageio.IIOImage ; 12 import javax.imageio.ImageTypeSpecifier ; 13 import javax.imageio.ImageReader ; 14 import javax.imageio.metadata.IIOInvalidTreeException ; 15 import javax.imageio.metadata.IIOMetadataNode ; 16 import javax.imageio.metadata.IIOMetadata ; 17 import javax.imageio.stream.ImageInputStream ; 18 import javax.imageio.stream.ImageOutputStream ; 19 import javax.imageio.stream.MemoryCacheImageOutputStream ; 20 import javax.imageio.event.IIOReadProgressListener ; 21 22 import java.awt.Graphics ; 23 import java.awt.color.ICC_Profile ; 24 import java.awt.color.ICC_ColorSpace ; 25 import java.awt.color.ColorSpace ; 26 import java.awt.image.ColorModel ; 27 import java.awt.image.SampleModel ; 28 import java.awt.image.IndexColorModel ; 29 import java.awt.image.ComponentColorModel ; 30 import java.awt.image.BufferedImage ; 31 import java.awt.image.DataBuffer ; 32 import java.awt.image.DataBufferByte ; 33 import java.awt.image.Raster ; 34 import java.awt.image.WritableRaster ; 35 import java.io.IOException ; 36 import java.io.ByteArrayOutputStream ; 37 import java.util.List ; 38 import java.util.ArrayList ; 39 import java.util.Iterator ; 40 41 import org.w3c.dom.Node ; 42 import org.w3c.dom.NodeList ; 43 import org.w3c.dom.NamedNodeMap ; 44 45 52 class JFIFMarkerSegment extends MarkerSegment { 53 int majorVersion; 54 int minorVersion; 55 int resUnits; 56 int Xdensity; 57 int Ydensity; 58 int thumbWidth; 59 int thumbHeight; 60 JFIFThumbRGB thumb = null; ArrayList extSegments = new ArrayList (); 62 ICCMarkerSegment iccSegment = null; private static final int THUMB_JPEG = 0x10; 64 private static final int THUMB_PALETTE = 0x11; 65 private static final int THUMB_UNASSIGNED = 0x12; 66 private static final int THUMB_RGB = 0x13; 67 private static final int DATA_SIZE = 14; 68 private static final int ID_SIZE = 5; 69 private final int MAX_THUMB_WIDTH = 255; 70 private final int MAX_THUMB_HEIGHT = 255; 71 72 private final boolean debug = false; 73 74 81 private boolean inICC = false; 82 83 88 private ICCMarkerSegment tempICCSegment = null; 89 90 91 94 JFIFMarkerSegment() { 95 super(JPEG.APP0); 96 majorVersion = 1; 97 minorVersion = 2; 98 resUnits = JPEG.DENSITY_UNIT_ASPECT_RATIO; 99 Xdensity = 1; 100 Ydensity = 1; 101 thumbWidth = 0; 102 thumbHeight = 0; 103 } 104 105 109 JFIFMarkerSegment(JPEGBuffer buffer) throws IOException { 110 super(buffer); 111 buffer.bufPtr += ID_SIZE; 113 majorVersion = buffer.buf[buffer.bufPtr++]; 114 minorVersion = buffer.buf[buffer.bufPtr++]; 115 resUnits = buffer.buf[buffer.bufPtr++]; 116 Xdensity = (buffer.buf[buffer.bufPtr++] & 0xff) << 8; 117 Xdensity |= buffer.buf[buffer.bufPtr++] & 0xff; 118 Ydensity = (buffer.buf[buffer.bufPtr++] & 0xff) << 8; 119 Ydensity |= buffer.buf[buffer.bufPtr++] & 0xff; 120 thumbWidth = buffer.buf[buffer.bufPtr++] & 0xff; 121 thumbHeight = buffer.buf[buffer.bufPtr++] & 0xff; 122 buffer.bufAvail -= DATA_SIZE; 123 if (thumbWidth > 0) { 124 thumb = new JFIFThumbRGB(buffer, thumbWidth, thumbHeight); 125 } 126 } 127 128 131 JFIFMarkerSegment(Node node) throws IIOInvalidTreeException { 132 this(); 133 updateFromNativeNode(node, true); 134 } 135 136 139 protected Object clone() { 140 JFIFMarkerSegment newGuy = (JFIFMarkerSegment) super.clone(); 141 if (!extSegments.isEmpty()) { newGuy.extSegments = new ArrayList (); 143 for (Iterator iter = extSegments.iterator(); iter.hasNext();) { 144 JFIFExtensionMarkerSegment jfxx = 145 (JFIFExtensionMarkerSegment) iter.next(); 146 newGuy.extSegments.add(jfxx.clone()); 147 } 148 } 149 if (iccSegment != null) { 150 newGuy.iccSegment = (ICCMarkerSegment) iccSegment.clone(); 151 } 152 return newGuy; 153 } 154 155 159 void addJFXX(JPEGBuffer buffer, JPEGImageReader reader) 160 throws IOException { 161 extSegments.add(new JFIFExtensionMarkerSegment(buffer, reader)); 162 } 163 164 168 void addICC(JPEGBuffer buffer) throws IOException { 169 if (inICC == false) { 170 if (iccSegment != null) { 171 throw new IIOException 172 ("> 1 ICC APP2 Marker Segment not supported"); 173 } 174 tempICCSegment = new ICCMarkerSegment(buffer); 175 if (inICC == false) { iccSegment = tempICCSegment; 177 tempICCSegment = null; 178 } 179 } else { 180 if (tempICCSegment.addData(buffer) == true) { 181 iccSegment = tempICCSegment; 182 tempICCSegment = null; 183 } 184 } 185 } 186 187 191 void addICC(ICC_ColorSpace cs) throws IOException { 192 if (iccSegment != null) { 193 throw new IIOException 194 ("> 1 ICC APP2 Marker Segment not supported"); 195 } 196 iccSegment = new ICCMarkerSegment(cs); 197 } 198 199 203 IIOMetadataNode getNativeNode() { 204 IIOMetadataNode node = new IIOMetadataNode ("app0JFIF"); 205 node.setAttribute("majorVersion", Integer.toString(majorVersion)); 206 node.setAttribute("minorVersion", Integer.toString(minorVersion)); 207 node.setAttribute("resUnits", Integer.toString(resUnits)); 208 node.setAttribute("Xdensity", Integer.toString(Xdensity)); 209 node.setAttribute("Ydensity", Integer.toString(Ydensity)); 210 node.setAttribute("thumbWidth", Integer.toString(thumbWidth)); 211 node.setAttribute("thumbHeight", Integer.toString(thumbHeight)); 212 if (!extSegments.isEmpty()) { 213 IIOMetadataNode JFXXnode = new IIOMetadataNode ("JFXX"); 214 node.appendChild(JFXXnode); 215 for (Iterator iter = extSegments.iterator(); iter.hasNext();) { 216 JFIFExtensionMarkerSegment seg = 217 (JFIFExtensionMarkerSegment) iter.next(); 218 JFXXnode.appendChild(seg.getNativeNode()); 219 } 220 } 221 if (iccSegment != null) { 222 node.appendChild(iccSegment.getNativeNode()); 223 } 224 225 return node; 226 } 227 228 235 void updateFromNativeNode(Node node, boolean fromScratch) 236 throws IIOInvalidTreeException { 237 NamedNodeMap attrs = node.getAttributes(); 239 if (attrs.getLength() > 0) { 240 int value = getAttributeValue(node, attrs, "majorVersion", 241 0, 255, false); 242 majorVersion = (value != -1) ? value : majorVersion; 243 value = getAttributeValue(node, attrs, "minorVersion", 244 0, 255, false); 245 minorVersion = (value != -1) ? value : minorVersion; 246 value = getAttributeValue(node, attrs, "resUnits", 0, 2, false); 247 resUnits = (value != -1) ? value : resUnits; 248 value = getAttributeValue(node, attrs, "Xdensity", 1, 65535, false); 249 Xdensity = (value != -1) ? value : Xdensity; 250 value = getAttributeValue(node, attrs, "Ydensity", 1, 65535, false); 251 Ydensity = (value != -1) ? value : Ydensity; 252 value = getAttributeValue(node, attrs, "thumbWidth", 0, 255, false); 253 thumbWidth = (value != -1) ? value : thumbWidth; 254 value = getAttributeValue(node, attrs, "thumbHeight", 0, 255, false); 255 thumbHeight = (value != -1) ? value : thumbHeight; 256 } 257 if (node.hasChildNodes()) { 258 NodeList children = node.getChildNodes(); 259 int count = children.getLength(); 260 if (count > 2) { 261 throw new IIOInvalidTreeException 262 ("app0JFIF node cannot have > 2 children", node); 263 } 264 for (int i = 0; i < count; i++) { 265 Node child = children.item(i); 266 String name = child.getNodeName(); 267 if (name.equals("JFXX")) { 268 if ((!extSegments.isEmpty()) && fromScratch) { 269 throw new IIOInvalidTreeException 270 ("app0JFIF node cannot have > 1 JFXX node", node); 271 } 272 NodeList exts = child.getChildNodes(); 273 int extCount = exts.getLength(); 274 for (int j = 0; j < extCount; j++) { 275 Node ext = exts.item(j); 276 extSegments.add(new JFIFExtensionMarkerSegment(ext)); 277 } 278 } 279 if (name.equals("app2ICC")) { 280 if ((iccSegment != null) && fromScratch) { 281 throw new IIOInvalidTreeException 282 ("> 1 ICC APP2 Marker Segment not supported", node); 283 } 284 iccSegment = new ICCMarkerSegment(child); 285 } 286 } 287 } 288 } 289 290 int getThumbnailWidth(int index) { 291 if (thumb != null) { 292 if (index == 0) { 293 return thumb.getWidth(); 294 } 295 index--; 296 } 297 JFIFExtensionMarkerSegment jfxx = 298 (JFIFExtensionMarkerSegment) extSegments.get(index); 299 return jfxx.thumb.getWidth(); 300 } 301 302 int getThumbnailHeight(int index) { 303 if (thumb != null) { 304 if (index == 0) { 305 return thumb.getHeight(); 306 } 307 index--; 308 } 309 JFIFExtensionMarkerSegment jfxx = 310 (JFIFExtensionMarkerSegment) extSegments.get(index); 311 return jfxx.thumb.getHeight(); 312 } 313 314 BufferedImage getThumbnail(ImageInputStream iis, 315 int index, 316 JPEGImageReader reader) throws IOException { 317 reader.thumbnailStarted(index); 318 BufferedImage ret = null; 319 if ((thumb != null) && (index == 0)) { 320 ret = thumb.getThumbnail(iis, reader); 321 } else { 322 if (thumb != null) { 323 index--; 324 } 325 JFIFExtensionMarkerSegment jfxx = 326 (JFIFExtensionMarkerSegment) extSegments.get(index); 327 ret = jfxx.thumb.getThumbnail(iis, reader); 328 } 329 reader.thumbnailComplete(); 330 return ret; 331 } 332 333 334 338 void write(ImageOutputStream ios, 339 JPEGImageWriter writer) throws IOException { 340 write(ios, null, writer); 342 } 343 344 351 void write(ImageOutputStream ios, 352 BufferedImage thumb, 353 JPEGImageWriter writer) throws IOException { 354 int thumbWidth = 0; 355 int thumbHeight = 0; 356 int thumbLength = 0; 357 int [] thumbData = null; 358 if (thumb != null) { 359 thumbWidth = thumb.getWidth(); 361 thumbHeight = thumb.getHeight(); 362 if ((thumbWidth > MAX_THUMB_WIDTH) 363 || (thumbHeight > MAX_THUMB_HEIGHT)) { 364 writer.warningOccurred(JPEGImageWriter.WARNING_THUMB_CLIPPED); 365 } 366 thumbWidth = Math.min(thumbWidth, MAX_THUMB_WIDTH); 367 thumbHeight = Math.min(thumbHeight, MAX_THUMB_HEIGHT); 368 thumbData = thumb.getRaster().getPixels(0, 0, 369 thumbWidth, thumbHeight, 370 (int []) null); 371 thumbLength = thumbData.length; 372 } 373 length = DATA_SIZE + LENGTH_SIZE + thumbLength; 374 writeTag(ios); 375 byte [] id = {0x4A, 0x46, 0x49, 0x46, 0x00}; 376 ios.write(id); 377 ios.write(majorVersion); 378 ios.write(minorVersion); 379 ios.write(resUnits); 380 write2bytes(ios, Xdensity); 381 write2bytes(ios, Ydensity); 382 ios.write(thumbWidth); 383 ios.write(thumbHeight); 384 if (thumbData != null) { 385 writer.thumbnailStarted(0); 386 writeThumbnailData(ios, thumbData, writer); 387 writer.thumbnailComplete(); 388 } 389 } 390 391 395 void writeThumbnailData(ImageOutputStream ios, 396 int [] thumbData, 397 JPEGImageWriter writer) throws IOException { 398 int progInterval = thumbData.length / 20; if (progInterval == 0) { 400 progInterval = 1; 401 } 402 for (int i = 0; i < thumbData.length; i++) { 403 ios.write(thumbData[i]); 404 if ((i > progInterval) && (i % progInterval == 0)) { 405 writer.thumbnailProgress 406 (((float) i * 100) / ((float) thumbData.length)); 407 } 408 } 409 } 410 411 420 void writeWithThumbs(ImageOutputStream ios, 421 List thumbnails, 422 JPEGImageWriter writer) throws IOException { 423 if (thumbnails != null) { 424 JFIFExtensionMarkerSegment jfxx = null; 425 if (thumbnails.size() == 1) { 426 if (!extSegments.isEmpty()) { 427 jfxx = (JFIFExtensionMarkerSegment) extSegments.get(0); 428 } 429 writeThumb(ios, 430 (BufferedImage ) thumbnails.get(0), 431 jfxx, 432 0, 433 true, 434 writer); 435 } else { 436 write(ios, writer); for (int i = 0; i < thumbnails.size(); i++) { 439 jfxx = null; 440 if (i < extSegments.size()) { 441 jfxx = (JFIFExtensionMarkerSegment) extSegments.get(i); 442 } 443 writeThumb(ios, 444 (BufferedImage ) thumbnails.get(i), 445 jfxx, 446 i, 447 false, 448 writer); 449 } 450 } 451 } else { write(ios, writer); 453 } 454 455 } 456 457 private void writeThumb(ImageOutputStream ios, 458 BufferedImage thumb, 459 JFIFExtensionMarkerSegment jfxx, 460 int index, 461 boolean onlyOne, 462 JPEGImageWriter writer) throws IOException { 463 ColorModel cm = thumb.getColorModel(); 464 ColorSpace cs = cm.getColorSpace(); 465 466 if (cm instanceof IndexColorModel ) { 467 if (onlyOne) { 470 write(ios, writer); 471 } 472 if ((jfxx == null) 473 || (jfxx.code == THUMB_PALETTE)) { 474 writeJFXXSegment(index, thumb, ios, writer); } else { 476 BufferedImage thumbRGB = 478 ((IndexColorModel ) cm).convertToIntDiscrete 479 (thumb.getRaster(), false); 480 jfxx.setThumbnail(thumbRGB); 481 writer.thumbnailStarted(index); 482 jfxx.write(ios, writer); writer.thumbnailComplete(); 484 } 485 } else if (cs.getType() == ColorSpace.TYPE_RGB) { 486 if (jfxx == null) { 487 if (onlyOne) { 488 write(ios, thumb, writer); } else { 490 writeJFXXSegment(index, thumb, ios, writer); } 492 } else { 493 if (onlyOne) { 495 write(ios, writer); 496 } 497 if (jfxx.code == THUMB_PALETTE) { 498 writeJFXXSegment(index, thumb, ios, writer); writer.warningOccurred 500 (JPEGImageWriter.WARNING_NO_RGB_THUMB_AS_INDEXED); 501 } else { 502 jfxx.setThumbnail(thumb); 503 writer.thumbnailStarted(index); 504 jfxx.write(ios, writer); writer.thumbnailComplete(); 506 } 507 } 508 } else if (cs.getType() == ColorSpace.TYPE_GRAY) { 509 if (jfxx == null) { 510 if (onlyOne) { 511 BufferedImage thumbRGB = expandGrayThumb(thumb); 512 write(ios, thumbRGB, writer); } else { 514 writeJFXXSegment(index, thumb, ios, writer); } 516 } else { 517 if (onlyOne) { 519 write(ios, writer); 520 } 521 if (jfxx.code == THUMB_RGB) { 522 BufferedImage thumbRGB = expandGrayThumb(thumb); 523 writeJFXXSegment(index, thumbRGB, ios, writer); 524 } else if (jfxx.code == THUMB_JPEG) { 525 jfxx.setThumbnail(thumb); 526 writer.thumbnailStarted(index); 527 jfxx.write(ios, writer); writer.thumbnailComplete(); 529 } else if (jfxx.code == THUMB_PALETTE) { 530 writeJFXXSegment(index, thumb, ios, writer); writer.warningOccurred 532 (JPEGImageWriter.WARNING_NO_GRAY_THUMB_AS_INDEXED); 533 } 534 } 535 } else { 536 writer.warningOccurred 537 (JPEGImageWriter.WARNING_ILLEGAL_THUMBNAIL); 538 } 539 } 540 541 private class IllegalThumbException extends Exception {} 544 545 548 private void writeJFXXSegment(int index, 549 BufferedImage thumbnail, 550 ImageOutputStream ios, 551 JPEGImageWriter writer) throws IOException { 552 JFIFExtensionMarkerSegment jfxx = null; 553 try { 554 jfxx = new JFIFExtensionMarkerSegment(thumbnail); 555 } catch (IllegalThumbException e) { 556 writer.warningOccurred 557 (JPEGImageWriter.WARNING_ILLEGAL_THUMBNAIL); 558 return; 559 } 560 writer.thumbnailStarted(index); 561 jfxx.write(ios, writer); 562 writer.thumbnailComplete(); 563 } 564 565 566 570 private static BufferedImage expandGrayThumb(BufferedImage thumb) { 571 BufferedImage ret = new BufferedImage (thumb.getWidth(), 572 thumb.getHeight(), 573 BufferedImage.TYPE_INT_RGB); 574 Graphics g = ret.getGraphics(); 575 g.drawImage(thumb, 0, 0, null); 576 return ret; 577 } 578 579 588 static void writeDefaultJFIF(ImageOutputStream ios, 589 List thumbnails, 590 ICC_Profile iccProfile, 591 JPEGImageWriter writer) 592 throws IOException { 593 594 JFIFMarkerSegment jfif = new JFIFMarkerSegment(); 595 jfif.writeWithThumbs(ios, thumbnails, writer); 596 if (iccProfile != null) { 597 writeICC(iccProfile, ios); 598 } 599 } 600 601 604 void print() { 605 printTag("JFIF"); 606 System.out.print("Version "); 607 System.out.print(majorVersion); 608 System.out.println(".0" 609 + Integer.toString(minorVersion)); 610 System.out.print("Resolution units: "); 611 System.out.println(resUnits); 612 System.out.print("X density: "); 613 System.out.println(Xdensity); 614 System.out.print("Y density: "); 615 System.out.println(Ydensity); 616 System.out.print("Thumbnail Width: "); 617 System.out.println(thumbWidth); 618 System.out.print("Thumbnail Height: "); 619 System.out.println(thumbHeight); 620 if (!extSegments.isEmpty()) { 621 for (Iterator iter = extSegments.iterator(); iter.hasNext();) { 622 JFIFExtensionMarkerSegment extSegment = 623 (JFIFExtensionMarkerSegment) iter.next(); 624 extSegment.print(); 625 } 626 } 627 if (iccSegment != null) { 628 iccSegment.print(); 629 } 630 } 631 632 635 class JFIFExtensionMarkerSegment extends MarkerSegment { 636 int code; 637 JFIFThumb thumb; 638 private static final int DATA_SIZE = 6; 639 private static final int ID_SIZE = 5; 640 641 JFIFExtensionMarkerSegment(JPEGBuffer buffer, JPEGImageReader reader) 642 throws IOException { 643 644 super(buffer); 645 buffer.bufPtr += ID_SIZE; 647 code = buffer.buf[buffer.bufPtr++] & 0xff; 648 buffer.bufAvail -= DATA_SIZE; 649 if (code == THUMB_JPEG) { 650 thumb = new JFIFThumbJPEG(buffer, length, reader); 651 } else { 652 buffer.loadBuf(2); 653 int thumbX = buffer.buf[buffer.bufPtr++] & 0xff; 654 int thumbY = buffer.buf[buffer.bufPtr++] & 0xff; 655 buffer.bufAvail -= 2; 656 if (code == THUMB_PALETTE) { 658 thumb = new JFIFThumbPalette(buffer, thumbX, thumbY); 659 } else { 660 thumb = new JFIFThumbRGB(buffer, thumbX, thumbY); 661 } 662 } 663 } 664 665 JFIFExtensionMarkerSegment(Node node) throws IIOInvalidTreeException { 666 super(JPEG.APP0); 667 NamedNodeMap attrs = node.getAttributes(); 668 if (attrs.getLength() > 0) { 669 code = getAttributeValue(node, 670 attrs, 671 "extensionCode", 672 THUMB_JPEG, 673 THUMB_RGB, 674 false); 675 if (code == THUMB_UNASSIGNED) { 676 throw new IIOInvalidTreeException 677 ("invalid extensionCode attribute value", node); 678 } 679 } else { 680 code = THUMB_UNASSIGNED; 681 } 682 if (node.getChildNodes().getLength() != 1) { 684 throw new IIOInvalidTreeException 685 ("app0JFXX node must have exactly 1 child", node); 686 } 687 Node child = node.getFirstChild(); 688 String name = child.getNodeName(); 689 if (name.equals("JFIFthumbJPEG")) { 690 if (code == THUMB_UNASSIGNED) { 691 code = THUMB_JPEG; 692 } 693 thumb = new JFIFThumbJPEG(child); 694 } else if (name.equals("JFIFthumbPalette")) { 695 if (code == THUMB_UNASSIGNED) { 696 code = THUMB_PALETTE; 697 } 698 thumb = new JFIFThumbPalette(child); 699 } else if (name.equals("JFIFthumbRGB")) { 700 if (code == THUMB_UNASSIGNED) { 701 code = THUMB_RGB; 702 } 703 thumb = new JFIFThumbRGB(child); 704 } else { 705 throw new IIOInvalidTreeException 706 ("unrecognized app0JFXX child node", node); 707 } 708 } 709 710 JFIFExtensionMarkerSegment(BufferedImage thumbnail) 711 throws IllegalThumbException { 712 713 super(JPEG.APP0); 714 ColorModel cm = thumbnail.getColorModel(); 715 int csType = cm.getColorSpace().getType(); 716 if (cm.hasAlpha()) { 717 throw new IllegalThumbException(); 718 } 719 if (cm instanceof IndexColorModel ) { 720 code = THUMB_PALETTE; 721 thumb = new JFIFThumbPalette(thumbnail); 722 } else if (csType == ColorSpace.TYPE_RGB) { 723 code = THUMB_RGB; 724 thumb = new JFIFThumbRGB(thumbnail); 725 } else if (csType == ColorSpace.TYPE_GRAY) { 726 code = THUMB_JPEG; 727 thumb = new JFIFThumbJPEG(thumbnail); 728 } else { 729 throw new IllegalThumbException(); 730 } 731 } 732 733 void setThumbnail(BufferedImage thumbnail) { 734 try { 735 switch (code) { 736 case THUMB_PALETTE: 737 thumb = new JFIFThumbPalette(thumbnail); 738 break; 739 case THUMB_RGB: 740 thumb = new JFIFThumbRGB(thumbnail); 741 break; 742 case THUMB_JPEG: 743 thumb = new JFIFThumbJPEG(thumbnail); 744 break; 745 } 746 } catch (IllegalThumbException e) { 747 throw new InternalError ("Illegal thumb in setThumbnail!"); 749 } 750 } 751 752 protected Object clone() { 753 JFIFExtensionMarkerSegment newGuy = 754 (JFIFExtensionMarkerSegment) super.clone(); 755 if (thumb != null) { 756 newGuy.thumb = (JFIFThumb) thumb.clone(); 757 } 758 return newGuy; 759 } 760 761 IIOMetadataNode getNativeNode() { 762 IIOMetadataNode node = new IIOMetadataNode ("app0JFXX"); 763 node.setAttribute("extensionCode", Integer.toString(code)); 764 node.appendChild(thumb.getNativeNode()); 765 return node; 766 } 767 768 void write(ImageOutputStream ios, 769 JPEGImageWriter writer) throws IOException { 770 length = LENGTH_SIZE + DATA_SIZE + thumb.getLength(); 771 writeTag(ios); 772 byte [] id = {0x4A, 0x46, 0x58, 0x58, 0x00}; 773 ios.write(id); 774 ios.write(code); 775 thumb.write(ios, writer); 776 } 777 778 void print() { 779 printTag("JFXX"); 780 thumb.print(); 781 } 782 } 783 784 788 abstract class JFIFThumb implements Cloneable { 789 long streamPos = -1L; abstract int getLength(); abstract int getWidth(); 792 abstract int getHeight(); 793 abstract BufferedImage getThumbnail(ImageInputStream iis, 794 JPEGImageReader reader) 795 throws IOException ; 796 797 protected JFIFThumb() {} 798 799 protected JFIFThumb(JPEGBuffer buffer) throws IOException { 800 streamPos = buffer.getStreamPosition(); 802 } 803 804 abstract void print(); 805 806 abstract IIOMetadataNode getNativeNode(); 807 808 abstract void write(ImageOutputStream ios, 809 JPEGImageWriter writer) throws IOException ; 810 811 protected Object clone() { 812 try { 813 return super.clone(); 814 } catch (CloneNotSupportedException e) {} return null; 816 } 817 818 } 819 820 abstract class JFIFThumbUncompressed extends JFIFThumb { 821 BufferedImage thumbnail = null; 822 int thumbWidth; 823 int thumbHeight; 824 String name; 825 826 JFIFThumbUncompressed(JPEGBuffer buffer, 827 int width, 828 int height, 829 int skip, 830 String name) 831 throws IOException { 832 super(buffer); 833 thumbWidth = width; 834 thumbHeight = height; 835 buffer.skipData(skip); 837 this.name = name; 838 } 839 840 JFIFThumbUncompressed(Node node, String name) 841 throws IIOInvalidTreeException { 842 843 thumbWidth = 0; 844 thumbHeight = 0; 845 this.name = name; 846 NamedNodeMap attrs = node.getAttributes(); 847 int count = attrs.getLength(); 848 if (count > 2) { 849 throw new IIOInvalidTreeException 850 (name +" node cannot have > 2 attributes", node); 851 } 852 if (count != 0) { 853 int value = getAttributeValue(node, attrs, "thumbWidth", 854 0, 255, false); 855 thumbWidth = (value != -1) ? value : thumbWidth; 856 value = getAttributeValue(node, attrs, "thumbHeight", 857 0, 255, false); 858 thumbHeight = (value != -1) ? value : thumbHeight; 859 } 860 } 861 862 JFIFThumbUncompressed(BufferedImage thumb) { 863 thumbnail = thumb; 864 thumbWidth = thumb.getWidth(); 865 thumbHeight = thumb.getHeight(); 866 name = null; } 868 869 void readByteBuffer(ImageInputStream iis, 870 byte [] data, 871 JPEGImageReader reader, 872 float workPortion, 873 float workOffset) throws IOException { 874 int progInterval = Math.max((int)(data.length/20/workPortion), 875 1); 876 for (int offset = 0; 877 offset < data.length;) { 878 int len = Math.min(progInterval, data.length-offset); 879 iis.read(data, offset, len); 880 offset += progInterval; 881 float percentDone = ((float) offset* 100) 882 / data.length 883 * workPortion + workOffset; 884 if (percentDone > 100.0F) { 885 percentDone = 100.0F; 886 } 887 reader.thumbnailProgress (percentDone); 888 } 889 } 890 891 892 int getWidth() { 893 return thumbWidth; 894 } 895 896 int getHeight() { 897 return thumbHeight; 898 } 899 900 IIOMetadataNode getNativeNode() { 901 IIOMetadataNode node = new IIOMetadataNode (name); 902 node.setAttribute("thumbWidth", Integer.toString(thumbWidth)); 903 node.setAttribute("thumbHeight", Integer.toString(thumbHeight)); 904 return node; 905 } 906 907 void write(ImageOutputStream ios, 908 JPEGImageWriter writer) throws IOException { 909 if ((thumbWidth > MAX_THUMB_WIDTH) 910 || (thumbHeight > MAX_THUMB_HEIGHT)) { 911 writer.warningOccurred(JPEGImageWriter.WARNING_THUMB_CLIPPED); 912 } 913 thumbWidth = Math.min(thumbWidth, MAX_THUMB_WIDTH); 914 thumbHeight = Math.min(thumbHeight, MAX_THUMB_HEIGHT); 915 ios.write(thumbWidth); 916 ios.write(thumbHeight); 917 } 918 919 void writePixels(ImageOutputStream ios, 920 JPEGImageWriter writer) throws IOException { 921 if ((thumbWidth > MAX_THUMB_WIDTH) 922 || (thumbHeight > MAX_THUMB_HEIGHT)) { 923 writer.warningOccurred(JPEGImageWriter.WARNING_THUMB_CLIPPED); 924 } 925 thumbWidth = Math.min(thumbWidth, MAX_THUMB_WIDTH); 926 thumbHeight = Math.min(thumbHeight, MAX_THUMB_HEIGHT); 927 int [] data = thumbnail.getRaster().getPixels(0, 0, 928 thumbWidth, 929 thumbHeight, 930 (int []) null); 931 writeThumbnailData(ios, data, writer); 932 } 933 934 void print() { 935 System.out.print(name + " width: "); 936 System.out.println(thumbWidth); 937 System.out.print(name + " height: "); 938 System.out.println(thumbHeight); 939 } 940 941 } 942 943 947 class JFIFThumbRGB extends JFIFThumbUncompressed { 948 949 JFIFThumbRGB(JPEGBuffer buffer, int width, int height) 950 throws IOException { 951 952 super(buffer, width, height, width*height*3, "JFIFthumbRGB"); 953 } 954 955 JFIFThumbRGB(Node node) throws IIOInvalidTreeException { 956 super(node, "JFIFthumbRGB"); 957 } 958 959 JFIFThumbRGB(BufferedImage thumb) throws IllegalThumbException { 960 super(thumb); 961 } 962 963 int getLength() { 964 return (thumbWidth*thumbHeight*3); 965 } 966 967 BufferedImage getThumbnail(ImageInputStream iis, 968 JPEGImageReader reader) 969 throws IOException { 970 iis.mark(); 971 iis.seek(streamPos); 972 DataBufferByte buffer = new DataBufferByte (getLength()); 973 readByteBuffer(iis, 974 buffer.getData(), 975 reader, 976 1.0F, 977 0.0F); 978 iis.reset(); 979 980 WritableRaster raster = 981 Raster.createInterleavedRaster(buffer, 982 thumbWidth, 983 thumbHeight, 984 thumbWidth*3, 985 3, 986 new int [] {0, 1, 2}, 987 null); 988 ColorModel cm = new ComponentColorModel (JPEG.sRGB, 989 false, 990 false, 991 ColorModel.OPAQUE, 992 DataBuffer.TYPE_BYTE); 993 return new BufferedImage (cm, 994 raster, 995 false, 996 null); 997 } 998 999 void write(ImageOutputStream ios, 1000 JPEGImageWriter writer) throws IOException { 1001 super.write(ios, writer); writePixels(ios, writer); 1003 } 1004 1005 } 1006 1007 1011 class JFIFThumbPalette extends JFIFThumbUncompressed { 1012 private static final int PALETTE_SIZE = 768; 1013 1014 JFIFThumbPalette(JPEGBuffer buffer, int width, int height) 1015 throws IOException { 1016 super(buffer, 1017 width, 1018 height, 1019 PALETTE_SIZE + width * height, 1020 "JFIFThumbPalette"); 1021 } 1022 1023 JFIFThumbPalette(Node node) throws IIOInvalidTreeException { 1024 super(node, "JFIFThumbPalette"); 1025 } 1026 1027 JFIFThumbPalette(BufferedImage thumb) throws IllegalThumbException { 1028 super(thumb); 1029 IndexColorModel icm = (IndexColorModel ) thumbnail.getColorModel(); 1030 if (icm.getMapSize() > 256) { 1031 throw new IllegalThumbException(); 1032 } 1033 } 1034 1035 int getLength() { 1036 return (thumbWidth*thumbHeight + PALETTE_SIZE); 1037 } 1038 1039 BufferedImage getThumbnail(ImageInputStream iis, 1040 JPEGImageReader reader) 1041 throws IOException { 1042 iis.mark(); 1043 iis.seek(streamPos); 1044 byte [] palette = new byte [PALETTE_SIZE]; 1046 float palettePart = ((float) PALETTE_SIZE) / getLength(); 1047 readByteBuffer(iis, 1048 palette, 1049 reader, 1050 palettePart, 1051 0.0F); 1052 DataBufferByte buffer = new DataBufferByte (thumbWidth*thumbHeight); 1053 readByteBuffer(iis, 1054 buffer.getData(), 1055 reader, 1056 1.0F-palettePart, 1057 palettePart); 1058 iis.read(); 1059 iis.reset(); 1060 1061 IndexColorModel cm = new IndexColorModel (8, 1062 256, 1063 palette, 1064 0, 1065 false); 1066 SampleModel sm = cm.createCompatibleSampleModel(thumbWidth, 1067 thumbHeight); 1068 WritableRaster raster = 1069 Raster.createWritableRaster(sm, buffer, null); 1070 return new BufferedImage (cm, 1071 raster, 1072 false, 1073 null); 1074 } 1075 1076 void write(ImageOutputStream ios, 1077 JPEGImageWriter writer) throws IOException { 1078 super.write(ios, writer); byte [] palette = new byte[768]; 1081 IndexColorModel icm = (IndexColorModel ) thumbnail.getColorModel(); 1082 byte [] reds = new byte [256]; 1083 byte [] greens = new byte [256]; 1084 byte [] blues = new byte [256]; 1085 icm.getReds(reds); 1086 icm.getGreens(greens); 1087 icm.getBlues(blues); 1088 for (int i = 0; i < 256; i++) { 1089 palette[i*3] = reds[i]; 1090 palette[i*3+1] = greens[i]; 1091 palette[i*3+2] = blues[i]; 1092 } 1093 ios.write(palette); 1094 writePixels(ios, writer); 1095 } 1096 } 1097 1098 1099 1105 class JFIFThumbJPEG extends JFIFThumb { 1106 JPEGMetadata thumbMetadata = null; 1107 byte [] data = null; private static final int PREAMBLE_SIZE = 6; 1109 1110 JFIFThumbJPEG(JPEGBuffer buffer, 1111 int length, 1112 JPEGImageReader reader) throws IOException { 1113 super(buffer); 1114 long finalPos = streamPos + (length - PREAMBLE_SIZE); 1116 buffer.iis.seek(streamPos); 1119 thumbMetadata = new JPEGMetadata(false, true, buffer.iis, reader); 1120 buffer.iis.seek(finalPos); 1122 buffer.bufAvail = 0; 1124 buffer.bufPtr = 0; 1125 } 1126 1127 JFIFThumbJPEG(Node node) throws IIOInvalidTreeException { 1128 if (node.getChildNodes().getLength() > 1) { 1129 throw new IIOInvalidTreeException 1130 ("JFIFThumbJPEG node must have 0 or 1 child", node); 1131 } 1132 Node child = node.getFirstChild(); 1133 if (child != null) { 1134 String name = child.getNodeName(); 1135 if (!name.equals("markerSequence")) { 1136 throw new IIOInvalidTreeException 1137 ("JFIFThumbJPEG child must be a markerSequence node", 1138 node); 1139 } 1140 thumbMetadata = new JPEGMetadata(false, true); 1141 thumbMetadata.setFromMarkerSequenceNode(child); 1142 } 1143 } 1144 1145 JFIFThumbJPEG(BufferedImage thumb) throws IllegalThumbException { 1146 int INITIAL_BUFSIZE = 4096; 1147 int MAZ_BUFSIZE = 65535 - 2 - PREAMBLE_SIZE; 1148 try { 1149 ByteArrayOutputStream baos = 1150 new ByteArrayOutputStream (INITIAL_BUFSIZE); 1151 MemoryCacheImageOutputStream mos = 1152 new MemoryCacheImageOutputStream (baos); 1153 1154 JPEGImageWriter thumbWriter = new JPEGImageWriter(null); 1155 1156 thumbWriter.setOutput(mos); 1157 1158 JPEGMetadata metadata = 1160 (JPEGMetadata) thumbWriter.getDefaultImageMetadata 1161 (new ImageTypeSpecifier (thumb), null); 1162 1163 MarkerSegment jfif = metadata.findMarkerSegment 1165 (JFIFMarkerSegment.class, true); 1166 if (jfif == null) { 1167 throw new IllegalThumbException(); 1168 } 1169 1170 metadata.markerSequence.remove(jfif); 1171 1172 1192 1193 thumbWriter.write(new IIOImage (thumb, null, metadata)); 1194 1195 thumbWriter.dispose(); 1196 if (baos.size() > MAZ_BUFSIZE) { 1198 throw new IllegalThumbException(); 1199 } 1200 data = baos.toByteArray(); 1201 } catch (IOException e) { 1202 throw new IllegalThumbException(); 1203 } 1204 } 1205 1206 int getWidth() { 1207 int retval = 0; 1208 SOFMarkerSegment sof = 1209 (SOFMarkerSegment) thumbMetadata.findMarkerSegment 1210 (SOFMarkerSegment.class, true); 1211 if (sof != null) { 1212 retval = sof.samplesPerLine; 1213 } 1214 return retval; 1215 } 1216 1217 int getHeight() { 1218 int retval = 0; 1219 SOFMarkerSegment sof = 1220 (SOFMarkerSegment) thumbMetadata.findMarkerSegment 1221 (SOFMarkerSegment.class, true); 1222 if (sof != null) { 1223 retval = sof.numLines; 1224 } 1225 return retval; 1226 } 1227 1228 private class ThumbnailReadListener 1229 implements IIOReadProgressListener { 1230 JPEGImageReader reader = null; 1231 ThumbnailReadListener (JPEGImageReader reader) { 1232 this.reader = reader; 1233 } 1234 public void sequenceStarted(ImageReader source, int minIndex) {} 1235 public void sequenceComplete(ImageReader source) {} 1236 public void imageStarted(ImageReader source, int imageIndex) {} 1237 public void imageProgress(ImageReader source, 1238 float percentageDone) { 1239 reader.thumbnailProgress(percentageDone); 1240 } 1241 public void imageComplete(ImageReader source) {} 1242 public void thumbnailStarted(ImageReader source, 1243 int imageIndex, int thumbnailIndex) {} 1244 public void thumbnailProgress(ImageReader source, float percentageDone) {} 1245 public void thumbnailComplete(ImageReader source) {} 1246 public void readAborted(ImageReader source) {} 1247 } 1248 1249 BufferedImage getThumbnail(ImageInputStream iis, 1250 JPEGImageReader reader) 1251 throws IOException { 1252 iis.mark(); 1253 iis.seek(streamPos); 1254 JPEGImageReader thumbReader = new JPEGImageReader(null); 1255 thumbReader.setInput(iis); 1256 thumbReader.addIIOReadProgressListener 1257 (new ThumbnailReadListener(reader)); 1258 BufferedImage ret = thumbReader.read(0, null); 1259 thumbReader.dispose(); 1260 iis.reset(); 1261 return ret; 1262 } 1263 1264 protected Object clone() { 1265 JFIFThumbJPEG newGuy = (JFIFThumbJPEG) super.clone(); 1266 if (thumbMetadata != null) { 1267 newGuy.thumbMetadata = (JPEGMetadata) thumbMetadata.clone(); 1268 } 1269 return newGuy; 1270 } 1271 1272 IIOMetadataNode getNativeNode() { 1273 IIOMetadataNode node = new IIOMetadataNode ("JFIFthumbJPEG"); 1274 if (thumbMetadata != null) { 1275 node.appendChild(thumbMetadata.getNativeTree()); 1276 } 1277 return node; 1278 } 1279 1280 int getLength() { 1281 if (data == null) { 1282 return 0; 1283 } else { 1284 return data.length; 1285 } 1286 } 1287 1288 void write(ImageOutputStream ios, 1289 JPEGImageWriter writer) throws IOException { 1290 int progInterval = data.length / 20; if (progInterval == 0) { 1292 progInterval = 1; 1293 } 1294 for (int offset = 0; 1295 offset < data.length;) { 1296 int len = Math.min(progInterval, data.length-offset); 1297 ios.write(data, offset, len); 1298 offset += progInterval; 1299 float percentDone = ((float) offset * 100) / data.length; 1300 if (percentDone > 100.0F) { 1301 percentDone = 100.0F; 1302 } 1303 writer.thumbnailProgress (percentDone); 1304 } 1305 } 1306 1307 void print () { 1308 System.out.println("JFIF thumbnail stored as JPEG"); 1309 } 1310 } 1311 1312 1318 static void writeICC(ICC_Profile profile, ImageOutputStream ios) 1319 throws IOException { 1320 int LENGTH_LENGTH = 2; 1321 final String ID = "ICC_PROFILE"; 1322 int ID_LENGTH = ID.length()+1; int COUNTS_LENGTH = 2; 1324 int MAX_ICC_CHUNK_SIZE = 1325 65535 - LENGTH_LENGTH - ID_LENGTH - COUNTS_LENGTH; 1326 1327 byte [] data = profile.getData(); 1328 int numChunks = data.length / MAX_ICC_CHUNK_SIZE; 1329 if ((data.length % MAX_ICC_CHUNK_SIZE) != 0) { 1330 numChunks++; 1331 } 1332 int chunkNum = 1; 1333 int offset = 0; 1334 for (int i = 0; i < numChunks; i++) { 1335 int dataLength = Math.min(data.length-offset, MAX_ICC_CHUNK_SIZE); 1336 int segLength = dataLength+COUNTS_LENGTH+ID_LENGTH+LENGTH_LENGTH; 1337 ios.write(0xff); 1338 ios.write(JPEG.APP2); 1339 MarkerSegment.write2bytes(ios, segLength); 1340 byte [] id = ID.getBytes("US-ASCII"); 1341 ios.write(id); 1342 ios.write(0); ios.write(chunkNum++); 1344 ios.write(numChunks); 1345 ios.write(data, offset, dataLength); 1346 offset += dataLength; 1347 } 1348 } 1349 1350 1356 class ICCMarkerSegment extends MarkerSegment { 1357 ArrayList chunks = null; 1358 byte [] profile = null; private static final int ID_SIZE = 12; 1361 int chunksRead; 1362 int numChunks; 1363 1364 ICCMarkerSegment(ICC_ColorSpace cs) { 1365 super(JPEG.APP2); 1366 chunks = null; 1367 chunksRead = 0; 1368 numChunks = 0; 1369 profile = cs.getProfile().getData(); 1370 } 1371 1372 ICCMarkerSegment(JPEGBuffer buffer) throws IOException { 1373 super(buffer); if (debug) { 1375 System.out.println("Creating new ICC segment"); 1376 } 1377 buffer.bufPtr += ID_SIZE; buffer.bufAvail -= ID_SIZE; 1379 1384 length -= ID_SIZE; 1385 1386 int chunkNum = buffer.buf[buffer.bufPtr] & 0xff; 1388 numChunks = buffer.buf[buffer.bufPtr+1] & 0xff; 1390 1391 if (chunkNum > numChunks) { 1392 throw new IIOException 1393 ("Image format Error; chunk num > num chunks"); 1394 } 1395 1396 if (numChunks == 1) { 1398 length -= 2; 1400 profile = new byte[length]; 1401 buffer.bufPtr += 2; 1402 buffer.bufAvail-=2; 1403 buffer.readData(profile); 1404 inICC = false; 1405 } else { 1406 byte [] profileData = new byte[length]; 1408 length -= 2; 1411 buffer.readData(profileData); 1412 chunks = new ArrayList (); 1413 chunks.add(profileData); 1414 chunksRead = 1; 1415 inICC = true; 1416 } 1417 } 1418 1419 ICCMarkerSegment(Node node) throws IIOInvalidTreeException { 1420 super(JPEG.APP2); 1421 if (node instanceof IIOMetadataNode ) { 1422 IIOMetadataNode ourNode = (IIOMetadataNode ) node; 1423 ICC_Profile prof = (ICC_Profile ) ourNode.getUserObject(); 1424 if (prof != null) { profile = prof.getData(); 1426 } 1427 } 1428 } 1429 1430 protected Object clone () { 1431 ICCMarkerSegment newGuy = (ICCMarkerSegment) super.clone(); 1432 if (profile != null) { 1433 newGuy.profile = (byte[]) profile.clone(); 1434 } 1435 return newGuy; 1436 } 1437 1438 boolean addData(JPEGBuffer buffer) throws IOException { 1439 if (debug) { 1440 System.out.println("Adding to ICC segment"); 1441 } 1442 buffer.bufPtr++; 1444 buffer.bufAvail--; 1445 int dataLen = (buffer.buf[buffer.bufPtr++] & 0xff) << 8; 1447 dataLen |= buffer.buf[buffer.bufPtr++] & 0xff; 1448 buffer.bufAvail -= 2; 1449 dataLen -= 2; 1451 buffer.bufPtr += ID_SIZE; buffer.bufAvail -= ID_SIZE; 1454 1459 dataLen -= ID_SIZE; 1460 1461 int chunkNum = buffer.buf[buffer.bufPtr] & 0xff; 1463 if (chunkNum > numChunks) { 1464 throw new IIOException 1465 ("Image format Error; chunk num > num chunks"); 1466 } 1467 1468 int newNumChunks = buffer.buf[buffer.bufPtr+1] & 0xff; 1470 if (numChunks != newNumChunks) { 1471 throw new IIOException 1472 ("Image format Error; icc num chunks mismatch"); 1473 } 1474 dataLen -= 2; 1475 if (debug) { 1476 System.out.println("chunkNum: " + chunkNum 1477 + ", numChunks: " + numChunks 1478 + ", dataLen: " + dataLen); 1479 } 1480 boolean retval = false; 1481 byte [] profileData = new byte[dataLen]; 1482 buffer.readData(profileData); 1483 chunks.add(profileData); 1484 length += dataLen; 1485 chunksRead++; 1486 if (chunksRead < numChunks) { 1487 inICC = true; 1488 } else { 1489 if (debug) { 1490 System.out.println("Completing profile; total length is " 1491 + length); 1492 } 1493 profile = new byte[length]; 1495 1498 int index = 0; 1499 for (int i = 1; i <= numChunks; i++) { 1500 boolean foundIt = false; 1501 for (int chunk = 0; chunk < chunks.size(); chunk++) { 1502 byte [] chunkData = (byte []) chunks.get(chunk); 1503 if (chunkData[0] == i) { System.arraycopy(chunkData, 2, 1505 profile, index, 1506 chunkData.length-2); 1507 index += chunkData.length-2; 1508 foundIt = true; 1509 } 1510 } 1511 if (foundIt == false) { 1512 throw new IIOException 1513 ("Image Format Error: Missing ICC chunk num " + i); 1514 } 1515 } 1516 1517 chunks = null; 1518 chunksRead = 0; 1519 numChunks = 0; 1520 inICC = false; 1521 retval = true; 1522 } 1523 return retval; 1524 } 1525 1526 IIOMetadataNode getNativeNode() { 1527 IIOMetadataNode node = new IIOMetadataNode ("app2ICC"); 1528 if (profile != null) { 1529 node.setUserObject(ICC_Profile.getInstance(profile)); 1530 } 1531 return node; 1532 } 1533 1534 1538 void write(ImageOutputStream ios) throws IOException { 1539 } 1541 1542 void print () { 1543 printTag("ICC Profile APP2"); 1544 } 1545 } 1546} 1547 1548 | Popular Tags |