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