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