FileDocCategorySizeDatePackage
JPEGImageWriter.javaAPI DocJava SE 5 API65600Fri Aug 26 14:54:42 BST 2005com.sun.imageio.plugins.jpeg

JPEGImageWriter

public class JPEGImageWriter extends ImageWriter

Fields Summary
private boolean
debug
private long
structPointer
The following variable contains a pointer to the IJG library structure for this reader. It is assigned in the constructor and then is passed in to every native call. It is set to 0 by dispose to avoid disposing twice.
private ImageOutputStream
ios
The output stream we write to
private Raster
srcRas
The Raster we will write from
private WritableRaster
raster
An intermediate Raster holding compressor-friendly data
private boolean
indexed
Set to true if we are writing an image with an indexed ColorModel
private IndexColorModel
indexCM
private boolean
convertTosRGB
private WritableRaster
converted
private boolean
isAlphaPremultiplied
private ColorModel
srcCM
private List
thumbnails
If there are thumbnails to be written, this is the list.
private ICC_Profile
iccProfile
If metadata should include an icc profile, store it here.
private int
sourceXOffset
private int
sourceYOffset
private int
sourceWidth
private int[]
srcBands
private int
sourceHeight
private int
currentImage
Used when calling listeners
private ColorConvertOp
convertOp
private JPEGQTable[]
streamQTables
private JPEGHuffmanTable[]
streamDCHuffmanTables
private JPEGHuffmanTable[]
streamACHuffmanTables
private boolean
ignoreJFIF
private boolean
forceJFIF
private boolean
ignoreAdobe
private int
newAdobeTransform
private boolean
writeDefaultJFIF
private boolean
writeAdobe
private JPEGMetadata
metadata
private boolean
sequencePrepared
private int
numScans
private Object
disposerReferent
The referent to be registered with the Disposer.
private DisposerRecord
disposerRecord
The DisposerRecord that handles the actual disposal of this writer.
protected static final int
WARNING_DEST_IGNORED
protected static final int
WARNING_STREAM_METADATA_IGNORED
protected static final int
WARNING_DEST_METADATA_COMP_MISMATCH
protected static final int
WARNING_DEST_METADATA_JFIF_MISMATCH
protected static final int
WARNING_DEST_METADATA_ADOBE_MISMATCH
protected static final int
WARNING_IMAGE_METADATA_JFIF_MISMATCH
protected static final int
WARNING_IMAGE_METADATA_ADOBE_MISMATCH
protected static final int
WARNING_METADATA_NOT_JPEG_FOR_RASTER
protected static final int
WARNING_NO_BANDS_ON_INDEXED
protected static final int
WARNING_ILLEGAL_THUMBNAIL
protected static final int
WARNING_IGNORING_THUMBS
protected static final int
WARNING_FORCING_JFIF
protected static final int
WARNING_THUMB_CLIPPED
protected static final int
WARNING_METADATA_ADJUSTED_FOR_THUMB
protected static final int
WARNING_NO_RGB_THUMB_AS_INDEXED
protected static final int
WARNING_NO_GRAY_THUMB_AS_INDEXED
private static final int
MAX_WARNING
static final Dimension[]
preferredThumbSizes
Constructors Summary
public JPEGImageWriter(ImageWriterSpi originator)


    ///////// End of Protected variables
    
    ///////// static initializer
    
     
        java.security.AccessController.doPrivileged(
            new sun.security.action.LoadLibraryAction("jpeg"));
        initWriterIDs(ImageOutputStream.class, 
                      JPEGQTable.class, 
                      JPEGHuffmanTable.class);
    
        super(originator);
        structPointer = initJPEGImageWriter();
        disposerRecord = new JPEGWriterDisposerRecord(structPointer);
        Disposer.addRecord(disposerReferent, disposerRecord);
    
Methods Summary
public synchronized voidabort()

        super.abort();
        abortWrite(structPointer);
    
private native voidabortWrite(long structPointer)
Aborts the current write in the native code

public booleancanWriteRasters()

        return true;
    
private voidcheckAdobe(com.sun.imageio.plugins.jpeg.AdobeMarkerSegment adobe, javax.imageio.ImageTypeSpecifier type, boolean input)

        if (adobe != null) {
            int rightTransform = JPEG.transformForType(type, input);
            if (adobe.transform != rightTransform) {
                warningOccurred(input 
                                ? WARNING_IMAGE_METADATA_ADOBE_MISMATCH
                                : WARNING_DEST_METADATA_ADOBE_MISMATCH);
                if (rightTransform == JPEG.ADOBE_IMPOSSIBLE) {
                    ignoreAdobe = true;
                } else {
                    newAdobeTransform = rightTransform;
                }
            }
        }
    
private voidcheckJFIF(com.sun.imageio.plugins.jpeg.JFIFMarkerSegment jfif, javax.imageio.ImageTypeSpecifier type, boolean input)

        if (jfif != null) {
            if (!JPEG.isJFIFcompliant(type, input)) {
                ignoreJFIF = true;  // type overrides metadata
                warningOccurred(input 
                                ? WARNING_IMAGE_METADATA_JFIF_MISMATCH
                                : WARNING_DEST_METADATA_JFIF_MISMATCH);
            }
        }
    
private voidcheckSOFBands(com.sun.imageio.plugins.jpeg.SOFMarkerSegment sof, int numBandsUsed)

        // Does the metadata frame header, if any, match numBandsUsed?
        if (sof != null) {
            if (sof.componentSpecs.length != numBandsUsed) {
                throw new IIOException
                    ("Metadata components != number of destination bands");
            }
        }
    
private javax.imageio.plugins.jpeg.JPEGHuffmanTable[]collectHTablesFromMetadata(com.sun.imageio.plugins.jpeg.JPEGMetadata metadata, boolean wantDC)
Finds all DHT marker segments and returns all the q tables as a single array of JPEGQTables. The metadata must not be for a progressive image, or an exception will be thrown when two Huffman tables with the same table id are encountered.

        ArrayList tables = new ArrayList();
        Iterator iter = metadata.markerSequence.iterator();
        while (iter.hasNext()) {
            MarkerSegment seg = (MarkerSegment) iter.next();
            if (seg instanceof DHTMarkerSegment) {
                DHTMarkerSegment dht = 
                    (DHTMarkerSegment) seg;
                for (int i = 0; i < dht.tables.size(); i++) {
                    DHTMarkerSegment.Htable htable = 
                        (DHTMarkerSegment.Htable) dht.tables.get(i);
                    if (htable.tableClass == (wantDC ? 0 : 1)) {
                        tables.add(htable);
                    }
                }
            }
        }
        JPEGHuffmanTable [] retval = null;
        if (tables.size() != 0) {
            DHTMarkerSegment.Htable [] htables = 
                new DHTMarkerSegment.Htable[tables.size()];
            tables.toArray(htables);
            retval = new JPEGHuffmanTable[tables.size()];
            for (int i = 0; i < retval.length; i++) {
                retval[i] = null;
                for (int j = 0; j < tables.size(); j++) {
                    if (htables[j].tableID == i) {
                        if (retval[i] != null) {
                            throw new IIOException("Metadata has duplicate Htables!");
                        }
                        retval[i] = new JPEGHuffmanTable(htables[j].numCodes, 
                                                         htables[j].values);
                    }
                }
            }
        }
        
        return retval;
    
