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