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