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