1 /*
   2  * Copyright (c) 2000, 2019, 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         jdk.internal.access.SharedSecrets.getJavaLangAccess().loadLibrary("javajpeg");
 178         initWriterIDs(JPEGQTable.class,
 179                       JPEGHuffmanTable.class);
 180     }
 181 
 182     //////// Public API
 183 
 184     public JPEGImageWriter(ImageWriterSpi originator) {
 185         super(originator);
 186         structPointer = initJPEGImageWriter();
 187         disposerRecord = new JPEGWriterDisposerRecord(structPointer);
 188         Disposer.addRecord(disposerReferent, disposerRecord);
 189     }
 190 
 191     public void setOutput(Object output) {
 192         setThreadLock();
 193         try {
 194             cbLock.check();
 195 
 196             super.setOutput(output); // validates output
 197             resetInternalState();
 198             ios = (ImageOutputStream) output; // so this will always work
 199             // Set the native destination
 200             setDest(structPointer);
 201         } finally {
 202             clearThreadLock();
 203         }
 204     }
 205 
 206     public ImageWriteParam getDefaultWriteParam() {
 207         return new JPEGImageWriteParam(null);
 208     }
 209 
 210     public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
 211         setThreadLock();
 212         try {
 213             return new JPEGMetadata(param, this);
 214         } finally {
 215             clearThreadLock();
 216         }
 217     }
 218 
 219     public IIOMetadata
 220         getDefaultImageMetadata(ImageTypeSpecifier imageType,
 221                                 ImageWriteParam param) {
 222         setThreadLock();
 223         try {
 224             return new JPEGMetadata(imageType, param, this);
 225         } finally {
 226             clearThreadLock();
 227         }
 228     }
 229 
 230     public IIOMetadata convertStreamMetadata(IIOMetadata inData,
 231                                              ImageWriteParam param) {
 232         // There isn't much we can do.  If it's one of ours, then
 233         // return it.  Otherwise just return null.  We use it only
 234         // for tables, so we can't get a default and modify it,
 235         // as this will usually not be what is intended.
 236         if (inData instanceof JPEGMetadata) {
 237             JPEGMetadata jpegData = (JPEGMetadata) inData;
 238             if (jpegData.isStream) {
 239                 return inData;
 240             }
 241         }
 242         return null;
 243     }
 244 
 245     public IIOMetadata
 246         convertImageMetadata(IIOMetadata inData,
 247                              ImageTypeSpecifier imageType,
 248                              ImageWriteParam param) {
 249         setThreadLock();
 250         try {
 251             return convertImageMetadataOnThread(inData, imageType, param);
 252         } finally {
 253             clearThreadLock();
 254         }
 255     }
 256 
 257     private IIOMetadata
 258         convertImageMetadataOnThread(IIOMetadata inData,
 259                                      ImageTypeSpecifier imageType,
 260                                      ImageWriteParam param) {
 261         // If it's one of ours, just return it
 262         if (inData instanceof JPEGMetadata) {
 263             JPEGMetadata jpegData = (JPEGMetadata) inData;
 264             if (!jpegData.isStream) {
 265                 return inData;
 266             } else {
 267                 // Can't convert stream metadata to image metadata
 268                 // XXX Maybe this should put out a warning?
 269                 return null;
 270             }
 271         }
 272         // If it's not one of ours, create a default and set it from
 273         // the standard tree from the input, if it exists.
 274         if (inData.isStandardMetadataFormatSupported()) {
 275             String formatName =
 276                 IIOMetadataFormatImpl.standardMetadataFormatName;
 277             Node tree = inData.getAsTree(formatName);
 278             if (tree != null) {
 279                 JPEGMetadata jpegData = new JPEGMetadata(imageType,
 280                                                          param,
 281                                                          this);
 282                 try {
 283                     jpegData.setFromTree(formatName, tree);
 284                 } catch (IIOInvalidTreeException e) {
 285                     // Other plug-in generates bogus standard tree
 286                     // XXX Maybe this should put out a warning?
 287                     return null;
 288                 }
 289 
 290                 return jpegData;
 291             }
 292         }
 293         return null;
 294     }
 295 
 296     public int getNumThumbnailsSupported(ImageTypeSpecifier imageType,
 297                                          ImageWriteParam param,
 298                                          IIOMetadata streamMetadata,
 299                                          IIOMetadata imageMetadata) {
 300         // Check whether sufficient data is available.
 301         if (imageType == null && imageMetadata == null) {
 302             // The method has been invoked with insufficient data. Henceforth
 303             // we return -1 as recommended by ImageWriter specification.
 304             return -1;
 305         }
 306 
 307         // Check if the image type and metadata are JFIF compatible.
 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 (jfif != null) {
 752                                 outCsType = JPEG.JCS_YCbCr;
 753                                 if (JPEG.isNonStandardICC(cs)
 754                                     || ((cs instanceof ICC_ColorSpace)
 755                                         && (jfif.iccSegment != null))) {
 756                                     iccProfile =
 757                                         ((ICC_ColorSpace) cs).getProfile();
 758                                 }
 759                             } else if (adobe != null) {
 760                                 switch (adobe.transform) {
 761                                 case JPEG.ADOBE_UNKNOWN:
 762                                     outCsType = JPEG.JCS_RGB;
 763                                     break;
 764                                 case JPEG.ADOBE_YCC:
 765                                     outCsType = JPEG.JCS_YCbCr;
 766                                     break;
 767                                 default:
 768                                     warningOccurred
 769                                     (WARNING_IMAGE_METADATA_ADOBE_MISMATCH);
 770                                     newAdobeTransform = JPEG.ADOBE_UNKNOWN;
 771                                     outCsType = JPEG.JCS_RGB;
 772                                     break;
 773                                 }
 774                             } else {
 775                                 // consult the ids
 776                                 int outCS = sof.getIDencodedCSType();
 777                                 // if they don't resolve it,
 778                                 // consult the sampling factors
 779                                 if (outCS != JPEG.JCS_UNKNOWN) {
 780                                     outCsType = outCS;
 781                                 } else {
 782                                     boolean subsampled =
 783                                     isSubsampled(sof.componentSpecs);
 784                                     if (subsampled) {
 785                                         outCsType = JPEG.JCS_YCbCr;
 786                                     } else {
 787                                         outCsType = JPEG.JCS_RGB;
 788                                     }
 789                                 }
 790                             }
 791                             break;
 792                         }
 793                     }
 794                 } // else no dest, metadata, not an image.  Defaults ok
 795             }
 796         }
 797 
 798         boolean metadataProgressive = false;
 799         int [] scans = null;
 800 
 801         if (metadata != null) {
 802             if (sof == null) {
 803                 sof = (SOFMarkerSegment) metadata.findMarkerSegment
 804                     (SOFMarkerSegment.class, true);
 805             }
 806             if ((sof != null) && (sof.tag == JPEG.SOF2)) {
 807                 metadataProgressive = true;
 808                 if (progressiveMode == ImageWriteParam.MODE_COPY_FROM_METADATA) {
 809                     scans = collectScans(metadata, sof);  // Might still be null
 810                 } else {
 811                     numScans = 0;
 812                 }
 813             }
 814             if (jfif == null) {
 815                 jfif = (JFIFMarkerSegment) metadata.findMarkerSegment
 816                     (JFIFMarkerSegment.class, true);
 817             }
 818         }
 819 
 820         thumbnails = image.getThumbnails();
 821         int numThumbs = image.getNumThumbnails();
 822         forceJFIF = false;
 823         // determine if thumbnails can be written
 824         // If we are going to add a default JFIF marker segment,
 825         // then thumbnails can be written
 826         if (!writeDefaultJFIF) {
 827             // If there is no metadata, then we can't write thumbnails
 828             if (metadata == null) {
 829                 thumbnails = null;
 830                 if (numThumbs != 0) {
 831                     warningOccurred(WARNING_IGNORING_THUMBS);
 832                 }
 833             } else {
 834                 // There is metadata
 835                 // If we are writing a raster or subbands,
 836                 // then the user must specify JFIF on the metadata
 837                 if (fullImage == false) {
 838                     if (jfif == null) {
 839                         thumbnails = null;  // Or we can't include thumbnails
 840                         if (numThumbs != 0) {
 841                             warningOccurred(WARNING_IGNORING_THUMBS);
 842                         }
 843                     }
 844                 } else {  // It is a full image, and there is metadata
 845                     if (jfif == null) {  // Not JFIF
 846                         // Can it have JFIF?
 847                         if ((outCsType == JPEG.JCS_GRAYSCALE)
 848                             || (outCsType == JPEG.JCS_YCbCr)) {
 849                             if (numThumbs != 0) {
 850                                 forceJFIF = true;
 851                                 warningOccurred(WARNING_FORCING_JFIF);
 852                             }
 853                         } else {  // Nope, not JFIF-compatible
 854                             thumbnails = null;
 855                             if (numThumbs != 0) {
 856                                 warningOccurred(WARNING_IGNORING_THUMBS);
 857                             }
 858                         }
 859                     }
 860                 }
 861             }
 862         }
 863 
 864         // Set up a boolean to indicate whether we need to call back to
 865         // write metadata
 866         boolean haveMetadata =
 867             ((metadata != null) || writeDefaultJFIF || writeAdobe);
 868 
 869         // Now that we have dealt with metadata, finalize our tables set up
 870 
 871         // Are we going to write tables?  By default, yes.
 872         boolean writeDQT = true;
 873         boolean writeDHT = true;
 874 
 875         // But if the metadata has no tables, no.
 876         DQTMarkerSegment dqt = null;
 877         DHTMarkerSegment dht = null;
 878 
 879         int restartInterval = 0;
 880 
 881         if (metadata != null) {
 882             dqt = (DQTMarkerSegment) metadata.findMarkerSegment
 883                 (DQTMarkerSegment.class, true);
 884             dht = (DHTMarkerSegment) metadata.findMarkerSegment
 885                 (DHTMarkerSegment.class, true);
 886             DRIMarkerSegment dri =
 887                 (DRIMarkerSegment) metadata.findMarkerSegment
 888                 (DRIMarkerSegment.class, true);
 889             if (dri != null) {
 890                 restartInterval = dri.restartInterval;
 891             }
 892 
 893             if (dqt == null) {
 894                 writeDQT = false;
 895             }
 896             if (dht == null) {
 897                 writeDHT = false;  // Ignored if optimizeHuffman is true
 898             }
 899         }
 900 
 901         // Whether we write tables or not, we need to figure out which ones
 902         // to use
 903         if (qTables == null) { // Get them from metadata, or use defaults
 904             if (dqt != null) {
 905                 qTables = collectQTablesFromMetadata(metadata);
 906             } else if (streamQTables != null) {
 907                 qTables = streamQTables;
 908             } else if ((jparam != null) && (jparam.areTablesSet())) {
 909                 qTables = jparam.getQTables();
 910             } else {
 911                 qTables = JPEG.getDefaultQTables();
 912             }
 913 
 914         }
 915 
 916         // If we are optimizing, we don't want any tables.
 917         if (optimizeHuffman == false) {
 918             // If they were for progressive scans, we can't use them.
 919             if ((dht != null) && (metadataProgressive == false)) {
 920                 DCHuffmanTables = collectHTablesFromMetadata(metadata, true);
 921                 ACHuffmanTables = collectHTablesFromMetadata(metadata, false);
 922             } else if (streamDCHuffmanTables != null) {
 923                 DCHuffmanTables = streamDCHuffmanTables;
 924                 ACHuffmanTables = streamACHuffmanTables;
 925             } else if ((jparam != null) && (jparam.areTablesSet())) {
 926                 DCHuffmanTables = jparam.getDCHuffmanTables();
 927                 ACHuffmanTables = jparam.getACHuffmanTables();
 928             } else {
 929                 DCHuffmanTables = JPEG.getDefaultHuffmanTables(true);
 930                 ACHuffmanTables = JPEG.getDefaultHuffmanTables(false);
 931             }
 932         }
 933 
 934         // By default, ids are 1 - N, no subsampling
 935         int [] componentIds = new int[numBandsUsed];
 936         int [] HsamplingFactors = new int[numBandsUsed];
 937         int [] VsamplingFactors = new int[numBandsUsed];
 938         int [] QtableSelectors = new int[numBandsUsed];
 939         for (int i = 0; i < numBandsUsed; i++) {
 940             componentIds[i] = i+1; // JFIF compatible
 941             HsamplingFactors[i] = 1;
 942             VsamplingFactors[i] = 1;
 943             QtableSelectors[i] = 0;
 944         }
 945 
 946         // Now override them with the contents of sof, if there is one,
 947         if (sof != null) {
 948             for (int i = 0; i < numBandsUsed; i++) {
 949                 if (forceJFIF == false) {  // else use JFIF-compatible default
 950                     componentIds[i] = sof.componentSpecs[i].componentId;
 951                 }
 952                 HsamplingFactors[i] = sof.componentSpecs[i].HsamplingFactor;
 953                 VsamplingFactors[i] = sof.componentSpecs[i].VsamplingFactor;
 954                 QtableSelectors[i] = sof.componentSpecs[i].QtableSelector;
 955             }
 956         }
 957 
 958         sourceXOffset += gridX;
 959         sourceWidth -= gridX;
 960         sourceYOffset += gridY;
 961         sourceHeight -= gridY;
 962 
 963         int destWidth = (sourceWidth + periodX - 1)/periodX;
 964         int destHeight = (sourceHeight + periodY - 1)/periodY;
 965 
 966         // Create an appropriate 1-line databuffer for writing
 967         int lineSize = sourceWidth*numBandsUsed;
 968 
 969         DataBufferByte buffer = new DataBufferByte(lineSize);
 970 
 971         // Create a raster from that
 972         int [] bandOffs = JPEG.bandOffsets[numBandsUsed-1];
 973 
 974         raster = Raster.createInterleavedRaster(buffer,
 975                                                 sourceWidth, 1,
 976                                                 lineSize,
 977                                                 numBandsUsed,
 978                                                 bandOffs,
 979                                                 null);
 980 
 981         // Call the writer, who will call back for every scanline
 982 
 983         clearAbortRequest();
 984         cbLock.lock();
 985         try {
 986             processImageStarted(currentImage);
 987         } finally {
 988             cbLock.unlock();
 989         }
 990 
 991         boolean aborted = false;
 992 
 993         if (debug) {
 994             System.out.println("inCsType: " + inCsType);
 995             System.out.println("outCsType: " + outCsType);
 996         }
 997 
 998         // Note that getData disables acceleration on buffer, but it is
 999         // just a 1-line intermediate data transfer buffer that does not
1000         // affect the acceleration of the source image.
1001         aborted = writeImage(structPointer,
1002                              buffer.getData(),
1003                              inCsType, outCsType,
1004                              numBandsUsed,
1005                              bandSizes,
1006                              sourceWidth,
1007                              destWidth, destHeight,
1008                              periodX, periodY,
1009                              qTables,
1010                              writeDQT,
1011                              DCHuffmanTables,
1012                              ACHuffmanTables,
1013                              writeDHT,
1014                              optimizeHuffman,
1015                              (progressiveMode
1016                               != ImageWriteParam.MODE_DISABLED),
1017                              numScans,
1018                              scans,
1019                              componentIds,
1020                              HsamplingFactors,
1021                              VsamplingFactors,
1022                              QtableSelectors,
1023                              haveMetadata,
1024                              restartInterval);
1025 
1026         cbLock.lock();
1027         try {
1028             if (aborted) {
1029                 processWriteAborted();
1030             } else {
1031                 processImageComplete();
1032             }
1033 
1034             ios.flush();
1035         } finally {
1036             cbLock.unlock();
1037         }
1038         currentImage++;  // After a successful write
1039     }
1040 
1041     @Override
1042     public boolean canWriteSequence() {
1043         return true;
1044     }
1045 
1046     public void prepareWriteSequence(IIOMetadata streamMetadata)
1047         throws IOException {
1048         setThreadLock();
1049         try {
1050             cbLock.check();
1051 
1052             prepareWriteSequenceOnThread(streamMetadata);
1053         } finally {
1054             clearThreadLock();
1055         }
1056     }
1057 
1058     private void prepareWriteSequenceOnThread(IIOMetadata streamMetadata)
1059         throws IOException {
1060         if (ios == null) {
1061             throw new IllegalStateException("Output has not been set!");
1062         }
1063 
1064         /*
1065          * from jpeg_metadata.html:
1066          * If no stream metadata is supplied to
1067          * {@code ImageWriter.prepareWriteSequence}, then no
1068          * tables-only image is written.  If stream metadata containing
1069          * no tables is supplied to
1070          * {@code ImageWriter.prepareWriteSequence}, then a tables-only
1071          * image containing default visually lossless tables is written.
1072          */
1073         if (streamMetadata != null) {
1074             if (streamMetadata instanceof JPEGMetadata) {
1075                 // write a complete tables-only image at the beginning of
1076                 // the stream.
1077                 JPEGMetadata jmeta = (JPEGMetadata) streamMetadata;
1078                 if (jmeta.isStream == false) {
1079                     throw new IllegalArgumentException
1080                         ("Invalid stream metadata object.");
1081                 }
1082                 // Check that we are
1083                 // at the beginning of the stream, or can go there, and haven't
1084                 // written out the metadata already.
1085                 if (currentImage != 0) {
1086                     throw new IIOException
1087                         ("JPEG Stream metadata must precede all images");
1088                 }
1089                 if (sequencePrepared == true) {
1090                     throw new IIOException("Stream metadata already written!");
1091                 }
1092 
1093                 // Set the tables
1094                 // If the metadata has no tables, use default tables.
1095                 streamQTables = collectQTablesFromMetadata(jmeta);
1096                 if (debug) {
1097                     System.out.println("after collecting from stream metadata, "
1098                                        + "streamQTables.length is "
1099                                        + streamQTables.length);
1100                 }
1101                 if (streamQTables == null) {
1102                     streamQTables = JPEG.getDefaultQTables();
1103                 }
1104                 streamDCHuffmanTables =
1105                     collectHTablesFromMetadata(jmeta, true);
1106                 if (streamDCHuffmanTables == null) {
1107                     streamDCHuffmanTables = JPEG.getDefaultHuffmanTables(true);
1108                 }
1109                 streamACHuffmanTables =
1110                     collectHTablesFromMetadata(jmeta, false);
1111                 if (streamACHuffmanTables == null) {
1112                     streamACHuffmanTables = JPEG.getDefaultHuffmanTables(false);
1113                 }
1114 
1115                 // Now write them out
1116                 writeTables(structPointer,
1117                             streamQTables,
1118                             streamDCHuffmanTables,
1119                             streamACHuffmanTables);
1120             } else {
1121                 throw new IIOException("Stream metadata must be JPEG metadata");
1122             }
1123         }
1124         sequencePrepared = true;
1125     }
1126 
1127     public void writeToSequence(IIOImage image, ImageWriteParam param)
1128         throws IOException {
1129         setThreadLock();
1130         try {
1131             cbLock.check();
1132 
1133             if (sequencePrepared == false) {
1134                 throw new IllegalStateException("sequencePrepared not called!");
1135             }
1136             // In the case of JPEG this does nothing different from write
1137             write(null, image, param);
1138         } finally {
1139             clearThreadLock();
1140         }
1141     }
1142 
1143     public void endWriteSequence() throws IOException {
1144         setThreadLock();
1145         try {
1146             cbLock.check();
1147 
1148             if (sequencePrepared == false) {
1149                 throw new IllegalStateException("sequencePrepared not called!");
1150             }
1151             sequencePrepared = false;
1152         } finally {
1153             clearThreadLock();
1154         }
1155     }
1156 
1157     public synchronized void abort() {
1158         setThreadLock();
1159         try {
1160             /**
1161              * NB: we do not check the call back lock here, we allow to abort
1162              * the reader any time.
1163              */
1164             super.abort();
1165             abortWrite(structPointer);
1166         } finally {
1167             clearThreadLock();
1168         }
1169     }
1170 
1171     @Override
1172     protected synchronized void clearAbortRequest() {
1173         setThreadLock();
1174         try {
1175             cbLock.check();
1176             if (abortRequested()) {
1177                 super.clearAbortRequest();
1178                 // reset C structures
1179                 resetWriter(structPointer);
1180                 // reset the native destination
1181                 setDest(structPointer);
1182             }
1183         } finally {
1184             clearThreadLock();
1185         }
1186     }
1187 
1188     private void resetInternalState() {
1189         // reset C structures
1190         resetWriter(structPointer);
1191 
1192         // reset local Java structures
1193         srcRas = null;
1194         raster = null;
1195         convertTosRGB = false;
1196         currentImage = 0;
1197         numScans = 0;
1198         metadata = null;
1199     }
1200 
1201     public void reset() {
1202         setThreadLock();
1203         try {
1204             cbLock.check();
1205 
1206             super.reset();
1207         } finally {
1208             clearThreadLock();
1209         }
1210     }
1211 
1212     public void dispose() {
1213         setThreadLock();
1214         try {
1215             cbLock.check();
1216 
1217             if (structPointer != 0) {
1218                 disposerRecord.dispose();
1219                 structPointer = 0;
1220             }
1221         } finally {
1222             clearThreadLock();
1223         }
1224     }
1225 
1226     ////////// End of public API
1227 
1228     ///////// Package-access API
1229 
1230     /**
1231      * Called by the native code or other classes to signal a warning.
1232      * The code is used to lookup a localized message to be used when
1233      * sending warnings to listeners.
1234      */
1235     void warningOccurred(int code) {
1236         cbLock.lock();
1237         try {
1238             if ((code < 0) || (code > MAX_WARNING)){
1239                 throw new InternalError("Invalid warning index");
1240             }
1241             processWarningOccurred
1242                 (currentImage,
1243                  "com.sun.imageio.plugins.jpeg.JPEGImageWriterResources",
1244                 Integer.toString(code));
1245         } finally {
1246             cbLock.unlock();
1247         }
1248     }
1249 
1250     /**
1251      * The library has it's own error facility that emits warning messages.
1252      * This routine is called by the native code when it has already
1253      * formatted a string for output.
1254      * XXX  For truly complete localization of all warning messages,
1255      * the sun_jpeg_output_message routine in the native code should
1256      * send only the codes and parameters to a method here in Java,
1257      * which will then format and send the warnings, using localized
1258      * strings.  This method will have to deal with all the parameters
1259      * and formats (%u with possibly large numbers, %02d, %02x, etc.)
1260      * that actually occur in the JPEG library.  For now, this prevents
1261      * library warnings from being printed to stderr.
1262      */
1263     void warningWithMessage(String msg) {
1264         cbLock.lock();
1265         try {
1266             processWarningOccurred(currentImage, msg);
1267         } finally {
1268             cbLock.unlock();
1269         }
1270     }
1271 
1272     void thumbnailStarted(int thumbnailIndex) {
1273         cbLock.lock();
1274         try {
1275             processThumbnailStarted(currentImage, thumbnailIndex);
1276         } finally {
1277             cbLock.unlock();
1278         }
1279     }
1280 
1281     // Provide access to protected superclass method
1282     void thumbnailProgress(float percentageDone) {
1283         cbLock.lock();
1284         try {
1285             processThumbnailProgress(percentageDone);
1286         } finally {
1287             cbLock.unlock();
1288         }
1289     }
1290 
1291     // Provide access to protected superclass method
1292     void thumbnailComplete() {
1293         cbLock.lock();
1294         try {
1295             processThumbnailComplete();
1296         } finally {
1297             cbLock.unlock();
1298         }
1299     }
1300 
1301     ///////// End of Package-access API
1302 
1303     ///////// Private methods
1304 
1305     ///////// Metadata handling
1306 
1307     private void checkSOFBands(SOFMarkerSegment sof, int numBandsUsed)
1308         throws IIOException {
1309         // Does the metadata frame header, if any, match numBandsUsed?
1310         if (sof != null) {
1311             if (sof.componentSpecs.length != numBandsUsed) {
1312                 throw new IIOException
1313                     ("Metadata components != number of destination bands");
1314             }
1315         }
1316     }
1317 
1318     private void checkJFIF(JFIFMarkerSegment jfif,
1319                            ImageTypeSpecifier type,
1320                            boolean input) {
1321         if (jfif != null) {
1322             if (!JPEG.isJFIFcompliant(type, input)) {
1323                 ignoreJFIF = true;  // type overrides metadata
1324                 warningOccurred(input
1325                                 ? WARNING_IMAGE_METADATA_JFIF_MISMATCH
1326                                 : WARNING_DEST_METADATA_JFIF_MISMATCH);
1327             }
1328         }
1329     }
1330 
1331     private void checkAdobe(AdobeMarkerSegment adobe,
1332                            ImageTypeSpecifier type,
1333                            boolean input) {
1334         if (adobe != null) {
1335             int rightTransform = JPEG.transformForType(type, input);
1336             if (adobe.transform != rightTransform) {
1337                 warningOccurred(input
1338                                 ? WARNING_IMAGE_METADATA_ADOBE_MISMATCH
1339                                 : WARNING_DEST_METADATA_ADOBE_MISMATCH);
1340                 if (rightTransform == JPEG.ADOBE_IMPOSSIBLE) {
1341                     ignoreAdobe = true;
1342                 } else {
1343                     newAdobeTransform = rightTransform;
1344                 }
1345             }
1346         }
1347     }
1348 
1349     /**
1350      * Collect all the scan info from the given metadata, and
1351      * organize it into the scan info array required by the
1352      * IJG libray.  It is much simpler to parse out this
1353      * data in Java and then just copy the data in C.
1354      */
1355     private int [] collectScans(JPEGMetadata metadata,
1356                                 SOFMarkerSegment sof) {
1357         List<SOSMarkerSegment> segments = new ArrayList<>();
1358         int SCAN_SIZE = 9;
1359         int MAX_COMPS_PER_SCAN = 4;
1360         for (Iterator<MarkerSegment> iter = metadata.markerSequence.iterator();
1361              iter.hasNext();) {
1362             MarkerSegment seg = iter.next();
1363             if (seg instanceof SOSMarkerSegment) {
1364                 segments.add((SOSMarkerSegment) seg);
1365             }
1366         }
1367         int [] retval = null;
1368         numScans = 0;
1369         if (!segments.isEmpty()) {
1370             numScans = segments.size();
1371             retval = new int [numScans*SCAN_SIZE];
1372             int index = 0;
1373             for (int i = 0; i < numScans; i++) {
1374                 SOSMarkerSegment sos = segments.get(i);
1375                 retval[index++] = sos.componentSpecs.length; // num comps
1376                 for (int j = 0; j < MAX_COMPS_PER_SCAN; j++) {
1377                     if (j < sos.componentSpecs.length) {
1378                         int compSel = sos.componentSpecs[j].componentSelector;
1379                         for (int k = 0; k < sof.componentSpecs.length; k++) {
1380                             if (compSel == sof.componentSpecs[k].componentId) {
1381                                 retval[index++] = k;
1382                                 break; // out of for over sof comps
1383                             }
1384                         }
1385                     } else {
1386                         retval[index++] = 0;
1387                     }
1388                 }
1389                 retval[index++] = sos.startSpectralSelection;
1390                 retval[index++] = sos.endSpectralSelection;
1391                 retval[index++] = sos.approxHigh;
1392                 retval[index++] = sos.approxLow;
1393             }
1394         }
1395         return retval;
1396     }
1397 
1398     /**
1399      * Finds all DQT marker segments and returns all the q
1400      * tables as a single array of JPEGQTables.
1401      */
1402     private JPEGQTable [] collectQTablesFromMetadata
1403         (JPEGMetadata metadata) {
1404         ArrayList<DQTMarkerSegment.Qtable> tables = new ArrayList<>();
1405         Iterator<MarkerSegment> iter = metadata.markerSequence.iterator();
1406         while (iter.hasNext()) {
1407             MarkerSegment seg = iter.next();
1408             if (seg instanceof DQTMarkerSegment) {
1409                 DQTMarkerSegment dqt =
1410                     (DQTMarkerSegment) seg;
1411                 tables.addAll(dqt.tables);
1412             }
1413         }
1414         JPEGQTable [] retval = null;
1415         if (tables.size() != 0) {
1416             retval = new JPEGQTable[tables.size()];
1417             for (int i = 0; i < retval.length; i++) {
1418                 retval[i] =
1419                     new JPEGQTable(tables.get(i).data);
1420             }
1421         }
1422         return retval;
1423     }
1424 
1425     /**
1426      * Finds all DHT marker segments and returns all the q
1427      * tables as a single array of JPEGQTables.  The metadata
1428      * must not be for a progressive image, or an exception
1429      * will be thrown when two Huffman tables with the same
1430      * table id are encountered.
1431      */
1432     private JPEGHuffmanTable[] collectHTablesFromMetadata
1433         (JPEGMetadata metadata, boolean wantDC) throws IIOException {
1434         ArrayList<DHTMarkerSegment.Htable> tables = new ArrayList<>();
1435         Iterator<MarkerSegment> iter = metadata.markerSequence.iterator();
1436         while (iter.hasNext()) {
1437             MarkerSegment seg = iter.next();
1438             if (seg instanceof DHTMarkerSegment) {
1439                 DHTMarkerSegment dht = (DHTMarkerSegment) seg;
1440                 for (int i = 0; i < dht.tables.size(); i++) {
1441                     DHTMarkerSegment.Htable htable = dht.tables.get(i);
1442                     if (htable.tableClass == (wantDC ? 0 : 1)) {
1443                         tables.add(htable);
1444                     }
1445                 }
1446             }
1447         }
1448         JPEGHuffmanTable [] retval = null;
1449         if (tables.size() != 0) {
1450             DHTMarkerSegment.Htable [] htables =
1451                 new DHTMarkerSegment.Htable[tables.size()];
1452             tables.toArray(htables);
1453             retval = new JPEGHuffmanTable[tables.size()];
1454             for (int i = 0; i < retval.length; i++) {
1455                 retval[i] = null;
1456                 for (int j = 0; j < tables.size(); j++) {
1457                     if (htables[j].tableID == i) {
1458                         if (retval[i] != null) {
1459                             throw new IIOException("Metadata has duplicate Htables!");
1460                         }
1461                         retval[i] = new JPEGHuffmanTable(htables[j].numCodes,
1462                                                          htables[j].values);
1463                     }
1464                 }
1465             }
1466         }
1467 
1468         return retval;
1469     }
1470 
1471     /////////// End of metadata handling
1472 
1473     ////////////// ColorSpace conversion
1474 
1475     private int getSrcCSType(ImageTypeSpecifier type) {
1476          return getSrcCSType(type.getColorModel());
1477     }
1478 
1479     private int getSrcCSType(RenderedImage rimage) {
1480         return getSrcCSType(rimage.getColorModel());
1481     }
1482 
1483     private int getSrcCSType(ColorModel cm) {
1484         int retval = JPEG.JCS_UNKNOWN;
1485         if (cm != null) {
1486             boolean alpha = cm.hasAlpha();
1487             ColorSpace cs = cm.getColorSpace();
1488             switch (cs.getType()) {
1489             case ColorSpace.TYPE_GRAY:
1490                 retval = JPEG.JCS_GRAYSCALE;
1491                 break;
1492             case ColorSpace.TYPE_RGB:
1493                 retval = JPEG.JCS_RGB;
1494                 break;
1495             case ColorSpace.TYPE_YCbCr:
1496                 retval = JPEG.JCS_YCbCr;
1497                 break;
1498             case ColorSpace.TYPE_CMYK:
1499                 retval = JPEG.JCS_CMYK;
1500                 break;
1501             }
1502         }
1503         return retval;
1504     }
1505 
1506     private int getDestCSType(ImageTypeSpecifier destType) {
1507         ColorModel cm = destType.getColorModel();
1508         boolean alpha = cm.hasAlpha();
1509         ColorSpace cs = cm.getColorSpace();
1510         int retval = JPEG.JCS_UNKNOWN;
1511         switch (cs.getType()) {
1512         case ColorSpace.TYPE_GRAY:
1513                 retval = JPEG.JCS_GRAYSCALE;
1514                 break;
1515             case ColorSpace.TYPE_RGB:
1516                 retval = JPEG.JCS_RGB;
1517                 break;
1518             case ColorSpace.TYPE_YCbCr:
1519                 retval = JPEG.JCS_YCbCr;
1520                 break;
1521             case ColorSpace.TYPE_CMYK:
1522                 retval = JPEG.JCS_CMYK;
1523                 break;
1524             }
1525         return retval;
1526         }
1527 
1528     private int getDefaultDestCSType(ImageTypeSpecifier type) {
1529         return getDefaultDestCSType(type.getColorModel());
1530     }
1531 
1532     private int getDefaultDestCSType(RenderedImage rimage) {
1533         return getDefaultDestCSType(rimage.getColorModel());
1534     }
1535 
1536     private int getDefaultDestCSType(ColorModel cm) {
1537         int retval = JPEG.JCS_UNKNOWN;
1538         if (cm != null) {
1539             boolean alpha = cm.hasAlpha();
1540             ColorSpace cs = cm.getColorSpace();
1541             switch (cs.getType()) {
1542             case ColorSpace.TYPE_GRAY:
1543                 retval = JPEG.JCS_GRAYSCALE;
1544                 break;
1545             case ColorSpace.TYPE_RGB:
1546                 retval = JPEG.JCS_YCbCr;
1547                 break;
1548             case ColorSpace.TYPE_YCbCr:
1549                 retval = JPEG.JCS_YCbCr;
1550                 break;
1551             case ColorSpace.TYPE_CMYK:
1552                 retval = JPEG.JCS_YCCK;
1553                 break;
1554             }
1555         }
1556         return retval;
1557     }
1558 
1559     private boolean isSubsampled(SOFMarkerSegment.ComponentSpec [] specs) {
1560         int hsamp0 = specs[0].HsamplingFactor;
1561         int vsamp0 = specs[0].VsamplingFactor;
1562         for (int i = 1; i < specs.length; i++) {
1563             if ((specs[i].HsamplingFactor != hsamp0) ||
1564                 (specs[i].VsamplingFactor != vsamp0))
1565                 return true;
1566         }
1567         return false;
1568     }
1569 
1570     ////////////// End of ColorSpace conversion
1571 
1572     ////////////// Native methods and callbacks
1573 
1574     /** Sets up static native structures. */
1575     private static native void initWriterIDs(Class<?> qTableClass,
1576                                              Class<?> huffClass);
1577 
1578     /** Sets up per-writer native structure and returns a pointer to it. */
1579     private native long initJPEGImageWriter();
1580 
1581     /** Sets up native structures for output stream */
1582     private native void setDest(long structPointer);
1583 
1584     /**
1585      * Returns {@code true} if the write was aborted.
1586      */
1587     private native boolean writeImage(long structPointer,
1588                                       byte [] data,
1589                                       int inCsType, int outCsType,
1590                                       int numBands,
1591                                       int [] bandSizes,
1592                                       int srcWidth,
1593                                       int destWidth, int destHeight,
1594                                       int stepX, int stepY,
1595                                       JPEGQTable [] qtables,
1596                                       boolean writeDQT,
1597                                       JPEGHuffmanTable[] DCHuffmanTables,
1598                                       JPEGHuffmanTable[] ACHuffmanTables,
1599                                       boolean writeDHT,
1600                                       boolean optimizeHuffman,
1601                                       boolean progressive,
1602                                       int numScans,
1603                                       int [] scans,
1604                                       int [] componentIds,
1605                                       int [] HsamplingFactors,
1606                                       int [] VsamplingFactors,
1607                                       int [] QtableSelectors,
1608                                       boolean haveMetadata,
1609                                       int restartInterval);
1610 
1611 
1612     /**
1613      * Writes the metadata out when called by the native code,
1614      * which will have already written the header to the stream
1615      * and established the library state.  This is simpler than
1616      * breaking the write call in two.
1617      */
1618     private void writeMetadata() throws IOException {
1619         if (metadata == null) {
1620             if (writeDefaultJFIF) {
1621                 JFIFMarkerSegment.writeDefaultJFIF(ios,
1622                                                    thumbnails,
1623                                                    iccProfile,
1624                                                    this);
1625             }
1626             if (writeAdobe) {
1627                 AdobeMarkerSegment.writeAdobeSegment(ios, newAdobeTransform);
1628             }
1629         } else {
1630             metadata.writeToStream(ios,
1631                                    ignoreJFIF,
1632                                    forceJFIF,
1633                                    thumbnails,
1634                                    iccProfile,
1635                                    ignoreAdobe,
1636                                    newAdobeTransform,
1637                                    this);
1638         }
1639     }
1640 
1641     /**
1642      * Write out a tables-only image to the stream.
1643      */
1644     private native void writeTables(long structPointer,
1645                                     JPEGQTable [] qtables,
1646                                     JPEGHuffmanTable[] DCHuffmanTables,
1647                                     JPEGHuffmanTable[] ACHuffmanTables);
1648 
1649     /**
1650      * Put the scanline y of the source ROI view Raster into the
1651      * 1-line Raster for writing.  This handles ROI and band
1652      * rearrangements, and expands indexed images.  Subsampling is
1653      * done in the native code.
1654      * This is called by the native code.
1655      */
1656     private void grabPixels(int y) {
1657 
1658         Raster sourceLine = null;
1659         if (indexed) {
1660             sourceLine = srcRas.createChild(sourceXOffset,
1661                                             sourceYOffset+y,
1662                                             sourceWidth, 1,
1663                                             0, 0,
1664                                             new int [] {0});
1665             // If the image has BITMASK transparency, we need to make sure
1666             // it gets converted to 32-bit ARGB, because the JPEG encoder
1667             // relies upon the full 8-bit alpha channel.
1668             boolean forceARGB =
1669                 (indexCM.getTransparency() != Transparency.OPAQUE);
1670             BufferedImage temp = indexCM.convertToIntDiscrete(sourceLine,
1671                                                               forceARGB);
1672             sourceLine = temp.getRaster();
1673         } else {
1674             sourceLine = srcRas.createChild(sourceXOffset,
1675                                             sourceYOffset+y,
1676                                             sourceWidth, 1,
1677                                             0, 0,
1678                                             srcBands);
1679         }
1680         if (convertTosRGB) {
1681             if (debug) {
1682                 System.out.println("Converting to sRGB");
1683             }
1684             // The first time through, converted is null, so
1685             // a new raster is allocated.  It is then reused
1686             // on subsequent lines.
1687             converted = convertOp.filter(sourceLine, converted);
1688             sourceLine = converted;
1689         }
1690         if (isAlphaPremultiplied) {
1691             WritableRaster wr = sourceLine.createCompatibleWritableRaster();
1692             int[] data = null;
1693             data = sourceLine.getPixels(sourceLine.getMinX(), sourceLine.getMinY(),
1694                                         sourceLine.getWidth(), sourceLine.getHeight(),
1695                                         data);
1696             wr.setPixels(sourceLine.getMinX(), sourceLine.getMinY(),
1697                          sourceLine.getWidth(), sourceLine.getHeight(),
1698                          data);
1699             srcCM.coerceData(wr, false);
1700             sourceLine = wr.createChild(wr.getMinX(), wr.getMinY(),
1701                                         wr.getWidth(), wr.getHeight(),
1702                                         0, 0,
1703                                         srcBands);
1704         }
1705         raster.setRect(sourceLine);
1706         if ((y > 7) && (y%8 == 0)) {  // Every 8 scanlines
1707             cbLock.lock();
1708             try {
1709                 processImageProgress((float) y / (float) sourceHeight * 100.0F);
1710             } finally {
1711                 cbLock.unlock();
1712             }
1713         }
1714     }
1715 
1716     /** Aborts the current write in the native code */
1717     private native void abortWrite(long structPointer);
1718 
1719     /** Resets native structures */
1720     private native void resetWriter(long structPointer);
1721 
1722     /** Releases native structures */
1723     private static native void disposeWriter(long structPointer);
1724 
1725     private static class JPEGWriterDisposerRecord implements DisposerRecord {
1726         private long pData;
1727 
1728         public JPEGWriterDisposerRecord(long pData) {
1729             this.pData = pData;
1730         }
1731 
1732         public synchronized void dispose() {
1733             if (pData != 0) {
1734                 disposeWriter(pData);
1735                 pData = 0;
1736             }
1737         }
1738     }
1739 
1740     /**
1741      * This method is called from native code in order to write encoder
1742      * output to the destination.
1743      *
1744      * We block any attempt to change the writer state during this
1745      * method, in order to prevent a corruption of the native encoder
1746      * state.
1747      */
1748     private void writeOutputData(byte[] data, int offset, int len)
1749             throws IOException
1750     {
1751         cbLock.lock();
1752         try {
1753             ios.write(data, offset, len);
1754         } finally {
1755             cbLock.unlock();
1756         }
1757     }
1758 
1759     private Thread theThread = null;
1760     private int theLockCount = 0;
1761 
1762     private synchronized void setThreadLock() {
1763         Thread currThread = Thread.currentThread();
1764         if (theThread != null) {
1765             if (theThread != currThread) {
1766                 // it looks like that this reader instance is used
1767                 // by multiple threads.
1768                 throw new IllegalStateException("Attempt to use instance of " +
1769                                                 this + " locked on thread " +
1770                                                 theThread + " from thread " +
1771                                                 currThread);
1772             } else {
1773                 theLockCount ++;
1774             }
1775         } else {
1776             theThread = currThread;
1777             theLockCount = 1;
1778         }
1779     }
1780 
1781     private synchronized void clearThreadLock() {
1782         Thread currThread = Thread.currentThread();
1783         if (theThread == null || theThread != currThread) {
1784             throw new IllegalStateException("Attempt to clear thread lock form wrong thread. " +
1785                                             "Locked thread: " + theThread +
1786                                             "; current thread: " + currThread);
1787         }
1788         theLockCount --;
1789         if (theLockCount == 0) {
1790             theThread = null;
1791         }
1792     }
1793 
1794     private CallBackLock cbLock = new CallBackLock();
1795 
1796     private static class CallBackLock {
1797 
1798         private State lockState;
1799 
1800         CallBackLock() {
1801             lockState = State.Unlocked;
1802         }
1803 
1804         void check() {
1805             if (lockState != State.Unlocked) {
1806                 throw new IllegalStateException("Access to the writer is not allowed");
1807             }
1808         }
1809 
1810         private void lock() {
1811             lockState = State.Locked;
1812         }
1813 
1814         private void unlock() {
1815             lockState = State.Unlocked;
1816         }
1817 
1818         private static enum State {
1819             Unlocked,
1820             Locked
1821         }
1822     }
1823 }