private javax.imageio.plugins.jpeg.JPEGQTable[]collectQTablesFromMetadata(com.sun.imageio.plugins.jpeg.JPEGMetadata metadata)
Finds all DQT marker segments and returns all the q tables as a single array of JPEGQTables.

        ArrayList tables = new ArrayList();
        Iterator iter = metadata.markerSequence.iterator();
        while (iter.hasNext()) {
            MarkerSegment seg = (MarkerSegment) iter.next();
            if (seg instanceof DQTMarkerSegment) {
                DQTMarkerSegment dqt = 
                    (DQTMarkerSegment) seg;
                tables.addAll(dqt.tables);
            }
        }
        JPEGQTable [] retval = null;
        if (tables.size() != 0) {
            retval = new JPEGQTable[tables.size()];
            for (int i = 0; i < retval.length; i++) {
                retval[i] = 
                    new JPEGQTable(((DQTMarkerSegment.Qtable)tables.get(i)).data);
            }
        }
        return retval;
    
private int[]collectScans(com.sun.imageio.plugins.jpeg.JPEGMetadata metadata, com.sun.imageio.plugins.jpeg.SOFMarkerSegment sof)
Collect all the scan info from the given metadata, and organize it into the scan info array required by the IJG libray. It is much simpler to parse out this data in Java and then just copy the data in C.

        List segments = new ArrayList();
        int SCAN_SIZE = 9;
        int MAX_COMPS_PER_SCAN = 4;
        for (Iterator iter = metadata.markerSequence.iterator();
             iter.hasNext();) {
            MarkerSegment seg = (MarkerSegment) iter.next();
            if (seg instanceof SOSMarkerSegment) {
                segments.add(seg);
            }
        }
        int [] retval = null;
        numScans = 0;
        if (!segments.isEmpty()) {
            numScans = segments.size();
            retval = new int [numScans*SCAN_SIZE];
            int index = 0;
            for (int i = 0; i < numScans; i++) {
                SOSMarkerSegment sos = (SOSMarkerSegment) segments.get(i);
                retval[index++] = sos.componentSpecs.length; // num comps
                for (int j = 0; j < MAX_COMPS_PER_SCAN; j++) {
                    if (j < sos.componentSpecs.length) {
                        int compSel = sos.componentSpecs[j].componentSelector;
                        for (int k = 0; k < sof.componentSpecs.length; k++) {
                            if (compSel == sof.componentSpecs[k].componentId) {
                                retval[index++] = k;
                                break; // out of for over sof comps
                            }
                        }
                    } else {
                        retval[index++] = 0;
                    }
                }
                retval[index++] = sos.startSpectralSelection;
                retval[index++] = sos.endSpectralSelection;
                retval[index++] = sos.approxHigh;
                retval[index++] = sos.approxLow;
            }
        }
        return retval;
    
public javax.imageio.metadata.IIOMetadataconvertImageMetadata(javax.imageio.metadata.IIOMetadata inData, javax.imageio.ImageTypeSpecifier imageType, javax.imageio.ImageWriteParam param)

        // If it's one of ours, just return it
        if (inData instanceof JPEGMetadata) {
            JPEGMetadata jpegData = (JPEGMetadata) inData;
            if (!jpegData.isStream) {
                return inData;
            } else {
                // Can't convert stream metadata to image metadata
                // XXX Maybe this should put out a warning?
                return null;
            }
        }
        // If it's not one of ours, create a default and set it from
        // the standard tree from the input, if it exists.
        if (inData.isStandardMetadataFormatSupported()) {
            String formatName = 
                IIOMetadataFormatImpl.standardMetadataFormatName;
            Node tree = inData.getAsTree(formatName);
            if (tree != null) {
                JPEGMetadata jpegData = new JPEGMetadata(imageType,
                                                         param,
                                                         this);
                try {
                    jpegData.setFromTree(formatName, tree);
                } catch (IIOInvalidTreeException e) {
                    // Other plug-in generates bogus standard tree
                    // XXX Maybe this should put out a warning?
                    return null;
                }

                return jpegData;
            }
        }
            return null;
    
public javax.imageio.metadata.IIOMetadataconvertStreamMetadata(javax.imageio.metadata.IIOMetadata inData, javax.imageio.ImageWriteParam param)

        // There isn't much we can do.  If it's one of ours, then
        // return it.  Otherwise just return null.  We use it only
        // for tables, so we can't get a default and modify it,
        // as this will usually not be what is intended.
        if (inData instanceof JPEGMetadata) {
            JPEGMetadata jpegData = (JPEGMetadata) inData;
            if (jpegData.isStream) {
                return inData;
            }
        }
        return null;
    
public voiddispose()
Note that there is no need to override reset() here, as the default implementation will call setOutput(null), which will invoke resetInternalState().

        if (structPointer != 0) {
            disposerRecord.dispose();
            structPointer = 0;
        }
    
private static native voiddisposeWriter(long structPointer)
Releases native structures

public voidendWriteSequence()

        if (sequencePrepared == false) {
            throw new IllegalStateException("sequencePrepared not called!");
        }
        sequencePrepared = false;
    
private intgetDefaultDestCSType(java.awt.image.RenderedImage rimage)

        int retval = JPEG.JCS_UNKNOWN;
        ColorModel cm = rimage.getColorModel();
        if (cm != null) {
            boolean alpha = cm.hasAlpha();
            ColorSpace cs = cm.getColorSpace();
            switch (cs.getType()) {
            case ColorSpace.TYPE_GRAY:
                retval = JPEG.JCS_GRAYSCALE;
                break;
            case ColorSpace.TYPE_RGB:
                if (alpha) {
                    retval = JPEG.JCS_YCbCrA;
                } else {
                    retval = JPEG.JCS_YCbCr;
                }
                break;
            case ColorSpace.TYPE_YCbCr:
                if (alpha) {
                    retval = JPEG.JCS_YCbCrA;
                } else {
                    retval = JPEG.JCS_YCbCr;
                }
                break;
            case ColorSpace.TYPE_3CLR:
                if (cs == JPEG.YCC) {
                    if (alpha) {
                        retval = JPEG.JCS_YCCA;
                    } else {
                        retval = JPEG.JCS_YCC;
                    }
                }
            case ColorSpace.TYPE_CMYK:
                retval = JPEG.JCS_YCCK;
                break;
            }
        }
        return retval;
    
