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