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