< prev index next >

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

Print this page




   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.imageio.plugins.png;
  27 
  28 import java.awt.Rectangle;
  29 import java.awt.image.ColorModel;
  30 import java.awt.image.IndexColorModel;
  31 import java.awt.image.Raster;
  32 import java.awt.image.WritableRaster;
  33 import java.awt.image.RenderedImage;
  34 import java.awt.image.SampleModel;
  35 import java.io.ByteArrayOutputStream;
  36 import java.io.DataOutput;
  37 import java.io.IOException;
  38 import java.io.OutputStream;
  39 import java.util.Iterator;
  40 import java.util.Locale;
  41 import java.util.zip.Deflater;
  42 import java.util.zip.DeflaterOutputStream;
  43 import javax.imageio.IIOException;
  44 import javax.imageio.IIOImage;
  45 import javax.imageio.ImageTypeSpecifier;
  46 import javax.imageio.ImageWriteParam;
  47 import javax.imageio.ImageWriter;
  48 import javax.imageio.metadata.IIOMetadata;
  49 import javax.imageio.metadata.IIOMetadata;
  50 import javax.imageio.spi.ImageWriterSpi;
  51 import javax.imageio.stream.ImageOutputStream;
  52 import javax.imageio.stream.ImageOutputStreamImpl;
  53 
  54 class CRC {
  55 
  56     private static int[] crcTable = new int[256];
  57     private int crc = 0xffffffff;
  58 
  59     static {
  60         // Initialize CRC table
  61         for (int n = 0; n < 256; n++) {
  62             int c = n;
  63             for (int k = 0; k < 8; k++) {
  64                 if ((c & 1) == 1) {
  65                     c = 0xedb88320 ^ (c >>> 1);
  66                 } else {
  67                     c >>>= 1;
  68                 }
  69 
  70                 crcTable[n] = c;
  71             }
  72         }
  73     }
  74 
  75     public CRC() {}
  76 
  77     public void reset() {
  78         crc = 0xffffffff;
  79     }
  80 
  81     public void update(byte[] data, int off, int len) {

  82         for (int n = 0; n < len; n++) {
  83             crc = crcTable[(crc ^ data[off + n]) & 0xff] ^ (crc >>> 8);
  84         }

  85     }
  86 
  87     public void update(int data) {
  88         crc = crcTable[(crc ^ data) & 0xff] ^ (crc >>> 8);
  89     }
  90 
  91     public int getValue() {
  92         return crc ^ 0xffffffff;
  93     }
  94 }
  95 
  96 
  97 final class ChunkStream extends ImageOutputStreamImpl {
  98 
  99     private ImageOutputStream stream;
 100     private long startPos;
 101     private CRC crc = new CRC();
 102 
 103     public ChunkStream(int type, ImageOutputStream stream) throws IOException {
 104         this.stream = stream;
 105         this.startPos = stream.getStreamPosition();
 106 
 107         stream.writeInt(-1); // length, will backpatch
 108         writeInt(type);
 109     }
 110 

 111     public int read() throws IOException {
 112         throw new RuntimeException("Method not available");
 113     }
 114 

 115     public int read(byte[] b, int off, int len) throws IOException {
 116         throw new RuntimeException("Method not available");
 117     }
 118 

 119     public void write(byte[] b, int off, int len) throws IOException {
 120         crc.update(b, off, len);
 121         stream.write(b, off, len);
 122     }
 123 

 124     public void write(int b) throws IOException {
 125         crc.update(b);
 126         stream.write(b);
 127     }
 128 
 129     public void finish() throws IOException {
 130         // Write CRC
 131         stream.writeInt(crc.getValue());
 132 
 133         // Write length
 134         long pos = stream.getStreamPosition();
 135         stream.seek(startPos);
 136         stream.writeInt((int)(pos - startPos) - 12);
 137 
 138         // Return to end of chunk and flush to minimize buffering
 139         stream.seek(pos);
 140         stream.flushBefore(pos);
 141     }
 142 

 143     protected void finalize() throws Throwable {
 144         // Empty finalizer (for improved performance; no need to call
 145         // super.finalize() in this case)
 146     }
 147 }
 148 
 149 // Compress output and write as a series of 'IDAT' chunks of
 150 // fixed length.
 151 final class IDATOutputStream extends ImageOutputStreamImpl {
 152 
 153     private static byte[] chunkType = {
 154         (byte)'I', (byte)'D', (byte)'A', (byte)'T'
 155     };
 156 
 157     private ImageOutputStream stream;
 158     private int chunkLength;
 159     private long startPos;
 160     private CRC crc = new CRC();
 161 
 162     Deflater def = new Deflater(Deflater.BEST_COMPRESSION);
 163     byte[] buf = new byte[512];


 164 
 165     private int bytesRemaining;
 166 
 167     public IDATOutputStream(ImageOutputStream stream, int chunkLength)
 168         throws IOException {

 169         this.stream = stream;
 170         this.chunkLength = chunkLength;


 171         startChunk();
 172     }
 173 
 174     private void startChunk() throws IOException {
 175         crc.reset();
 176         this.startPos = stream.getStreamPosition();
 177         stream.writeInt(-1); // length, will backpatch
 178 
 179         crc.update(chunkType, 0, 4);
 180         stream.write(chunkType, 0, 4);
 181 
 182         this.bytesRemaining = chunkLength;
 183     }
 184 
 185     private void finishChunk() throws IOException {
 186         // Write CRC
 187         stream.writeInt(crc.getValue());
 188 
 189         // Write length
 190         long pos = stream.getStreamPosition();
 191         stream.seek(startPos);
 192         stream.writeInt((int)(pos - startPos) - 12);
 193 
 194         // Return to end of chunk and flush to minimize buffering
 195         stream.seek(pos);
 196         try {
 197             stream.flushBefore(pos);
 198         } catch (IOException e) {
 199             /*
 200              * If flushBefore() fails we try to access startPos in finally
 201              * block of write_IDAT(). We should update startPos to avoid
 202              * IndexOutOfBoundException while seek() is happening.
 203              */
 204             this.startPos = stream.getStreamPosition();
 205             throw e;
 206         }
 207     }
 208 

 209     public int read() throws IOException {
 210         throw new RuntimeException("Method not available");
 211     }
 212 

 213     public int read(byte[] b, int off, int len) throws IOException {
 214         throw new RuntimeException("Method not available");
 215     }
 216 

 217     public void write(byte[] b, int off, int len) throws IOException {
 218         if (len == 0) {
 219             return;
 220         }
 221 
 222         if (!def.finished()) {
 223             def.setInput(b, off, len);
 224             while (!def.needsInput()) {
 225                 deflate();
 226             }
 227         }
 228     }
 229 
 230     public void deflate() throws IOException {
 231         int len = def.deflate(buf, 0, buf.length);
 232         int off = 0;
 233 
 234         while (len > 0) {
 235             if (bytesRemaining == 0) {
 236                 finishChunk();
 237                 startChunk();
 238             }
 239 
 240             int nbytes = Math.min(len, bytesRemaining);
 241             crc.update(buf, off, nbytes);
 242             stream.write(buf, off, nbytes);
 243 
 244             off += nbytes;
 245             len -= nbytes;
 246             bytesRemaining -= nbytes;
 247         }
 248     }
 249 

 250     public void write(int b) throws IOException {
 251         byte[] wbuf = new byte[1];
 252         wbuf[0] = (byte)b;
 253         write(wbuf, 0, 1);
 254     }
 255 
 256     public void finish() throws IOException {
 257         try {
 258             if (!def.finished()) {
 259                 def.finish();
 260                 while (!def.finished()) {
 261                     deflate();
 262                 }
 263             }
 264             finishChunk();
 265         } finally {
 266             def.end();
 267         }
 268     }
 269 

 270     protected void finalize() throws Throwable {
 271         // Empty finalizer (for improved performance; no need to call
 272         // super.finalize() in this case)
 273     }
 274 }
 275 
 276 
 277 class PNGImageWriteParam extends ImageWriteParam {











 278 
 279     public PNGImageWriteParam(Locale locale) {
 280         super();
 281         this.canWriteProgressive = true;
 282         this.locale = locale;












































 283     }
 284 }
 285 
 286 /**
 287  */
 288 public class PNGImageWriter extends ImageWriter {



 289 
 290     ImageOutputStream stream = null;
 291 



 292     PNGMetadata metadata = null;
 293 
 294     // Factors from the ImageWriteParam
 295     int sourceXOffset = 0;
 296     int sourceYOffset = 0;
 297     int sourceWidth = 0;
 298     int sourceHeight = 0;
 299     int[] sourceBands = null;
 300     int periodX = 1;
 301     int periodY = 1;
 302 
 303     int numBands;
 304     int bpp;
 305 
 306     RowFilter rowFilter = new RowFilter();
 307     byte[] prevRow = null;
 308     byte[] currRow = null;
 309     byte[][] filteredRows = null;
 310 
 311     // Per-band scaling tables


 317     // given by sampleSize, and an output bit depth given by scalingBitDepth.
 318     //
 319     int[] sampleSize = null; // Sample size per band, in bits
 320     int scalingBitDepth = -1; // Output bit depth of the scaling tables
 321 
 322     // Tables for 1, 2, 4, or 8 bit output
 323     byte[][] scale = null; // 8 bit table
 324     byte[] scale0 = null; // equivalent to scale[0]
 325 
 326     // Tables for 16 bit output
 327     byte[][] scaleh = null; // High bytes of output
 328     byte[][] scalel = null; // Low bytes of output
 329 
 330     int totalPixels; // Total number of pixels to be written by write_IDAT
 331     int pixelsDone; // Running count of pixels written by write_IDAT
 332 
 333     public PNGImageWriter(ImageWriterSpi originatingProvider) {
 334         super(originatingProvider);
 335     }
 336 

 337     public void setOutput(Object output) {
 338         super.setOutput(output);
 339         if (output != null) {
 340             if (!(output instanceof ImageOutputStream)) {
 341                 throw new IllegalArgumentException("output not an ImageOutputStream!");
 342             }
 343             this.stream = (ImageOutputStream)output;
 344         } else {
 345             this.stream = null;
 346         }
 347     }
 348 
 349     private static int[] allowedProgressivePasses = { 1, 7 };
 350 
 351     public ImageWriteParam getDefaultWriteParam() {
 352         return new PNGImageWriteParam(getLocale());
 353     }
 354 

 355     public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
 356         return null;
 357     }
 358 

 359     public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType,
 360                                                ImageWriteParam param) {
 361         PNGMetadata m = new PNGMetadata();
 362         m.initialize(imageType, imageType.getSampleModel().getNumBands());
 363         return m;
 364     }
 365 

 366     public IIOMetadata convertStreamMetadata(IIOMetadata inData,
 367                                              ImageWriteParam param) {
 368         return null;
 369     }
 370 

 371     public IIOMetadata convertImageMetadata(IIOMetadata inData,
 372                                             ImageTypeSpecifier imageType,
 373                                             ImageWriteParam param) {
 374         // TODO - deal with imageType
 375         if (inData instanceof PNGMetadata) {
 376             return (PNGMetadata)((PNGMetadata)inData).clone();
 377         } else {
 378             return new PNGMetadata(inData);
 379         }
 380     }
 381 
 382     private void write_magic() throws IOException {
 383         // Write signature
 384         byte[] magic = { (byte)137, 80, 78, 71, 13, 10, 26, 10 };
 385         stream.write(magic);
 386     }
 387 
 388     private void write_IHDR() throws IOException {
 389         // Write IHDR chunk
 390         ChunkStream cs = new ChunkStream(PNGImageReader.IHDR_TYPE, stream);


 918             os.write(filteredRows[filterType], bpp, bytesPerRow);
 919 
 920             // Swap current and previous rows
 921             byte[] swap = currRow;
 922             currRow = prevRow;
 923             prevRow = swap;
 924 
 925             pixelsDone += hpixels;
 926             processImageProgress(100.0F*pixelsDone/totalPixels);
 927 
 928             // If write has been aborted, just return;
 929             // processWriteAborted will be called later
 930             if (abortRequested()) {
 931                 return;
 932             }
 933         }
 934     }
 935 
 936     // Use sourceXOffset, etc.
 937     private void write_IDAT(RenderedImage image) throws IOException {
 938         IDATOutputStream ios = new IDATOutputStream(stream, 32768);

 939         try {
 940             if (metadata.IHDR_interlaceMethod == 1) {
 941                 for (int i = 0; i < 7; i++) {
 942                     encodePass(ios, image,
 943                                PNGImageReader.adam7XOffset[i],
 944                                PNGImageReader.adam7YOffset[i],
 945                                PNGImageReader.adam7XSubsampling[i],
 946                                PNGImageReader.adam7YSubsampling[i]);
 947                     if (abortRequested()) {
 948                         break;
 949                     }
 950                 }
 951             } else {
 952                 encodePass(ios, image, 0, 0, 1, 1);
 953             }
 954         } finally {
 955             ios.finish();
 956         }
 957     }
 958 


