< prev index next >

src/java.desktop/share/classes/com/sun/imageio/plugins/png/PNGImageWriter.java

Print this page

        

@@ -24,38 +24,34 @@
  */
 
 package com.sun.imageio.plugins.png;
 
 import java.awt.Rectangle;
-import java.awt.image.ColorModel;
 import java.awt.image.IndexColorModel;
 import java.awt.image.Raster;
 import java.awt.image.WritableRaster;
 import java.awt.image.RenderedImage;
 import java.awt.image.SampleModel;
 import java.io.ByteArrayOutputStream;
-import java.io.DataOutput;
 import java.io.IOException;
-import java.io.OutputStream;
 import java.util.Iterator;
 import java.util.Locale;
 import java.util.zip.Deflater;
 import java.util.zip.DeflaterOutputStream;
 import javax.imageio.IIOException;
 import javax.imageio.IIOImage;
 import javax.imageio.ImageTypeSpecifier;
 import javax.imageio.ImageWriteParam;
 import javax.imageio.ImageWriter;
 import javax.imageio.metadata.IIOMetadata;
-import javax.imageio.metadata.IIOMetadata;
 import javax.imageio.spi.ImageWriterSpi;
 import javax.imageio.stream.ImageOutputStream;
 import javax.imageio.stream.ImageOutputStreamImpl;
 
