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