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