public javax.imageio.metadata.IIOMetadatagetDefaultImageMetadata(javax.imageio.ImageTypeSpecifier imageType, javax.imageio.ImageWriteParam param)

        return new JPEGMetadata(imageType, param, this);
    
public javax.imageio.metadata.IIOMetadatagetDefaultStreamMetadata(javax.imageio.ImageWriteParam param)

        return new JPEGMetadata(param, this);
    
public javax.imageio.ImageWriteParamgetDefaultWriteParam()

        return new JPEGImageWriteParam(null);
    
private intgetDestCSType(javax.imageio.ImageTypeSpecifier destType)

        ColorModel cm = destType.getColorModel();
        boolean alpha = cm.hasAlpha();
        ColorSpace cs = cm.getColorSpace();
        int retval = JPEG.JCS_UNKNOWN;
        switch (cs.getType()) {
        case ColorSpace.TYPE_GRAY:
                retval = JPEG.JCS_GRAYSCALE;
                break;
            case ColorSpace.TYPE_RGB:
                if (alpha) {
                    retval = JPEG.JCS_RGBA;
                } else {
                    retval = JPEG.JCS_RGB;
                }
                break;
            case ColorSpace.TYPE_YCbCr:
                if (alpha) {
                    retval = JPEG.JCS_YCbCrA;
                } else {
                    retval = JPEG.JCS_YCbCr;
                }
                break;
            case ColorSpace.TYPE_3CLR:
                if (cs == JPEG.YCC) {
                    if (alpha) {
                        retval = JPEG.JCS_YCCA;
                    } else {
                        retval = JPEG.JCS_YCC;
                    }
                }
            case ColorSpace.TYPE_CMYK:
                retval = JPEG.JCS_CMYK;
                break;
            }
        return retval;
        
public intgetNumThumbnailsSupported(javax.imageio.ImageTypeSpecifier imageType, javax.imageio.ImageWriteParam param, javax.imageio.metadata.IIOMetadata streamMetadata, javax.imageio.metadata.IIOMetadata imageMetadata)

        if (jfifOK(imageType, param, streamMetadata, imageMetadata)) {
            return Integer.MAX_VALUE;
        } 
        return 0;
    
public java.awt.Dimension[]getPreferredThumbnailSizes(javax.imageio.ImageTypeSpecifier imageType, javax.imageio.ImageWriteParam param, javax.imageio.metadata.IIOMetadata streamMetadata, javax.imageio.metadata.IIOMetadata imageMetadata)

    
       
                                                   
                                                   
                                                    
        if (jfifOK(imageType, param, streamMetadata, imageMetadata)) {
            return (Dimension [])preferredThumbSizes.clone();
        }
        return null;
    
private intgetSrcCSType(java.awt.image.RenderedImage rimage)

        int retval = JPEG.JCS_UNKNOWN;
        ColorModel cm = rimage.getColorModel();
        if (cm != null) {
            boolean alpha = cm.hasAlpha();
            ColorSpace cs = cm.getColorSpace();
            switch (cs.getType()) {
            case ColorSpace.TYPE_GRAY:
                retval = JPEG.JCS_GRAYSCALE;
                break;
            case ColorSpace.TYPE_RGB:
                if (alpha) {
                    retval = JPEG.JCS_RGBA;
                } else {
                    retval = JPEG.JCS_RGB;
                }
                break;
            case ColorSpace.TYPE_YCbCr:
                if (alpha) {
                    retval = JPEG.JCS_YCbCrA;
                } else {
                    retval = JPEG.JCS_YCbCr;
                }
                break;
            case ColorSpace.TYPE_3CLR:
                if (cs == JPEG.YCC) {
                    if (alpha) {
                        retval = JPEG.JCS_YCCA;
                    } else {
                        retval = JPEG.JCS_YCC;
                    }
                }
            case ColorSpace.TYPE_CMYK:
                retval = JPEG.JCS_CMYK;
                break;
            }
        }
        return retval;
    
private voidgrabPixels(int y)
Put the scanline y of the source ROI view Raster into the 1-line Raster for writing. This handles ROI and band rearrangements, and expands indexed images. Subsampling is done in the native code. This is called by the native code.


        Raster sourceLine = null;
        if (indexed) {
            sourceLine = srcRas.createChild(sourceXOffset, 
                                            sourceYOffset+y,
                                            sourceWidth, 1,
                                            0, 0,
                                            new int [] {0});
            // If the image has BITMASK transparency, we need to make sure
            // it gets converted to 32-bit ARGB, because the JPEG encoder
            // relies upon the full 8-bit alpha channel.
            boolean forceARGB =
                (indexCM.getTransparency() != Transparency.OPAQUE);
            BufferedImage temp = indexCM.convertToIntDiscrete(sourceLine,
                                                              forceARGB);
            sourceLine = temp.getRaster();
        } else {
            sourceLine = srcRas.createChild(sourceXOffset, 
                                            sourceYOffset+y,
                                            sourceWidth, 1,
                                            0, 0,
                                            srcBands);
        }
        if (convertTosRGB) {
            if (debug) {
                System.out.println("Converting to sRGB");
            }
            // The first time through, converted is null, so
            // a new raster is allocated.  It is then reused
            // on subsequent lines.
            converted = convertOp.filter(sourceLine, converted);
            sourceLine = converted;
        }
        if (isAlphaPremultiplied) {
            WritableRaster wr = sourceLine.createCompatibleWritableRaster();
            int[] data = null;
            data = sourceLine.getPixels(sourceLine.getMinX(), sourceLine.getMinY(),
                                        sourceLine.getWidth(), sourceLine.getHeight(),
                                        data);
            wr.setPixels(sourceLine.getMinX(), sourceLine.getMinY(),
                         sourceLine.getWidth(), sourceLine.getHeight(),
                         data);
            srcCM.coerceData(wr, false);
            sourceLine = wr.createChild(wr.getMinX(), wr.getMinY(),
                                        wr.getWidth(), wr.getHeight(),
                                        0, 0,
                                        srcBands);
        }
        raster.setRect(sourceLine);
        if ((y > 7) && (y%8 == 0)) {  // Every 8 scanlines
            processImageProgress((float) y / (float) sourceHeight * 100.0F);
        }
    
