1 /* 2 * Copyright (c) 2000, 2007, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.imageio.plugins.jpeg; 27 28 import javax.imageio.IIOException; 29 import javax.imageio.ImageWriter; 30 import javax.imageio.ImageWriteParam; 31 import javax.imageio.IIOImage; 32 import javax.imageio.ImageTypeSpecifier; 33 import javax.imageio.metadata.IIOMetadata; 34 import javax.imageio.metadata.IIOMetadataFormatImpl; 35 import javax.imageio.metadata.IIOInvalidTreeException; 36 import javax.imageio.spi.ImageWriterSpi; 37 import javax.imageio.stream.ImageOutputStream; 38 import javax.imageio.plugins.jpeg.JPEGImageWriteParam; 39 import javax.imageio.plugins.jpeg.JPEGQTable; 40 import javax.imageio.plugins.jpeg.JPEGHuffmanTable; 41 42 import org.w3c.dom.Node; 43 44 import java.awt.image.Raster; 45 import java.awt.image.WritableRaster; 46 import java.awt.image.SampleModel; 47 import java.awt.image.DataBuffer; 48 import java.awt.image.DataBufferByte; 49 import java.awt.image.ColorModel; 50 import java.awt.image.IndexColorModel; 51 import java.awt.image.ColorConvertOp; 52 import java.awt.image.RenderedImage; 53 import java.awt.image.BufferedImage; 54 import java.awt.color.ColorSpace; 55 import java.awt.color.ICC_ColorSpace; 56 import java.awt.color.ICC_Profile; 57 import java.awt.Dimension; 58 import java.awt.Rectangle; 59 import java.awt.Transparency; 60 61 import java.io.IOException; 62 63 import java.util.List; 64 import java.util.ArrayList; 65 import java.util.Iterator; 66 67 import sun.java2d.Disposer; 68 import sun.java2d.DisposerRecord; 69 70 public class JPEGImageWriter extends ImageWriter { 71 72 ///////// Private variables 73 74 private boolean debug = false; 75 76 /** 77 * The following variable contains a pointer to the IJG library 78 * structure for this reader. It is assigned in the constructor 79 * and then is passed in to every native call. It is set to 0 80 * by dispose to avoid disposing twice. 81 */ 82 private long structPointer = 0; 83 84 85 /** The output stream we write to */ 86 private ImageOutputStream ios = null; 87 88 /** The Raster we will write from */ 89 private Raster srcRas = null; 90 91 /** An intermediate Raster holding compressor-friendly data */ 92 private WritableRaster raster = null; 93 94 /** 95 * Set to true if we are writing an image with an 96 * indexed ColorModel 97 */ 98 private boolean indexed = false; 99 private IndexColorModel indexCM = null; 100 101 private boolean convertTosRGB = false; // Used by PhotoYCC only 102 private WritableRaster converted = null; 103 104 private boolean isAlphaPremultiplied = false; 105 private ColorModel srcCM = null; 106 107 /** 108 * If there are thumbnails to be written, this is the list. 109 */ 110 private List thumbnails = null; 111 112 /** 113 * If metadata should include an icc profile, store it here. 114 */ 115 private ICC_Profile iccProfile = null; 116 117 private int sourceXOffset = 0; 118 private int sourceYOffset = 0; 119 private int sourceWidth = 0; 120 private int [] srcBands = null; 121 private int sourceHeight = 0; 122 123 /** Used when calling listeners */ 124 private int currentImage = 0; 125 126 private ColorConvertOp convertOp = null; 127 128 private JPEGQTable [] streamQTables = null; 129 private JPEGHuffmanTable[] streamDCHuffmanTables = null; 130 private JPEGHuffmanTable[] streamACHuffmanTables = null; 131 132 // Parameters for writing metadata 133 private boolean ignoreJFIF = false; // If it's there, use it 134 private boolean forceJFIF = false; // Add one for the thumbnails 135 private boolean ignoreAdobe = false; // If it's there, use it 136 private int newAdobeTransform = JPEG.ADOBE_IMPOSSIBLE; // Change if needed 137 private boolean writeDefaultJFIF = false; 138 private boolean writeAdobe = false; 139 private JPEGMetadata metadata = null; 140 141 private boolean sequencePrepared = false; 142 143 private int numScans = 0; 144 145 /** The referent to be registered with the Disposer. */ 146 private Object disposerReferent = new Object(); 147 148 /** The DisposerRecord that handles the actual disposal of this writer. */ 149 private DisposerRecord disposerRecord; 150 151 ///////// End of Private variables 152 153 ///////// Protected variables 154 155 protected static final int WARNING_DEST_IGNORED = 0; 156 protected static final int WARNING_STREAM_METADATA_IGNORED = 1; 157 protected static final int WARNING_DEST_METADATA_COMP_MISMATCH = 2; 158 protected static final int WARNING_DEST_METADATA_JFIF_MISMATCH = 3; 159 protected static final int WARNING_DEST_METADATA_ADOBE_MISMATCH = 4; 160 protected static final int WARNING_IMAGE_METADATA_JFIF_MISMATCH = 5; 161 protected static final int WARNING_IMAGE_METADATA_ADOBE_MISMATCH = 6; 162 protected static final int WARNING_METADATA_NOT_JPEG_FOR_RASTER = 7; 163 protected static final int WARNING_NO_BANDS_ON_INDEXED = 8; 164 protected static final int WARNING_ILLEGAL_THUMBNAIL = 9; 165 protected static final int WARNING_IGNORING_THUMBS = 10; 166 protected static final int WARNING_FORCING_JFIF = 11; 167 protected static final int WARNING_THUMB_CLIPPED = 12; 168 protected static final int WARNING_METADATA_ADJUSTED_FOR_THUMB = 13; 169 protected static final int WARNING_NO_RGB_THUMB_AS_INDEXED = 14; 170 protected static final int WARNING_NO_GRAY_THUMB_AS_INDEXED = 15; 171 172 private static final int MAX_WARNING = WARNING_NO_GRAY_THUMB_AS_INDEXED; 173 174 ///////// End of Protected variables 175 176 ///////// static initializer 177 178 static { 179 java.security.AccessController.doPrivileged( 180 new java.security.PrivilegedAction<Void>() { 181 public Void run() { 182 System.loadLibrary("jpeg"); 183 return null; 184 } 185 }); 186 initWriterIDs(ImageOutputStream.class, 187 JPEGQTable.class, 188 JPEGHuffmanTable.class); 189 } 190 191 //////// Public API 192 193 public JPEGImageWriter(ImageWriterSpi originator) { 194 super(originator); 195 structPointer = initJPEGImageWriter(); 196 disposerRecord = new JPEGWriterDisposerRecord(structPointer); 197 Disposer.addRecord(disposerReferent, disposerRecord); 198 } 199 200 public void setOutput(Object output) { 201 setThreadLock(); 202 try { 203 super.setOutput(output); // validates output 204 resetInternalState(); 205 ios = (ImageOutputStream) output; // so this will always work 206 // Set the native destination 207 setDest(structPointer, ios); 208 } finally { 209 clearThreadLock(); 210 } 211 } 212 213 public ImageWriteParam getDefaultWriteParam() { 214 return new JPEGImageWriteParam(null); 215 } 216 217 public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) { 218 setThreadLock(); 219 try { 220 return new JPEGMetadata(param, this); 221 } finally { 222 clearThreadLock(); 223 } 224 } 225 226 public IIOMetadata 227 getDefaultImageMetadata(ImageTypeSpecifier imageType, 228 ImageWriteParam param) { 229 setThreadLock(); 230 try { 231 return new JPEGMetadata(imageType, param, this); 232 } finally { 233 clearThreadLock(); 234 } 235 } 236 237 public IIOMetadata convertStreamMetadata(IIOMetadata inData, 238 ImageWriteParam param) { 239 // There isn't much we can do. If it's one of ours, then 240 // return it. Otherwise just return null. We use it only 241 // for tables, so we can't get a default and modify it, 242 // as this will usually not be what is intended. 243 if (inData instanceof JPEGMetadata) { 244 JPEGMetadata jpegData = (JPEGMetadata) inData; 245 if (jpegData.isStream) { 246 return inData; 247 } 248 } 249 return null; 250 } 251 252 public IIOMetadata 253 convertImageMetadata(IIOMetadata inData, 254 ImageTypeSpecifier imageType, 255 ImageWriteParam param) { 256 setThreadLock(); 257 try { 258 return convertImageMetadataOnThread(inData, imageType, param); 259 } finally { 260 clearThreadLock(); 261 } 262 } 263 264 private IIOMetadata 265 convertImageMetadataOnThread(IIOMetadata inData, 266 ImageTypeSpecifier imageType, 267 ImageWriteParam param) { 268 // If it's one of ours, just return it 269 if (inData instanceof JPEGMetadata) { 270 JPEGMetadata jpegData = (JPEGMetadata) inData; 271 if (!jpegData.isStream) { 272 return inData; 273 } else { 274 // Can't convert stream metadata to image metadata 275 // XXX Maybe this should put out a warning? 276 return null; 277 } 278 } 279 // If it's not one of ours, create a default and set it from 280 // the standard tree from the input, if it exists. 281 if (inData.isStandardMetadataFormatSupported()) { 282 String formatName = 283 IIOMetadataFormatImpl.standardMetadataFormatName; 284 Node tree = inData.getAsTree(formatName); 285 if (tree != null) { 286 JPEGMetadata jpegData = new JPEGMetadata(imageType, 287 param, 288 this); 289 try { 290 jpegData.setFromTree(formatName, tree); 291 } catch (IIOInvalidTreeException e) { 292 // Other plug-in generates bogus standard tree 293 // XXX Maybe this should put out a warning? 294 return null; 295 } 296 297 return jpegData; 298 } 299 } 300 return null; 301 } 302 303 public int getNumThumbnailsSupported(ImageTypeSpecifier imageType, 304 ImageWriteParam param, 305 IIOMetadata streamMetadata, 306 IIOMetadata imageMetadata) { 307 if (jfifOK(imageType, param, streamMetadata, imageMetadata)) { 308 return Integer.MAX_VALUE; 309 } 310 return 0; 311 } 312 313 static final Dimension [] preferredThumbSizes = {new Dimension(1, 1), 314 new Dimension(255, 255)}; 315 316 public Dimension[] getPreferredThumbnailSizes(ImageTypeSpecifier imageType, 317 ImageWriteParam param, 318 IIOMetadata streamMetadata, 319 IIOMetadata imageMetadata) { 320 if (jfifOK(imageType, param, streamMetadata, imageMetadata)) { 321 return (Dimension [])preferredThumbSizes.clone(); 322 } 323 return null; 324 } 325 326 private boolean jfifOK(ImageTypeSpecifier imageType, 327 ImageWriteParam param, 328 IIOMetadata streamMetadata, 329 IIOMetadata imageMetadata) { 330 // If the image type and metadata are JFIF compatible, return true 331 if ((imageType != null) && 332 (!JPEG.isJFIFcompliant(imageType, true))) { 333 return false; 334 } 335 if (imageMetadata != null) { 336 JPEGMetadata metadata = null; 337 if (imageMetadata instanceof JPEGMetadata) { 338 metadata = (JPEGMetadata) imageMetadata; 339 } else { 340 metadata = (JPEGMetadata)convertImageMetadata(imageMetadata, 341 imageType, 342 param); 343 } 344 // metadata must have a jfif node 345 if (metadata.findMarkerSegment 346 (JFIFMarkerSegment.class, true) == null){ 347 return false; 348 } 349 } 350 return true; 351 } 352 353 public boolean canWriteRasters() { 354 return true; 355 } 356 357 public void write(IIOMetadata streamMetadata, 358 IIOImage image, 359 ImageWriteParam param) throws IOException { 360 setThreadLock(); 361 try { 362 writeOnThread(streamMetadata, image, param); 363 } finally { 364 clearThreadLock(); 365 } 366 } 367 368 private void writeOnThread(IIOMetadata streamMetadata, 369 IIOImage image, 370 ImageWriteParam param) throws IOException { 371 372 if (ios == null) { 373 throw new IllegalStateException("Output has not been set!"); 374 } 375 376 if (image == null) { 377 throw new IllegalArgumentException("image is null!"); 378 } 379 380 // if streamMetadata is not null, issue a warning 381 if (streamMetadata != null) { 382 warningOccurred(WARNING_STREAM_METADATA_IGNORED); 383 } 384 385 // Obtain the raster and image, if there is one 386 boolean rasterOnly = image.hasRaster(); 387 388 RenderedImage rimage = null; 389 if (rasterOnly) { 390 srcRas = image.getRaster(); 391 } else { 392 rimage = image.getRenderedImage(); 393 if (rimage instanceof BufferedImage) { 394 // Use the Raster directly. 395 srcRas = ((BufferedImage)rimage).getRaster(); 396 } else if (rimage.getNumXTiles() == 1 && 397 rimage.getNumYTiles() == 1) 398 { 399 // Get the unique tile. 400 srcRas = rimage.getTile(rimage.getMinTileX(), 401 rimage.getMinTileY()); 402 403 // Ensure the Raster has dimensions of the image, 404 // as the tile dimensions might differ. 405 if (srcRas.getWidth() != rimage.getWidth() || 406 srcRas.getHeight() != rimage.getHeight()) 407 { 408 srcRas = srcRas.createChild(srcRas.getMinX(), 409 srcRas.getMinY(), 410 rimage.getWidth(), 411 rimage.getHeight(), 412 srcRas.getMinX(), 413 srcRas.getMinY(), 414 null); 415 } 416 } else { 417 // Image is tiled so get a contiguous raster by copying. 418 srcRas = rimage.getData(); 419 } 420 } 421 422 // Now determine if we are using a band subset 423 424 // By default, we are using all source bands 425 int numSrcBands = srcRas.getNumBands(); 426 indexed = false; 427 indexCM = null; 428 ColorModel cm = null; 429 ColorSpace cs = null; 430 isAlphaPremultiplied = false; 431 srcCM = null; 432 if (!rasterOnly) { 433 cm = rimage.getColorModel(); 434 if (cm != null) { 435 cs = cm.getColorSpace(); 436 if (cm instanceof IndexColorModel) { 437 indexed = true; 438 indexCM = (IndexColorModel) cm; 439 numSrcBands = cm.getNumComponents(); 440 } 441 if (cm.isAlphaPremultiplied()) { 442 isAlphaPremultiplied = true; 443 srcCM = cm; 444 } 445 } 446 } 447 448 srcBands = JPEG.bandOffsets[numSrcBands-1]; 449 int numBandsUsed = numSrcBands; 450 // Consult the param to determine if we're writing a subset 451 452 if (param != null) { 453 int[] sBands = param.getSourceBands(); 454 if (sBands != null) { 455 if (indexed) { 456 warningOccurred(WARNING_NO_BANDS_ON_INDEXED); 457 } else { 458 srcBands = sBands; 459 numBandsUsed = srcBands.length; 460 if (numBandsUsed > numSrcBands) { 461 throw new IIOException 462 ("ImageWriteParam specifies too many source bands"); 463 } 464 } 465 } 466 } 467 468 boolean usingBandSubset = (numBandsUsed != numSrcBands); 469 boolean fullImage = ((!rasterOnly) && (!usingBandSubset)); 470 471 int [] bandSizes = null; 472 if (!indexed) { 473 bandSizes = srcRas.getSampleModel().getSampleSize(); 474 // If this is a subset, we must adjust bandSizes 475 if (usingBandSubset) { 476 int [] temp = new int [numBandsUsed]; 477 for (int i = 0; i < numBandsUsed; i++) { 478 temp[i] = bandSizes[srcBands[i]]; 479 } 480 bandSizes = temp; 481 } 482 } else { 483 int [] tempSize = srcRas.getSampleModel().getSampleSize(); 484 bandSizes = new int [numSrcBands]; 485 for (int i = 0; i < numSrcBands; i++) { 486 bandSizes[i] = tempSize[0]; // All the same 487 } 488 } 489 490 for (int i = 0; i < bandSizes.length; i++) { 491 // 4450894 part 1: The IJG libraries are compiled so they only 492 // handle <= 8-bit samples. We now check the band sizes and throw 493 // an exception for images, such as USHORT_GRAY, with > 8 bits 494 // per sample. 495 if (bandSizes[i] > 8) { 496 throw new IIOException("Sample size must be <= 8"); 497 } 498 // 4450894 part 2: We expand IndexColorModel images to full 24- 499 // or 32-bit in grabPixels() for each scanline. For indexed 500 // images such as BYTE_BINARY, we need to ensure that we update 501 // bandSizes to account for the scaling from 1-bit band sizes 502 // to 8-bit. 503 if (indexed) { 504 bandSizes[i] = 8; 505 } 506 } 507 508 if (debug) { 509 System.out.println("numSrcBands is " + numSrcBands); 510 System.out.println("numBandsUsed is " + numBandsUsed); 511 System.out.println("usingBandSubset is " + usingBandSubset); 512 System.out.println("fullImage is " + fullImage); 513 System.out.print("Band sizes:"); 514 for (int i = 0; i< bandSizes.length; i++) { 515 System.out.print(" " + bandSizes[i]); 516 } 517 System.out.println(); 518 } 519 520 // Destination type, if there is one 521 ImageTypeSpecifier destType = null; 522 if (param != null) { 523 destType = param.getDestinationType(); 524 // Ignore dest type if we are writing a complete image 525 if ((fullImage) && (destType != null)) { 526 warningOccurred(WARNING_DEST_IGNORED); 527 destType = null; 528 } 529 } 530 531 // Examine the param 532 533 sourceXOffset = srcRas.getMinX(); 534 sourceYOffset = srcRas.getMinY(); 535 int imageWidth = srcRas.getWidth(); 536 int imageHeight = srcRas.getHeight(); 537 sourceWidth = imageWidth; 538 sourceHeight = imageHeight; 539 int periodX = 1; 540 int periodY = 1; 541 int gridX = 0; 542 int gridY = 0; 543 JPEGQTable [] qTables = null; 544 JPEGHuffmanTable[] DCHuffmanTables = null; 545 JPEGHuffmanTable[] ACHuffmanTables = null; 546 boolean optimizeHuffman = false; 547 JPEGImageWriteParam jparam = null; 548 int progressiveMode = ImageWriteParam.MODE_DISABLED; 549 550 if (param != null) { 551 552 Rectangle sourceRegion = param.getSourceRegion(); 553 if (sourceRegion != null) { 554 Rectangle imageBounds = new Rectangle(sourceXOffset, 555 sourceYOffset, 556 sourceWidth, 557 sourceHeight); 558 sourceRegion = sourceRegion.intersection(imageBounds); 559 sourceXOffset = sourceRegion.x; 560 sourceYOffset = sourceRegion.y; 561 sourceWidth = sourceRegion.width; 562 sourceHeight = sourceRegion.height; 563 } 564 565 if (sourceWidth + sourceXOffset > imageWidth) { 566 sourceWidth = imageWidth - sourceXOffset; 567 } 568 if (sourceHeight + sourceYOffset > imageHeight) { 569 sourceHeight = imageHeight - sourceYOffset; 570 } 571 572 periodX = param.getSourceXSubsampling(); 573 periodY = param.getSourceYSubsampling(); 574 gridX = param.getSubsamplingXOffset(); 575 gridY = param.getSubsamplingYOffset(); 576 577 switch(param.getCompressionMode()) { 578 case ImageWriteParam.MODE_DISABLED: 579 throw new IIOException("JPEG compression cannot be disabled"); 580 case ImageWriteParam.MODE_EXPLICIT: 581 float quality = param.getCompressionQuality(); 582 quality = JPEG.convertToLinearQuality(quality); 583 qTables = new JPEGQTable[2]; 584 qTables[0] = JPEGQTable.K1Luminance.getScaledInstance 585 (quality, true); 586 qTables[1] = JPEGQTable.K2Chrominance.getScaledInstance 587 (quality, true); 588 break; 589 case ImageWriteParam.MODE_DEFAULT: 590 qTables = new JPEGQTable[2]; 591 qTables[0] = JPEGQTable.K1Div2Luminance; 592 qTables[1] = JPEGQTable.K2Div2Chrominance; 593 break; 594 // We'll handle the metadata case later 595 } 596 597 progressiveMode = param.getProgressiveMode(); 598 599 if (param instanceof JPEGImageWriteParam) { 600 jparam = (JPEGImageWriteParam)param; 601 optimizeHuffman = jparam.getOptimizeHuffmanTables(); 602 } 603 } 604 605 // Now examine the metadata 606 IIOMetadata mdata = image.getMetadata(); 607 if (mdata != null) { 608 if (mdata instanceof JPEGMetadata) { 609 metadata = (JPEGMetadata) mdata; 610 if (debug) { 611 System.out.println 612 ("We have metadata, and it's JPEG metadata"); 613 } 614 } else { 615 if (!rasterOnly) { 616 ImageTypeSpecifier type = destType; 617 if (type == null) { 618 type = new ImageTypeSpecifier(rimage); 619 } 620 metadata = (JPEGMetadata) convertImageMetadata(mdata, 621 type, 622 param); 623 } else { 624 warningOccurred(WARNING_METADATA_NOT_JPEG_FOR_RASTER); 625 } 626 } 627 } 628 629 // First set a default state 630 631 ignoreJFIF = false; // If it's there, use it 632 ignoreAdobe = false; // If it's there, use it 633 newAdobeTransform = JPEG.ADOBE_IMPOSSIBLE; // Change if needed 634 writeDefaultJFIF = false; 635 writeAdobe = false; 636 637 // By default we'll do no conversion: 638 int inCsType = JPEG.JCS_UNKNOWN; 639 int outCsType = JPEG.JCS_UNKNOWN; 640 641 JFIFMarkerSegment jfif = null; 642 AdobeMarkerSegment adobe = null; 643 SOFMarkerSegment sof = null; 644 645 if (metadata != null) { 646 jfif = (JFIFMarkerSegment) metadata.findMarkerSegment 647 (JFIFMarkerSegment.class, true); 648 adobe = (AdobeMarkerSegment) metadata.findMarkerSegment 649 (AdobeMarkerSegment.class, true); 650 sof = (SOFMarkerSegment) metadata.findMarkerSegment 651 (SOFMarkerSegment.class, true); 652 } 653 654 iccProfile = null; // By default don't write one 655 convertTosRGB = false; // PhotoYCC does this 656 converted = null; 657 658 if (destType != null) { 659 if (numBandsUsed != destType.getNumBands()) { 660 throw new IIOException 661 ("Number of source bands != number of destination bands"); 662 } 663 cs = destType.getColorModel().getColorSpace(); 664 // Check the metadata against the destination type 665 if (metadata != null) { 666 checkSOFBands(sof, numBandsUsed); 667 668 checkJFIF(jfif, destType, false); 669 // Do we want to write an ICC profile? 670 if ((jfif != null) && (ignoreJFIF == false)) { 671 if (JPEG.isNonStandardICC(cs)) { 672 iccProfile = ((ICC_ColorSpace) cs).getProfile(); 673 } 674 } 675 checkAdobe(adobe, destType, false); 676 677 } else { // no metadata, but there is a dest type 678 // If we can add a JFIF or an Adobe marker segment, do so 679 if (JPEG.isJFIFcompliant(destType, false)) { 680 writeDefaultJFIF = true; 681 // Do we want to write an ICC profile? 682 if (JPEG.isNonStandardICC(cs)) { 683 iccProfile = ((ICC_ColorSpace) cs).getProfile(); 684 } 685 } else { 686 int transform = JPEG.transformForType(destType, false); 687 if (transform != JPEG.ADOBE_IMPOSSIBLE) { 688 writeAdobe = true; 689 newAdobeTransform = transform; 690 } 691 } 692 // re-create the metadata 693 metadata = new JPEGMetadata(destType, null, this); 694 } 695 inCsType = getSrcCSType(destType); 696 outCsType = getDefaultDestCSType(destType); 697 } else { // no destination type 698 if (metadata == null) { 699 if (fullImage) { // no dest, no metadata, full image 700 // Use default metadata matching the image and param 701 metadata = new JPEGMetadata(new ImageTypeSpecifier(rimage), 702 param, this); 703 if (metadata.findMarkerSegment 704 (JFIFMarkerSegment.class, true) != null) { 705 cs = rimage.getColorModel().getColorSpace(); 706 if (JPEG.isNonStandardICC(cs)) { 707 iccProfile = ((ICC_ColorSpace) cs).getProfile(); 708 } 709 } 710 711 inCsType = getSrcCSType(rimage); 712 outCsType = getDefaultDestCSType(rimage); 713 } 714 // else no dest, no metadata, not an image, 715 // so no special headers, no color conversion 716 } else { // no dest type, but there is metadata 717 checkSOFBands(sof, numBandsUsed); 718 if (fullImage) { // no dest, metadata, image 719 // Check that the metadata and the image match 720 721 ImageTypeSpecifier inputType = 722 new ImageTypeSpecifier(rimage); 723 724 inCsType = getSrcCSType(rimage); 725 726 if (cm != null) { 727 boolean alpha = cm.hasAlpha(); 728 switch (cs.getType()) { 729 case ColorSpace.TYPE_GRAY: 730 if (!alpha) { 731 outCsType = JPEG.JCS_GRAYSCALE; 732 } else { 733 if (jfif != null) { 734 ignoreJFIF = true; 735 warningOccurred 736 (WARNING_IMAGE_METADATA_JFIF_MISMATCH); 737 } 738 // out colorspace remains unknown 739 } 740 if ((adobe != null) 741 && (adobe.transform != JPEG.ADOBE_UNKNOWN)) { 742 newAdobeTransform = JPEG.ADOBE_UNKNOWN; 743 warningOccurred 744 (WARNING_IMAGE_METADATA_ADOBE_MISMATCH); 745 } 746 break; 747 case ColorSpace.TYPE_RGB: 748 if (!alpha) { 749 if (jfif != null) { 750 outCsType = JPEG.JCS_YCbCr; 751 if (JPEG.isNonStandardICC(cs) 752 || ((cs instanceof ICC_ColorSpace) 753 && (jfif.iccSegment != null))) { 754 iccProfile = 755 ((ICC_ColorSpace) cs).getProfile(); 756 } 757 } else if (adobe != null) { 758 switch (adobe.transform) { 759 case JPEG.ADOBE_UNKNOWN: 760 outCsType = JPEG.JCS_RGB; 761 break; 762 case JPEG.ADOBE_YCC: 763 outCsType = JPEG.JCS_YCbCr; 764 break; 765 default: 766 warningOccurred 767 (WARNING_IMAGE_METADATA_ADOBE_MISMATCH); 768 newAdobeTransform = JPEG.ADOBE_UNKNOWN; 769 outCsType = JPEG.JCS_RGB; 770 break; 771 } 772 } else { 773 // consult the ids 774 int outCS = sof.getIDencodedCSType(); 775 // if they don't resolve it, 776 // consult the sampling factors 777 if (outCS != JPEG.JCS_UNKNOWN) { 778 outCsType = outCS; 779 } else { 780 boolean subsampled = 781 isSubsampled(sof.componentSpecs); 782 if (subsampled) { 783 outCsType = JPEG.JCS_YCbCr; 784 } else { 785 outCsType = JPEG.JCS_RGB; 786 } 787 } 788 } 789 } else { // RGBA 790 if (jfif != null) { 791 ignoreJFIF = true; 792 warningOccurred 793 (WARNING_IMAGE_METADATA_JFIF_MISMATCH); 794 } 795 if (adobe != null) { 796 if (adobe.transform 797 != JPEG.ADOBE_UNKNOWN) { 798 newAdobeTransform = JPEG.ADOBE_UNKNOWN; 799 warningOccurred 800 (WARNING_IMAGE_METADATA_ADOBE_MISMATCH); 801 } 802 outCsType = JPEG.JCS_RGBA; 803 } else { 804 // consult the ids 805 int outCS = sof.getIDencodedCSType(); 806 // if they don't resolve it, 807 // consult the sampling factors 808 if (outCS != JPEG.JCS_UNKNOWN) { 809 outCsType = outCS; 810 } else { 811 boolean subsampled = 812 isSubsampled(sof.componentSpecs); 813 outCsType = subsampled ? 814 JPEG.JCS_YCbCrA : JPEG.JCS_RGBA; 815 } 816 } 817 } 818 break; 819 case ColorSpace.TYPE_3CLR: 820 if (cs == JPEG.JCS.getYCC()) { 821 if (!alpha) { 822 if (jfif != null) { 823 convertTosRGB = true; 824 convertOp = 825 new ColorConvertOp(cs, 826 JPEG.JCS.sRGB, 827 null); 828 outCsType = JPEG.JCS_YCbCr; 829 } else if (adobe != null) { 830 if (adobe.transform 831 != JPEG.ADOBE_YCC) { 832 newAdobeTransform = JPEG.ADOBE_YCC; 833 warningOccurred 834 (WARNING_IMAGE_METADATA_ADOBE_MISMATCH); 835 } 836 outCsType = JPEG.JCS_YCC; 837 } else { 838 outCsType = JPEG.JCS_YCC; 839 } 840 } else { // PhotoYCCA 841 if (jfif != null) { 842 ignoreJFIF = true; 843 warningOccurred 844 (WARNING_IMAGE_METADATA_JFIF_MISMATCH); 845 } else if (adobe != null) { 846 if (adobe.transform 847 != JPEG.ADOBE_UNKNOWN) { 848 newAdobeTransform 849 = JPEG.ADOBE_UNKNOWN; 850 warningOccurred 851 (WARNING_IMAGE_METADATA_ADOBE_MISMATCH); 852 } 853 } 854 outCsType = JPEG.JCS_YCCA; 855 } 856 } 857 } 858 } 859 } // else no dest, metadata, not an image. Defaults ok 860 } 861 } 862 863 boolean metadataProgressive = false; 864 int [] scans = null; 865 866 if (metadata != null) { 867 if (sof == null) { 868 sof = (SOFMarkerSegment) metadata.findMarkerSegment 869 (SOFMarkerSegment.class, true); 870 } 871 if ((sof != null) && (sof.tag == JPEG.SOF2)) { 872 metadataProgressive = true; 873 if (progressiveMode == ImageWriteParam.MODE_COPY_FROM_METADATA) { 874 scans = collectScans(metadata, sof); // Might still be null 875 } else { 876 numScans = 0; 877 } 878 } 879 if (jfif == null) { 880 jfif = (JFIFMarkerSegment) metadata.findMarkerSegment 881 (JFIFMarkerSegment.class, true); 882 } 883 } 884 885 thumbnails = image.getThumbnails(); 886 int numThumbs = image.getNumThumbnails(); 887 forceJFIF = false; 888 // determine if thumbnails can be written 889 // If we are going to add a default JFIF marker segment, 890 // then thumbnails can be written 891 if (!writeDefaultJFIF) { 892 // If there is no metadata, then we can't write thumbnails 893 if (metadata == null) { 894 thumbnails = null; 895 if (numThumbs != 0) { 896 warningOccurred(WARNING_IGNORING_THUMBS); 897 } 898 } else { 899 // There is metadata 900 // If we are writing a raster or subbands, 901 // then the user must specify JFIF on the metadata 902 if (fullImage == false) { 903 if (jfif == null) { 904 thumbnails = null; // Or we can't include thumbnails 905 if (numThumbs != 0) { 906 warningOccurred(WARNING_IGNORING_THUMBS); 907 } 908 } 909 } else { // It is a full image, and there is metadata 910 if (jfif == null) { // Not JFIF 911 // Can it have JFIF? 912 if ((outCsType == JPEG.JCS_GRAYSCALE) 913 || (outCsType == JPEG.JCS_YCbCr)) { 914 if (numThumbs != 0) { 915 forceJFIF = true; 916 warningOccurred(WARNING_FORCING_JFIF); 917 } 918 } else { // Nope, not JFIF-compatible 919 thumbnails = null; 920 if (numThumbs != 0) { 921 warningOccurred(WARNING_IGNORING_THUMBS); 922 } 923 } 924 } 925 } 926 } 927 } 928 929 // Set up a boolean to indicate whether we need to call back to 930 // write metadata 931 boolean haveMetadata = 932 ((metadata != null) || writeDefaultJFIF || writeAdobe); 933 934 // Now that we have dealt with metadata, finalize our tables set up 935 936 // Are we going to write tables? By default, yes. 937 boolean writeDQT = true; 938 boolean writeDHT = true; 939 940 // But if the metadata has no tables, no. 941 DQTMarkerSegment dqt = null; 942 DHTMarkerSegment dht = null; 943 944 int restartInterval = 0; 945 946 if (metadata != null) { 947 dqt = (DQTMarkerSegment) metadata.findMarkerSegment 948 (DQTMarkerSegment.class, true); 949 dht = (DHTMarkerSegment) metadata.findMarkerSegment 950 (DHTMarkerSegment.class, true); 951 DRIMarkerSegment dri = 952 (DRIMarkerSegment) metadata.findMarkerSegment 953 (DRIMarkerSegment.class, true); 954 if (dri != null) { 955 restartInterval = dri.restartInterval; 956 } 957 958 if (dqt == null) { 959 writeDQT = false; 960 } 961 if (dht == null) { 962 writeDHT = false; // Ignored if optimizeHuffman is true 963 } 964 } 965 966 // Whether we write tables or not, we need to figure out which ones 967 // to use 968 if (qTables == null) { // Get them from metadata, or use defaults 969 if (dqt != null) { 970 qTables = collectQTablesFromMetadata(metadata); 971 } else if (streamQTables != null) { 972 qTables = streamQTables; 973 } else if ((jparam != null) && (jparam.areTablesSet())) { 974 qTables = jparam.getQTables(); 975 } else { 976 qTables = JPEG.getDefaultQTables(); 977 } 978 979 } 980 981 // If we are optimizing, we don't want any tables. 982 if (optimizeHuffman == false) { 983 // If they were for progressive scans, we can't use them. 984 if ((dht != null) && (metadataProgressive == false)) { 985 DCHuffmanTables = collectHTablesFromMetadata(metadata, true); 986 ACHuffmanTables = collectHTablesFromMetadata(metadata, false); 987 } else if (streamDCHuffmanTables != null) { 988 DCHuffmanTables = streamDCHuffmanTables; 989 ACHuffmanTables = streamACHuffmanTables; 990 } else if ((jparam != null) && (jparam.areTablesSet())) { 991 DCHuffmanTables = jparam.getDCHuffmanTables(); 992 ACHuffmanTables = jparam.getACHuffmanTables(); 993 } else { 994 DCHuffmanTables = JPEG.getDefaultHuffmanTables(true); 995 ACHuffmanTables = JPEG.getDefaultHuffmanTables(false); 996 } 997 } 998 999 // By default, ids are 1 - N, no subsampling 1000 int [] componentIds = new int[numBandsUsed]; 1001 int [] HsamplingFactors = new int[numBandsUsed]; 1002 int [] VsamplingFactors = new int[numBandsUsed]; 1003 int [] QtableSelectors = new int[numBandsUsed]; 1004 for (int i = 0; i < numBandsUsed; i++) { 1005 componentIds[i] = i+1; // JFIF compatible 1006 HsamplingFactors[i] = 1; 1007 VsamplingFactors[i] = 1; 1008 QtableSelectors[i] = 0; 1009 } 1010 1011 // Now override them with the contents of sof, if there is one, 1012 if (sof != null) { 1013 for (int i = 0; i < numBandsUsed; i++) { 1014 if (forceJFIF == false) { // else use JFIF-compatible default 1015 componentIds[i] = sof.componentSpecs[i].componentId; 1016 } 1017 HsamplingFactors[i] = sof.componentSpecs[i].HsamplingFactor; 1018 VsamplingFactors[i] = sof.componentSpecs[i].VsamplingFactor; 1019 QtableSelectors[i] = sof.componentSpecs[i].QtableSelector; 1020 } 1021 } 1022 1023 sourceXOffset += gridX; 1024 sourceWidth -= gridX; 1025 sourceYOffset += gridY; 1026 sourceHeight -= gridY; 1027 1028 int destWidth = (sourceWidth + periodX - 1)/periodX; 1029 int destHeight = (sourceHeight + periodY - 1)/periodY; 1030 1031 // Create an appropriate 1-line databuffer for writing 1032 int lineSize = sourceWidth*numBandsUsed; 1033 1034 DataBufferByte buffer = new DataBufferByte(lineSize); 1035 1036 // Create a raster from that 1037 int [] bandOffs = JPEG.bandOffsets[numBandsUsed-1]; 1038 1039 raster = Raster.createInterleavedRaster(buffer, 1040 sourceWidth, 1, 1041 lineSize, 1042 numBandsUsed, 1043 bandOffs, 1044 null); 1045 1046 // Call the writer, who will call back for every scanline 1047 1048 processImageStarted(currentImage); 1049 1050 boolean aborted = false; 1051 1052 if (debug) { 1053 System.out.println("inCsType: " + inCsType); 1054 System.out.println("outCsType: " + outCsType); 1055 } 1056 1057 // Note that getData disables acceleration on buffer, but it is 1058 // just a 1-line intermediate data transfer buffer that does not 1059 // affect the acceleration of the source image. 1060 aborted = writeImage(structPointer, 1061 buffer.getData(), 1062 inCsType, outCsType, 1063 numBandsUsed, 1064 bandSizes, 1065 sourceWidth, 1066 destWidth, destHeight, 1067 periodX, periodY, 1068 qTables, 1069 writeDQT, 1070 DCHuffmanTables, 1071 ACHuffmanTables, 1072 writeDHT, 1073 optimizeHuffman, 1074 (progressiveMode 1075 != ImageWriteParam.MODE_DISABLED), 1076 numScans, 1077 scans, 1078 componentIds, 1079 HsamplingFactors, 1080 VsamplingFactors, 1081 QtableSelectors, 1082 haveMetadata, 1083 restartInterval); 1084 1085 if (aborted) { 1086 processWriteAborted(); 1087 } else { 1088 processImageComplete(); 1089 } 1090 1091 ios.flush(); 1092 currentImage++; // After a successful write 1093 } 1094 1095 public void prepareWriteSequence(IIOMetadata streamMetadata) 1096 throws IOException { 1097 setThreadLock(); 1098 try { 1099 prepareWriteSequenceOnThread(streamMetadata); 1100 } finally { 1101 clearThreadLock(); 1102 } 1103 } 1104 1105 private void prepareWriteSequenceOnThread(IIOMetadata streamMetadata) 1106 throws IOException { 1107 if (ios == null) { 1108 throw new IllegalStateException("Output has not been set!"); 1109 } 1110 1111 /* 1112 * from jpeg_metadata.html: 1113 * If no stream metadata is supplied to 1114 * <code>ImageWriter.prepareWriteSequence</code>, then no 1115 * tables-only image is written. If stream metadata containing 1116 * no tables is supplied to 1117 * <code>ImageWriter.prepareWriteSequence</code>, then a tables-only 1118 * image containing default visually lossless tables is written. 1119 */ 1120 if (streamMetadata != null) { 1121 if (streamMetadata instanceof JPEGMetadata) { 1122 // write a complete tables-only image at the beginning of 1123 // the stream. 1124 JPEGMetadata jmeta = (JPEGMetadata) streamMetadata; 1125 if (jmeta.isStream == false) { 1126 throw new IllegalArgumentException 1127 ("Invalid stream metadata object."); 1128 } 1129 // Check that we are 1130 // at the beginning of the stream, or can go there, and haven't 1131 // written out the metadata already. 1132 if (currentImage != 0) { 1133 throw new IIOException 1134 ("JPEG Stream metadata must precede all images"); 1135 } 1136 if (sequencePrepared == true) { 1137 throw new IIOException("Stream metadata already written!"); 1138 } 1139 1140 // Set the tables 1141 // If the metadata has no tables, use default tables. 1142 streamQTables = collectQTablesFromMetadata(jmeta); 1143 if (debug) { 1144 System.out.println("after collecting from stream metadata, " 1145 + "streamQTables.length is " 1146 + streamQTables.length); 1147 } 1148 if (streamQTables == null) { 1149 streamQTables = JPEG.getDefaultQTables(); 1150 } 1151 streamDCHuffmanTables = 1152 collectHTablesFromMetadata(jmeta, true); 1153 if (streamDCHuffmanTables == null) { 1154 streamDCHuffmanTables = JPEG.getDefaultHuffmanTables(true); 1155 } 1156 streamACHuffmanTables = 1157 collectHTablesFromMetadata(jmeta, false); 1158 if (streamACHuffmanTables == null) { 1159 streamACHuffmanTables = JPEG.getDefaultHuffmanTables(false); 1160 } 1161 1162 // Now write them out 1163 writeTables(structPointer, 1164 streamQTables, 1165 streamDCHuffmanTables, 1166 streamACHuffmanTables); 1167 } else { 1168 throw new IIOException("Stream metadata must be JPEG metadata"); 1169 } 1170 } 1171 sequencePrepared = true; 1172 } 1173 1174 public void writeToSequence(IIOImage image, ImageWriteParam param) 1175 throws IOException { 1176 setThreadLock(); 1177 try { 1178 if (sequencePrepared == false) { 1179 throw new IllegalStateException("sequencePrepared not called!"); 1180 } 1181 // In the case of JPEG this does nothing different from write 1182 write(null, image, param); 1183 } finally { 1184 clearThreadLock(); 1185 } 1186 } 1187 1188 public void endWriteSequence() throws IOException { 1189 setThreadLock(); 1190 try { 1191 if (sequencePrepared == false) { 1192 throw new IllegalStateException("sequencePrepared not called!"); 1193 } 1194 sequencePrepared = false; 1195 } finally { 1196 clearThreadLock(); 1197 } 1198 } 1199 1200 public synchronized void abort() { 1201 setThreadLock(); 1202 try { 1203 super.abort(); 1204 abortWrite(structPointer); 1205 } finally { 1206 clearThreadLock(); 1207 } 1208 } 1209 1210 private void resetInternalState() { 1211 // reset C structures 1212 resetWriter(structPointer); 1213 1214 // reset local Java structures 1215 srcRas = null; 1216 raster = null; 1217 convertTosRGB = false; 1218 currentImage = 0; 1219 numScans = 0; 1220 metadata = null; 1221 } 1222 1223 public void reset() { 1224 setThreadLock(); 1225 try { 1226 super.reset(); 1227 } finally { 1228 clearThreadLock(); 1229 } 1230 } 1231 1232 public void dispose() { 1233 setThreadLock(); 1234 try { 1235 if (structPointer != 0) { 1236 disposerRecord.dispose(); 1237 structPointer = 0; 1238 } 1239 } finally { 1240 clearThreadLock(); 1241 } 1242 } 1243 1244 ////////// End of public API 1245 1246 ///////// Package-access API 1247 1248 /** 1249 * Called by the native code or other classes to signal a warning. 1250 * The code is used to lookup a localized message to be used when 1251 * sending warnings to listeners. 1252 */ 1253 void warningOccurred(int code) { 1254 if ((code < 0) || (code > MAX_WARNING)){ 1255 throw new InternalError("Invalid warning index"); 1256 } 1257 processWarningOccurred 1258 (currentImage, 1259 "com.sun.imageio.plugins.jpeg.JPEGImageWriterResources", 1260 Integer.toString(code)); 1261 } 1262 1263 /** 1264 * The library has it's own error facility that emits warning messages. 1265 * This routine is called by the native code when it has already 1266 * formatted a string for output. 1267 * XXX For truly complete localization of all warning messages, 1268 * the sun_jpeg_output_message routine in the native code should 1269 * send only the codes and parameters to a method here in Java, 1270 * which will then format and send the warnings, using localized 1271 * strings. This method will have to deal with all the parameters 1272 * and formats (%u with possibly large numbers, %02d, %02x, etc.) 1273 * that actually occur in the JPEG library. For now, this prevents 1274 * library warnings from being printed to stderr. 1275 */ 1276 void warningWithMessage(String msg) { 1277 processWarningOccurred(currentImage, msg); 1278 } 1279 1280 void thumbnailStarted(int thumbnailIndex) { 1281 processThumbnailStarted(currentImage, thumbnailIndex); 1282 } 1283 1284 // Provide access to protected superclass method 1285 void thumbnailProgress(float percentageDone) { 1286 processThumbnailProgress(percentageDone); 1287 } 1288 1289 // Provide access to protected superclass method 1290 void thumbnailComplete() { 1291 processThumbnailComplete(); 1292 } 1293 1294 ///////// End of Package-access API 1295 1296 ///////// Private methods 1297 1298 ///////// Metadata handling 1299 1300 private void checkSOFBands(SOFMarkerSegment sof, int numBandsUsed) 1301 throws IIOException { 1302 // Does the metadata frame header, if any, match numBandsUsed? 1303 if (sof != null) { 1304 if (sof.componentSpecs.length != numBandsUsed) { 1305 throw new IIOException 1306 ("Metadata components != number of destination bands"); 1307 } 1308 } 1309 } 1310 1311 private void checkJFIF(JFIFMarkerSegment jfif, 1312 ImageTypeSpecifier type, 1313 boolean input) { 1314 if (jfif != null) { 1315 if (!JPEG.isJFIFcompliant(type, input)) { 1316 ignoreJFIF = true; // type overrides metadata 1317 warningOccurred(input 1318 ? WARNING_IMAGE_METADATA_JFIF_MISMATCH 1319 : WARNING_DEST_METADATA_JFIF_MISMATCH); 1320 } 1321 } 1322 } 1323 1324 private void checkAdobe(AdobeMarkerSegment adobe, 1325 ImageTypeSpecifier type, 1326 boolean input) { 1327 if (adobe != null) { 1328 int rightTransform = JPEG.transformForType(type, input); 1329 if (adobe.transform != rightTransform) { 1330 warningOccurred(input 1331 ? WARNING_IMAGE_METADATA_ADOBE_MISMATCH 1332 : WARNING_DEST_METADATA_ADOBE_MISMATCH); 1333 if (rightTransform == JPEG.ADOBE_IMPOSSIBLE) { 1334 ignoreAdobe = true; 1335 } else { 1336 newAdobeTransform = rightTransform; 1337 } 1338 } 1339 } 1340 } 1341 1342 /** 1343 * Collect all the scan info from the given metadata, and 1344 * organize it into the scan info array required by the 1345 * IJG libray. It is much simpler to parse out this 1346 * data in Java and then just copy the data in C. 1347 */ 1348 private int [] collectScans(JPEGMetadata metadata, 1349 SOFMarkerSegment sof) { 1350 List segments = new ArrayList(); 1351 int SCAN_SIZE = 9; 1352 int MAX_COMPS_PER_SCAN = 4; 1353 for (Iterator iter = metadata.markerSequence.iterator(); 1354 iter.hasNext();) { 1355 MarkerSegment seg = (MarkerSegment) iter.next(); 1356 if (seg instanceof SOSMarkerSegment) { 1357 segments.add(seg); 1358 } 1359 } 1360 int [] retval = null; 1361 numScans = 0; 1362 if (!segments.isEmpty()) { 1363 numScans = segments.size(); 1364 retval = new int [numScans*SCAN_SIZE]; 1365 int index = 0; 1366 for (int i = 0; i < numScans; i++) { 1367 SOSMarkerSegment sos = (SOSMarkerSegment) segments.get(i); 1368 retval[index++] = sos.componentSpecs.length; // num comps 1369 for (int j = 0; j < MAX_COMPS_PER_SCAN; j++) { 1370 if (j < sos.componentSpecs.length) { 1371 int compSel = sos.componentSpecs[j].componentSelector; 1372 for (int k = 0; k < sof.componentSpecs.length; k++) { 1373 if (compSel == sof.componentSpecs[k].componentId) { 1374 retval[index++] = k; 1375 break; // out of for over sof comps 1376 } 1377 } 1378 } else { 1379 retval[index++] = 0; 1380 } 1381 } 1382 retval[index++] = sos.startSpectralSelection; 1383 retval[index++] = sos.endSpectralSelection; 1384 retval[index++] = sos.approxHigh; 1385 retval[index++] = sos.approxLow; 1386 } 1387 } 1388 return retval; 1389 } 1390 1391 /** 1392 * Finds all DQT marker segments and returns all the q 1393 * tables as a single array of JPEGQTables. 1394 */ 1395 private JPEGQTable [] collectQTablesFromMetadata 1396 (JPEGMetadata metadata) { 1397 ArrayList tables = new ArrayList(); 1398 Iterator iter = metadata.markerSequence.iterator(); 1399 while (iter.hasNext()) { 1400 MarkerSegment seg = (MarkerSegment) iter.next(); 1401 if (seg instanceof DQTMarkerSegment) { 1402 DQTMarkerSegment dqt = 1403 (DQTMarkerSegment) seg; 1404 tables.addAll(dqt.tables); 1405 } 1406 } 1407 JPEGQTable [] retval = null; 1408 if (tables.size() != 0) { 1409 retval = new JPEGQTable[tables.size()]; 1410 for (int i = 0; i < retval.length; i++) { 1411 retval[i] = 1412 new JPEGQTable(((DQTMarkerSegment.Qtable)tables.get(i)).data); 1413 } 1414 } 1415 return retval; 1416 } 1417 1418 /** 1419 * Finds all DHT marker segments and returns all the q 1420 * tables as a single array of JPEGQTables. The metadata 1421 * must not be for a progressive image, or an exception 1422 * will be thrown when two Huffman tables with the same 1423 * table id are encountered. 1424 */ 1425 private JPEGHuffmanTable[] collectHTablesFromMetadata 1426 (JPEGMetadata metadata, boolean wantDC) throws IIOException { 1427 ArrayList tables = new ArrayList(); 1428 Iterator iter = metadata.markerSequence.iterator(); 1429 while (iter.hasNext()) { 1430 MarkerSegment seg = (MarkerSegment) iter.next(); 1431 if (seg instanceof DHTMarkerSegment) { 1432 DHTMarkerSegment dht = 1433 (DHTMarkerSegment) seg; 1434 for (int i = 0; i < dht.tables.size(); i++) { 1435 DHTMarkerSegment.Htable htable = 1436 (DHTMarkerSegment.Htable) dht.tables.get(i); 1437 if (htable.tableClass == (wantDC ? 0 : 1)) { 1438 tables.add(htable); 1439 } 1440 } 1441 } 1442 } 1443 JPEGHuffmanTable [] retval = null; 1444 if (tables.size() != 0) { 1445 DHTMarkerSegment.Htable [] htables = 1446 new DHTMarkerSegment.Htable[tables.size()]; 1447 tables.toArray(htables); 1448 retval = new JPEGHuffmanTable[tables.size()]; 1449 for (int i = 0; i < retval.length; i++) { 1450 retval[i] = null; 1451 for (int j = 0; j < tables.size(); j++) { 1452 if (htables[j].tableID == i) { 1453 if (retval[i] != null) { 1454 throw new IIOException("Metadata has duplicate Htables!"); 1455 } 1456 retval[i] = new JPEGHuffmanTable(htables[j].numCodes, 1457 htables[j].values); 1458 } 1459 } 1460 } 1461 } 1462 1463 return retval; 1464 } 1465 1466 /////////// End of metadata handling 1467 1468 ////////////// ColorSpace conversion 1469 1470 private int getSrcCSType(ImageTypeSpecifier type) { 1471 return getSrcCSType(type.getColorModel()); 1472 } 1473 1474 private int getSrcCSType(RenderedImage rimage) { 1475 return getSrcCSType(rimage.getColorModel()); 1476 } 1477 1478 private int getSrcCSType(ColorModel cm) { 1479 int retval = JPEG.JCS_UNKNOWN; 1480 if (cm != null) { 1481 boolean alpha = cm.hasAlpha(); 1482 ColorSpace cs = cm.getColorSpace(); 1483 switch (cs.getType()) { 1484 case ColorSpace.TYPE_GRAY: 1485 retval = JPEG.JCS_GRAYSCALE; 1486 break; 1487 case ColorSpace.TYPE_RGB: 1488 if (alpha) { 1489 retval = JPEG.JCS_RGBA; 1490 } else { 1491 retval = JPEG.JCS_RGB; 1492 } 1493 break; 1494 case ColorSpace.TYPE_YCbCr: 1495 if (alpha) { 1496 retval = JPEG.JCS_YCbCrA; 1497 } else { 1498 retval = JPEG.JCS_YCbCr; 1499 } 1500 break; 1501 case ColorSpace.TYPE_3CLR: 1502 if (cs == JPEG.JCS.getYCC()) { 1503 if (alpha) { 1504 retval = JPEG.JCS_YCCA; 1505 } else { 1506 retval = JPEG.JCS_YCC; 1507 } 1508 } 1509 case ColorSpace.TYPE_CMYK: 1510 retval = JPEG.JCS_CMYK; 1511 break; 1512 } 1513 } 1514 return retval; 1515 } 1516 1517 private int getDestCSType(ImageTypeSpecifier destType) { 1518 ColorModel cm = destType.getColorModel(); 1519 boolean alpha = cm.hasAlpha(); 1520 ColorSpace cs = cm.getColorSpace(); 1521 int retval = JPEG.JCS_UNKNOWN; 1522 switch (cs.getType()) { 1523 case ColorSpace.TYPE_GRAY: 1524 retval = JPEG.JCS_GRAYSCALE; 1525 break; 1526 case ColorSpace.TYPE_RGB: 1527 if (alpha) { 1528 retval = JPEG.JCS_RGBA; 1529 } else { 1530 retval = JPEG.JCS_RGB; 1531 } 1532 break; 1533 case ColorSpace.TYPE_YCbCr: 1534 if (alpha) { 1535 retval = JPEG.JCS_YCbCrA; 1536 } else { 1537 retval = JPEG.JCS_YCbCr; 1538 } 1539 break; 1540 case ColorSpace.TYPE_3CLR: 1541 if (cs == JPEG.JCS.getYCC()) { 1542 if (alpha) { 1543 retval = JPEG.JCS_YCCA; 1544 } else { 1545 retval = JPEG.JCS_YCC; 1546 } 1547 } 1548 case ColorSpace.TYPE_CMYK: 1549 retval = JPEG.JCS_CMYK; 1550 break; 1551 } 1552 return retval; 1553 } 1554 1555 private int getDefaultDestCSType(ImageTypeSpecifier type) { 1556 return getDefaultDestCSType(type.getColorModel()); 1557 } 1558 1559 private int getDefaultDestCSType(RenderedImage rimage) { 1560 return getDefaultDestCSType(rimage.getColorModel()); 1561 } 1562 1563 private int getDefaultDestCSType(ColorModel cm) { 1564 int retval = JPEG.JCS_UNKNOWN; 1565 if (cm != null) { 1566 boolean alpha = cm.hasAlpha(); 1567 ColorSpace cs = cm.getColorSpace(); 1568 switch (cs.getType()) { 1569 case ColorSpace.TYPE_GRAY: 1570 retval = JPEG.JCS_GRAYSCALE; 1571 break; 1572 case ColorSpace.TYPE_RGB: 1573 if (alpha) { 1574 retval = JPEG.JCS_YCbCrA; 1575 } else { 1576 retval = JPEG.JCS_YCbCr; 1577 } 1578 break; 1579 case ColorSpace.TYPE_YCbCr: 1580 if (alpha) { 1581 retval = JPEG.JCS_YCbCrA; 1582 } else { 1583 retval = JPEG.JCS_YCbCr; 1584 } 1585 break; 1586 case ColorSpace.TYPE_3CLR: 1587 if (cs == JPEG.JCS.getYCC()) { 1588 if (alpha) { 1589 retval = JPEG.JCS_YCCA; 1590 } else { 1591 retval = JPEG.JCS_YCC; 1592 } 1593 } 1594 case ColorSpace.TYPE_CMYK: 1595 retval = JPEG.JCS_YCCK; 1596 break; 1597 } 1598 } 1599 return retval; 1600 } 1601 1602 private boolean isSubsampled(SOFMarkerSegment.ComponentSpec [] specs) { 1603 int hsamp0 = specs[0].HsamplingFactor; 1604 int vsamp0 = specs[0].VsamplingFactor; 1605 for (int i = 1; i < specs.length; i++) { 1606 if ((specs[i].HsamplingFactor != hsamp0) || 1607 (specs[i].HsamplingFactor != hsamp0)) 1608 return true; 1609 } 1610 return false; 1611 } 1612 1613 ////////////// End of ColorSpace conversion 1614 1615 ////////////// Native methods and callbacks 1616 1617 /** Sets up static native structures. */ 1618 private static native void initWriterIDs(Class iosClass, 1619 Class qTableClass, 1620 Class huffClass); 1621 1622 /** Sets up per-writer native structure and returns a pointer to it. */ 1623 private native long initJPEGImageWriter(); 1624 1625 /** Sets up native structures for output stream */ 1626 private native void setDest(long structPointer, 1627 ImageOutputStream ios); 1628 1629 /** 1630 * Returns <code>true</code> if the write was aborted. 1631 */ 1632 private native boolean writeImage(long structPointer, 1633 byte [] data, 1634 int inCsType, int outCsType, 1635 int numBands, 1636 int [] bandSizes, 1637 int srcWidth, 1638 int destWidth, int destHeight, 1639 int stepX, int stepY, 1640 JPEGQTable [] qtables, 1641 boolean writeDQT, 1642 JPEGHuffmanTable[] DCHuffmanTables, 1643 JPEGHuffmanTable[] ACHuffmanTables, 1644 boolean writeDHT, 1645 boolean optimizeHuffman, 1646 boolean progressive, 1647 int numScans, 1648 int [] scans, 1649 int [] componentIds, 1650 int [] HsamplingFactors, 1651 int [] VsamplingFactors, 1652 int [] QtableSelectors, 1653 boolean haveMetadata, 1654 int restartInterval); 1655 1656 1657 /** 1658 * Writes the metadata out when called by the native code, 1659 * which will have already written the header to the stream 1660 * and established the library state. This is simpler than 1661 * breaking the write call in two. 1662 */ 1663 private void writeMetadata() throws IOException { 1664 if (metadata == null) { 1665 if (writeDefaultJFIF) { 1666 JFIFMarkerSegment.writeDefaultJFIF(ios, 1667 thumbnails, 1668 iccProfile, 1669 this); 1670 } 1671 if (writeAdobe) { 1672 AdobeMarkerSegment.writeAdobeSegment(ios, newAdobeTransform); 1673 } 1674 } else { 1675 metadata.writeToStream(ios, 1676 ignoreJFIF, 1677 forceJFIF, 1678 thumbnails, 1679 iccProfile, 1680 ignoreAdobe, 1681 newAdobeTransform, 1682 this); 1683 } 1684 } 1685 1686 /** 1687 * Write out a tables-only image to the stream. 1688 */ 1689 private native void writeTables(long structPointer, 1690 JPEGQTable [] qtables, 1691 JPEGHuffmanTable[] DCHuffmanTables, 1692 JPEGHuffmanTable[] ACHuffmanTables); 1693 1694 /** 1695 * Put the scanline y of the source ROI view Raster into the 1696 * 1-line Raster for writing. This handles ROI and band 1697 * rearrangements, and expands indexed images. Subsampling is 1698 * done in the native code. 1699 * This is called by the native code. 1700 */ 1701 private void grabPixels(int y) { 1702 1703 Raster sourceLine = null; 1704 if (indexed) { 1705 sourceLine = srcRas.createChild(sourceXOffset, 1706 sourceYOffset+y, 1707 sourceWidth, 1, 1708 0, 0, 1709 new int [] {0}); 1710 // If the image has BITMASK transparency, we need to make sure 1711 // it gets converted to 32-bit ARGB, because the JPEG encoder 1712 // relies upon the full 8-bit alpha channel. 1713 boolean forceARGB = 1714 (indexCM.getTransparency() != Transparency.OPAQUE); 1715 BufferedImage temp = indexCM.convertToIntDiscrete(sourceLine, 1716 forceARGB); 1717 sourceLine = temp.getRaster(); 1718 } else { 1719 sourceLine = srcRas.createChild(sourceXOffset, 1720 sourceYOffset+y, 1721 sourceWidth, 1, 1722 0, 0, 1723 srcBands); 1724 } 1725 if (convertTosRGB) { 1726 if (debug) { 1727 System.out.println("Converting to sRGB"); 1728 } 1729 // The first time through, converted is null, so 1730 // a new raster is allocated. It is then reused 1731 // on subsequent lines. 1732 converted = convertOp.filter(sourceLine, converted); 1733 sourceLine = converted; 1734 } 1735 if (isAlphaPremultiplied) { 1736 WritableRaster wr = sourceLine.createCompatibleWritableRaster(); 1737 int[] data = null; 1738 data = sourceLine.getPixels(sourceLine.getMinX(), sourceLine.getMinY(), 1739 sourceLine.getWidth(), sourceLine.getHeight(), 1740 data); 1741 wr.setPixels(sourceLine.getMinX(), sourceLine.getMinY(), 1742 sourceLine.getWidth(), sourceLine.getHeight(), 1743 data); 1744 srcCM.coerceData(wr, false); 1745 sourceLine = wr.createChild(wr.getMinX(), wr.getMinY(), 1746 wr.getWidth(), wr.getHeight(), 1747 0, 0, 1748 srcBands); 1749 } 1750 raster.setRect(sourceLine); 1751 if ((y > 7) && (y%8 == 0)) { // Every 8 scanlines 1752 processImageProgress((float) y / (float) sourceHeight * 100.0F); 1753 } 1754 } 1755 1756 /** Aborts the current write in the native code */ 1757 private native void abortWrite(long structPointer); 1758 1759 /** Resets native structures */ 1760 private native void resetWriter(long structPointer); 1761 1762 /** Releases native structures */ 1763 private static native void disposeWriter(long structPointer); 1764 1765 private static class JPEGWriterDisposerRecord implements DisposerRecord { 1766 private long pData; 1767 1768 public JPEGWriterDisposerRecord(long pData) { 1769 this.pData = pData; 1770 } 1771 1772 public synchronized void dispose() { 1773 if (pData != 0) { 1774 disposeWriter(pData); 1775 pData = 0; 1776 } 1777 } 1778 } 1779 1780 private Thread theThread = null; 1781 private int theLockCount = 0; 1782 1783 private synchronized void setThreadLock() { 1784 Thread currThread = Thread.currentThread(); 1785 if (theThread != null) { 1786 if (theThread != currThread) { 1787 // it looks like that this reader instance is used 1788 // by multiple threads. 1789 throw new IllegalStateException("Attempt to use instance of " + 1790 this + " locked on thread " + 1791 theThread + " from thread " + 1792 currThread); 1793 } else { 1794 theLockCount ++; 1795 } 1796 } else { 1797 theThread = currThread; 1798 theLockCount = 1; 1799 } 1800 } 1801 1802 private synchronized void clearThreadLock() { 1803 Thread currThread = Thread.currentThread(); 1804 if (theThread == null || theThread != currThread) { 1805 throw new IllegalStateException("Attempt to clear thread lock form wrong thread. " + 1806 "Locked thread: " + theThread + 1807 "; current thread: " + currThread); 1808 } 1809 theLockCount --; 1810 if (theLockCount == 0) { 1811 theThread = null; 1812 } 1813 } 1814 }