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