1011             // Divide scaling table into high and low bytes
1012             scaleh = new byte[numBands][];
1013             scalel = new byte[numBands][];
1014 
1015             for (int b = 0; b < numBands; b++) {
1016                 int maxInSample = (1 << sampleSize[b]) - 1;
1017                 int halfMaxInSample = maxInSample/2;
1018                 scaleh[b] = new byte[maxInSample + 1];
1019                 scalel[b] = new byte[maxInSample + 1];
1020                 for (int s = 0; s <= maxInSample; s++) {
1021                     int val = (s*maxOutSample + halfMaxInSample)/maxInSample;
1022                     scaleh[b][s] = (byte)(val >> 8);
1023                     scalel[b][s] = (byte)(val & 0xff);
1024                 }
1025             }
1026             scale = null;
1027             scale0 = null;
1028         }
1029     }
1030 

1031     public void write(IIOMetadata streamMetadata,
1032                       IIOImage image,
1033                       ImageWriteParam param) throws IIOException {
1034         if (stream == null) {
1035             throw new IllegalStateException("output == null!");
1036         }
1037         if (image == null) {
1038             throw new IllegalArgumentException("image == null!");
1039         }
1040         if (image.hasRaster()) {
1041             throw new UnsupportedOperationException("image has a Raster!");
1042         }
1043 
1044         RenderedImage im = image.getRenderedImage();
1045         SampleModel sampleModel = im.getSampleModel();
1046         this.numBands = sampleModel.getNumBands();
1047 
1048         // Set source region and subsampling to default values
1049         this.sourceXOffset = im.getMinX();
1050         this.sourceYOffset = im.getMinY();


1093         int destWidth = (sourceWidth + periodX - 1)/periodX;
1094         int destHeight = (sourceHeight + periodY - 1)/periodY;
1095         if (destWidth <= 0 || destHeight <= 0) {
1096             throw new IllegalArgumentException("Empty source region!");
1097         }
1098 
1099         // Compute total number of pixels for progress notification
1100         this.totalPixels = destWidth*destHeight;
1101         this.pixelsDone = 0;
1102 
1103         // Create metadata
1104         IIOMetadata imd = image.getMetadata();
1105         if (imd != null) {
1106             metadata = (PNGMetadata)convertImageMetadata(imd,
1107                                ImageTypeSpecifier.createFromRenderedImage(im),
1108                                                          null);
1109         } else {
1110             metadata = new PNGMetadata();
1111         }
1112 



1113         if (param != null) {













1114             // Use Adam7 interlacing if set in write param
1115             switch (param.getProgressiveMode()) {
1116             case ImageWriteParam.MODE_DEFAULT:
1117                 metadata.IHDR_interlaceMethod = 1;
1118                 break;
1119             case ImageWriteParam.MODE_DISABLED:
1120                 metadata.IHDR_interlaceMethod = 0;
1121                 break;
1122                 // MODE_COPY_FROM_METADATA should alreay be taken care of
1123                 // MODE_EXPLICIT is not allowed

1124             }
1125         }
1126 
1127         // Initialize bitDepth and colorType
1128         metadata.initialize(new ImageTypeSpecifier(im), numBands);
1129 
1130         // Overwrite IHDR width and height values with values from image
1131         metadata.IHDR_width = destWidth;
1132         metadata.IHDR_height = destHeight;
1133 
1134         this.bpp = numBands*((metadata.IHDR_bitDepth == 16) ? 2 : 1);
1135 
1136         // Initialize scaling tables for this image
1137         initializeScaleTables(sampleModel.getSampleSize());
1138 
1139         clearAbortRequest();
1140 
1141         processImageStarted(0);
1142 
1143         try {




   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.imageio.plugins.png;
  27 
  28 import java.awt.Rectangle;

  29 import java.awt.image.IndexColorModel;
  30 import java.awt.image.Raster;
  31 import java.awt.image.WritableRaster;
  32 import java.awt.image.RenderedImage;
  33 import java.awt.image.SampleModel;
  34 import java.io.ByteArrayOutputStream;

  35 import java.io.IOException;

  36 import java.util.Iterator;
  37 import java.util.Locale;
  38 import java.util.zip.Deflater;
  39 import java.util.zip.DeflaterOutputStream;
  40 import javax.imageio.IIOException;
  41 import javax.imageio.IIOImage;
  42 import javax.imageio.ImageTypeSpecifier;
  43 import javax.imageio.ImageWriteParam;
  44 import javax.imageio.ImageWriter;
  45 import javax.imageio.metadata.IIOMetadata;

  46 import javax.imageio.spi.ImageWriterSpi;
  47 import javax.imageio.stream.ImageOutputStream;
  48 import javax.imageio.stream.ImageOutputStreamImpl;
  49 
  50 final class CRC {
  51 
  52     private static final int[] crcTable = new int[256];
  53     private int crc = 0xffffffff;
  54 
  55     static {
  56         // Initialize CRC table
  57         for (int n = 0; n < 256; n++) {
  58             int c = n;
  59             for (int k = 0; k < 8; k++) {
  60                 if ((c & 1) == 1) {
  61                     c = 0xedb88320 ^ (c >>> 1);
  62                 } else {
  63                     c >>>= 1;
  64                 }
  65 
  66                 crcTable[n] = c;
  67             }
  68         }
  69     }
  70 
  71     CRC() {}
  72 
  73     void reset() {
  74         crc = 0xffffffff;
  75     }
  76 
  77     void update(byte[] data, int off, int len) {
  78         int c = crc;
  79         for (int n = 0; n < len; n++) {
  80             c = crcTable[(c ^ data[off + n]) & 0xff] ^ (c >>> 8);
  81         }
  82         crc = c;
  83     }
  84 
  85     void update(int data) {
  86         crc = crcTable[(crc ^ data) & 0xff] ^ (crc >>> 8);
  87     }
  88 
  89     int getValue() {
  90         return crc ^ 0xffffffff;
  91     }
  92 }
  93 
  94 
  95 final class ChunkStream extends ImageOutputStreamImpl {
  96 
  97     private final ImageOutputStream stream;
  98     private final long startPos;
  99     private final CRC crc = new CRC();
 100 
 101     ChunkStream(int type, ImageOutputStream stream) throws IOException {
 102         this.stream = stream;
 103         this.startPos = stream.getStreamPosition();
 104 
 105         stream.writeInt(-1); // length, will backpatch
 106         writeInt(type);
 107     }
 108 
 109     @Override
 110     public int read() throws IOException {
 111         throw new RuntimeException("Method not available");
 112     }
 113 
 114     @Override
 115     public int read(byte[] b, int off, int len) throws IOException {
 116         throw new RuntimeException("Method not available");
 117     }
 118 
 119     @Override
 120     public void write(byte[] b, int off, int len) throws IOException {
 121         crc.update(b, off, len);
 122         stream.write(b, off, len);
 123     }
 124 
 125     @Override
 126     public void write(int b) throws IOException {
 127         crc.update(b);
 128         stream.write(b);
 129     }
 130 
 131     void finish() throws IOException {
 132         // Write CRC
 133         stream.writeInt(crc.getValue());
 134 
 135         // Write length
 136         long pos = stream.getStreamPosition();
 137         stream.seek(startPos);
 138         stream.writeInt((int)(pos - startPos) - 12);
 139 
 140         // Return to end of chunk and flush to minimize buffering
 141         stream.seek(pos);
 142         stream.flushBefore(pos);
 143     }
 144 
 145     @Override
 146     protected void finalize() throws Throwable {
 147         // Empty finalizer (for improved performance; no need to call
 148         // super.finalize() in this case)
 149     }
 150 }
 151 
 152 // Compress output and write as a series of 'IDAT' chunks of
 153 // fixed length.
 154 final class IDATOutputStream extends ImageOutputStreamImpl {
 155 
 156     private static final byte[] chunkType = {
 157         (byte)'I', (byte)'D', (byte)'A', (byte)'T'
 158     };
 159 
 160     private final ImageOutputStream stream;
 161     private final int chunkLength;
 162     private long startPos;
 163     private final CRC crc = new CRC();
 164 
 165     private final Deflater def;
 166     private final byte[] buf = new byte[512];
 167     // reused 1 byte[] array:
 168     private final byte[] wbuf1 = new byte[1];
 169 
 170     private int bytesRemaining;
 171 
 172     IDATOutputStream(ImageOutputStream stream, int chunkLength,
 173                             int deflaterLevel) throws IOException
 174     {
 175         this.stream = stream;
 176         this.chunkLength = chunkLength;
 177         this.def = new Deflater(deflaterLevel);
 178 
 179         startChunk();
 180     }
 181 
 182     private void startChunk() throws IOException {
 183         crc.reset();
 184         this.startPos = stream.getStreamPosition();
 185         stream.writeInt(-1); // length, will backpatch
 186 
 187         crc.update(chunkType, 0, 4);
 188         stream.write(chunkType, 0, 4);
 189 
 190         this.bytesRemaining = chunkLength;
 191     }
 192 
 193     private void finishChunk() throws IOException {
 194         // Write CRC
 195         stream.writeInt(crc.getValue());
 196 
 197         // Write length
 198         long pos = stream.getStreamPosition();
 199         stream.seek(startPos);
 200         stream.writeInt((int)(pos - startPos) - 12);
 201 
 202         // Return to end of chunk and flush to minimize buffering
 203         stream.seek(pos);
 204         try {
 205             stream.flushBefore(pos);
 206         } catch (IOException e) {
 207             /*
 208              * If flushBefore() fails we try to access startPos in finally
 209              * block of write_IDAT(). We should update startPos to avoid
 210              * IndexOutOfBoundException while seek() is happening.
 211              */
 212             this.startPos = stream.getStreamPosition();
 213             throw e;
 214         }
 215     }
 216 
 217     @Override
 218     public int read() throws IOException {
 219         throw new RuntimeException("Method not available");
 220     }
 221 
 222     @Override
 223     public int read(byte[] b, int off, int len) throws IOException {
 224         throw new RuntimeException("Method not available");
 225     }
 226 
 227     @Override
 228     public void write(byte[] b, int off, int len) throws IOException {
 229         if (len == 0) {
 230             return;
 231         }
 232 
 233         if (!def.finished()) {
 234             def.setInput(b, off, len);
 235             while (!def.needsInput()) {
 236                 deflate();
 237             }
 238         }
 239     }
 240 
 241     void deflate() throws IOException {
 242         int len = def.deflate(buf, 0, buf.length);
 243         int off = 0;
 244 
 245         while (len > 0) {
 246             if (bytesRemaining == 0) {
 247                 finishChunk();
 248                 startChunk();
 249             }
 250 
 251             int nbytes = Math.min(len, bytesRemaining);
 252             crc.update(buf, off, nbytes);
 253             stream.write(buf, off, nbytes);
 254 
 255             off += nbytes;
 256             len -= nbytes;
 257             bytesRemaining -= nbytes;
 258         }
 259     }
 260 
 261     @Override
 262     public void write(int b) throws IOException {
 263         wbuf1[0] = (byte)b;
 264         write(wbuf1, 0, 1);

 265     }
 266 
 267     void finish() throws IOException {
 268         try {
 269             if (!def.finished()) {
 270                 def.finish();
 271                 while (!def.finished()) {
 272                     deflate();
 273                 }
 274             }
 275             finishChunk();
 276         } finally {
 277             def.end();
 278         }
 279     }
 280 
 281     @Override
 282     protected void finalize() throws Throwable {
 283         // Empty finalizer (for improved performance; no need to call
 284         // super.finalize() in this case)
 285     }
 286 }
 287 
 288 
 289 final class PNGImageWriteParam extends ImageWriteParam {
 290 
 291     /** Default quality level = 0.5 ie medium compression */
 292     private static final float DEFAULT_QUALITY = 0.5f;
 293 
 294     private static final String[] compressionNames = {"Deflate"};
 295     private static final float[] qualityVals = { 0.00F, 0.30F, 0.75F, 1.00F };
 296     private static final String[] qualityDescs = {
 297         "High compression",   // 0.00 -> 0.30
 298         "Medium compression", // 0.30 -> 0.75
 299         "Low compression"     // 0.75 -> 1.00
 300     };
 301 
 302     PNGImageWriteParam(Locale locale) {
 303         super();
 304         this.canWriteProgressive = true;
 305         this.locale = locale;
 306         this.canWriteCompressed = true;
 307         this.compressionTypes = compressionNames;
 308         this.compressionType = compressionTypes[0];
 309         this.compressionMode = MODE_DEFAULT;
 310         this.compressionQuality = DEFAULT_QUALITY;
 311     }
 312 
 313     /**
 314      * Removes any previous compression quality setting.
 315      *
 316      * <p> The default implementation resets the compression quality
 317      * to <code>0.5F</code>.
 318      *
 319      * @exception IllegalStateException if the compression mode is not
 320      * <code>MODE_EXPLICIT</code>.
 321      */
 322     @Override
 323     public void unsetCompression() {
 324         super.unsetCompression();
 325         this.compressionType = compressionTypes[0];
 326         this.compressionQuality = DEFAULT_QUALITY;
 327     }
 328 
 329     /**
 330      * Returns <code>true</code> since the PNG plug-in only supports
 331      * lossless compression.
 332      *
 333      * @return <code>true</code>.
 334      */
 335     @Override
 336     public boolean isCompressionLossless() {
 337         return true;
 338     }
 339 
 340     @Override
 341     public String[] getCompressionQualityDescriptions() {
 342         super.getCompressionQualityDescriptions();
 343         return qualityDescs.clone();
 344     }
 345 
 346     @Override
 347     public float[] getCompressionQualityValues() {
 348         super.getCompressionQualityValues();
 349         return qualityVals.clone();
 350     }
 351 }
 352 
 353 /**
 354  */
 355 public final class PNGImageWriter extends ImageWriter {
 356 
 357     /** Default compression level = 4 ie medium compression */
 358     private static final int DEFAULT_COMPRESSION_LEVEL = 4;
 359 
 360     ImageOutputStream stream = null;
 361 
 362     // compression level
 363     private int deflaterLevel;
 364 
 365     PNGMetadata metadata = null;
 366 
 367     // Factors from the ImageWriteParam
 368     int sourceXOffset = 0;
 369     int sourceYOffset = 0;
 370     int sourceWidth = 0;
 371     int sourceHeight = 0;
 372     int[] sourceBands = null;
 373     int periodX = 1;
 374     int periodY = 1;
 375 
 376     int numBands;
 377     int bpp;
 378 
 379     RowFilter rowFilter = new RowFilter();
 380     byte[] prevRow = null;
 381     byte[] currRow = null;
 382     byte[][] filteredRows = null;
 383 
 384     // Per-band scaling tables


 390     // given by sampleSize, and an output bit depth given by scalingBitDepth.
 391     //
 392     int[] sampleSize = null; // Sample size per band, in bits
 393     int scalingBitDepth = -1; // Output bit depth of the scaling tables
 394 
 395     // Tables for 1, 2, 4, or 8 bit output
 396     byte[][] scale = null; // 8 bit table
 397     byte[] scale0 = null; // equivalent to scale[0]
 398 
 399     // Tables for 16 bit output
 400     byte[][] scaleh = null; // High bytes of output
 401     byte[][] scalel = null; // Low bytes of output
 402 
 403     int totalPixels; // Total number of pixels to be written by write_IDAT
 404     int pixelsDone; // Running count of pixels written by write_IDAT
 405 
 406     public PNGImageWriter(ImageWriterSpi originatingProvider) {
 407         super(originatingProvider);
 408     }
 409 
 410     @Override
 411     public void setOutput(Object output) {
 412         super.setOutput(output);
 413         if (output != null) {
 414             if (!(output instanceof ImageOutputStream)) {
 415                 throw new IllegalArgumentException("output not an ImageOutputStream!");
 416             }
 417             this.stream = (ImageOutputStream)output;
 418         } else {
 419             this.stream = null;
 420         }
 421     }
 422 
 423     @Override

 424     public ImageWriteParam getDefaultWriteParam() {
 425         return new PNGImageWriteParam(getLocale());
 426     }
 427 
 428     @Override
 429     public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
 430         return null;
 431     }
 432 
 433     @Override
 434     public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType,
 435                                                ImageWriteParam param) {
 436         PNGMetadata m = new PNGMetadata();
 437         m.initialize(imageType, imageType.getSampleModel().getNumBands());
 438         return m;
 439     }
 440 
 441     @Override
 442     public IIOMetadata convertStreamMetadata(IIOMetadata inData,
 443                                              ImageWriteParam param) {
 444         return null;
 445     }
 446 
 447     @Override
 448     public IIOMetadata convertImageMetadata(IIOMetadata inData,
 449                                             ImageTypeSpecifier imageType,
 450                                             ImageWriteParam param) {
 451         // TODO - deal with imageType
 452         if (inData instanceof PNGMetadata) {
 453             return (PNGMetadata)((PNGMetadata)inData).clone();
 454         } else {
 455             return new PNGMetadata(inData);
 456         }
 457     }
 458 
 459     private void write_magic() throws IOException {
 460         // Write signature
 461         byte[] magic = { (byte)137, 80, 78, 71, 13, 10, 26, 10 };
 462         stream.write(magic);
 463     }
 464 
 465     private void write_IHDR() throws IOException {
 466         // Write IHDR chunk
 467         ChunkStream cs = new ChunkStream(PNGImageReader.IHDR_TYPE, stream);


 995             os.write(filteredRows[filterType], bpp, bytesPerRow);
 996 
 997             // Swap current and previous rows
 998             byte[] swap = currRow;
 999             currRow = prevRow;
1000             prevRow = swap;
1001 
1002             pixelsDone += hpixels;
1003             processImageProgress(100.0F*pixelsDone/totalPixels);
1004 
1005             // If write has been aborted, just return;
1006             // processWriteAborted will be called later
1007             if (abortRequested()) {
1008                 return;
1009             }
1010         }
1011     }
1012 
1013     // Use sourceXOffset, etc.
1014     private void write_IDAT(RenderedImage image) throws IOException {
1015         IDATOutputStream ios = new IDATOutputStream(stream, 32768,
1016                                                     deflaterLevel);
1017         try {
1018             if (metadata.IHDR_interlaceMethod == 1) {
1019                 for (int i = 0; i < 7; i++) {
1020                     encodePass(ios, image,
1021                                PNGImageReader.adam7XOffset[i],
1022                                PNGImageReader.adam7YOffset[i],
1023                                PNGImageReader.adam7XSubsampling[i],
1024                                PNGImageReader.adam7YSubsampling[i]);
1025                     if (abortRequested()) {
1026                         break;
1027                     }
1028                 }
1029             } else {
1030                 encodePass(ios, image, 0, 0, 1, 1);
1031             }
1032         } finally {
1033             ios.finish();
1034         }
1035     }
1036 


