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