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 }