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