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