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