private native longinitJPEGImageWriter()
Sets up per-writer native structure and returns a pointer to it.

private static native voidinitWriterIDs(java.lang.Class iosClass, java.lang.Class qTableClass, java.lang.Class huffClass)
Sets up static native structures.

private booleanisSubsampled(com.sun.imageio.plugins.jpeg.SOFMarkerSegment$ComponentSpec[] specs)

        int hsamp0 = specs[0].HsamplingFactor;
        int vsamp0 = specs[0].VsamplingFactor;
        for (int i = 1; i < specs.length; i++) {
            if ((specs[i].HsamplingFactor != hsamp0) ||
                (specs[i].HsamplingFactor != hsamp0))
                return true;
        }
        return false;
    
private booleanjfifOK(javax.imageio.ImageTypeSpecifier imageType, javax.imageio.ImageWriteParam param, javax.imageio.metadata.IIOMetadata streamMetadata, javax.imageio.metadata.IIOMetadata imageMetadata)

        // If the image type and metadata are JFIF compatible, return true
        if ((imageType != null) &&
            (!JPEG.isJFIFcompliant(imageType, true))) {
            return false;
        }
        if (imageMetadata != null) {
            JPEGMetadata metadata = null;
            if (imageMetadata instanceof JPEGMetadata) {
                metadata = (JPEGMetadata) imageMetadata;
            } else {
                metadata = (JPEGMetadata)convertImageMetadata(imageMetadata,
                                                              imageType,
                                                              param);
            }
            // metadata must have a jfif node
            if (metadata.findMarkerSegment
                (JFIFMarkerSegment.class, true) == null){
                return false;
            }
        }
        return true;
    
public voidprepareWriteSequence(javax.imageio.metadata.IIOMetadata streamMetadata)

        if (ios == null) {
            throw new IllegalStateException("Output has not been set!");
        }

        /*
         * from jpeg_metadata.html:
         * If no stream metadata is supplied to
         * <code>ImageWriter.prepareWriteSequence</code>, then no 
         * tables-only image is written.  If stream metadata containing
         * no tables is supplied to
         * <code>ImageWriter.prepareWriteSequence</code>, then a tables-only
         * image containing default visually lossless tables is written.
         */
        if (streamMetadata != null) {
            if (streamMetadata instanceof JPEGMetadata) {
                // write a complete tables-only image at the beginning of 
                // the stream.
                JPEGMetadata jmeta = (JPEGMetadata) streamMetadata;
                if (jmeta.isStream == false) {
                    throw new IllegalArgumentException
                        ("Invalid stream metadata object.");
                }
                // Check that we are
                // at the beginning of the stream, or can go there, and haven't
                // written out the metadata already.
                if (currentImage != 0) {
                    throw new IIOException
                        ("JPEG Stream metadata must precede all images");
                }
                if (sequencePrepared == true) {
                    throw new IIOException("Stream metadata already written!");
                }
                
                // Set the tables
                // If the metadata has no tables, use default tables.
                streamQTables = collectQTablesFromMetadata(jmeta);
                if (debug) {
                    System.out.println("after collecting from stream metadata, "
                                       + "streamQTables.length is " 
                                       + streamQTables.length);
                }
                if (streamQTables == null) {
                    streamQTables = JPEG.getDefaultQTables();
                }
                streamDCHuffmanTables = 
                    collectHTablesFromMetadata(jmeta, true);
                if (streamDCHuffmanTables == null) {
                    streamDCHuffmanTables = JPEG.getDefaultHuffmanTables(true);
                }
                streamACHuffmanTables = 
                    collectHTablesFromMetadata(jmeta, false);
                if (streamACHuffmanTables == null) {
                    streamACHuffmanTables = JPEG.getDefaultHuffmanTables(false);
                }

                // Now write them out
                writeTables(structPointer,
                            streamQTables, 
                            streamDCHuffmanTables, 
                            streamACHuffmanTables);
            } else { 
                throw new IIOException("Stream metadata must be JPEG metadata");
            }
        }
        sequencePrepared = true;
    
private voidresetInternalState()

        // reset C structures
        resetWriter(structPointer);

        // reset local Java structures
        srcRas = null;
        raster = null;
        convertTosRGB = false;
        currentImage = 0;
        numScans = 0;
        metadata = null;
    
private native voidresetWriter(long structPointer)
Resets native structures

private native voidsetDest(long structPointer, javax.imageio.stream.ImageOutputStream ios)
Sets up native structures for output stream

public voidsetOutput(java.lang.Object output)

        super.setOutput(output); // validates output
        resetInternalState();
        ios = (ImageOutputStream) output; // so this will always work
        // Set the native destination
        setDest(structPointer, ios);
    
voidthumbnailComplete()

        processThumbnailComplete();
    
voidthumbnailProgress(float percentageDone)

        processThumbnailProgress(percentageDone);
    
voidthumbnailStarted(int thumbnailIndex)

        processThumbnailStarted(currentImage, thumbnailIndex);
    
voidwarningOccurred(int code)
Called by the native code or other classes to signal a warning. The code is used to lookup a localized message to be used when sending warnings to listeners.

        if ((code < 0) || (code > MAX_WARNING)){
            throw new InternalError("Invalid warning index");
        }
        processWarningOccurred
            (currentImage, 
             "com.sun.imageio.plugins.jpeg.JPEGImageWriterResources",
             Integer.toString(code));
    
voidwarningWithMessage(java.lang.String msg)
The library has it's own error facility that emits warning messages. This routine is called by the native code when it has already formatted a string for output. XXX For truly complete localization of all warning messages, the sun_jpeg_output_message routine in the native code should send only the codes and parameters to a method here in Java, which will then format and send the warnings, using localized strings. This method will have to deal with all the parameters and formats (%u with possibly large numbers, %02d, %02x, etc.) that actually occur in the JPEG library. For now, this prevents library warnings from being printed to stderr.

        processWarningOccurred(currentImage, msg);
    
