1809 lines · 69.7 KB · Java
RawDownload
/* * Copyright (c) 2000, 2007, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation.  Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.imageio.plugins.jpeg; import javax.imageio.IIOException;import javax.imageio.ImageWriter;import javax.imageio.ImageWriteParam;import javax.imageio.IIOImage;import javax.imageio.ImageTypeSpecifier;import javax.imageio.metadata.IIOMetadata;import javax.imageio.metadata.IIOMetadataFormatImpl;import javax.imageio.metadata.IIOInvalidTreeException;import javax.imageio.spi.ImageWriterSpi;import javax.imageio.stream.ImageOutputStream;import javax.imageio.plugins.jpeg.JPEGImageWriteParam;import javax.imageio.plugins.jpeg.JPEGQTable;import javax.imageio.plugins.jpeg.JPEGHuffmanTable; import org.w3c.dom.Node; import java.awt.image.Raster;import java.awt.image.WritableRaster;import java.awt.image.SampleModel;import java.awt.image.DataBuffer;import java.awt.image.DataBufferByte;import java.awt.image.ColorModel;import java.awt.image.IndexColorModel;import java.awt.image.ColorConvertOp;import java.awt.image.RenderedImage;import java.awt.image.BufferedImage;import java.awt.color.ColorSpace;import java.awt.color.ICC_ColorSpace;import java.awt.color.ICC_Profile;import java.awt.Dimension;import java.awt.Rectangle;import java.awt.Transparency; import java.io.IOException; import java.util.List;import java.util.ArrayList;import java.util.Iterator; import sun.java2d.Disposer;import sun.java2d.DisposerRecord; public class JPEGImageWriter extends ImageWriter {     ///////// Private variables     private boolean debug = false;     /**     * 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 long structPointer = 0;      /** The output stream we write to */    private ImageOutputStream ios = null;     /** The Raster we will write from */    private Raster srcRas = null;     /** An intermediate Raster holding compressor-friendly data */    private WritableRaster raster = null;     /**     * Set to true if we are writing an image with an     * indexed ColorModel     */    private boolean indexed = false;    private IndexColorModel indexCM = null;     private boolean convertTosRGB = false;  // Used by PhotoYCC only    private WritableRaster converted = null;     private boolean isAlphaPremultiplied = false;    private ColorModel srcCM = null;     /**     * If there are thumbnails to be written, this is the list.     */    private List thumbnails = null;     /**     * If metadata should include an icc profile, store it here.     */    private ICC_Profile iccProfile = null;     private int sourceXOffset = 0;    private int sourceYOffset = 0;    private int sourceWidth = 0;    private int [] srcBands = null;    private int sourceHeight = 0;     /** Used when calling listeners */    private int currentImage = 0;     private ColorConvertOp convertOp = null;     private JPEGQTable [] streamQTables = null;    private JPEGHuffmanTable[] streamDCHuffmanTables = null;    private JPEGHuffmanTable[] streamACHuffmanTables = null;     // Parameters for writing metadata    private boolean ignoreJFIF = false;  // If it's there, use it    private boolean forceJFIF = false;  // Add one for the thumbnails    private boolean ignoreAdobe = false;  // If it's there, use it    private int newAdobeTransform = JPEG.ADOBE_IMPOSSIBLE;  // Change if needed    private boolean writeDefaultJFIF = false;    private boolean writeAdobe = false;    private JPEGMetadata metadata = null;     private boolean sequencePrepared = false;     private int numScans = 0;     /** The referent to be registered with the Disposer. */    private Object disposerReferent = new Object();     /** The DisposerRecord that handles the actual disposal of this writer. */    private DisposerRecord disposerRecord;     ///////// End of Private variables     ///////// Protected variables     protected static final int WARNING_DEST_IGNORED = 0;    protected static final int WARNING_STREAM_METADATA_IGNORED = 1;    protected static final int WARNING_DEST_METADATA_COMP_MISMATCH = 2;    protected static final int WARNING_DEST_METADATA_JFIF_MISMATCH = 3;    protected static final int WARNING_DEST_METADATA_ADOBE_MISMATCH = 4;    protected static final int WARNING_IMAGE_METADATA_JFIF_MISMATCH = 5;    protected static final int WARNING_IMAGE_METADATA_ADOBE_MISMATCH = 6;    protected static final int WARNING_METADATA_NOT_JPEG_FOR_RASTER = 7;    protected static final int WARNING_NO_BANDS_ON_INDEXED = 8;    protected static final int WARNING_ILLEGAL_THUMBNAIL = 9;    protected static final int WARNING_IGNORING_THUMBS = 10;    protected static final int WARNING_FORCING_JFIF = 11;    protected static final int WARNING_THUMB_CLIPPED = 12;    protected static final int WARNING_METADATA_ADJUSTED_FOR_THUMB = 13;    protected static final int WARNING_NO_RGB_THUMB_AS_INDEXED = 14;    protected static final int WARNING_NO_GRAY_THUMB_AS_INDEXED = 15;     private static final int MAX_WARNING = WARNING_NO_GRAY_THUMB_AS_INDEXED;     ///////// End of Protected variables     ///////// static initializer     static {        java.security.AccessController.doPrivileged(            new sun.security.action.LoadLibraryAction("jpeg"));        initWriterIDs(ImageOutputStream.class,                      JPEGQTable.class,                      JPEGHuffmanTable.class);    }     //////// Public API     public JPEGImageWriter(ImageWriterSpi originator) {        super(originator);        structPointer = initJPEGImageWriter();        disposerRecord = new JPEGWriterDisposerRecord(structPointer);        Disposer.addRecord(disposerReferent, disposerRecord);    }     public void setOutput(Object output) {        setThreadLock();        try {            super.setOutput(output); // validates output            resetInternalState();            ios = (ImageOutputStream) output; // so this will always work            // Set the native destination            setDest(structPointer, ios);        } finally {            clearThreadLock();        }    }     public ImageWriteParam getDefaultWriteParam() {        return new JPEGImageWriteParam(null);    }     public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {        setThreadLock();        try {            return new JPEGMetadata(param, this);        } finally {            clearThreadLock();        }    }     public IIOMetadata        getDefaultImageMetadata(ImageTypeSpecifier imageType,                                ImageWriteParam param) {        setThreadLock();        try {            return new JPEGMetadata(imageType, param, this);        } finally {            clearThreadLock();        }    }     public IIOMetadata convertStreamMetadata(IIOMetadata inData,                                             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 IIOMetadata        convertImageMetadata(IIOMetadata inData,                             ImageTypeSpecifier imageType,                             ImageWriteParam param) {        setThreadLock();        try {            return convertImageMetadataOnThread(inData, imageType, param);        } finally {            clearThreadLock();        }    }     private IIOMetadata        convertImageMetadataOnThread(IIOMetadata inData,                                     ImageTypeSpecifier imageType,                                     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 int getNumThumbnailsSupported(ImageTypeSpecifier imageType,                                         ImageWriteParam param,                                         IIOMetadata streamMetadata,                                         IIOMetadata imageMetadata) {        if (jfifOK(imageType, param, streamMetadata, imageMetadata)) {            return Integer.MAX_VALUE;        }        return 0;    }     static final Dimension [] preferredThumbSizes = {new Dimension(1, 1),                                                     new Dimension(255, 255)};     public Dimension[] getPreferredThumbnailSizes(ImageTypeSpecifier imageType,                                                  ImageWriteParam param,                                                  IIOMetadata streamMetadata,                                                  IIOMetadata imageMetadata) {        if (jfifOK(imageType, param, streamMetadata, imageMetadata)) {            return (Dimension [])preferredThumbSizes.clone();        }        return null;    }     private boolean jfifOK(ImageTypeSpecifier imageType,                           ImageWriteParam param,                           IIOMetadata streamMetadata,                           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 boolean canWriteRasters() {        return true;    }     public void write(IIOMetadata streamMetadata,                      IIOImage image,                      ImageWriteParam param) throws IOException {        setThreadLock();        try {            writeOnThread(streamMetadata, image, param);        } finally {            clearThreadLock();        }    }     private void writeOnThread(IIOMetadata streamMetadata,                      IIOImage image,                      ImageWriteParam param) throws IOException {         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) {                // Use the Raster directly.                srcRas = ((BufferedImage)rimage).getRaster();            } else if (rimage.getNumXTiles() == 1 &&                       rimage.getNumYTiles() == 1)            {                // Get the unique tile.                srcRas = rimage.getTile(rimage.getMinTileX(),                                        rimage.getMinTileY());                 // Ensure the Raster has dimensions of the image,                // as the tile dimensions might differ.                if (srcRas.getWidth() != rimage.getWidth() ||                    srcRas.getHeight() != rimage.getHeight())                {                    srcRas = srcRas.createChild(srcRas.getMinX(),                                                srcRas.getMinY(),                                                rimage.getWidth(),                                                rimage.getHeight(),                                                srcRas.getMinX(),                                                srcRas.getMinY(),                                                null);                }            } else {                // Image is tiled so get a contiguous raster by copying.                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;            }

Lines 1–500 of 1809.