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