public voidwrite(javax.imageio.metadata.IIOMetadata streamMetadata, javax.imageio.IIOImage image, javax.imageio.ImageWriteParam param)


        if (ios == null) {
            throw new IllegalStateException("Output has not been set!");
        }

        if (image == null) {
            throw new IllegalArgumentException("image is null!");
        }

        // if streamMetadata is not null, issue a warning
        if (streamMetadata != null) {
            warningOccurred(WARNING_STREAM_METADATA_IGNORED);
        }

        // Obtain the raster and image, if there is one
        boolean rasterOnly = image.hasRaster();

        RenderedImage rimage = null;
        if (rasterOnly) {
            srcRas = image.getRaster();
        } else {
            rimage = image.getRenderedImage();
            if (rimage instanceof BufferedImage) {
                srcRas = ((BufferedImage)rimage).getRaster();
            } else {
                // XXX - this makes a copy, which is memory-inefficient
                srcRas = rimage.getData();
            }
        }

        // Now determine if we are using a band subset

        // By default, we are using all source bands
        int numSrcBands = srcRas.getNumBands();
        indexed = false;
        indexCM = null;
        ColorModel cm = null;
        ColorSpace cs = null;
        isAlphaPremultiplied = false;
        srcCM = null;
        if (!rasterOnly) {
            cm = rimage.getColorModel();
            if (cm != null) {
                cs = cm.getColorSpace();
                if (cm instanceof IndexColorModel) {
                    indexed = true;
                    indexCM = (IndexColorModel) cm;
                    numSrcBands = cm.getNumComponents();
                }
                if (cm.isAlphaPremultiplied()) {
                    isAlphaPremultiplied = true;
                    srcCM = cm;
                }
            }
        }

        srcBands = JPEG.bandOffsets[numSrcBands-1];
        int numBandsUsed = numSrcBands;
        // Consult the param to determine if we're writing a subset
        
        if (param != null) {
            int[] sBands = param.getSourceBands();
            if (sBands != null) {
                if (indexed) {
                    warningOccurred(WARNING_NO_BANDS_ON_INDEXED);
                } else {
                    srcBands = sBands;
                    numBandsUsed = srcBands.length;
                    if (numBandsUsed > numSrcBands) {
                        throw new IIOException
                        ("ImageWriteParam specifies too many source bands");
                    }
                }
            }
        }

        boolean usingBandSubset = (numBandsUsed != numSrcBands);
        boolean fullImage = ((!rasterOnly) && (!usingBandSubset));

        int [] bandSizes = null;
        if (!indexed) {
            bandSizes = srcRas.getSampleModel().getSampleSize();
            // If this is a subset, we must adjust bandSizes
            if (usingBandSubset) {
                int [] temp = new int [numBandsUsed];
                for (int i = 0; i < numBandsUsed; i++) {
                    temp[i] = bandSizes[srcBands[i]];
                }
                bandSizes = temp;
            }
        } else {
            int [] tempSize = srcRas.getSampleModel().getSampleSize();
            bandSizes = new int [numSrcBands];
            for (int i = 0; i < numSrcBands; i++) {
                bandSizes[i] = tempSize[0];  // All the same
            }
        }

        for (int i = 0; i < bandSizes.length; i++) {
            // 4450894 part 1: The IJG libraries are compiled so they only
            // handle <= 8-bit samples.  We now check the band sizes and throw
            // an exception for images, such as USHORT_GRAY, with > 8 bits
            // per sample.
            if (bandSizes[i] > 8) {
                throw new IIOException("Sample size must be <= 8");
            }
            // 4450894 part 2: We expand IndexColorModel images to full 24-
            // or 32-bit in grabPixels() for each scanline.  For indexed
            // images such as BYTE_BINARY, we need to ensure that we update
            // bandSizes to account for the scaling from 1-bit band sizes
            // to 8-bit.
            if (indexed) {
                bandSizes[i] = 8;
            }
        }

        if (debug) {
            System.out.println("numSrcBands is " + numSrcBands);
            System.out.println("numBandsUsed is " + numBandsUsed);
            System.out.println("usingBandSubset is " + usingBandSubset);
            System.out.println("fullImage is " + fullImage);
            System.out.print("Band sizes:");
            for (int i = 0; i< bandSizes.length; i++) {
                System.out.print(" " + bandSizes[i]);
            }
            System.out.println();
        }

        // Destination type, if there is one
        ImageTypeSpecifier destType = null;
        if (param != null) {
            destType = param.getDestinationType();
            // Ignore dest type if we are writing a complete image
            if ((fullImage) && (destType != null)) {
                warningOccurred(WARNING_DEST_IGNORED);
                destType = null;
            }
        }

        // Examine the param

        sourceXOffset = srcRas.getMinX();
        sourceYOffset = srcRas.getMinY();
        int imageWidth = srcRas.getWidth();
        int imageHeight = srcRas.getHeight();
        sourceWidth = imageWidth;
        sourceHeight = imageHeight;
        int periodX = 1;
        int periodY = 1;
        int gridX = 0;
        int gridY = 0;
        JPEGQTable [] qTables = null;
        JPEGHuffmanTable[] DCHuffmanTables = null;
        JPEGHuffmanTable[] ACHuffmanTables = null;
        boolean optimizeHuffman = false;
        JPEGImageWriteParam jparam = null;
        int progressiveMode = ImageWriteParam.MODE_DISABLED;

        if (param != null) {

            Rectangle sourceRegion = param.getSourceRegion();
            if (sourceRegion != null) {
                Rectangle imageBounds = new Rectangle(sourceXOffset,
                                                      sourceYOffset,
                                                      sourceWidth,
                                                      sourceHeight);
                sourceRegion = sourceRegion.intersection(imageBounds);
                sourceXOffset = sourceRegion.x;
                sourceYOffset = sourceRegion.y;
                sourceWidth = sourceRegion.width;
                sourceHeight = sourceRegion.height;
            }

            if (sourceWidth + sourceXOffset > imageWidth) {
                sourceWidth = imageWidth - sourceXOffset;
            }
            if (sourceHeight + sourceYOffset > imageHeight) {
                sourceHeight = imageHeight - sourceYOffset;
            }

            periodX = param.getSourceXSubsampling();
            periodY = param.getSourceYSubsampling();
            gridX = param.getSubsamplingXOffset();
            gridY = param.getSubsamplingYOffset();

            switch(param.getCompressionMode()) {
            case ImageWriteParam.MODE_DISABLED:
                throw new IIOException("JPEG compression cannot be disabled");
            case ImageWriteParam.MODE_EXPLICIT:
                float quality = param.getCompressionQuality();
                quality = JPEG.convertToLinearQuality(quality);
                qTables = new JPEGQTable[2];
                qTables[0] = JPEGQTable.K1Luminance.getScaledInstance
                    (quality, true);
                qTables[1] = JPEGQTable.K2Chrominance.getScaledInstance
                    (quality, true);
                break;
            case ImageWriteParam.MODE_DEFAULT:
                qTables = new JPEGQTable[2];
                qTables[0] = JPEGQTable.K1Div2Luminance;
                qTables[1] = JPEGQTable.K2Div2Chrominance;
                break;
            // We'll handle the metadata case later
            }

            progressiveMode = param.getProgressiveMode();

            if (param instanceof JPEGImageWriteParam) {
                jparam = (JPEGImageWriteParam)param;                
                optimizeHuffman = jparam.getOptimizeHuffmanTables();
            }
        }

        // Now examine the metadata
        IIOMetadata mdata = image.getMetadata();
        if (mdata != null) {
            if (mdata instanceof JPEGMetadata) {
                metadata = (JPEGMetadata) mdata;
                if (debug) {
                    System.out.println
                        ("We have metadata, and it's JPEG metadata");
                }
            } else {
                if (!rasterOnly) {
                    ImageTypeSpecifier type = destType;
                    if (type == null) {
                        type = new ImageTypeSpecifier(rimage);
                    }
                    metadata = (JPEGMetadata) convertImageMetadata(mdata,
                                                                   type,
                                                                   param);
                } else {
                    warningOccurred(WARNING_METADATA_NOT_JPEG_FOR_RASTER);
                }                            
            }
        }

        // First set a default state

        ignoreJFIF = false;  // If it's there, use it
        ignoreAdobe = false;  // If it's there, use it
        newAdobeTransform = JPEG.ADOBE_IMPOSSIBLE;  // Change if needed
        writeDefaultJFIF = false;
        writeAdobe = false;

        // By default we'll do no conversion:
        int inCsType = JPEG.JCS_UNKNOWN;
        int outCsType = JPEG.JCS_UNKNOWN;

        JFIFMarkerSegment jfif = null;
        AdobeMarkerSegment adobe = null;
        SOFMarkerSegment sof = null;

        if (metadata != null) {
            jfif = (JFIFMarkerSegment) metadata.findMarkerSegment
                (JFIFMarkerSegment.class, true);
            adobe = (AdobeMarkerSegment) metadata.findMarkerSegment
                (AdobeMarkerSegment.class, true);
            sof = (SOFMarkerSegment) metadata.findMarkerSegment
                (SOFMarkerSegment.class, true);
        }
        
        iccProfile = null;  // By default don't write one
        convertTosRGB = false;  // PhotoYCC does this
        converted = null;

        if (destType != null) {
            if (numBandsUsed != destType.getNumBands()) {
                throw new IIOException
                    ("Number of source bands != number of destination bands");
            }
            cs = destType.getColorModel().getColorSpace();
            // Check the metadata against the destination type
            if (metadata != null) {
                checkSOFBands(sof, numBandsUsed);

                checkJFIF(jfif, destType, false);
                // Do we want to write an ICC profile?
                if ((jfif != null) && (ignoreJFIF == false)) {
                    if (JPEG.isNonStandardICC(cs)) {
                        iccProfile = ((ICC_ColorSpace) cs).getProfile();
                    }                    
                }
                checkAdobe(adobe, destType, false);

            } else { // no metadata, but there is a dest type
                // If we can add a JFIF or an Adobe marker segment, do so
                if (JPEG.isJFIFcompliant(destType, false)) {
                    writeDefaultJFIF = true;
                    // Do we want to write an ICC profile?
                    if (JPEG.isNonStandardICC(cs)) {
                        iccProfile = ((ICC_ColorSpace) cs).getProfile();
                    }
                } else {
                    int transform = JPEG.transformForType(destType, false);
                    if (transform != JPEG.ADOBE_IMPOSSIBLE) {
                        writeAdobe = true;
                        newAdobeTransform = transform;
                    }
                }
            }
        } else { // no destination type
            if (metadata == null) {
                if (fullImage) {  // no dest, no metadata, full image
                    // Use default metadata matching the image and param
                    metadata = new JPEGMetadata(new ImageTypeSpecifier(rimage),
                                                param, this);
                    if (metadata.findMarkerSegment
                        (JFIFMarkerSegment.class, true) != null) {
                        cs = rimage.getColorModel().getColorSpace();
                        if (JPEG.isNonStandardICC(cs)) {
                            iccProfile = ((ICC_ColorSpace) cs).getProfile();
                        }
                    }

                    inCsType = getSrcCSType(rimage);
                    outCsType = getDefaultDestCSType(rimage);
                }
                // else no dest, no metadata, not an image, 
                // so no special headers, no color conversion
            } else { // no dest type, but there is metadata
                checkSOFBands(sof, numBandsUsed);
                if (fullImage) {  // no dest, metadata, image
                    // Check that the metadata and the image match

                    ImageTypeSpecifier inputType = 
                        new ImageTypeSpecifier(rimage);

                    inCsType = getSrcCSType(rimage);

                    if (cm != null) {
                        boolean alpha = cm.hasAlpha();
                        switch (cs.getType()) {
                        case ColorSpace.TYPE_GRAY:
                            if (!alpha) {
                                outCsType = JPEG.JCS_GRAYSCALE;
                            } else {
                                if (jfif != null) {
                                    ignoreJFIF = true;
                                    warningOccurred
                                    (WARNING_IMAGE_METADATA_JFIF_MISMATCH);
                                }
                                // out colorspace remains unknown
                            }
                            if ((adobe != null) 
                                && (adobe.transform != JPEG.ADOBE_UNKNOWN)) {
                                newAdobeTransform = JPEG.ADOBE_UNKNOWN;
                                warningOccurred
                                (WARNING_IMAGE_METADATA_ADOBE_MISMATCH);
                            }
                            break;
                        case ColorSpace.TYPE_RGB:
                            if (!alpha) {
                                if (jfif != null) {
                                    outCsType = JPEG.JCS_YCbCr;
                                    if (JPEG.isNonStandardICC(cs) 
                                        || ((cs instanceof ICC_ColorSpace) 
                                            && (jfif.iccSegment != null))) {
                                        iccProfile = 
                                            ((ICC_ColorSpace) cs).getProfile();
                                    }
                                } else if (adobe != null) {
                                    switch (adobe.transform) { 
                                    case JPEG.ADOBE_UNKNOWN:
                                        outCsType = JPEG.JCS_RGB;
                                        break;
                                    case JPEG.ADOBE_YCC:
                                        outCsType = JPEG.JCS_YCbCr;
                                        break;
                                    default:
                                        warningOccurred
                                        (WARNING_IMAGE_METADATA_ADOBE_MISMATCH);
                                        newAdobeTransform = JPEG.ADOBE_UNKNOWN;
                                        outCsType = JPEG.JCS_RGB;
                                        break;
                                    }
                                } else {
                                    // consult the ids
                                    int outCS = sof.getIDencodedCSType();
                                    // if they don't resolve it,
                                    // consult the sampling factors
                                    if (outCS != JPEG.JCS_UNKNOWN) {
                                        outCsType = outCS;
                                    } else {
                                        boolean subsampled = 
                                        isSubsampled(sof.componentSpecs);
                                        if (subsampled) {
                                            outCsType = JPEG.JCS_YCbCr;
                                        } else {
                                            outCsType = JPEG.JCS_RGB;
                                        }
                                    }
                                }
                            } else { // RGBA
                                if (jfif != null) {
                                    ignoreJFIF = true;
                                    warningOccurred
                                    (WARNING_IMAGE_METADATA_JFIF_MISMATCH);
                                }
                                if (adobe != null) {
                                    if (adobe.transform 
                                        != JPEG.ADOBE_UNKNOWN) {
                                        newAdobeTransform = JPEG.ADOBE_UNKNOWN;
                                        warningOccurred
                                        (WARNING_IMAGE_METADATA_ADOBE_MISMATCH);
                                    }
                                    outCsType = JPEG.JCS_RGBA;
                                } else {
                                    // consult the ids
                                    int outCS = sof.getIDencodedCSType();
                                    // if they don't resolve it,
                                    // consult the sampling factors
                                    if (outCS != JPEG.JCS_UNKNOWN) {
                                        outCsType = outCS;
                                    } else {
                                        boolean subsampled = 
                                        isSubsampled(sof.componentSpecs);
                                        outCsType = subsampled ? 
                                            JPEG.JCS_YCbCrA : JPEG.JCS_RGBA;
                                    }
                                }
                            }
                            break;
                        case ColorSpace.TYPE_3CLR:
                            if (cs == JPEG.YCC) {
                                if (!alpha) {
                                    if (jfif != null) {
                                        convertTosRGB = true;
                                        convertOp = 
                                        new ColorConvertOp(cs, 
                                                           JPEG.sRGB, 
                                                           null);
                                        outCsType = JPEG.JCS_YCbCr;
                                    } else if (adobe != null) {
                                        if (adobe.transform 
                                            != JPEG.ADOBE_YCC) {
                                            newAdobeTransform = JPEG.ADOBE_YCC;
                                            warningOccurred
                                            (WARNING_IMAGE_METADATA_ADOBE_MISMATCH);
                                        }
                                        outCsType = JPEG.JCS_YCC;
                                    } else {
                                        outCsType = JPEG.JCS_YCC;
                                    }
                                } else { // PhotoYCCA
                                    if (jfif != null) {
                                        ignoreJFIF = true;
                                        warningOccurred
                                        (WARNING_IMAGE_METADATA_JFIF_MISMATCH);
                                    } else if (adobe != null) {
                                        if (adobe.transform 
                                            != JPEG.ADOBE_UNKNOWN) {
                                            newAdobeTransform 
                                            = JPEG.ADOBE_UNKNOWN;
                                            warningOccurred
                                            (WARNING_IMAGE_METADATA_ADOBE_MISMATCH);
                                        }
                                    }
                                    outCsType = JPEG.JCS_YCCA;
                                }
                            }
                        }
                    }
                } // else no dest, metadata, not an image.  Defaults ok
            }
        }

        boolean metadataProgressive = false;
        int [] scans = null;

        if (metadata != null) {
            if (sof == null) {
                sof = (SOFMarkerSegment) metadata.findMarkerSegment
                    (SOFMarkerSegment.class, true);
            }
            if ((sof != null) && (sof.tag == JPEG.SOF2)) {
                metadataProgressive = true;
                if (progressiveMode == ImageWriteParam.MODE_COPY_FROM_METADATA) {
                    scans = collectScans(metadata, sof);  // Might still be null
                } else {
                    numScans = 0;
                }
            }
            if (jfif == null) {
                jfif = (JFIFMarkerSegment) metadata.findMarkerSegment
                    (JFIFMarkerSegment.class, true);
            }
        }

        thumbnails = image.getThumbnails();
        int numThumbs = image.getNumThumbnails();
        forceJFIF = false;
        // determine if thumbnails can be written
        // If we are going to add a default JFIF marker segment,
        // then thumbnails can be written
        if (!writeDefaultJFIF) {
            // If there is no metadata, then we can't write thumbnails
            if (metadata == null) {
                thumbnails = null;
                if (numThumbs != 0) {
                    warningOccurred(WARNING_IGNORING_THUMBS);
                }
            } else {
                // There is metadata
                // If we are writing a raster or subbands,
                // then the user must specify JFIF on the metadata
                if (fullImage == false) {
                    if (jfif == null) {
                        thumbnails = null;  // Or we can't include thumbnails
                        if (numThumbs != 0) {
                            warningOccurred(WARNING_IGNORING_THUMBS);
                        }
                    }
                } else {  // It is a full image, and there is metadata
                    if (jfif == null) {  // Not JFIF
                        // Can it have JFIF?
                        if ((outCsType == JPEG.JCS_GRAYSCALE)
                            || (outCsType == JPEG.JCS_YCbCr)) {
                            if (numThumbs != 0) {
                                forceJFIF = true;
                                warningOccurred(WARNING_FORCING_JFIF);
                            }
                        } else {  // Nope, not JFIF-compatible
                            thumbnails = null;
                            if (numThumbs != 0) {
                                warningOccurred(WARNING_IGNORING_THUMBS);
                            }
                        }
                    }
                }
            }
        }

        // Set up a boolean to indicate whether we need to call back to 
        // write metadata
        boolean haveMetadata = 
            ((metadata != null) || writeDefaultJFIF || writeAdobe);

        // Now that we have dealt with metadata, finalize our tables set up

        // Are we going to write tables?  By default, yes.
        boolean writeDQT = true;
        boolean writeDHT = true;

        // But if the metadata has no tables, no.
        DQTMarkerSegment dqt = null;
        DHTMarkerSegment dht = null;

        int restartInterval = 0;

        if (metadata != null) {
            dqt = (DQTMarkerSegment) metadata.findMarkerSegment
                (DQTMarkerSegment.class, true);
            dht = (DHTMarkerSegment) metadata.findMarkerSegment
                (DHTMarkerSegment.class, true);
            DRIMarkerSegment dri = 
                (DRIMarkerSegment) metadata.findMarkerSegment
                (DRIMarkerSegment.class, true);
            if (dri != null) {
                restartInterval = dri.restartInterval;
            }

            if (dqt == null) {
                writeDQT = false;
            }
            if (dht == null) {
                writeDHT = false;  // Ignored if optimizeHuffman is true
            }
        }

        // Whether we write tables or not, we need to figure out which ones 
        // to use
        if (qTables == null) { // Get them from metadata, or use defaults
            if (dqt != null) {
                qTables = collectQTablesFromMetadata(metadata);
            } else if (streamQTables != null) { 
                qTables = streamQTables;
            } else if ((jparam != null) && (jparam.areTablesSet())) {
                qTables = jparam.getQTables();
            } else {
                qTables = JPEG.getDefaultQTables();
            }

        }

        // If we are optimizing, we don't want any tables.
        if (optimizeHuffman == false) {
            // If they were for progressive scans, we can't use them.
            if ((dht != null) && (metadataProgressive == false)) {
                DCHuffmanTables = collectHTablesFromMetadata(metadata, true);
                ACHuffmanTables = collectHTablesFromMetadata(metadata, false);
            } else if (streamDCHuffmanTables != null) { 
                DCHuffmanTables = streamDCHuffmanTables;
                ACHuffmanTables = streamACHuffmanTables;
            } else if ((jparam != null) && (jparam.areTablesSet())) {
                DCHuffmanTables = jparam.getDCHuffmanTables();
                ACHuffmanTables = jparam.getACHuffmanTables();
            } else {
                DCHuffmanTables = JPEG.getDefaultHuffmanTables(true);
                ACHuffmanTables = JPEG.getDefaultHuffmanTables(false);
            }
        }

        // By default, ids are 1 - N, no subsampling
        int [] componentIds = new int[numBandsUsed];
        int [] HsamplingFactors = new int[numBandsUsed];
        int [] VsamplingFactors = new int[numBandsUsed];
        int [] QtableSelectors = new int[numBandsUsed];
        for (int i = 0; i < numBandsUsed; i++) {
            componentIds[i] = i+1; // JFIF compatible
            HsamplingFactors[i] = 1;
            VsamplingFactors[i] = 1;
            QtableSelectors[i] = 0;
        }

        // Now override them with the contents of sof, if there is one,
        if (sof != null) {
            for (int i = 0; i < numBandsUsed; i++) {
                if (forceJFIF == false) {  // else use JFIF-compatible default
                    componentIds[i] = sof.componentSpecs[i].componentId;
                }
                HsamplingFactors[i] = sof.componentSpecs[i].HsamplingFactor;
                VsamplingFactors[i] = sof.componentSpecs[i].VsamplingFactor;
                QtableSelectors[i] = sof.componentSpecs[i].QtableSelector;
            }
        }

        sourceXOffset += gridX;
        sourceWidth -= gridX;
        sourceYOffset += gridY;
        sourceHeight -= gridY;

        int destWidth = (sourceWidth + periodX - 1)/periodX;
        int destHeight = (sourceHeight + periodY - 1)/periodY;

        // Create an appropriate 1-line databuffer for writing
        int lineSize = sourceWidth*numBandsUsed;
        
        DataBufferByte buffer = new DataBufferByte(lineSize);

        // Create a raster from that
        int [] bandOffs = JPEG.bandOffsets[numBandsUsed-1];
        
        raster = Raster.createInterleavedRaster(buffer, 
                                                sourceWidth, 1, 
                                                lineSize,
                                                numBandsUsed,
                                                bandOffs,
                                                null);

        // Call the writer, who will call back for every scanline

        processImageStarted(currentImage);
            
        boolean aborted = false;

        if (debug) {
            System.out.println("inCsType: " + inCsType);
            System.out.println("outCsType: " + outCsType);
        }

        aborted = writeImage(structPointer,
                             buffer.getData(), 
                             inCsType, outCsType,
                             numBandsUsed,
                             bandSizes,
                             sourceWidth,
                             destWidth, destHeight,
                             periodX, periodY,
                             qTables,
                             writeDQT,
                             DCHuffmanTables,
                             ACHuffmanTables,
                             writeDHT,
                             optimizeHuffman,
                             (progressiveMode 
                              != ImageWriteParam.MODE_DISABLED),
                             numScans,
                             scans,
                             componentIds,
                             HsamplingFactors,
                             VsamplingFactors,
                             QtableSelectors,
                             haveMetadata,
                             restartInterval);

        if (aborted) {
            processWriteAborted();
        } else {
            processImageComplete();
        }

        ios.flush();
        currentImage++;  // After a successful write
    
