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