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