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 (jfif != null) {
 758                                 outCsType = JPEG.JCS_YCbCr;
 759                                 if (JPEG.isNonStandardICC(cs)
 760                                     || ((cs instanceof ICC_ColorSpace)
 761                                         && (jfif.iccSegment != null))) {
 762                                     iccProfile =
 763                                         ((ICC_ColorSpace) cs).getProfile();
 764                                 }
 765                             } else if (adobe != null) {
 766                                 switch (adobe.transform) {
 767                                 case JPEG.ADOBE_UNKNOWN:
 768                                     outCsType = JPEG.JCS_RGB;
 769                                     break;
 770                                 case JPEG.ADOBE_YCC:
 771                                     outCsType = JPEG.JCS_YCbCr;
 772                                     break;
 773                                 default:
 774                                     warningOccurred
 775                                     (WARNING_IMAGE_METADATA_ADOBE_MISMATCH);
 776                                     newAdobeTransform = JPEG.ADOBE_UNKNOWN;
 777                                     outCsType = JPEG.JCS_RGB;
 778                                     break;
 779                                 }
 780                             } else {
 781                                 // consult the ids
 782                                 int outCS = sof.getIDencodedCSType();
 783                                 // if they don't resolve it,
 784                                 // consult the sampling factors
 785                                 if (outCS != JPEG.JCS_UNKNOWN) {
 786                                     outCsType = outCS;
 787                                 } else {
 788                                     boolean subsampled =
 789                                     isSubsampled(sof.componentSpecs);
 790                                     if (subsampled) {
 791                                         outCsType = JPEG.JCS_YCbCr;
 792                                     } else {
 793                                         outCsType = JPEG.JCS_RGB;
 794                                     }
 795                                 }
 796                             }
 797                             break;
 798                         }
 799                     }
 800                 } // else no dest, metadata, not an image.  Defaults ok
 801             }
 802         }
 803 
 804         boolean metadataProgressive = false;
 805         int [] scans = null;
 806 
 807         if (metadata != null) {
 808             if (sof == null) {
 809                 sof = (SOFMarkerSegment) metadata.findMarkerSegment
 810                     (SOFMarkerSegment.class, true);
 811             }
 812             if ((sof != null) && (sof.tag == JPEG.SOF2)) {
 813                 metadataProgressive = true;
 814                 if (progressiveMode == ImageWriteParam.MODE_COPY_FROM_METADATA) {
 815                     scans = collectScans(metadata, sof);  // Might still be null
 816                 } else {
 817                     numScans = 0;
 818                 }
 819             }
 820             if (jfif == null) {
 821                 jfif = (JFIFMarkerSegment) metadata.findMarkerSegment
 822                     (JFIFMarkerSegment.class, true);
 823             }
 824         }
 825 
 826         thumbnails = image.getThumbnails();
 827         int numThumbs = image.getNumThumbnails();
 828         forceJFIF = false;
 829         // determine if thumbnails can be written
 830         // If we are going to add a default JFIF marker segment,
 831         // then thumbnails can be written
 832         if (!writeDefaultJFIF) {
 833             // If there is no metadata, then we can't write thumbnails
 834             if (metadata == null) {
 835                 thumbnails = null;
 836                 if (numThumbs != 0) {
 837                     warningOccurred(WARNING_IGNORING_THUMBS);
 838                 }
 839             } else {
 840                 // There is metadata
 841                 // If we are writing a raster or subbands,
 842                 // then the user must specify JFIF on the metadata
 843                 if (fullImage == false) {
 844                     if (jfif == null) {
 845                         thumbnails = null;  // Or we can't include thumbnails
 846                         if (numThumbs != 0) {
 847                             warningOccurred(WARNING_IGNORING_THUMBS);
 848                         }
 849                     }
 850                 } else {  // It is a full image, and there is metadata
 851                     if (jfif == null) {  // Not JFIF
 852                         // Can it have JFIF?
 853                         if ((outCsType == JPEG.JCS_GRAYSCALE)
 854                             || (outCsType == JPEG.JCS_YCbCr)) {
 855                             if (numThumbs != 0) {
 856                                 forceJFIF = true;
 857                                 warningOccurred(WARNING_FORCING_JFIF);
 858                             }
 859                         } else {  // Nope, not JFIF-compatible
 860                             thumbnails = null;
 861                             if (numThumbs != 0) {
 862                                 warningOccurred(WARNING_IGNORING_THUMBS);
 863                             }
 864                         }
 865                     }
 866                 }
 867             }
 868         }
 869 
 870         // Set up a boolean to indicate whether we need to call back to
 871         // write metadata
 872         boolean haveMetadata =
 873             ((metadata != null) || writeDefaultJFIF || writeAdobe);
 874 
 875         // Now that we have dealt with metadata, finalize our tables set up
 876 
 877         // Are we going to write tables?  By default, yes.
 878         boolean writeDQT = true;
 879         boolean writeDHT = true;
 880 
 881         // But if the metadata has no tables, no.
 882         DQTMarkerSegment dqt = null;
 883         DHTMarkerSegment dht = null;
 884 
 885         int restartInterval = 0;
 886 
 887         if (metadata != null) {
 888             dqt = (DQTMarkerSegment) metadata.findMarkerSegment
 889                 (DQTMarkerSegment.class, true);
 890             dht = (DHTMarkerSegment) metadata.findMarkerSegment
 891                 (DHTMarkerSegment.class, true);
 892             DRIMarkerSegment dri =
 893                 (DRIMarkerSegment) metadata.findMarkerSegment
 894                 (DRIMarkerSegment.class, true);
 895             if (dri != null) {
 896                 restartInterval = dri.restartInterval;
 897             }
 898 
 899             if (dqt == null) {
 900                 writeDQT = false;
 901             }
 902             if (dht == null) {
 903                 writeDHT = false;  // Ignored if optimizeHuffman is true
 904             }
 905         }
 906 
 907         // Whether we write tables or not, we need to figure out which ones
 908         // to use
 909         if (qTables == null) { // Get them from metadata, or use defaults
 910             if (dqt != null) {
 911                 qTables = collectQTablesFromMetadata(metadata);
 912             } else if (streamQTables != null) {
 913                 qTables = streamQTables;
 914             } else if ((jparam != null) && (jparam.areTablesSet())) {
 915                 qTables = jparam.getQTables();
 916             } else {
 917                 qTables = JPEG.getDefaultQTables();
 918             }
 919 
 920         }
 921 
 922         // If we are optimizing, we don't want any tables.
 923         if (optimizeHuffman == false) {
 924             // If they were for progressive scans, we can't use them.
 925             if ((dht != null) && (metadataProgressive == false)) {
 926                 DCHuffmanTables = collectHTablesFromMetadata(metadata, true);
 927                 ACHuffmanTables = collectHTablesFromMetadata(metadata, false);
 928             } else if (streamDCHuffmanTables != null) {
 929                 DCHuffmanTables = streamDCHuffmanTables;
 930                 ACHuffmanTables = streamACHuffmanTables;
 931             } else if ((jparam != null) && (jparam.areTablesSet())) {
 932                 DCHuffmanTables = jparam.getDCHuffmanTables();
 933                 ACHuffmanTables = jparam.getACHuffmanTables();
 934             } else {
 935                 DCHuffmanTables = JPEG.getDefaultHuffmanTables(true);
 936                 ACHuffmanTables = JPEG.getDefaultHuffmanTables(false);
 937             }
 938         }
 939 
 940         // By default, ids are 1 - N, no subsampling
 941         int [] componentIds = new int[numBandsUsed];
 942         int [] HsamplingFactors = new int[numBandsUsed];
 943         int [] VsamplingFactors = new int[numBandsUsed];
 944         int [] QtableSelectors = new int[numBandsUsed];
 945         for (int i = 0; i < numBandsUsed; i++) {
 946             componentIds[i] = i+1; // JFIF compatible
 947             HsamplingFactors[i] = 1;
 948             VsamplingFactors[i] = 1;
 949             QtableSelectors[i] = 0;
 950         }
 951 
 952         // Now override them with the contents of sof, if there is one,
 953         if (sof != null) {
 954             for (int i = 0; i < numBandsUsed; i++) {
 955                 if (forceJFIF == false) {  // else use JFIF-compatible default
 956                     componentIds[i] = sof.componentSpecs[i].componentId;
 957                 }
 958                 HsamplingFactors[i] = sof.componentSpecs[i].HsamplingFactor;
 959                 VsamplingFactors[i] = sof.componentSpecs[i].VsamplingFactor;
 960                 QtableSelectors[i] = sof.componentSpecs[i].QtableSelector;
 961             }
 962         }
 963 
 964         sourceXOffset += gridX;
 965         sourceWidth -= gridX;
 966         sourceYOffset += gridY;
 967         sourceHeight -= gridY;
 968 
 969         int destWidth = (sourceWidth + periodX - 1)/periodX;
 970         int destHeight = (sourceHeight + periodY - 1)/periodY;
 971 
 972         // Create an appropriate 1-line databuffer for writing
 973         int lineSize = sourceWidth*numBandsUsed;
 974 
 975         DataBufferByte buffer = new DataBufferByte(lineSize);
 976 
 977         // Create a raster from that
 978         int [] bandOffs = JPEG.bandOffsets[numBandsUsed-1];
 979 
 980         raster = Raster.createInterleavedRaster(buffer,
 981                                                 sourceWidth, 1,
 982                                                 lineSize,
 983                                                 numBandsUsed,
 984                                                 bandOffs,
 985                                                 null);
 986 
 987         // Call the writer, who will call back for every scanline
 988 
 989         clearAbortRequest();
 990         cbLock.lock();
 991         try {
 992             processImageStarted(currentImage);
 993         } finally {
 994             cbLock.unlock();
 995         }
 996 
 997         boolean aborted = false;
 998 
 999         if (debug) {
1000             System.out.println("inCsType: " + inCsType);
1001             System.out.println("outCsType: " + outCsType);
1002         }
1003 
1004         // Note that getData disables acceleration on buffer, but it is
1005         // just a 1-line intermediate data transfer buffer that does not
1006         // affect the acceleration of the source image.
1007         aborted = writeImage(structPointer,
1008                              buffer.getData(),
1009                              inCsType, outCsType,
1010                              numBandsUsed,
1011                              bandSizes,
1012                              sourceWidth,
1013                              destWidth, destHeight,
1014                              periodX, periodY,
1015                              qTables,
1016                              writeDQT,
1017                              DCHuffmanTables,
1018                              ACHuffmanTables,
1019                              writeDHT,
1020                              optimizeHuffman,
1021                              (progressiveMode
1022                               != ImageWriteParam.MODE_DISABLED),
1023                              numScans,
1024                              scans,
1025                              componentIds,
1026                              HsamplingFactors,
1027                              VsamplingFactors,
1028                              QtableSelectors,
1029                              haveMetadata,
1030                              restartInterval);
1031 
1032         cbLock.lock();
1033         try {
1034             if (aborted) {
1035                 processWriteAborted();
1036             } else {
1037                 processImageComplete();
1038             }
1039 
1040             ios.flush();
1041         } finally {
1042             cbLock.unlock();
1043         }
1044         currentImage++;  // After a successful write
1045     }
1046 
1047     @Override
1048     public boolean canWriteSequence() {
1049         return true;
1050     }
1051 
1052     public void prepareWriteSequence(IIOMetadata streamMetadata)
1053         throws IOException {
1054         setThreadLock();
1055         try {
1056             cbLock.check();
1057 
1058             prepareWriteSequenceOnThread(streamMetadata);
1059         } finally {
1060             clearThreadLock();
1061         }
1062     }
1063 
1064     private void prepareWriteSequenceOnThread(IIOMetadata streamMetadata)
1065         throws IOException {
1066         if (ios == null) {
1067             throw new IllegalStateException("Output has not been set!");
1068         }
1069 
1070         /*
1071          * from jpeg_metadata.html:
1072          * If no stream metadata is supplied to
1073          * {@code ImageWriter.prepareWriteSequence}, then no
1074          * tables-only image is written.  If stream metadata containing
1075          * no tables is supplied to
1076          * {@code ImageWriter.prepareWriteSequence}, then a tables-only
1077          * image containing default visually lossless tables is written.
1078          */
1079         if (streamMetadata != null) {
1080             if (streamMetadata instanceof JPEGMetadata) {
1081                 // write a complete tables-only image at the beginning of
1082                 // the stream.
1083                 JPEGMetadata jmeta = (JPEGMetadata) streamMetadata;
1084                 if (jmeta.isStream == false) {
1085                     throw new IllegalArgumentException
1086                         ("Invalid stream metadata object.");
1087                 }
1088                 // Check that we are
1089                 // at the beginning of the stream, or can go there, and haven't
1090                 // written out the metadata already.
1091                 if (currentImage != 0) {
1092                     throw new IIOException
1093                         ("JPEG Stream metadata must precede all images");
1094                 }
1095                 if (sequencePrepared == true) {
1096                     throw new IIOException("Stream metadata already written!");
1097                 }
1098 
1099                 // Set the tables
1100                 // If the metadata has no tables, use default tables.
1101                 streamQTables = collectQTablesFromMetadata(jmeta);
1102                 if (debug) {
1103                     System.out.println("after collecting from stream metadata, "
1104                                        + "streamQTables.length is "
1105                                        + streamQTables.length);
1106                 }
1107                 if (streamQTables == null) {
1108                     streamQTables = JPEG.getDefaultQTables();
1109                 }
1110                 streamDCHuffmanTables =
1111                     collectHTablesFromMetadata(jmeta, true);
1112                 if (streamDCHuffmanTables == null) {
1113                     streamDCHuffmanTables = JPEG.getDefaultHuffmanTables(true);
1114                 }
1115                 streamACHuffmanTables =
1116                     collectHTablesFromMetadata(jmeta, false);
1117                 if (streamACHuffmanTables == null) {
1118                     streamACHuffmanTables = JPEG.getDefaultHuffmanTables(false);
1119                 }
1120 
1121                 // Now write them out
1122                 writeTables(structPointer,
1123                             streamQTables,
1124                             streamDCHuffmanTables,
1125                             streamACHuffmanTables);
1126             } else {
1127                 throw new IIOException("Stream metadata must be JPEG metadata");
1128             }
1129         }
1130         sequencePrepared = true;
1131     }
1132 
1133     public void writeToSequence(IIOImage image, ImageWriteParam param)
1134         throws IOException {
1135         setThreadLock();
1136         try {
1137             cbLock.check();
1138 
1139             if (sequencePrepared == false) {
1140                 throw new IllegalStateException("sequencePrepared not called!");
1141             }
1142             // In the case of JPEG this does nothing different from write
1143             write(null, image, param);
1144         } finally {
1145             clearThreadLock();
1146         }
1147     }
1148 
1149     public void endWriteSequence() throws IOException {
1150         setThreadLock();
1151         try {
1152             cbLock.check();
1153 
1154             if (sequencePrepared == false) {
1155                 throw new IllegalStateException("sequencePrepared not called!");
1156             }
1157             sequencePrepared = false;
1158         } finally {
1159             clearThreadLock();
1160         }
1161     }
1162 
1163     public synchronized void abort() {
1164         setThreadLock();
1165         try {
1166             /**
1167              * NB: we do not check the call back lock here, we allow to abort
1168              * the reader any time.
1169              */
1170             super.abort();
1171             abortWrite(structPointer);
1172         } finally {
1173             clearThreadLock();
1174         }
1175     }
1176 
1177     @Override
1178     protected synchronized void clearAbortRequest() {
1179         setThreadLock();
1180         try {
1181             cbLock.check();
1182             if (abortRequested()) {
1183                 super.clearAbortRequest();
1184                 // reset C structures
1185                 resetWriter(structPointer);
1186                 // reset the native destination
1187                 setDest(structPointer);
1188             }
1189         } finally {
1190             clearThreadLock();
1191         }
1192     }
1193 
1194     private void resetInternalState() {
1195         // reset C structures
1196         resetWriter(structPointer);
1197 
1198         // reset local Java structures
1199         srcRas = null;
1200         raster = null;
1201         convertTosRGB = false;
1202         currentImage = 0;
1203         numScans = 0;
1204         metadata = null;
1205     }
1206 
1207     public void reset() {
1208         setThreadLock();
1209         try {
1210             cbLock.check();
1211 
1212             super.reset();
1213         } finally {
1214             clearThreadLock();
1215         }
1216     }
1217 
1218     public void dispose() {
1219         setThreadLock();
1220         try {
1221             cbLock.check();
1222 
1223             if (structPointer != 0) {
1224                 disposerRecord.dispose();
1225                 structPointer = 0;
1226             }
1227         } finally {
1228             clearThreadLock();
1229         }
1230     }
1231 
1232     ////////// End of public API
1233 
1234     ///////// Package-access API
1235 
1236     /**
1237      * Called by the native code or other classes to signal a warning.
1238      * The code is used to lookup a localized message to be used when
1239      * sending warnings to listeners.
1240      */
1241     void warningOccurred(int code) {
1242         cbLock.lock();
1243         try {
1244             if ((code < 0) || (code > MAX_WARNING)){
1245                 throw new InternalError("Invalid warning index");
1246             }
1247             processWarningOccurred
1248                 (currentImage,
1249                  "com.sun.imageio.plugins.jpeg.JPEGImageWriterResources",
1250                 Integer.toString(code));
1251         } finally {
1252             cbLock.unlock();
1253         }
1254     }
1255 
1256     /**
1257      * The library has it's own error facility that emits warning messages.
1258      * This routine is called by the native code when it has already
1259      * formatted a string for output.
1260      * XXX  For truly complete localization of all warning messages,
1261      * the sun_jpeg_output_message routine in the native code should
1262      * send only the codes and parameters to a method here in Java,
1263      * which will then format and send the warnings, using localized
1264      * strings.  This method will have to deal with all the parameters
1265      * and formats (%u with possibly large numbers, %02d, %02x, etc.)
1266      * that actually occur in the JPEG library.  For now, this prevents
1267      * library warnings from being printed to stderr.
1268      */
1269     void warningWithMessage(String msg) {
1270         cbLock.lock();
1271         try {
1272             processWarningOccurred(currentImage, msg);
1273         } finally {
1274             cbLock.unlock();
1275         }
1276     }
1277 
1278     void thumbnailStarted(int thumbnailIndex) {
1279         cbLock.lock();
1280         try {
1281             processThumbnailStarted(currentImage, thumbnailIndex);
1282         } finally {
1283             cbLock.unlock();
1284         }
1285     }
1286 
1287     // Provide access to protected superclass method
1288     void thumbnailProgress(float percentageDone) {
1289         cbLock.lock();
1290         try {
1291             processThumbnailProgress(percentageDone);
1292         } finally {
1293             cbLock.unlock();
1294         }
1295     }
1296 
1297     // Provide access to protected superclass method
1298     void thumbnailComplete() {
1299         cbLock.lock();
1300         try {
1301             processThumbnailComplete();
1302         } finally {
1303             cbLock.unlock();
1304         }
1305     }
1306 
1307     ///////// End of Package-access API
1308 
1309     ///////// Private methods
1310 
1311     ///////// Metadata handling
1312 
1313     private void checkSOFBands(SOFMarkerSegment sof, int numBandsUsed)
1314         throws IIOException {
1315         // Does the metadata frame header, if any, match numBandsUsed?
1316         if (sof != null) {
1317             if (sof.componentSpecs.length != numBandsUsed) {
1318                 throw new IIOException
1319                     ("Metadata components != number of destination bands");
1320             }
1321         }
1322     }
1323 
1324     private void checkJFIF(JFIFMarkerSegment jfif,
1325                            ImageTypeSpecifier type,
1326                            boolean input) {
1327         if (jfif != null) {
1328             if (!JPEG.isJFIFcompliant(type, input)) {
1329                 ignoreJFIF = true;  // type overrides metadata
1330                 warningOccurred(input
1331                                 ? WARNING_IMAGE_METADATA_JFIF_MISMATCH
1332                                 : WARNING_DEST_METADATA_JFIF_MISMATCH);
1333             }
1334         }
1335     }
1336 
1337     private void checkAdobe(AdobeMarkerSegment adobe,
1338                            ImageTypeSpecifier type,
1339                            boolean input) {
1340         if (adobe != null) {
1341             int rightTransform = JPEG.transformForType(type, input);
1342             if (adobe.transform != rightTransform) {
1343                 warningOccurred(input
1344                                 ? WARNING_IMAGE_METADATA_ADOBE_MISMATCH
1345                                 : WARNING_DEST_METADATA_ADOBE_MISMATCH);
1346                 if (rightTransform == JPEG.ADOBE_IMPOSSIBLE) {
1347                     ignoreAdobe = true;
1348                 } else {
1349                     newAdobeTransform = rightTransform;
1350                 }
1351             }
1352         }
1353     }
1354 
1355     /**
1356      * Collect all the scan info from the given metadata, and
1357      * organize it into the scan info array required by the
1358      * IJG libray.  It is much simpler to parse out this
1359      * data in Java and then just copy the data in C.
1360      */
1361     private int [] collectScans(JPEGMetadata metadata,
1362                                 SOFMarkerSegment sof) {
1363         List<SOSMarkerSegment> segments = new ArrayList<>();
1364         int SCAN_SIZE = 9;
1365         int MAX_COMPS_PER_SCAN = 4;
1366         for (Iterator<MarkerSegment> iter = metadata.markerSequence.iterator();
1367              iter.hasNext();) {
1368             MarkerSegment seg = iter.next();
1369             if (seg instanceof SOSMarkerSegment) {
1370                 segments.add((SOSMarkerSegment) seg);
1371             }
1372         }
1373         int [] retval = null;
1374         numScans = 0;
1375         if (!segments.isEmpty()) {
1376             numScans = segments.size();
1377             retval = new int [numScans*SCAN_SIZE];
1378             int index = 0;
1379             for (int i = 0; i < numScans; i++) {
1380                 SOSMarkerSegment sos = segments.get(i);
1381                 retval[index++] = sos.componentSpecs.length; // num comps
1382                 for (int j = 0; j < MAX_COMPS_PER_SCAN; j++) {
1383                     if (j < sos.componentSpecs.length) {
1384                         int compSel = sos.componentSpecs[j].componentSelector;
1385                         for (int k = 0; k < sof.componentSpecs.length; k++) {
1386                             if (compSel == sof.componentSpecs[k].componentId) {
1387                                 retval[index++] = k;
1388                                 break; // out of for over sof comps
1389                             }
1390                         }
1391                     } else {
1392                         retval[index++] = 0;
1393                     }
1394                 }
1395                 retval[index++] = sos.startSpectralSelection;
1396                 retval[index++] = sos.endSpectralSelection;
1397                 retval[index++] = sos.approxHigh;
1398                 retval[index++] = sos.approxLow;
1399             }
1400         }
1401         return retval;
1402     }
1403 
1404     /**
1405      * Finds all DQT marker segments and returns all the q
1406      * tables as a single array of JPEGQTables.
1407      */
1408     private JPEGQTable [] collectQTablesFromMetadata
1409         (JPEGMetadata metadata) {
1410         ArrayList<DQTMarkerSegment.Qtable> tables = new ArrayList<>();
1411         Iterator<MarkerSegment> iter = metadata.markerSequence.iterator();
1412         while (iter.hasNext()) {
1413             MarkerSegment seg = iter.next();
1414             if (seg instanceof DQTMarkerSegment) {
1415                 DQTMarkerSegment dqt =
1416                     (DQTMarkerSegment) seg;
1417                 tables.addAll(dqt.tables);
1418             }
1419         }
1420         JPEGQTable [] retval = null;
1421         if (tables.size() != 0) {
1422             retval = new JPEGQTable[tables.size()];
1423             for (int i = 0; i < retval.length; i++) {
1424                 retval[i] =
1425                     new JPEGQTable(tables.get(i).data);
1426             }
1427         }
1428         return retval;
1429     }
1430 
1431     /**
1432      * Finds all DHT marker segments and returns all the q
1433      * tables as a single array of JPEGQTables.  The metadata
1434      * must not be for a progressive image, or an exception
1435      * will be thrown when two Huffman tables with the same
1436      * table id are encountered.
1437      */
1438     private JPEGHuffmanTable[] collectHTablesFromMetadata
1439         (JPEGMetadata metadata, boolean wantDC) throws IIOException {
1440         ArrayList<DHTMarkerSegment.Htable> tables = new ArrayList<>();
1441         Iterator<MarkerSegment> iter = metadata.markerSequence.iterator();
1442         while (iter.hasNext()) {
1443             MarkerSegment seg = iter.next();
1444             if (seg instanceof DHTMarkerSegment) {
1445                 DHTMarkerSegment dht = (DHTMarkerSegment) seg;
1446                 for (int i = 0; i < dht.tables.size(); i++) {
1447                     DHTMarkerSegment.Htable htable = dht.tables.get(i);
1448                     if (htable.tableClass == (wantDC ? 0 : 1)) {
1449                         tables.add(htable);
1450                     }
1451                 }
1452             }
1453         }
1454         JPEGHuffmanTable [] retval = null;
1455         if (tables.size() != 0) {
1456             DHTMarkerSegment.Htable [] htables =
1457                 new DHTMarkerSegment.Htable[tables.size()];
1458             tables.toArray(htables);
1459             retval = new JPEGHuffmanTable[tables.size()];
1460             for (int i = 0; i < retval.length; i++) {
1461                 retval[i] = null;
1462                 for (int j = 0; j < tables.size(); j++) {
1463                     if (htables[j].tableID == i) {
1464                         if (retval[i] != null) {
1465                             throw new IIOException("Metadata has duplicate Htables!");
1466                         }
1467                         retval[i] = new JPEGHuffmanTable(htables[j].numCodes,
1468                                                          htables[j].values);
1469                     }
1470                 }
1471             }
1472         }
1473 
1474         return retval;
1475     }
1476 
1477     /////////// End of metadata handling
1478 
1479     ////////////// ColorSpace conversion
1480 
1481     private int getSrcCSType(ImageTypeSpecifier type) {
1482          return getSrcCSType(type.getColorModel());
1483     }
1484 
1485     private int getSrcCSType(RenderedImage rimage) {
1486         return getSrcCSType(rimage.getColorModel());
1487     }
1488 
1489     private int getSrcCSType(ColorModel cm) {
1490         int retval = JPEG.JCS_UNKNOWN;
1491         if (cm != null) {
1492             boolean alpha = cm.hasAlpha();
1493             ColorSpace cs = cm.getColorSpace();
1494             switch (cs.getType()) {
1495             case ColorSpace.TYPE_GRAY:
1496                 retval = JPEG.JCS_GRAYSCALE;
1497                 break;
1498             case ColorSpace.TYPE_RGB:
1499                 retval = JPEG.JCS_RGB;
1500                 break;
1501             case ColorSpace.TYPE_YCbCr:
1502                 retval = JPEG.JCS_YCbCr;
1503                 break;
1504             case ColorSpace.TYPE_CMYK:
1505                 retval = JPEG.JCS_CMYK;
1506                 break;
1507             }
1508         }
1509         return retval;
1510     }
1511 
1512     private int getDestCSType(ImageTypeSpecifier destType) {
1513         ColorModel cm = destType.getColorModel();
1514         boolean alpha = cm.hasAlpha();
1515         ColorSpace cs = cm.getColorSpace();
1516         int retval = JPEG.JCS_UNKNOWN;
1517         switch (cs.getType()) {
1518         case ColorSpace.TYPE_GRAY:
1519                 retval = JPEG.JCS_GRAYSCALE;
1520                 break;
1521             case ColorSpace.TYPE_RGB:
1522                 retval = JPEG.JCS_RGB;
1523                 break;
1524             case ColorSpace.TYPE_YCbCr:
1525                 retval = JPEG.JCS_YCbCr;
1526                 break;
1527             case ColorSpace.TYPE_CMYK:
1528                 retval = JPEG.JCS_CMYK;
1529                 break;
1530             }
1531         return retval;
1532         }
1533 
1534     private int getDefaultDestCSType(ImageTypeSpecifier type) {
1535         return getDefaultDestCSType(type.getColorModel());
1536     }
1537 
1538     private int getDefaultDestCSType(RenderedImage rimage) {
1539         return getDefaultDestCSType(rimage.getColorModel());
1540     }
1541 
1542     private int getDefaultDestCSType(ColorModel cm) {
1543         int retval = JPEG.JCS_UNKNOWN;
1544         if (cm != null) {
1545             boolean alpha = cm.hasAlpha();
1546             ColorSpace cs = cm.getColorSpace();
1547             switch (cs.getType()) {
1548             case ColorSpace.TYPE_GRAY:
1549                 retval = JPEG.JCS_GRAYSCALE;
1550                 break;
1551             case ColorSpace.TYPE_RGB:
1552                 retval = JPEG.JCS_YCbCr;
1553                 break;
1554             case ColorSpace.TYPE_YCbCr:
1555                 retval = JPEG.JCS_YCbCr;
1556                 break;
1557             case ColorSpace.TYPE_CMYK:
1558                 retval = JPEG.JCS_YCCK;
1559                 break;
1560             }
1561         }
1562         return retval;
1563     }
1564 
1565     private boolean isSubsampled(SOFMarkerSegment.ComponentSpec [] specs) {
1566         int hsamp0 = specs[0].HsamplingFactor;
1567         int vsamp0 = specs[0].VsamplingFactor;
1568         for (int i = 1; i < specs.length; i++) {
1569             if ((specs[i].HsamplingFactor != hsamp0) ||
1570                 (specs[i].VsamplingFactor != vsamp0))
1571                 return true;
1572         }
1573         return false;
1574     }
1575 
1576     ////////////// End of ColorSpace conversion
1577 
1578     ////////////// Native methods and callbacks
1579 
1580     /** Sets up static native structures. */
1581     private static native void initWriterIDs(Class<?> qTableClass,
1582                                              Class<?> huffClass);
1583 
1584     /** Sets up per-writer native structure and returns a pointer to it. */
1585     private native long initJPEGImageWriter();
1586 
1587     /** Sets up native structures for output stream */
1588     private native void setDest(long structPointer);
1589 
1590     /**
1591      * Returns {@code true} if the write was aborted.
1592      */
1593     private native boolean writeImage(long structPointer,
1594                                       byte [] data,
1595                                       int inCsType, int outCsType,
1596                                       int numBands,
1597                                       int [] bandSizes,
1598                                       int srcWidth,
1599                                       int destWidth, int destHeight,
1600                                       int stepX, int stepY,
1601                                       JPEGQTable [] qtables,
1602                                       boolean writeDQT,
1603                                       JPEGHuffmanTable[] DCHuffmanTables,
1604                                       JPEGHuffmanTable[] ACHuffmanTables,
1605                                       boolean writeDHT,
1606                                       boolean optimizeHuffman,
1607                                       boolean progressive,
1608                                       int numScans,
1609                                       int [] scans,
1610                                       int [] componentIds,
1611                                       int [] HsamplingFactors,
1612                                       int [] VsamplingFactors,
1613                                       int [] QtableSelectors,
1614                                       boolean haveMetadata,
1615                                       int restartInterval);
1616 
1617 
1618     /**
1619      * Writes the metadata out when called by the native code,
1620      * which will have already written the header to the stream
1621      * and established the library state.  This is simpler than
1622      * breaking the write call in two.
1623      */
1624     private void writeMetadata() throws IOException {
1625         if (metadata == null) {
1626             if (writeDefaultJFIF) {
1627                 JFIFMarkerSegment.writeDefaultJFIF(ios,
1628                                                    thumbnails,
1629                                                    iccProfile,
1630                                                    this);
1631             }
1632             if (writeAdobe) {
1633                 AdobeMarkerSegment.writeAdobeSegment(ios, newAdobeTransform);
1634             }
1635         } else {
1636             metadata.writeToStream(ios,
1637                                    ignoreJFIF,
1638                                    forceJFIF,
1639                                    thumbnails,
1640                                    iccProfile,
1641                                    ignoreAdobe,
1642                                    newAdobeTransform,
1643                                    this);
1644         }
1645     }
1646 
1647     /**
1648      * Write out a tables-only image to the stream.
1649      */
1650     private native void writeTables(long structPointer,
1651                                     JPEGQTable [] qtables,
1652                                     JPEGHuffmanTable[] DCHuffmanTables,
1653                                     JPEGHuffmanTable[] ACHuffmanTables);
1654 
1655     /**
1656      * Put the scanline y of the source ROI view Raster into the
1657      * 1-line Raster for writing.  This handles ROI and band
1658      * rearrangements, and expands indexed images.  Subsampling is
1659      * done in the native code.
1660      * This is called by the native code.
1661      */
1662     private void grabPixels(int y) {
1663 
1664         Raster sourceLine = null;
1665         if (indexed) {
1666             sourceLine = srcRas.createChild(sourceXOffset,
1667                                             sourceYOffset+y,
1668                                             sourceWidth, 1,
1669                                             0, 0,
1670                                             new int [] {0});
1671             // If the image has BITMASK transparency, we need to make sure
1672             // it gets converted to 32-bit ARGB, because the JPEG encoder
1673             // relies upon the full 8-bit alpha channel.
1674             boolean forceARGB =
1675                 (indexCM.getTransparency() != Transparency.OPAQUE);
1676             BufferedImage temp = indexCM.convertToIntDiscrete(sourceLine,
1677                                                               forceARGB);
1678             sourceLine = temp.getRaster();
1679         } else {
1680             sourceLine = srcRas.createChild(sourceXOffset,
1681                                             sourceYOffset+y,
1682                                             sourceWidth, 1,
1683                                             0, 0,
1684                                             srcBands);
1685         }
1686         if (convertTosRGB) {
1687             if (debug) {
1688                 System.out.println("Converting to sRGB");
1689             }
1690             // The first time through, converted is null, so
1691             // a new raster is allocated.  It is then reused
1692             // on subsequent lines.
1693             converted = convertOp.filter(sourceLine, converted);
1694             sourceLine = converted;
1695         }
1696         if (isAlphaPremultiplied) {
1697             WritableRaster wr = sourceLine.createCompatibleWritableRaster();
1698             int[] data = null;
1699             data = sourceLine.getPixels(sourceLine.getMinX(), sourceLine.getMinY(),
1700                                         sourceLine.getWidth(), sourceLine.getHeight(),
1701                                         data);
1702             wr.setPixels(sourceLine.getMinX(), sourceLine.getMinY(),
1703                          sourceLine.getWidth(), sourceLine.getHeight(),
1704                          data);
1705             srcCM.coerceData(wr, false);
1706             sourceLine = wr.createChild(wr.getMinX(), wr.getMinY(),
1707                                         wr.getWidth(), wr.getHeight(),
1708                                         0, 0,
1709                                         srcBands);
1710         }
1711         raster.setRect(sourceLine);
1712         if ((y > 7) && (y%8 == 0)) {  // Every 8 scanlines
1713             cbLock.lock();
1714             try {
1715                 processImageProgress((float) y / (float) sourceHeight * 100.0F);
1716             } finally {
1717                 cbLock.unlock();
1718             }
1719         }
1720     }
1721 
1722     /** Aborts the current write in the native code */
1723     private native void abortWrite(long structPointer);
1724 
1725     /** Resets native structures */
1726     private native void resetWriter(long structPointer);
1727 
1728     /** Releases native structures */
1729     private static native void disposeWriter(long structPointer);
1730 
1731     private static class JPEGWriterDisposerRecord implements DisposerRecord {
1732         private long pData;
1733 
1734         public JPEGWriterDisposerRecord(long pData) {
1735             this.pData = pData;
1736         }
1737 
1738         public synchronized void dispose() {
1739             if (pData != 0) {
1740                 disposeWriter(pData);
1741                 pData = 0;
1742             }
1743         }
1744     }
1745 
1746     /**
1747      * This method is called from native code in order to write encoder
1748      * output to the destination.
1749      *
1750      * We block any attempt to change the writer state during this
1751      * method, in order to prevent a corruption of the native encoder
1752      * state.
1753      */
1754     private void writeOutputData(byte[] data, int offset, int len)
1755             throws IOException
1756     {
1757         cbLock.lock();
1758         try {
1759             ios.write(data, offset, len);
1760         } finally {
1761             cbLock.unlock();
1762         }
1763     }
1764 
1765     private Thread theThread = null;
1766     private int theLockCount = 0;
1767 
1768     private synchronized void setThreadLock() {
1769         Thread currThread = Thread.currentThread();
1770         if (theThread != null) {
1771             if (theThread != currThread) {
1772                 // it looks like that this reader instance is used
1773                 // by multiple threads.
1774                 throw new IllegalStateException("Attempt to use instance of " +
1775                                                 this + " locked on thread " +
1776                                                 theThread + " from thread " +
1777                                                 currThread);
1778             } else {
1779                 theLockCount ++;
1780             }
1781         } else {
1782             theThread = currThread;
1783             theLockCount = 1;
1784         }
1785     }
1786 
1787     private synchronized void clearThreadLock() {
1788         Thread currThread = Thread.currentThread();
1789         if (theThread == null || theThread != currThread) {
1790             throw new IllegalStateException("Attempt to clear thread lock form wrong thread. " +
1791                                             "Locked thread: " + theThread +
1792                                             "; current thread: " + currThread);
1793         }
1794         theLockCount --;
1795         if (theLockCount == 0) {
1796             theThread = null;
1797         }
1798     }
1799 
1800     private CallBackLock cbLock = new CallBackLock();
1801 
1802     private static class CallBackLock {
1803 
1804         private State lockState;
1805 
1806         CallBackLock() {
1807             lockState = State.Unlocked;
1808         }
1809 
1810         void check() {
1811             if (lockState != State.Unlocked) {
1812                 throw new IllegalStateException("Access to the writer is not allowed");
1813             }
1814         }
1815 
1816         private void lock() {
1817             lockState = State.Locked;
1818         }
1819 
1820         private void unlock() {
1821             lockState = State.Unlocked;
1822         }
1823 
1824         private static enum State {
1825             Unlocked,
1826             Locked
1827         }
1828     }
1829 }