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