-class CRC {
+final class CRC {
 
-    private static int[] crcTable = new int[256];
+    private static final int[] crcTable = new int[256];
     private int crc = 0xffffffff;
 
     static {
         // Initialize CRC table
         for (int n = 0; n < 256; n++) {

@@ -70,65 +66,71 @@
                 crcTable[n] = c;
             }
         }
     }
 
-    public CRC() {}
+    CRC() {}
 
-    public void reset() {
+    void reset() {
         crc = 0xffffffff;
     }
 
-    public void update(byte[] data, int off, int len) {
+    void update(byte[] data, int off, int len) {
+        int c = crc;
         for (int n = 0; n < len; n++) {
-            crc = crcTable[(crc ^ data[off + n]) & 0xff] ^ (crc >>> 8);
+            c = crcTable[(c ^ data[off + n]) & 0xff] ^ (c >>> 8);
         }
+        crc = c;
     }
 
-    public void update(int data) {
+    void update(int data) {
         crc = crcTable[(crc ^ data) & 0xff] ^ (crc >>> 8);
     }
 
-    public int getValue() {
+    int getValue() {
         return crc ^ 0xffffffff;
     }
 }
 
 
 final class ChunkStream extends ImageOutputStreamImpl {
 
-    private ImageOutputStream stream;
-    private long startPos;
-    private CRC crc = new CRC();
+    private final ImageOutputStream stream;
+    private final long startPos;
+    private final CRC crc = new CRC();
 
-    public ChunkStream(int type, ImageOutputStream stream) throws IOException {
+    ChunkStream(int type, ImageOutputStream stream) throws IOException {
         this.stream = stream;
         this.startPos = stream.getStreamPosition();
 
         stream.writeInt(-1); // length, will backpatch
         writeInt(type);
     }
 
+    @Override
     public int read() throws IOException {
         throw new RuntimeException("Method not available");
     }
 
+    @Override
     public int read(byte[] b, int off, int len) throws IOException {
         throw new RuntimeException("Method not available");
     }
 
+    @Override
     public void write(byte[] b, int off, int len) throws IOException {
         crc.update(b, off, len);
         stream.write(b, off, len);
     }
 
+    @Override
     public void write(int b) throws IOException {
         crc.update(b);
         stream.write(b);
     }
 
-    public void finish() throws IOException {
+    void finish() throws IOException {
         // Write CRC
         stream.writeInt(crc.getValue());
 
         // Write length
         long pos = stream.getStreamPosition();

@@ -138,38 +140,44 @@
         // Return to end of chunk and flush to minimize buffering
         stream.seek(pos);
         stream.flushBefore(pos);
     }
 
+    @Override
     protected void finalize() throws Throwable {
         // Empty finalizer (for improved performance; no need to call
         // super.finalize() in this case)
     }
 }
 
 // Compress output and write as a series of 'IDAT' chunks of
 // fixed length.
 final class IDATOutputStream extends ImageOutputStreamImpl {
 
-    private static byte[] chunkType = {
+    private static final byte[] chunkType = {
         (byte)'I', (byte)'D', (byte)'A', (byte)'T'
     };
 
-    private ImageOutputStream stream;
-    private int chunkLength;
+    private final ImageOutputStream stream;
+    private final int chunkLength;
     private long startPos;
-    private CRC crc = new CRC();
+    private final CRC crc = new CRC();
 
-    Deflater def = new Deflater(Deflater.BEST_COMPRESSION);
-    byte[] buf = new byte[512];
+    private final Deflater def;
+    private final byte[] buf = new byte[512];
+    // reused 1 byte[] array:
+    private final byte[] wbuf1 = new byte[1];
 
     private int bytesRemaining;
 
-    public IDATOutputStream(ImageOutputStream stream, int chunkLength)
-        throws IOException {
+    IDATOutputStream(ImageOutputStream stream, int chunkLength,
+                            int deflaterLevel) throws IOException
+    {
         this.stream = stream;
         this.chunkLength = chunkLength;
+        this.def = new Deflater(deflaterLevel);
+
         startChunk();
     }
 
     private void startChunk() throws IOException {
         crc.reset();

@@ -204,18 +212,21 @@
             this.startPos = stream.getStreamPosition();
             throw e;
         }
     }
 
+    @Override
     public int read() throws IOException {
         throw new RuntimeException("Method not available");
     }
 
+    @Override
     public int read(byte[] b, int off, int len) throws IOException {
         throw new RuntimeException("Method not available");
     }
 
+    @Override
     public void write(byte[] b, int off, int len) throws IOException {
         if (len == 0) {
             return;
         }
 

@@ -225,11 +236,11 @@
                 deflate();
             }
         }
     }
 
-    public void deflate() throws IOException {
+    void deflate() throws IOException {
         int len = def.deflate(buf, 0, buf.length);
         int off = 0;
 
         while (len > 0) {
             if (bytesRemaining == 0) {

@@ -245,17 +256,17 @@
             len -= nbytes;
             bytesRemaining -= nbytes;
         }
     }
 
+    @Override
     public void write(int b) throws IOException {
-        byte[] wbuf = new byte[1];
-        wbuf[0] = (byte)b;
-        write(wbuf, 0, 1);
+        wbuf1[0] = (byte)b;
+        write(wbuf1, 0, 1);
     }
 
-    public void finish() throws IOException {
+    void finish() throws IOException {
         try {
             if (!def.finished()) {
                 def.finish();
                 while (!def.finished()) {
                     deflate();

@@ -265,32 +276,94 @@
         } finally {
             def.end();
         }
     }
 
+    @Override
     protected void finalize() throws Throwable {
         // Empty finalizer (for improved performance; no need to call
         // super.finalize() in this case)
     }
 }
 
 
-class PNGImageWriteParam extends ImageWriteParam {
+final class PNGImageWriteParam extends ImageWriteParam {
+
+    /** Default quality level = 0.5 ie medium compression */
+    private static final float DEFAULT_QUALITY = 0.5f;
+
+    private static final String[] compressionNames = {"Deflate"};
+    private static final float[] qualityVals = { 0.00F, 0.30F, 0.75F, 1.00F };
+    private static final String[] qualityDescs = {
+        "High compression",   // 0.00 -> 0.30
+        "Medium compression", // 0.30 -> 0.75
+        "Low compression"     // 0.75 -> 1.00
+    };
 
-    public PNGImageWriteParam(Locale locale) {
+    PNGImageWriteParam(Locale locale) {
         super();
         this.canWriteProgressive = true;
         this.locale = locale;
+        this.canWriteCompressed = true;
+        this.compressionTypes = compressionNames;
+        this.compressionType = compressionTypes[0];
+        this.compressionMode = MODE_DEFAULT;
+        this.compressionQuality = DEFAULT_QUALITY;
+    }
+
+    /**
+     * Removes any previous compression quality setting.
+     *
+     * <p> The default implementation resets the compression quality
+     * to <code>0.5F</code>.
+     *
+     * @exception IllegalStateException if the compression mode is not
+     * <code>MODE_EXPLICIT</code>.
+     */
+    @Override
+    public void unsetCompression() {
+        super.unsetCompression();
+        this.compressionType = compressionTypes[0];
+        this.compressionQuality = DEFAULT_QUALITY;
+    }
+
+    /**
+     * Returns <code>true</code> since the PNG plug-in only supports
+     * lossless compression.
+     *
+     * @return <code>true</code>.
+     */
+    @Override
+    public boolean isCompressionLossless() {
+        return true;
+    }
+
+    @Override
+    public String[] getCompressionQualityDescriptions() {
+        super.getCompressionQualityDescriptions();
+        return qualityDescs.clone();
+    }
+
+    @Override
+    public float[] getCompressionQualityValues() {
+        super.getCompressionQualityValues();
+        return qualityVals.clone();
     }
 }
 
 /**
  */
-public class PNGImageWriter extends ImageWriter {
+public final class PNGImageWriter extends ImageWriter {
+
+    /** Default compression level = 4 ie medium compression */
+    private static final int DEFAULT_COMPRESSION_LEVEL = 4;
 
     ImageOutputStream stream = null;
 
+    // compression level
+    int deflaterLevel = DEFAULT_COMPRESSION_LEVEL;
+
     PNGMetadata metadata = null;
 
     // Factors from the ImageWriteParam
     int sourceXOffset = 0;
     int sourceYOffset = 0;

@@ -332,10 +405,11 @@
 
     public PNGImageWriter(ImageWriterSpi originatingProvider) {
         super(originatingProvider);
     }
 
+    @Override
     public void setOutput(Object output) {
         super.setOutput(output);
         if (output != null) {
             if (!(output instanceof ImageOutputStream)) {
                 throw new IllegalArgumentException("output not an ImageOutputStream!");

@@ -344,32 +418,35 @@
         } else {
             this.stream = null;
         }
     }
 
-    private static int[] allowedProgressivePasses = { 1, 7 };
-
+    @Override
     public ImageWriteParam getDefaultWriteParam() {
         return new PNGImageWriteParam(getLocale());
     }
 
+    @Override
     public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
         return null;
     }
 
+    @Override
     public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType,
                                                ImageWriteParam param) {
         PNGMetadata m = new PNGMetadata();
         m.initialize(imageType, imageType.getSampleModel().getNumBands());
         return m;
     }
 
+    @Override
     public IIOMetadata convertStreamMetadata(IIOMetadata inData,
                                              ImageWriteParam param) {
         return null;
     }
 
+    @Override
     public IIOMetadata convertImageMetadata(IIOMetadata inData,
                                             ImageTypeSpecifier imageType,
                                             ImageWriteParam param) {
         // TODO - deal with imageType
         if (inData instanceof PNGMetadata) {

@@ -933,11 +1010,12 @@
         }
     }
 
     // Use sourceXOffset, etc.
     private void write_IDAT(RenderedImage image) throws IOException {
-        IDATOutputStream ios = new IDATOutputStream(stream, 32768);
+        IDATOutputStream ios = new IDATOutputStream(stream, 32768,
+                                                    deflaterLevel);
         try {
             if (metadata.IHDR_interlaceMethod == 1) {
                 for (int i = 0; i < 7; i++) {
                     encodePass(ios, image,
                                PNGImageReader.adam7XOffset[i],

@@ -1026,10 +1104,11 @@
             scale = null;
             scale0 = null;
         }
     }
 
+    @Override
     public void write(IIOMetadata streamMetadata,
                       IIOImage image,
                       ImageWriteParam param) throws IIOException {
         if (stream == null) {
             throw new IllegalStateException("output == null!");

@@ -1109,20 +1188,39 @@
         } else {
             metadata = new PNGMetadata();
         }
 
         if (param != null) {
+            switch(param.getCompressionMode()) {
+            case ImageWriteParam.MODE_DISABLED:
+                deflaterLevel = Deflater.NO_COMPRESSION;
+                break;
+            case ImageWriteParam.MODE_EXPLICIT:
+                float quality = param.getCompressionQuality();
+                if (quality >= 0f && quality <= 1f) {
+                    deflaterLevel = 9 - Math.round(9f * quality);
+                } else {
+                    deflaterLevel = DEFAULT_COMPRESSION_LEVEL;
+                }
+                break;
+            case ImageWriteParam.MODE_DEFAULT:
+                deflaterLevel = DEFAULT_COMPRESSION_LEVEL;
+                break;
+            default:
+            }
+
             // Use Adam7 interlacing if set in write param
             switch (param.getProgressiveMode()) {
             case ImageWriteParam.MODE_DEFAULT:
                 metadata.IHDR_interlaceMethod = 1;
                 break;
             case ImageWriteParam.MODE_DISABLED:
                 metadata.IHDR_interlaceMethod = 0;
                 break;
-                // MODE_COPY_FROM_METADATA should alreay be taken care of
+                // MODE_COPY_FROM_METADATA should already be taken care of
                 // MODE_EXPLICIT is not allowed
+            default:
             }
         }
 
         // Initialize bitDepth and colorType
         metadata.initialize(new ImageTypeSpecifier(im), numBands);
< prev index next >