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