1089             // Divide scaling table into high and low bytes
1090             scaleh = new byte[numBands][];
1091             scalel = new byte[numBands][];
1092 
1093             for (int b = 0; b < numBands; b++) {
1094                 int maxInSample = (1 << sampleSize[b]) - 1;
1095                 int halfMaxInSample = maxInSample/2;
1096                 scaleh[b] = new byte[maxInSample + 1];
1097                 scalel[b] = new byte[maxInSample + 1];
1098                 for (int s = 0; s <= maxInSample; s++) {
1099                     int val = (s*maxOutSample + halfMaxInSample)/maxInSample;
1100                     scaleh[b][s] = (byte)(val >> 8);
1101                     scalel[b][s] = (byte)(val & 0xff);
1102                 }
1103             }
1104             scale = null;
1105             scale0 = null;
1106         }
1107     }
1108 
1109     @Override
1110     public void write(IIOMetadata streamMetadata,
1111                       IIOImage image,
1112                       ImageWriteParam param) throws IIOException {
1113         if (stream == null) {
1114             throw new IllegalStateException("output == null!");
1115         }
1116         if (image == null) {
1117             throw new IllegalArgumentException("image == null!");
1118         }
1119         if (image.hasRaster()) {
1120             throw new UnsupportedOperationException("image has a Raster!");
1121         }
1122 
1123         RenderedImage im = image.getRenderedImage();
1124         SampleModel sampleModel = im.getSampleModel();
1125         this.numBands = sampleModel.getNumBands();
1126 
1127         // Set source region and subsampling to default values
1128         this.sourceXOffset = im.getMinX();
1129         this.sourceYOffset = im.getMinY();


1172         int destWidth = (sourceWidth + periodX - 1)/periodX;
1173         int destHeight = (sourceHeight + periodY - 1)/periodY;
1174         if (destWidth <= 0 || destHeight <= 0) {
1175             throw new IllegalArgumentException("Empty source region!");
1176         }
1177 
1178         // Compute total number of pixels for progress notification
1179         this.totalPixels = destWidth*destHeight;
1180         this.pixelsDone = 0;
1181 
1182         // Create metadata
1183         IIOMetadata imd = image.getMetadata();
1184         if (imd != null) {
1185             metadata = (PNGMetadata)convertImageMetadata(imd,
1186                                ImageTypeSpecifier.createFromRenderedImage(im),
1187                                                          null);
1188         } else {
1189             metadata = new PNGMetadata();
1190         }
1191 
1192         // reset compression level to default:
1193         deflaterLevel = DEFAULT_COMPRESSION_LEVEL;
1194 
1195         if (param != null) {
1196             switch(param.getCompressionMode()) {
1197             case ImageWriteParam.MODE_DISABLED:
1198                 deflaterLevel = Deflater.NO_COMPRESSION;
1199                 break;
1200             case ImageWriteParam.MODE_EXPLICIT:
1201                 float quality = param.getCompressionQuality();
1202                 if (quality >= 0f && quality <= 1f) {
1203                     deflaterLevel = 9 - Math.round(9f * quality);
1204                 }
1205                 break;
1206             default:
1207             }
1208 
1209             // Use Adam7 interlacing if set in write param
1210             switch (param.getProgressiveMode()) {
1211             case ImageWriteParam.MODE_DEFAULT:
1212                 metadata.IHDR_interlaceMethod = 1;
1213                 break;
1214             case ImageWriteParam.MODE_DISABLED:
1215                 metadata.IHDR_interlaceMethod = 0;
1216                 break;
1217                 // MODE_COPY_FROM_METADATA should already be taken care of
1218                 // MODE_EXPLICIT is not allowed
1219             default:
1220             }
1221         }
1222 
1223         // Initialize bitDepth and colorType
1224         metadata.initialize(new ImageTypeSpecifier(im), numBands);
1225 
1226         // Overwrite IHDR width and height values with values from image
1227         metadata.IHDR_width = destWidth;
1228         metadata.IHDR_height = destHeight;
1229 
1230         this.bpp = numBands*((metadata.IHDR_bitDepth == 16) ? 2 : 1);
1231 
1232         // Initialize scaling tables for this image
1233         initializeScaleTables(sampleModel.getSampleSize());
1234 
1235         clearAbortRequest();
1236 
1237         processImageStarted(0);
1238 
1239         try {


< prev index next >