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