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