1 7 8 package com.sun.imageio.plugins.jpeg; 9 10 import javax.imageio.IIOException ; 11 import javax.imageio.ImageWriter ; 12 import javax.imageio.ImageWriteParam ; 13 import javax.imageio.IIOImage ; 14 import javax.imageio.ImageTypeSpecifier ; 15 import javax.imageio.metadata.IIOMetadata ; 16 import javax.imageio.metadata.IIOMetadataFormatImpl ; 17 import javax.imageio.metadata.IIOInvalidTreeException ; 18 import javax.imageio.spi.ImageWriterSpi ; 19 import javax.imageio.stream.ImageOutputStream ; 20 import javax.imageio.plugins.jpeg.JPEGImageWriteParam ; 21 import javax.imageio.plugins.jpeg.JPEGQTable ; 22 import javax.imageio.plugins.jpeg.JPEGHuffmanTable ; 23 24 import org.w3c.dom.Node ; 25 26 import java.awt.image.Raster ; 27 import java.awt.image.WritableRaster ; 28 import java.awt.image.SampleModel ; 29 import java.awt.image.DataBuffer ; 30 import java.awt.image.DataBufferByte ; 31 import java.awt.image.ColorModel ; 32 import java.awt.image.IndexColorModel ; 33 import java.awt.image.ColorConvertOp ; 34 import java.awt.image.RenderedImage ; 35 import java.awt.image.BufferedImage ; 36 import java.awt.color.ColorSpace ; 37 import java.awt.color.ICC_ColorSpace ; 38 import java.awt.color.ICC_Profile ; 39 import java.awt.Dimension ; 40 import java.awt.Rectangle ; 41 import java.awt.Transparency ; 42 43 import java.io.IOException ; 44 45 import java.util.List ; 46 import java.util.ArrayList ; 47 import java.util.Iterator ; 48 49 import sun.java2d.Disposer; 50 import sun.java2d.DisposerRecord; 51 52 public class JPEGImageWriter extends ImageWriter { 53 54 56 private boolean debug = false; 57 58 64 private long structPointer = 0; 65 66 67 68 private ImageOutputStream ios = null; 69 70 71 private Raster srcRas = null; 72 73 74 private WritableRaster raster = null; 75 76 80 private boolean indexed = false; 81 private IndexColorModel indexCM = null; 82 83 private boolean convertTosRGB = false; private WritableRaster converted = null; 85 86 private boolean isAlphaPremultiplied = false; 87 private ColorModel srcCM = null; 88 89 92 private List thumbnails = null; 93 94 97 private ICC_Profile iccProfile = null; 98 99 private int sourceXOffset = 0; 100 private int sourceYOffset = 0; 101 private int sourceWidth = 0; 102 private int [] srcBands = null; 103 private int sourceHeight = 0; 104 105 106 private int currentImage = 0; 107 108 private ColorConvertOp convertOp = null; 109 110 private JPEGQTable [] streamQTables = null; 111 private JPEGHuffmanTable [] streamDCHuffmanTables = null; 112 private JPEGHuffmanTable [] streamACHuffmanTables = null; 113 114 private boolean ignoreJFIF = false; private boolean forceJFIF = false; private boolean ignoreAdobe = false; private int newAdobeTransform = JPEG.ADOBE_IMPOSSIBLE; private boolean writeDefaultJFIF = false; 120 private boolean writeAdobe = false; 121 private JPEGMetadata metadata = null; 122 123 private boolean sequencePrepared = false; 124 125 private int numScans = 0; 126 127 128 private Object disposerReferent = new Object (); 129 130 131 private DisposerRecord disposerRecord; 132 133 135 137 protected static final int WARNING_DEST_IGNORED = 0; 138 protected static final int WARNING_STREAM_METADATA_IGNORED = 1; 139 protected static final int WARNING_DEST_METADATA_COMP_MISMATCH = 2; 140 protected static final int WARNING_DEST_METADATA_JFIF_MISMATCH = 3; 141 protected static final int WARNING_DEST_METADATA_ADOBE_MISMATCH = 4; 142 protected static final int WARNING_IMAGE_METADATA_JFIF_MISMATCH = 5; 143 protected static final int WARNING_IMAGE_METADATA_ADOBE_MISMATCH = 6; 144 protected static final int WARNING_METADATA_NOT_JPEG_FOR_RASTER = 7; 145 protected static final int WARNING_NO_BANDS_ON_INDEXED = 8; 146 protected static final int WARNING_ILLEGAL_THUMBNAIL = 9; 147 protected static final int WARNING_IGNORING_THUMBS = 10; 148 protected static final int WARNING_FORCING_JFIF = 11; 149 protected static final int WARNING_THUMB_CLIPPED = 12; 150 protected static final int WARNING_METADATA_ADJUSTED_FOR_THUMB = 13; 151 protected static final int WARNING_NO_RGB_THUMB_AS_INDEXED = 14; 152 protected static final int WARNING_NO_GRAY_THUMB_AS_INDEXED = 15; 153 154 private static final int MAX_WARNING = WARNING_NO_GRAY_THUMB_AS_INDEXED; 155 156 158 160 static { 161 java.security.AccessController.doPrivileged( 162 new sun.security.action.LoadLibraryAction("jpeg")); 163 initWriterIDs(ImageOutputStream .class, 164 JPEGQTable .class, 165 JPEGHuffmanTable .class); 166 } 167 168 170 public JPEGImageWriter(ImageWriterSpi originator) { 171 super(originator); 172 structPointer = initJPEGImageWriter(); 173 disposerRecord = new JPEGWriterDisposerRecord(structPointer); 174 Disposer.addRecord(disposerReferent, disposerRecord); 175 } 176 177 public void setOutput(Object output) { 178 super.setOutput(output); resetInternalState(); 180 ios = (ImageOutputStream ) output; setDest(structPointer, ios); 183 } 184 185 public ImageWriteParam getDefaultWriteParam() { 186 return new JPEGImageWriteParam (null); 187 } 188 189 public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) { 190 return new JPEGMetadata(param, this); 191 } 192 193 public IIOMetadata 194 getDefaultImageMetadata(ImageTypeSpecifier imageType, 195 ImageWriteParam param) { 196 return new JPEGMetadata(imageType, param, this); 197 } 198 199 public IIOMetadata convertStreamMetadata(IIOMetadata inData, 200 ImageWriteParam param) { 201 if (inData instanceof JPEGMetadata) { 206 JPEGMetadata jpegData = (JPEGMetadata) inData; 207 if (jpegData.isStream) { 208 return inData; 209 } 210 } 211 return null; 212 } 213 214 public IIOMetadata 215 convertImageMetadata(IIOMetadata inData, 216 ImageTypeSpecifier imageType, 217 ImageWriteParam param) { 218 if (inData instanceof JPEGMetadata) { 220 JPEGMetadata jpegData = (JPEGMetadata) inData; 221 if (!jpegData.isStream) { 222 return inData; 223 } else { 224 return null; 227 } 228 } 229 if (inData.isStandardMetadataFormatSupported()) { 232 String formatName = 233 IIOMetadataFormatImpl.standardMetadataFormatName; 234 Node tree = inData.getAsTree(formatName); 235 if (tree != null) { 236 JPEGMetadata jpegData = new JPEGMetadata(imageType, 237 param, 238 this); 239 try { 240 jpegData.setFromTree(formatName, tree); 241 } catch (IIOInvalidTreeException e) { 242 return null; 245 } 246 247 return jpegData; 248 } 249 } 250 return null; 251 } 252 253 public int getNumThumbnailsSupported(ImageTypeSpecifier imageType, 254 ImageWriteParam param, 255 IIOMetadata streamMetadata, 256 IIOMetadata imageMetadata) { 257 if (jfifOK(imageType, param, streamMetadata, imageMetadata)) { 258 return Integer.MAX_VALUE; 259 } 260 return 0; 261 } 262 263 static final Dimension [] preferredThumbSizes = {new Dimension (1, 1), 264 new Dimension (255, 255)}; 265 266 public Dimension [] getPreferredThumbnailSizes(ImageTypeSpecifier imageType, 267 ImageWriteParam param, 268 IIOMetadata streamMetadata, 269 IIOMetadata imageMetadata) { 270 if (jfifOK(imageType, param, streamMetadata, imageMetadata)) { 271 return (Dimension [])preferredThumbSizes.clone(); 272 } 273 return null; 274 } 275 276 private boolean jfifOK(ImageTypeSpecifier imageType, 277 ImageWriteParam param, 278 IIOMetadata streamMetadata, 279 IIOMetadata imageMetadata) { 280 if ((imageType != null) && 282 (!JPEG.isJFIFcompliant(imageType, true))) { 283 return false; 284 } 285 if (imageMetadata != null) { 286 JPEGMetadata metadata = null; 287 if (imageMetadata instanceof JPEGMetadata) { 288 metadata = (JPEGMetadata) imageMetadata; 289 } else { 290 metadata = (JPEGMetadata)convertImageMetadata(imageMetadata, 291 imageType, 292 param); 293 } 294 if (metadata.findMarkerSegment 296 (JFIFMarkerSegment.class, true) == null){ 297 return false; 298 } 299 } 300 return true; 301 } 302 303 public boolean canWriteRasters() { 304 return true; 305 } 306 307 public void write(IIOMetadata streamMetadata, 308 IIOImage image, 309 ImageWriteParam param) throws IOException { 310 311 if (ios == null) { 312 throw new IllegalStateException ("Output has not been set!"); 313 } 314 315 if (image == null) { 316 throw new IllegalArgumentException ("image is null!"); 317 } 318 319 if (streamMetadata != null) { 321 warningOccurred(WARNING_STREAM_METADATA_IGNORED); 322 } 323 324 boolean rasterOnly = image.hasRaster(); 326 327 RenderedImage rimage = null; 328 if (rasterOnly) { 329 srcRas = image.getRaster(); 330 } else { 331 rimage = image.getRenderedImage(); 332 if (rimage instanceof BufferedImage ) { 333 srcRas = ((BufferedImage )rimage).getRaster(); 334 } else { 335 srcRas = rimage.getData(); 337 } 338 } 339 340 342 int numSrcBands = srcRas.getNumBands(); 344 indexed = false; 345 indexCM = null; 346 ColorModel cm = null; 347 ColorSpace cs = null; 348 isAlphaPremultiplied = false; 349 srcCM = null; 350 if (!rasterOnly) { 351 cm = rimage.getColorModel(); 352 if (cm != null) { 353 cs = cm.getColorSpace(); 354 if (cm instanceof IndexColorModel ) { 355 indexed = true; 356 indexCM = (IndexColorModel ) cm; 357 numSrcBands = cm.getNumComponents(); 358 } 359 if (cm.isAlphaPremultiplied()) { 360 isAlphaPremultiplied = true; 361 srcCM = cm; 362 } 363 } 364 } 365 366 srcBands = JPEG.bandOffsets[numSrcBands-1]; 367 int numBandsUsed = numSrcBands; 368 370 if (param != null) { 371 int[] sBands = param.getSourceBands(); 372 if (sBands != null) { 373 if (indexed) { 374 warningOccurred(WARNING_NO_BANDS_ON_INDEXED); 375 } else { 376 srcBands = sBands; 377 numBandsUsed = srcBands.length; 378 if (numBandsUsed > numSrcBands) { 379 throw new IIOException 380 ("ImageWriteParam specifies too many source bands"); 381 } 382 } 383 } 384 } 385 386 boolean usingBandSubset = (numBandsUsed != numSrcBands); 387 boolean fullImage = ((!rasterOnly) && (!usingBandSubset)); 388 389 int [] bandSizes = null; 390 if (!indexed) { 391 bandSizes = srcRas.getSampleModel().getSampleSize(); 392 if (usingBandSubset) { 394 int [] temp = new int [numBandsUsed]; 395 for (int i = 0; i < numBandsUsed; i++) { 396 temp[i] = bandSizes[srcBands[i]]; 397 } 398 bandSizes = temp; 399 } 400 } else { 401 int [] tempSize = srcRas.getSampleModel().getSampleSize(); 402 bandSizes = new int [numSrcBands]; 403 for (int i = 0; i < numSrcBands; i++) { 404 bandSizes[i] = tempSize[0]; } 406 } 407 408 for (int i = 0; i < bandSizes.length; i++) { 409 if (bandSizes[i] > 8) { 414 throw new IIOException ("Sample size must be <= 8"); 415 } 416 if (indexed) { 422 bandSizes[i] = 8; 423 } 424 } 425 426 if (debug) { 427 System.out.println("numSrcBands is " + numSrcBands); 428 System.out.println("numBandsUsed is " + numBandsUsed); 429 System.out.println("usingBandSubset is " + usingBandSubset); 430 System.out.println("fullImage is " + fullImage); 431 System.out.print("Band sizes:"); 432 for (int i = 0; i< bandSizes.length; i++) { 433 System.out.print(" " + bandSizes[i]); 434 } 435 System.out.println(); 436 } 437 438 ImageTypeSpecifier destType = null; 440 if (param != null) { 441 destType = param.getDestinationType(); 442 if ((fullImage) && (destType != null)) { 444 warningOccurred(WARNING_DEST_IGNORED); 445 destType = null; 446 } 447 } 448 449 451 sourceXOffset = srcRas.getMinX(); 452 sourceYOffset = srcRas.getMinY(); 453 int imageWidth = srcRas.getWidth(); 454 int imageHeight = srcRas.getHeight(); 455 sourceWidth = imageWidth; 456 sourceHeight = imageHeight; 457 int periodX = 1; 458 int periodY = 1; 459 int gridX = 0; 460 int gridY = 0; 461 JPEGQTable [] qTables = null; 462 JPEGHuffmanTable [] DCHuffmanTables = null; 463 JPEGHuffmanTable [] ACHuffmanTables = null; 464 boolean optimizeHuffman = false; 465 JPEGImageWriteParam jparam = null; 466 int progressiveMode = ImageWriteParam.MODE_DISABLED; 467 468 if (param != null) { 469 470 Rectangle sourceRegion = param.getSourceRegion(); 471 if (sourceRegion != null) { 472 Rectangle imageBounds = new Rectangle (sourceXOffset, 473 sourceYOffset, 474 sourceWidth, 475 sourceHeight); 476 sourceRegion = sourceRegion.intersection(imageBounds); 477 sourceXOffset = sourceRegion.x; 478 sourceYOffset = sourceRegion.y; 479 sourceWidth = sourceRegion.width; 480 sourceHeight = sourceRegion.height; 481 } 482 483 if (sourceWidth + sourceXOffset > imageWidth) { 484 sourceWidth = imageWidth - sourceXOffset; 485 } 486 if (sourceHeight + sourceYOffset > imageHeight) { 487 sourceHeight = imageHeight - sourceYOffset; 488 } 489 490 periodX = param.getSourceXSubsampling(); 491 periodY = param.getSourceYSubsampling(); 492 gridX = param.getSubsamplingXOffset(); 493 gridY = param.getSubsamplingYOffset(); 494 495 switch(param.getCompressionMode()) { 496 case ImageWriteParam.MODE_DISABLED: 497 throw new IIOException ("JPEG compression cannot be disabled"); 498 case ImageWriteParam.MODE_EXPLICIT: 499 float quality = param.getCompressionQuality(); 500 quality = JPEG.convertToLinearQuality(quality); 501 qTables = new JPEGQTable [2]; 502 qTables[0] = JPEGQTable.K1Luminance.getScaledInstance 503 (quality, true); 504 qTables[1] = JPEGQTable.K2Chrominance.getScaledInstance 505 (quality, true); 506 break; 507 case ImageWriteParam.MODE_DEFAULT: 508 qTables = new JPEGQTable [2]; 509 qTables[0] = JPEGQTable.K1Div2Luminance; 510 qTables[1] = JPEGQTable.K2Div2Chrominance; 511 break; 512 } 514 515 progressiveMode = param.getProgressiveMode(); 516 517 if (param instanceof JPEGImageWriteParam ) { 518 jparam = (JPEGImageWriteParam )param; 519 optimizeHuffman = jparam.getOptimizeHuffmanTables(); 520 } 521 } 522 523 IIOMetadata mdata = image.getMetadata(); 525 if (mdata != null) { 526 if (mdata instanceof JPEGMetadata) { 527 metadata = (JPEGMetadata) mdata; 528 if (debug) { 529 System.out.println 530 ("We have metadata, and it's JPEG metadata"); 531 } 532 } else { 533 if (!rasterOnly) { 534 ImageTypeSpecifier type = destType; 535 if (type == null) { 536 type = new ImageTypeSpecifier (rimage); 537 } 538 metadata = (JPEGMetadata) convertImageMetadata(mdata, 539 type, 540 param); 541 } else { 542 warningOccurred(WARNING_METADATA_NOT_JPEG_FOR_RASTER); 543 } 544 } 545 } 546 547 549 ignoreJFIF = false; ignoreAdobe = false; newAdobeTransform = JPEG.ADOBE_IMPOSSIBLE; writeDefaultJFIF = false; 553 writeAdobe = false; 554 555 int inCsType = JPEG.JCS_UNKNOWN; 557 int outCsType = JPEG.JCS_UNKNOWN; 558 559 JFIFMarkerSegment jfif = null; 560 AdobeMarkerSegment adobe = null; 561 SOFMarkerSegment sof = null; 562 563 if (metadata != null) { 564 jfif = (JFIFMarkerSegment) metadata.findMarkerSegment 565 (JFIFMarkerSegment.class, true); 566 adobe = (AdobeMarkerSegment) metadata.findMarkerSegment 567 (AdobeMarkerSegment.class, true); 568 sof = (SOFMarkerSegment) metadata.findMarkerSegment 569 (SOFMarkerSegment.class, true); 570 } 571 572 iccProfile = null; convertTosRGB = false; converted = null; 575 576 if (destType != null) { 577 if (numBandsUsed != destType.getNumBands()) { 578 throw new IIOException 579 ("Number of source bands != number of destination bands"); 580 } 581 cs = destType.getColorModel().getColorSpace(); 582 if (metadata != null) { 584 checkSOFBands(sof, numBandsUsed); 585 586 checkJFIF(jfif, destType, false); 587 if ((jfif != null) && (ignoreJFIF == false)) { 589 if (JPEG.isNonStandardICC(cs)) { 590 iccProfile = ((ICC_ColorSpace ) cs).getProfile(); 591 } 592 } 593 checkAdobe(adobe, destType, false); 594 595 } else { if (JPEG.isJFIFcompliant(destType, false)) { 598 writeDefaultJFIF = true; 599 if (JPEG.isNonStandardICC(cs)) { 601 iccProfile = ((ICC_ColorSpace ) cs).getProfile(); 602 } 603 } else { 604 int transform = JPEG.transformForType(destType, false); 605 if (transform != JPEG.ADOBE_IMPOSSIBLE) { 606 writeAdobe = true; 607 newAdobeTransform = transform; 608 } 609 } 610 } 611 } else { if (metadata == null) { 613 if (fullImage) { metadata = new JPEGMetadata(new ImageTypeSpecifier (rimage), 616 param, this); 617 if (metadata.findMarkerSegment 618 (JFIFMarkerSegment.class, true) != null) { 619 cs = rimage.getColorModel().getColorSpace(); 620 if (JPEG.isNonStandardICC(cs)) { 621 iccProfile = ((ICC_ColorSpace ) cs).getProfile(); 622 } 623 } 624 625 inCsType = getSrcCSType(rimage); 626 outCsType = getDefaultDestCSType(rimage); 627 } 628 } else { checkSOFBands(sof, numBandsUsed); 632 if (fullImage) { 635 ImageTypeSpecifier inputType = 636 new ImageTypeSpecifier (rimage); 637 638 inCsType = getSrcCSType(rimage); 639 640 if (cm != null) { 641 boolean alpha = cm.hasAlpha(); 642 switch (cs.getType()) { 643 case ColorSpace.TYPE_GRAY: 644 if (!alpha) { 645 outCsType = JPEG.JCS_GRAYSCALE; 646 } else { 647 if (jfif != null) { 648 ignoreJFIF = true; 649 warningOccurred 650 (WARNING_IMAGE_METADATA_JFIF_MISMATCH); 651 } 652 } 654 if ((adobe != null) 655 && (adobe.transform != JPEG.ADOBE_UNKNOWN)) { 656 newAdobeTransform = JPEG.ADOBE_UNKNOWN; 657 warningOccurred 658 (WARNING_IMAGE_METADATA_ADOBE_MISMATCH); 659 } 660 break; 661 case ColorSpace.TYPE_RGB: 662 if (!alpha) { 663 if (jfif != null) { 664 outCsType = JPEG.JCS_YCbCr; 665 if (JPEG.isNonStandardICC(cs) 666 || ((cs instanceof ICC_ColorSpace ) 667 && (jfif.iccSegment != null))) { 668 iccProfile = 669 ((ICC_ColorSpace ) cs).getProfile(); 670 } 671 } else if (adobe != null) { 672 switch (adobe.transform) { 673 case JPEG.ADOBE_UNKNOWN: 674 outCsType = JPEG.JCS_RGB; 675 break; 676 case JPEG.ADOBE_YCC: 677 outCsType = JPEG.JCS_YCbCr; 678 break; 679 default: 680 warningOccurred 681 (WARNING_IMAGE_METADATA_ADOBE_MISMATCH); 682 newAdobeTransform = JPEG.ADOBE_UNKNOWN; 683 outCsType = JPEG.JCS_RGB; 684 break; 685 } 686 } else { 687 int outCS = sof.getIDencodedCSType(); 689 if (outCS != JPEG.JCS_UNKNOWN) { 692 outCsType = outCS; 693 } else { 694 boolean subsampled = 695 isSubsampled(sof.componentSpecs); 696 if (subsampled) { 697 outCsType = JPEG.JCS_YCbCr; 698 } else { 699 outCsType = JPEG.JCS_RGB; 700 } 701 } 702 } 703 } else { if (jfif != null) { 705 ignoreJFIF = true; 706 warningOccurred 707 (WARNING_IMAGE_METADATA_JFIF_MISMATCH); 708 } 709 if (adobe != null) { 710 if (adobe.transform 711 != JPEG.ADOBE_UNKNOWN) { 712 newAdobeTransform = JPEG.ADOBE_UNKNOWN; 713 warningOccurred 714 (WARNING_IMAGE_METADATA_ADOBE_MISMATCH); 715 } 716 outCsType = JPEG.JCS_RGBA; 717 } else { 718 int outCS = sof.getIDencodedCSType(); 720 if (outCS != JPEG.JCS_UNKNOWN) { 723 outCsType = outCS; 724 } else { 725 boolean subsampled = 726 isSubsampled(sof.componentSpecs); 727 outCsType = subsampled ? 728 JPEG.JCS_YCbCrA : JPEG.JCS_RGBA; 729 } 730 } 731 } 732 break; 733 case ColorSpace.TYPE_3CLR: 734 if (cs == JPEG.YCC) { 735 if (!alpha) { 736 if (jfif != null) { 737 convertTosRGB = true; 738 convertOp = 739 new ColorConvertOp (cs, 740 JPEG.sRGB, 741 null); 742 outCsType = JPEG.JCS_YCbCr; 743 } else if (adobe != null) { 744 if (adobe.transform 745 != JPEG.ADOBE_YCC) { 746 newAdobeTransform = JPEG.ADOBE_YCC; 747 warningOccurred 748 (WARNING_IMAGE_METADATA_ADOBE_MISMATCH); 749 } 750 outCsType = JPEG.JCS_YCC; 751 } else { 752 outCsType = JPEG.JCS_YCC; 753 } 754 } else { if (jfif != null) { 756 ignoreJFIF = true; 757 warningOccurred 758 (WARNING_IMAGE_METADATA_JFIF_MISMATCH); 759 } else if (adobe != null) { 760 if (adobe.transform 761 != JPEG.ADOBE_UNKNOWN) { 762 newAdobeTransform 763 = JPEG.ADOBE_UNKNOWN; 764 warningOccurred 765 (WARNING_IMAGE_METADATA_ADOBE_MISMATCH); 766 } 767 } 768 outCsType = JPEG.JCS_YCCA; 769 } 770 } 771 } 772 } 773 } } 775 } 776 777 boolean metadataProgressive = false; 778 int [] scans = null; 779 780 if (metadata != null) { 781 if (sof == null) { 782 sof = (SOFMarkerSegment) metadata.findMarkerSegment 783 (SOFMarkerSegment.class, true); 784 } 785 if ((sof != null) && (sof.tag == JPEG.SOF2)) { 786 metadataProgressive = true; 787 if (progressiveMode == ImageWriteParam.MODE_COPY_FROM_METADATA) { 788 scans = collectScans(metadata, sof); } else { 790 numScans = 0; 791 } 792 } 793 if (jfif == null) { 794 jfif = (JFIFMarkerSegment) metadata.findMarkerSegment 795 (JFIFMarkerSegment.class, true); 796 } 797 } 798 799 thumbnails = image.getThumbnails(); 800 int numThumbs = image.getNumThumbnails(); 801 forceJFIF = false; 802 if (!writeDefaultJFIF) { 806 if (metadata == null) { 808 thumbnails = null; 809 if (numThumbs != 0) { 810 warningOccurred(WARNING_IGNORING_THUMBS); 811 } 812 } else { 813 if (fullImage == false) { 817 if (jfif == null) { 818 thumbnails = null; if (numThumbs != 0) { 820 warningOccurred(WARNING_IGNORING_THUMBS); 821 } 822 } 823 } else { if (jfif == null) { if ((outCsType == JPEG.JCS_GRAYSCALE) 827 || (outCsType == JPEG.JCS_YCbCr)) { 828 if (numThumbs != 0) { 829 forceJFIF = true; 830 warningOccurred(WARNING_FORCING_JFIF); 831 } 832 } else { thumbnails = null; 834 if (numThumbs != 0) { 835 warningOccurred(WARNING_IGNORING_THUMBS); 836 } 837 } 838 } 839 } 840 } 841 } 842 843 boolean haveMetadata = 846 ((metadata != null) || writeDefaultJFIF || writeAdobe); 847 848 850 boolean writeDQT = true; 852 boolean writeDHT = true; 853 854 DQTMarkerSegment dqt = null; 856 DHTMarkerSegment dht = null; 857 858 int restartInterval = 0; 859 860 if (metadata != null) { 861 dqt = (DQTMarkerSegment) metadata.findMarkerSegment 862 (DQTMarkerSegment.class, true); 863 dht = (DHTMarkerSegment) metadata.findMarkerSegment 864 (DHTMarkerSegment.class, true); 865 DRIMarkerSegment dri = 866 (DRIMarkerSegment) metadata.findMarkerSegment 867 (DRIMarkerSegment.class, true); 868 if (dri != null) { 869 restartInterval = dri.restartInterval; 870 } 871 872 if (dqt == null) { 873 writeDQT = false; 874 } 875 if (dht == null) { 876 writeDHT = false; } 878 } 879 880 if (qTables == null) { if (dqt != null) { 884 qTables = collectQTablesFromMetadata(metadata); 885 } else if (streamQTables != null) { 886 qTables = streamQTables; 887 } else if ((jparam != null) && (jparam.areTablesSet())) { 888 qTables = jparam.getQTables(); 889 } else { 890 qTables = JPEG.getDefaultQTables(); 891 } 892 893 } 894 895 if (optimizeHuffman == false) { 897 if ((dht != null) && (metadataProgressive == false)) { 899 DCHuffmanTables = collectHTablesFromMetadata(metadata, true); 900 ACHuffmanTables = collectHTablesFromMetadata(metadata, false); 901 } else if (streamDCHuffmanTables != null) { 902 DCHuffmanTables = streamDCHuffmanTables; 903 ACHuffmanTables = streamACHuffmanTables; 904 } else if ((jparam != null) && (jparam.areTablesSet())) { 905 DCHuffmanTables = jparam.getDCHuffmanTables(); 906 ACHuffmanTables = jparam.getACHuffmanTables(); 907 } else { 908 DCHuffmanTables = JPEG.getDefaultHuffmanTables(true); 909 ACHuffmanTables = JPEG.getDefaultHuffmanTables(false); 910 } 911 } 912 913 int [] componentIds = new int[numBandsUsed]; 915 int [] HsamplingFactors = new int[numBandsUsed]; 916 int [] VsamplingFactors = new int[numBandsUsed]; 917 int [] QtableSelectors = new int[numBandsUsed]; 918 for (int i = 0; i < numBandsUsed; i++) { 919 componentIds[i] = i+1; HsamplingFactors[i] = 1; 921 VsamplingFactors[i] = 1; 922 QtableSelectors[i] = 0; 923 } 924 925 if (sof != null) { 927 for (int i = 0; i < numBandsUsed; i++) { 928 if (forceJFIF == false) { componentIds[i] = sof.componentSpecs[i].componentId; 930 } 931 HsamplingFactors[i] = sof.componentSpecs[i].HsamplingFactor; 932 VsamplingFactors[i] = sof.componentSpecs[i].VsamplingFactor; 933 QtableSelectors[i] = sof.componentSpecs[i].QtableSelector; 934 } 935 } 936 937 sourceXOffset += gridX; 938 sourceWidth -= gridX; 939 sourceYOffset += gridY; 940 sourceHeight -= gridY; 941 942 int destWidth = (sourceWidth + periodX - 1)/periodX; 943 int destHeight = (sourceHeight + periodY - 1)/periodY; 944 945 int lineSize = sourceWidth*numBandsUsed; 947 948 DataBufferByte buffer = new DataBufferByte (lineSize); 949 950 int [] bandOffs = JPEG.bandOffsets[numBandsUsed-1]; 952 953 raster = Raster.createInterleavedRaster(buffer, 954 sourceWidth, 1, 955 lineSize, 956 numBandsUsed, 957 bandOffs, 958 null); 959 960 962 processImageStarted(currentImage); 963 964 boolean aborted = false; 965 966 if (debug) { 967 System.out.println("inCsType: " + inCsType); 968 System.out.println("outCsType: " + outCsType); 969 } 970 971 aborted = writeImage(structPointer, 972 buffer.getData(), 973 inCsType, outCsType, 974 numBandsUsed, 975 bandSizes, 976 sourceWidth, 977 destWidth, destHeight, 978 periodX, periodY, 979 qTables, 980 writeDQT, 981 DCHuffmanTables, 982 ACHuffmanTables, 983 writeDHT, 984 optimizeHuffman, 985 (progressiveMode 986 != ImageWriteParam.MODE_DISABLED), 987 numScans, 988 scans, 989 componentIds, 990 HsamplingFactors, 991 VsamplingFactors, 992 QtableSelectors, 993 haveMetadata, 994 restartInterval); 995 996 if (aborted) { 997 processWriteAborted(); 998 } else { 999 processImageComplete(); 1000 } 1001 1002 ios.flush(); 1003 currentImage++; } 1005 1006 public void prepareWriteSequence(IIOMetadata streamMetadata) 1007 throws IOException { 1008 if (ios == null) { 1009 throw new IllegalStateException ("Output has not been set!"); 1010 } 1011 1012 1021 if (streamMetadata != null) { 1022 if (streamMetadata instanceof JPEGMetadata) { 1023 JPEGMetadata jmeta = (JPEGMetadata) streamMetadata; 1026 if (jmeta.isStream == false) { 1027 throw new IllegalArgumentException 1028 ("Invalid stream metadata object."); 1029 } 1030 if (currentImage != 0) { 1034 throw new IIOException 1035 ("JPEG Stream metadata must precede all images"); 1036 } 1037 if (sequencePrepared == true) { 1038 throw new IIOException ("Stream metadata already written!"); 1039 } 1040 1041 streamQTables = collectQTablesFromMetadata(jmeta); 1044 if (debug) { 1045 System.out.println("after collecting from stream metadata, " 1046 + "streamQTables.length is " 1047 + streamQTables.length); 1048 } 1049 if (streamQTables == null) { 1050 streamQTables = JPEG.getDefaultQTables(); 1051 } 1052 streamDCHuffmanTables = 1053 collectHTablesFromMetadata(jmeta, true); 1054 if (streamDCHuffmanTables == null) { 1055 streamDCHuffmanTables = JPEG.getDefaultHuffmanTables(true); 1056 } 1057 streamACHuffmanTables = 1058 collectHTablesFromMetadata(jmeta, false); 1059 if (streamACHuffmanTables == null) { 1060 streamACHuffmanTables = JPEG.getDefaultHuffmanTables(false); 1061 } 1062 1063 writeTables(structPointer, 1065 streamQTables, 1066 streamDCHuffmanTables, 1067 streamACHuffmanTables); 1068 } else { 1069 throw new IIOException ("Stream metadata must be JPEG metadata"); 1070 } 1071 } 1072 sequencePrepared = true; 1073 } 1074 1075 public void writeToSequence(IIOImage image, ImageWriteParam param) 1076 throws IOException { 1077 if (sequencePrepared == false) { 1078 throw new IllegalStateException ("sequencePrepared not called!"); 1079 } 1080 write(null, image, param); 1082 } 1083 1084 public void endWriteSequence() throws IOException { 1085 if (sequencePrepared == false) { 1086 throw new IllegalStateException ("sequencePrepared not called!"); 1087 } 1088 sequencePrepared = false; 1089 } 1090 1091 public synchronized void abort() { 1092 super.abort(); 1093 abortWrite(structPointer); 1094 } 1095 1096 private void resetInternalState() { 1097 resetWriter(structPointer); 1099 1100 srcRas = null; 1102 raster = null; 1103 convertTosRGB = false; 1104 currentImage = 0; 1105 numScans = 0; 1106 metadata = null; 1107 } 1108 1109 1114 1115 public void dispose() { 1116 if (structPointer != 0) { 1117 disposerRecord.dispose(); 1118 structPointer = 0; 1119 } 1120 } 1121 1122 1124 1126 1131 void warningOccurred(int code) { 1132 if ((code < 0) || (code > MAX_WARNING)){ 1133 throw new InternalError ("Invalid warning index"); 1134 } 1135 processWarningOccurred 1136 (currentImage, 1137 "com.sun.imageio.plugins.jpeg.JPEGImageWriterResources", 1138 Integer.toString(code)); 1139 } 1140 1141 1154 void warningWithMessage(String msg) { 1155 processWarningOccurred(currentImage, msg); 1156 } 1157 1158 void thumbnailStarted(int thumbnailIndex) { 1159 processThumbnailStarted(currentImage, thumbnailIndex); 1160 } 1161 1162 void thumbnailProgress(float percentageDone) { 1164 processThumbnailProgress(percentageDone); 1165 } 1166 1167 void thumbnailComplete() { 1169 processThumbnailComplete(); 1170 } 1171 1172 1174 1176 1178 private void checkSOFBands(SOFMarkerSegment sof, int numBandsUsed) 1179 throws IIOException { 1180 if (sof != null) { 1182 if (sof.componentSpecs.length != numBandsUsed) { 1183 throw new IIOException 1184 ("Metadata components != number of destination bands"); 1185 } 1186 } 1187 } 1188 1189 private void checkJFIF(JFIFMarkerSegment jfif, 1190 ImageTypeSpecifier type, 1191 boolean input) { 1192 if (jfif != null) { 1193 if (!JPEG.isJFIFcompliant(type, input)) { 1194 ignoreJFIF = true; warningOccurred(input 1196 ? WARNING_IMAGE_METADATA_JFIF_MISMATCH 1197 : WARNING_DEST_METADATA_JFIF_MISMATCH); 1198 } 1199 } 1200 } 1201 1202 private void checkAdobe(AdobeMarkerSegment adobe, 1203 ImageTypeSpecifier type, 1204 boolean input) { 1205 if (adobe != null) { 1206 int rightTransform = JPEG.transformForType(type, input); 1207 if (adobe.transform != rightTransform) { 1208 warningOccurred(input 1209 ? WARNING_IMAGE_METADATA_ADOBE_MISMATCH 1210 : WARNING_DEST_METADATA_ADOBE_MISMATCH); 1211 if (rightTransform == JPEG.ADOBE_IMPOSSIBLE) { 1212 ignoreAdobe = true; 1213 } else { 1214 newAdobeTransform = rightTransform; 1215 } 1216 } 1217 } 1218 } 1219 1220 1226 private int [] collectScans(JPEGMetadata metadata, 1227 SOFMarkerSegment sof) { 1228 List segments = new ArrayList (); 1229 int SCAN_SIZE = 9; 1230 int MAX_COMPS_PER_SCAN = 4; 1231 for (Iterator iter = metadata.markerSequence.iterator(); 1232 iter.hasNext();) { 1233 MarkerSegment seg = (MarkerSegment) iter.next(); 1234 if (seg instanceof SOSMarkerSegment) { 1235 segments.add(seg); 1236 } 1237 } 1238 int [] retval = null; 1239 numScans = 0; 1240 if (!segments.isEmpty()) { 1241 numScans = segments.size(); 1242 retval = new int [numScans*SCAN_SIZE]; 1243 int index = 0; 1244 for (int i = 0; i < numScans; i++) { 1245 SOSMarkerSegment sos = (SOSMarkerSegment) segments.get(i); 1246 retval[index++] = sos.componentSpecs.length; for (int j = 0; j < MAX_COMPS_PER_SCAN; j++) { 1248 if (j < sos.componentSpecs.length) { 1249 int compSel = sos.componentSpecs[j].componentSelector; 1250 for (int k = 0; k < sof.componentSpecs.length; k++) { 1251 if (compSel == sof.componentSpecs[k].componentId) { 1252 retval[index++] = k; 1253 break; } 1255 } 1256 } else { 1257 retval[index++] = 0; 1258 } 1259 } 1260 retval[index++] = sos.startSpectralSelection; 1261 retval[index++] = sos.endSpectralSelection; 1262 retval[index++] = sos.approxHigh; 1263 retval[index++] = sos.approxLow; 1264 } 1265 } 1266 return retval; 1267 } 1268 1269 1273 private JPEGQTable [] collectQTablesFromMetadata 1274 (JPEGMetadata metadata) { 1275 ArrayList tables = new ArrayList (); 1276 Iterator iter = metadata.markerSequence.iterator(); 1277 while (iter.hasNext()) { 1278 MarkerSegment seg = (MarkerSegment) iter.next(); 1279 if (seg instanceof DQTMarkerSegment) { 1280 DQTMarkerSegment dqt = 1281 (DQTMarkerSegment) seg; 1282 tables.addAll(dqt.tables); 1283 } 1284 } 1285 JPEGQTable [] retval = null; 1286 if (tables.size() != 0) { 1287 retval = new JPEGQTable [tables.size()]; 1288 for (int i = 0; i < retval.length; i++) { 1289 retval[i] = 1290 new JPEGQTable (((DQTMarkerSegment.Qtable)tables.get(i)).data); 1291 } 1292 } 1293 return retval; 1294 } 1295 1296 1303 private JPEGHuffmanTable [] collectHTablesFromMetadata 1304 (JPEGMetadata metadata, boolean wantDC) throws IIOException { 1305 ArrayList tables = new ArrayList (); 1306 Iterator iter = metadata.markerSequence.iterator(); 1307 while (iter.hasNext()) { 1308 MarkerSegment seg = (MarkerSegment) iter.next(); 1309 if (seg instanceof DHTMarkerSegment) { 1310 DHTMarkerSegment dht = 1311 (DHTMarkerSegment) seg; 1312 for (int i = 0; i < dht.tables.size(); i++) { 1313 DHTMarkerSegment.Htable htable = 1314 (DHTMarkerSegment.Htable) dht.tables.get(i); 1315 if (htable.tableClass == (wantDC ? 0 : 1)) { 1316 tables.add(htable); 1317 } 1318 } 1319 } 1320 } 1321 JPEGHuffmanTable [] retval = null; 1322 if (tables.size() != 0) { 1323 DHTMarkerSegment.Htable [] htables = 1324 new DHTMarkerSegment.Htable[tables.size()]; 1325 tables.toArray(htables); 1326 retval = new JPEGHuffmanTable [tables.size()]; 1327 for (int i = 0; i < retval.length; i++) { 1328 retval[i] = null; 1329 for (int j = 0; j < tables.size(); j++) { 1330 if (htables[j].tableID == i) { 1331 if (retval[i] != null) { 1332 throw new IIOException ("Metadata has duplicate Htables!"); 1333 } 1334 retval[i] = new JPEGHuffmanTable (htables[j].numCodes, 1335 htables[j].values); 1336 } 1337 } 1338 } 1339 } 1340 1341 return retval; 1342 } 1343 1344 1346 1348 private int getSrcCSType(RenderedImage rimage) { 1349 int retval = JPEG.JCS_UNKNOWN; 1350 ColorModel cm = rimage.getColorModel(); 1351 if (cm != null) { 1352 boolean alpha = cm.hasAlpha(); 1353 ColorSpace cs = cm.getColorSpace(); 1354 switch (cs.getType()) { 1355 case ColorSpace.TYPE_GRAY: 1356 retval = JPEG.JCS_GRAYSCALE; 1357 break; 1358 case ColorSpace.TYPE_RGB: 1359 if (alpha) { 1360 retval = JPEG.JCS_RGBA; 1361 } else { 1362 retval = JPEG.JCS_RGB; 1363 } 1364 break; 1365 case ColorSpace.TYPE_YCbCr: 1366 if (alpha) { 1367 retval = JPEG.JCS_YCbCrA; 1368 } else { 1369 retval = JPEG.JCS_YCbCr; 1370 } 1371 break; 1372 case ColorSpace.TYPE_3CLR: 1373 if (cs == JPEG.YCC) { 1374 if (alpha) { 1375 retval = JPEG.JCS_YCCA; 1376 } else { 1377 retval = JPEG.JCS_YCC; 1378 } 1379 } 1380 case ColorSpace.TYPE_CMYK: 1381 retval = JPEG.JCS_CMYK; 1382 break; 1383 } 1384 } 1385 return retval; 1386 } 1387 1388 private int getDestCSType(ImageTypeSpecifier destType) { 1389 ColorModel cm = destType.getColorModel(); 1390 boolean alpha = cm.hasAlpha(); 1391 ColorSpace cs = cm.getColorSpace(); 1392 int retval = JPEG.JCS_UNKNOWN; 1393 switch (cs.getType()) { 1394 case ColorSpace.TYPE_GRAY: 1395 retval = JPEG.JCS_GRAYSCALE; 1396 break; 1397 case ColorSpace.TYPE_RGB: 1398 if (alpha) { 1399 retval = JPEG.JCS_RGBA; 1400 } else { 1401 retval = JPEG.JCS_RGB; 1402 } 1403 break; 1404 case ColorSpace.TYPE_YCbCr: 1405 if (alpha) { 1406 retval = JPEG.JCS_YCbCrA; 1407 } else { 1408 retval = JPEG.JCS_YCbCr; 1409 } 1410 break; 1411 case ColorSpace.TYPE_3CLR: 1412 if (cs == JPEG.YCC) { 1413 if (alpha) { 1414 retval = JPEG.JCS_YCCA; 1415 } else { 1416 retval = JPEG.JCS_YCC; 1417 } 1418 } 1419 case ColorSpace.TYPE_CMYK: 1420 retval = JPEG.JCS_CMYK; 1421 break; 1422 } 1423 return retval; 1424 } 1425 1426 private int getDefaultDestCSType(RenderedImage rimage) { 1427 int retval = JPEG.JCS_UNKNOWN; 1428 ColorModel cm = rimage.getColorModel(); 1429 if (cm != null) { 1430 boolean alpha = cm.hasAlpha(); 1431 ColorSpace cs = cm.getColorSpace(); 1432 switch (cs.getType()) { 1433 case ColorSpace.TYPE_GRAY: 1434 retval = JPEG.JCS_GRAYSCALE; 1435 break; 1436 case ColorSpace.TYPE_RGB: 1437 if (alpha) { 1438 retval = JPEG.JCS_YCbCrA; 1439 } else { 1440 retval = JPEG.JCS_YCbCr; 1441 } 1442 break; 1443 case ColorSpace.TYPE_YCbCr: 1444 if (alpha) { 1445 retval = JPEG.JCS_YCbCrA; 1446 } else { 1447 retval = JPEG.JCS_YCbCr; 1448 } 1449 break; 1450 case ColorSpace.TYPE_3CLR: 1451 if (cs == JPEG.YCC) { 1452 if (alpha) { 1453 retval = JPEG.JCS_YCCA; 1454 } else { 1455 retval = JPEG.JCS_YCC; 1456 } 1457 } 1458 case ColorSpace.TYPE_CMYK: 1459 retval = JPEG.JCS_YCCK; 1460 break; 1461 } 1462 } 1463 return retval; 1464 } 1465 1466 private boolean isSubsampled(SOFMarkerSegment.ComponentSpec [] specs) { 1467 int hsamp0 = specs[0].HsamplingFactor; 1468 int vsamp0 = specs[0].VsamplingFactor; 1469 for (int i = 1; i < specs.length; i++) { 1470 if ((specs[i].HsamplingFactor != hsamp0) || 1471 (specs[i].HsamplingFactor != hsamp0)) 1472 return true; 1473 } 1474 return false; 1475 } 1476 1477 1479 1481 1482 private static native void initWriterIDs(Class iosClass, 1483 Class qTableClass, 1484 Class huffClass); 1485 1486 1487 private native long initJPEGImageWriter(); 1488 1489 1490 private native void setDest(long structPointer, 1491 ImageOutputStream ios); 1492 1493 1496 private native boolean writeImage(long structPointer, 1497 byte [] data, 1498 int inCsType, int outCsType, 1499 int numBands, 1500 int [] bandSizes, 1501 int srcWidth, 1502 int destWidth, int destHeight, 1503 int stepX, int stepY, 1504 JPEGQTable [] qtables, 1505 boolean writeDQT, 1506 JPEGHuffmanTable [] DCHuffmanTables, 1507 JPEGHuffmanTable [] ACHuffmanTables, 1508 boolean writeDHT, 1509 boolean optimizeHuffman, 1510 boolean progressive, 1511 int numScans, 1512 int [] scans, 1513 int [] componentIds, 1514 int [] HsamplingFactors, 1515 int [] VsamplingFactors, 1516 int [] QtableSelectors, 1517 boolean haveMetadata, 1518 int restartInterval); 1519 1520 1521 1527 private void writeMetadata() throws IOException { 1528 if (metadata == null) { 1529 if (writeDefaultJFIF) { 1530 JFIFMarkerSegment.writeDefaultJFIF(ios, 1531 thumbnails, 1532 iccProfile, 1533 this); 1534 } 1535 if (writeAdobe) { 1536 AdobeMarkerSegment.writeAdobeSegment(ios, newAdobeTransform); 1537 } 1538 } else { 1539 metadata.writeToStream(ios, 1540 ignoreJFIF, 1541 forceJFIF, 1542 thumbnails, 1543 iccProfile, 1544 ignoreAdobe, 1545 newAdobeTransform, 1546 this); 1547 } 1548 } 1549 1550 1553 private native void writeTables(long structPointer, 1554 JPEGQTable [] qtables, 1555 JPEGHuffmanTable [] DCHuffmanTables, 1556 JPEGHuffmanTable [] ACHuffmanTables); 1557 1558 1565 private void grabPixels(int y) { 1566 1567 Raster sourceLine = null; 1568 if (indexed) { 1569 sourceLine = srcRas.createChild(sourceXOffset, 1570 sourceYOffset+y, 1571 sourceWidth, 1, 1572 0, 0, 1573 new int [] {0}); 1574 boolean forceARGB = 1578 (indexCM.getTransparency() != Transparency.OPAQUE); 1579 BufferedImage temp = indexCM.convertToIntDiscrete(sourceLine, 1580 forceARGB); 1581 sourceLine = temp.getRaster(); 1582 } else { 1583 sourceLine = srcRas.createChild(sourceXOffset, 1584 sourceYOffset+y, 1585 sourceWidth, 1, 1586 0, 0, 1587 srcBands); 1588 } 1589 if (convertTosRGB) { 1590 if (debug) { 1591 System.out.println("Converting to sRGB"); 1592 } 1593 converted = convertOp.filter(sourceLine, converted); 1597 sourceLine = converted; 1598 } 1599 if (isAlphaPremultiplied) { 1600 WritableRaster wr = sourceLine.createCompatibleWritableRaster(); 1601 int[] data = null; 1602 data = sourceLine.getPixels(sourceLine.getMinX(), sourceLine.getMinY(), 1603 sourceLine.getWidth(), sourceLine.getHeight(), 1604 data); 1605 wr.setPixels(sourceLine.getMinX(), sourceLine.getMinY(), 1606 sourceLine.getWidth(), sourceLine.getHeight(), 1607 data); 1608 srcCM.coerceData(wr, false); 1609 sourceLine = wr.createChild(wr.getMinX(), wr.getMinY(), 1610 wr.getWidth(), wr.getHeight(), 1611 0, 0, 1612 srcBands); 1613 } 1614 raster.setRect(sourceLine); 1615 if ((y > 7) && (y%8 == 0)) { processImageProgress((float) y / (float) sourceHeight * 100.0F); 1617 } 1618 } 1619 1620 1621 private native void abortWrite(long structPointer); 1622 1623 1624 private native void resetWriter(long structPointer); 1625 1626 1627 private static native void disposeWriter(long structPointer); 1628 1629 private static class JPEGWriterDisposerRecord extends DisposerRecord { 1630 private long pData; 1631 1632 public JPEGWriterDisposerRecord(long pData) { 1633 this.pData = pData; 1634 } 1635 1636 public synchronized void dispose() { 1637 if (pData != 0) { 1638 disposeWriter(pData); 1639 pData = 0; 1640 } 1641 } 1642 } 1643} 1644 | Popular Tags |