private native booleanwriteImage(long structPointer, byte[] data, int inCsType, int outCsType, int numBands, int[] bandSizes, int srcWidth, int destWidth, int destHeight, int stepX, int stepY, javax.imageio.plugins.jpeg.JPEGQTable[] qtables, boolean writeDQT, javax.imageio.plugins.jpeg.JPEGHuffmanTable[] DCHuffmanTables, javax.imageio.plugins.jpeg.JPEGHuffmanTable[] ACHuffmanTables, boolean writeDHT, boolean optimizeHuffman, boolean progressive, int numScans, int[] scans, int[] componentIds, int[] HsamplingFactors, int[] VsamplingFactors, int[] QtableSelectors, boolean haveMetadata, int restartInterval)
Returns true if the write was aborted.

private voidwriteMetadata()
Writes the metadata out when called by the native code, which will have already written the header to the stream and established the library state. This is simpler than breaking the write call in two.

        if (metadata == null) {
            if (writeDefaultJFIF) {
                JFIFMarkerSegment.writeDefaultJFIF(ios, 
                                                   thumbnails, 
                                                   iccProfile,
                                                   this);
            }
            if (writeAdobe) {
                AdobeMarkerSegment.writeAdobeSegment(ios, newAdobeTransform);
            }
        } else {
            metadata.writeToStream(ios, 
                                   ignoreJFIF, 
                                   forceJFIF,
                                   thumbnails,
                                   iccProfile,
                                   ignoreAdobe, 
                                   newAdobeTransform,
                                   this);
        }
    
private native voidwriteTables(long structPointer, javax.imageio.plugins.jpeg.JPEGQTable[] qtables, javax.imageio.plugins.jpeg.JPEGHuffmanTable[] DCHuffmanTables, javax.imageio.plugins.jpeg.JPEGHuffmanTable[] ACHuffmanTables)
Write out a tables-only image to the stream.

public voidwriteToSequence(javax.imageio.IIOImage image, javax.imageio.ImageWriteParam param)

        if (sequencePrepared == false) {
            throw new IllegalStateException("sequencePrepared not called!");
        }
        // In the case of JPEG this does nothing different from write
        write(null, image, param);