KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > swt > internal > image > PNGFileFormat


1 /*******************************************************************************
2  * Copyright (c) 2000, 2006 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11 package org.eclipse.swt.internal.image;
12
13
14 import java.io.*;
15 import org.eclipse.swt.*;
16 import org.eclipse.swt.graphics.*;
17 import org.eclipse.swt.internal.*;
18
19 final class PNGFileFormat extends FileFormat {
20     static final int SIGNATURE_LENGTH = 8;
21     static final int PRIME = 65521;
22     PngIhdrChunk headerChunk;
23     PngPlteChunk paletteChunk;
24     ImageData imageData;
25     byte[] data;
26     byte[] alphaPalette;
27     byte headerByte1;
28     byte headerByte2;
29     int adler;
30
31 /**
32  * Skip over signature data. This has already been
33  * verified in isFileFormat().
34  */

35 void readSignature() throws IOException {
36     byte[] signature = new byte[SIGNATURE_LENGTH];
37     inputStream.read(signature);
38 }
39 /**
40  * Load the PNG image from the byte stream.
41  */

42 ImageData[] loadFromByteStream() {
43     try {
44         readSignature();
45         PngChunkReader chunkReader = new PngChunkReader(inputStream);
46         headerChunk = chunkReader.getIhdrChunk();
47         int width = headerChunk.getWidth(), height = headerChunk.getHeight();
48         if (width <= 0 || height <= 0) SWT.error(SWT.ERROR_INVALID_IMAGE);
49         int imageSize = getAlignedBytesPerRow() * height;
50         data = new byte[imageSize];
51         imageData = ImageData.internal_new(
52             width,
53             height,
54             headerChunk.getSwtBitsPerPixel(),
55             new PaletteData(0, 0, 0),
56             4,
57             data,
58             0,
59             null,
60             null,
61             -1,
62             -1,
63             SWT.IMAGE_PNG,
64             0,
65             0,
66             0,
67             0);
68             
69         if (headerChunk.usesDirectColor()) {
70             imageData.palette = headerChunk.getPaletteData();
71         }
72         
73         // Read and process chunks until the IEND chunk is encountered.
74
while (chunkReader.hasMoreChunks()) {
75             readNextChunk(chunkReader);
76         }
77                         
78         return new ImageData[] {imageData};
79     } catch (IOException e) {
80         SWT.error(SWT.ERROR_INVALID_IMAGE);
81         return null;
82     }
83 }
84 /**
85  * Read and handle the next chunk of data from the
86  * PNG file.
87  */

88 void readNextChunk(PngChunkReader chunkReader) throws IOException {
89     PngChunk chunk = chunkReader.readNextChunk();
90     switch (chunk.getChunkType()) {
91         case PngChunk.CHUNK_IEND:
92             break;
93         case PngChunk.CHUNK_PLTE:
94             if (!headerChunk.usesDirectColor()) {
95                 paletteChunk = (PngPlteChunk) chunk;
96                 imageData.palette = paletteChunk.getPaletteData();
97             }
98             break;
99         case PngChunk.CHUNK_tRNS:
100             PngTrnsChunk trnsChunk = (PngTrnsChunk) chunk;
101             if (trnsChunk.getTransparencyType(headerChunk) ==
102                 PngTrnsChunk.TRANSPARENCY_TYPE_PIXEL)
103             {
104                 imageData.transparentPixel =
105                     trnsChunk.getSwtTransparentPixel(headerChunk);
106             } else {
107                 alphaPalette = trnsChunk.getAlphaValues(headerChunk, paletteChunk);
108                 int transparentCount = 0, transparentPixel = -1;
109                 for (int i = 0; i < alphaPalette.length; i++) {
110                     if ((alphaPalette[i] & 0xFF) != 255) {
111                         transparentCount++;
112                         transparentPixel = i;
113                     }
114                 }
115                 if (transparentCount == 0) {
116                     alphaPalette = null;
117                 } else if (transparentCount == 1 && alphaPalette[transparentPixel] == 0) {
118                     alphaPalette = null;
119                     imageData.transparentPixel = transparentPixel;
120                 }
121             }
122             break;
123         case PngChunk.CHUNK_IDAT:
124             if (chunkReader.readPixelData()) {
125                 // All IDAT chunks in an image file must be
126
// sequential. If the pixel data has already
127
// been read and another IDAT block is encountered,
128
// then this is an invalid image.
129
SWT.error(SWT.ERROR_INVALID_IMAGE);
130             } else {
131                 // Read in the pixel data for the image. This should
132
// go through all the image's IDAT chunks.
133
PngIdatChunk dataChunk = (PngIdatChunk) chunk;
134                 readPixelData(dataChunk, chunkReader);
135             }
136             break;
137         default:
138             if (chunk.isCritical()) {
139                 // All critical chunks must be supported.
140
SWT.error(SWT.ERROR_NOT_IMPLEMENTED);
141             }
142     }
143 }
144 void unloadIntoByteStream(ImageLoader loader) {
145     PngEncoder encoder = new PngEncoder(loader);
146     encoder.encode(outputStream);
147 }
148 boolean isFileFormat(LEDataInputStream stream) {
149     try {
150         byte[] signature = new byte[SIGNATURE_LENGTH];
151         stream.read(signature);
152         stream.unread(signature);
153         if ((signature[0] & 0xFF) != 137) return false; //137
154
if ((signature[1] & 0xFF) != 80) return false; //P
155
if ((signature[2] & 0xFF) != 78) return false; //N
156
if ((signature[3] & 0xFF) != 71) return false; //G
157
if ((signature[4] & 0xFF) != 13) return false; //<RETURN>
158
if ((signature[5] & 0xFF) != 10) return false; //<LINEFEED>
159
if ((signature[6] & 0xFF) != 26) return false; //<CTRL/Z>
160
if ((signature[7] & 0xFF) != 10) return false; //<LINEFEED>
161
return true;
162     } catch (Exception JavaDoc e) {
163         return false;
164     }
165 }
166 /**
167  * SWT does not support 16-bit depths. If this image uses
168  * 16-bit depths, convert the data to an 8-bit depth.
169  */

170 byte[] validateBitDepth(byte[] data) {
171     if (headerChunk.getBitDepth() > 8) {
172         byte[] result = new byte[data.length / 2];
173         compress16BitDepthTo8BitDepth(data, 0, result, 0, result.length);
174         return result;
175     } else {
176         return data;
177     }
178 }
179 /**
180  * SWT does not support greyscale as a color type. For
181  * plain grayscale, we create a palette. For Grayscale
182  * with Alpha, however, we need to convert the pixels
183  * to use RGB values.
184  * Note: This method assumes that the bit depth of the
185  * data has already been restricted to 8 or less.
186  */

187 void setPixelData(byte[] data, ImageData imageData) {
188     switch (headerChunk.getColorType()) {
189         case PngIhdrChunk.COLOR_TYPE_GRAYSCALE_WITH_ALPHA:
190         {
191             int width = imageData.width;
192             int height = imageData.height;
193             int destBytesPerLine = imageData.bytesPerLine;
194             /*
195             * If the image uses 16-bit depth, it is converted
196             * to an 8-bit depth image.
197             */

198             int srcBytesPerLine = getAlignedBytesPerRow();
199             if (headerChunk.getBitDepth() > 8) srcBytesPerLine /= 2;
200
201             byte[] rgbData = new byte[destBytesPerLine * height];
202             byte[] alphaData = new byte[width * height];
203             for (int y = 0; y < height; y++) {
204                 int srcIndex = srcBytesPerLine * y;
205                 int destIndex = destBytesPerLine * y;
206                 int destAlphaIndex = width * y;
207                 for (int x = 0; x < width; x++) {
208                     byte grey = data[srcIndex];
209                     byte alpha = data[srcIndex + 1];
210                     rgbData[destIndex + 0] = grey;
211                     rgbData[destIndex + 1] = grey;
212                     rgbData[destIndex + 2] = grey;
213                     alphaData[destAlphaIndex] = alpha;
214                     srcIndex += 2;
215                     destIndex += 3;
216                     destAlphaIndex++;
217                 }
218             }
219             imageData.data = rgbData;
220             imageData.alphaData = alphaData;
221             break;
222         }
223         case PngIhdrChunk.COLOR_TYPE_RGB_WITH_ALPHA:
224         {
225             int width = imageData.width;
226             int height = imageData.height;
227             int destBytesPerLine = imageData.bytesPerLine;
228             int srcBytesPerLine = getAlignedBytesPerRow();
229             /*
230             * If the image uses 16-bit depth, it is converted
231             * to an 8-bit depth image.
232             */

233             if (headerChunk.getBitDepth() > 8) srcBytesPerLine /= 2;
234
235             byte[] rgbData = new byte[destBytesPerLine * height];
236             byte[] alphaData = new byte[width * height];
237             for (int y = 0; y < height; y++) {
238                 int srcIndex = srcBytesPerLine * y;
239                 int destIndex = destBytesPerLine * y;
240                 int destAlphaIndex = width * y;
241                 for (int x = 0; x < width; x++) {
242                     rgbData[destIndex + 0] = data[srcIndex + 0];
243                     rgbData[destIndex + 1] = data[srcIndex + 1];
244                     rgbData[destIndex + 2] = data[srcIndex + 2];
245                     alphaData[destAlphaIndex] = data[srcIndex + 3];
246                     srcIndex += 4;
247                     destIndex += 3;
248                     destAlphaIndex++;
249                 }
250             }
251             imageData.data = rgbData;
252             imageData.alphaData = alphaData;
253             break;
254         }
255         case PngIhdrChunk.COLOR_TYPE_RGB:
256             imageData.data = data;
257             break;
258         case PngIhdrChunk.COLOR_TYPE_PALETTE:
259             imageData.data = data;
260             if (alphaPalette != null) {
261                 int size = imageData.width * imageData.height;
262                 byte[] alphaData = new byte[size];
263                 byte[] pixelData = new byte[size];
264                 imageData.getPixels(0, 0, size, pixelData, 0);
265                 for (int i = 0; i < pixelData.length; i++) {
266                     alphaData[i] = alphaPalette[pixelData[i] & 0xFF];
267                 }
268                 imageData.alphaData = alphaData;
269             }
270             break;
271         default:
272             imageData.data = data;
273             break;
274     }
275 }
276 /**
277  * PNG supports some color types and bit depths that are
278  * unsupported by SWT. If the image uses an unsupported
279  * color type (either of the gray scale types) or bit
280  * depth (16), convert the data to an SWT-supported
281  * format. Then assign the data into the ImageData given.
282  */

283 void setImageDataValues(byte[] data, ImageData imageData) {
284     byte[] result = validateBitDepth(data);
285     setPixelData(result, imageData);
286 }
287 /**
288  * Read the image data from the data stream. This must handle
289  * decoding the data, filtering, and interlacing.
290  */

291 void readPixelData(PngIdatChunk chunk, PngChunkReader chunkReader) throws IOException {
292     InputStream stream = new PngInputStream(chunk, chunkReader);
293     //TEMPORARY CODE
294
boolean use3_2 = System.getProperty("org.eclipse.swt.internal.image.PNGFileFormat_3.2") != null;
295     InputStream inflaterStream = use3_2 ? null : Compatibility.newInflaterInputStream(stream);
296     if (inflaterStream != null) {
297         stream = new BufferedInputStream(inflaterStream);
298     } else {
299         stream = new PngDecodingDataStream(stream);
300     }
301     int interlaceMethod = headerChunk.getInterlaceMethod();
302     if (interlaceMethod == PngIhdrChunk.INTERLACE_METHOD_NONE) {
303         readNonInterlacedImage(stream);
304     } else {
305         readInterlacedImage(stream);
306     }
307     /*
308     * InflaterInputStream does not consume all bytes in the stream
309     * when it is closed. This may leave unread IDAT chunks. The fix
310     * is to read all available bytes before closing it.
311     */

312     while (stream.available() > 0) stream.read();
313     stream.close();
314 }
315 /**
316  * Answer the number of bytes in a word-aligned row of pixel data.
317  */

318 int getAlignedBytesPerRow() {
319     return ((getBytesPerRow(headerChunk.getWidth()) + 3) / 4) * 4;
320 }
321 /**
322  * Answer the number of bytes in each row of the image
323  * data. Each PNG row is byte-aligned, so images with bit
324  * depths less than a byte may have unused bits at the
325  * end of each row. The value of these bits is undefined.
326  */

327 int getBytesPerRow() {
328     return getBytesPerRow(headerChunk.getWidth());
329 }
330 /**
331  * Answer the number of bytes needed to represent a pixel.
332  * This value depends on the image's color type and bit
333  * depth.
334  * Note that this method rounds up if an image's pixel size
335  * isn't byte-aligned.
336  */

337 int getBytesPerPixel() {
338     int bitsPerPixel = headerChunk.getBitsPerPixel();
339     return (bitsPerPixel + 7) / 8;
340 }
341 /**
342  * Answer the number of bytes in a row of the given pixel
343  * width. Each row is byte-aligned, so images with bit
344  * depths less than a byte may have unused bits at the
345  * end of each row. The value of these bits is undefined.
346  */

347 int getBytesPerRow(int rowWidthInPixels) {
348     int bitsPerPixel = headerChunk.getBitsPerPixel();
349     int bitsPerRow = bitsPerPixel * rowWidthInPixels;
350     int bitsPerByte = 8;
351     return (bitsPerRow + (bitsPerByte - 1)) / bitsPerByte;
352 }
353 /**
354  * 1. Read one of the seven frames of interlaced data.
355  * 2. Update the imageData.
356  * 3. Notify the image loader's listeners of the frame load.
357  */

358 void readInterlaceFrame(
359     InputStream inputStream,
360     int rowInterval,
361     int columnInterval,
362     int startRow,
363     int startColumn,
364     int frameCount) throws IOException
365 {
366     int width = headerChunk.getWidth();
367     int alignedBytesPerRow = getAlignedBytesPerRow();
368     int height = headerChunk.getHeight();
369     if (startRow >= height || startColumn >= width) return;
370     
371     int pixelsPerRow = (width - startColumn + columnInterval - 1) / columnInterval;
372     int bytesPerRow = getBytesPerRow(pixelsPerRow);
373     byte[] row1 = new byte[bytesPerRow];
374     byte[] row2 = new byte[bytesPerRow];
375     byte[] currentRow = row1;
376     byte[] lastRow = row2;
377     for (int row = startRow; row < height; row += rowInterval) {
378         byte filterType = (byte)inputStream.read();
379         int read = 0;
380         while (read != bytesPerRow) {
381             read += inputStream.read(currentRow, read, bytesPerRow - read);
382         }
383         filterRow(currentRow, lastRow, filterType);
384         if (headerChunk.getBitDepth() >= 8) {
385             int bytesPerPixel = getBytesPerPixel();
386             int dataOffset = (row * alignedBytesPerRow) + (startColumn * bytesPerPixel);
387             for (int rowOffset = 0; rowOffset < currentRow.length; rowOffset += bytesPerPixel) {
388                 for (int byteOffset = 0; byteOffset < bytesPerPixel; byteOffset++) {
389                     data[dataOffset + byteOffset] = currentRow[rowOffset + byteOffset];
390                 }
391                 dataOffset += (columnInterval * bytesPerPixel);
392             }
393         } else {
394             int bitsPerPixel = headerChunk.getBitDepth();
395             int pixelsPerByte = 8 / bitsPerPixel;
396             int column = startColumn;
397             int rowBase = row * alignedBytesPerRow;
398             int valueMask = 0;
399             for (int i = 0; i < bitsPerPixel; i++) {
400                 valueMask <<= 1;
401                 valueMask |= 1;
402             }
403             int maxShift = 8 - bitsPerPixel;
404             for (int byteOffset = 0; byteOffset < currentRow.length; byteOffset++) {
405                 for (int bitOffset = maxShift; bitOffset >= 0; bitOffset -= bitsPerPixel) {
406                     if (column < width) {
407                         int dataOffset = rowBase + (column * bitsPerPixel / 8);
408                         int value = (currentRow[byteOffset] >> bitOffset) & valueMask;
409                         int dataShift = maxShift - (bitsPerPixel * (column % pixelsPerByte));
410                         data[dataOffset] |= value << dataShift;
411                     }
412                     column += columnInterval;
413                 }
414             }
415         }
416         currentRow = (currentRow == row1) ? row2 : row1;
417         lastRow = (lastRow == row1) ? row2 : row1;
418     }
419     setImageDataValues(data, imageData);
420     fireInterlacedFrameEvent(frameCount);
421 }
422 /**
423  * Read the pixel data for an interlaced image from the
424  * data stream.
425  */

426 void readInterlacedImage(InputStream inputStream) throws IOException {
427     readInterlaceFrame(inputStream, 8, 8, 0, 0, 0);
428     readInterlaceFrame(inputStream, 8, 8, 0, 4, 1);
429     readInterlaceFrame(inputStream, 8, 4, 4, 0, 2);
430     readInterlaceFrame(inputStream, 4, 4, 0, 2, 3);
431     readInterlaceFrame(inputStream, 4, 2, 2, 0, 4);
432     readInterlaceFrame(inputStream, 2, 2, 0, 1, 5);
433     readInterlaceFrame(inputStream, 2, 1, 1, 0, 6);
434 }
435 /**
436  * Fire an event to let listeners know that an interlaced
437  * frame has been loaded.
438  * finalFrame should be true if the image has finished
439  * loading, false if there are more frames to come.
440  */

441 void fireInterlacedFrameEvent(int frameCount) {
442     if (loader.hasListeners()) {
443         ImageData image = (ImageData) imageData.clone();
444         boolean finalFrame = frameCount == 6;
445         loader.notifyListeners(new ImageLoaderEvent(loader, image, frameCount, finalFrame));
446     }
447 }
448 /**
449  * Read the pixel data for a non-interlaced image from the
450  * data stream.
451  * Update the imageData to reflect the new data.
452  */

453 void readNonInterlacedImage(InputStream inputStream) throws IOException {
454     int dataOffset = 0;
455     int alignedBytesPerRow = getAlignedBytesPerRow();
456     int bytesPerRow = getBytesPerRow();
457     byte[] row1 = new byte[bytesPerRow];
458     byte[] row2 = new byte[bytesPerRow];
459     byte[] currentRow = row1;
460     byte[] lastRow = row2;
461     int height = headerChunk.getHeight();
462     for (int row = 0; row < height; row++) {
463         byte filterType = (byte)inputStream.read();
464         int read = 0;
465         while (read != bytesPerRow) {
466             read += inputStream.read(currentRow, read, bytesPerRow - read);
467         }
468         filterRow(currentRow, lastRow, filterType);
469         System.arraycopy(currentRow, 0, data, dataOffset, bytesPerRow);
470         dataOffset += alignedBytesPerRow;
471         currentRow = (currentRow == row1) ? row2 : row1;
472         lastRow = (lastRow == row1) ? row2 : row1;
473     }
474     setImageDataValues(data, imageData);
475 }
476 /**
477  * SWT does not support 16-bit depth color formats.
478  * Convert the 16-bit data to 8-bit data.
479  * The correct way to do this is to multiply each
480  * 16 bit value by the value:
481  * (2^8 - 1) / (2^16 - 1).
482  * The fast way to do this is just to drop the low
483  * byte of the 16-bit value.
484  */

485 static void compress16BitDepthTo8BitDepth(
486     byte[] source,
487     int sourceOffset,
488     byte[] destination,
489     int destinationOffset,
490     int numberOfValues)
491 {
492     //double multiplier = (Compatibility.pow2(8) - 1) / (Compatibility.pow2(16) - 1);
493
for (int i = 0; i < numberOfValues; i++) {
494         int sourceIndex = sourceOffset + (2 * i);
495         int destinationIndex = destinationOffset + i;
496         //int value = (source[sourceIndex] << 8) | source[sourceIndex + 1];
497
//byte compressedValue = (byte)(value * multiplier);
498
byte compressedValue = source[sourceIndex];
499         destination[destinationIndex] = compressedValue;
500     }
501 }
502 /**
503  * SWT does not support 16-bit depth color formats.
504  * Convert the 16-bit data to 8-bit data.
505  * The correct way to do this is to multiply each
506  * 16 bit value by the value:
507  * (2^8 - 1) / (2^16 - 1).
508  * The fast way to do this is just to drop the low
509  * byte of the 16-bit value.
510  */

511 static int compress16BitDepthTo8BitDepth(int value) {
512     //double multiplier = (Compatibility.pow2(8) - 1) / (Compatibility.pow2(16) - 1);
513
//byte compressedValue = (byte)(value * multiplier);
514
return value >> 8;
515 }
516 /**
517  * PNG supports four filtering types. These types are applied
518  * per row of image data. This method unfilters the given row
519  * based on the filterType.
520  */

521 void filterRow(byte[] row, byte[] previousRow, int filterType) {
522     int byteOffset = headerChunk.getFilterByteOffset();
523     switch (filterType) {
524         case PngIhdrChunk.FILTER_NONE:
525             break;
526         case PngIhdrChunk.FILTER_SUB:
527             for (int i = byteOffset; i < row.length; i++) {
528                 int current = row[i] & 0xFF;
529                 int left = row[i - byteOffset] & 0xFF;
530                 row[i] = (byte)((current + left) & 0xFF);
531             }
532             break;
533         case PngIhdrChunk.FILTER_UP:
534             for (int i = 0; i < row.length; i++) {
535                 int current = row[i] & 0xFF;
536                 int above = previousRow[i] & 0xFF;
537                 row[i] = (byte)((current + above) & 0xFF);
538             }
539             break;
540         case PngIhdrChunk.FILTER_AVERAGE:
541             for (int i = 0; i < row.length; i++) {
542                 int left = (i < byteOffset) ? 0 : row[i - byteOffset] & 0xFF;
543                 int above = previousRow[i] & 0xFF;
544                 int current = row[i] & 0xFF;
545                 row[i] = (byte)((current + ((left + above) / 2)) & 0xFF);
546             }
547             break;
548         case PngIhdrChunk.FILTER_PAETH:
549             for (int i = 0; i < row.length; i++) {
550                 int left = (i < byteOffset) ? 0 : row[i - byteOffset] & 0xFF;
551                 int aboveLeft = (i < byteOffset) ? 0 : previousRow[i - byteOffset] & 0xFF;
552                 int above = previousRow[i] & 0xFF;
553                 
554                 int a = Math.abs(above - aboveLeft);
555                 int b = Math.abs(left - aboveLeft);
556                 int c = Math.abs(left - aboveLeft + above - aboveLeft);
557                 
558                 int preductor = 0;
559                 if (a <= b && a <= c) {
560                     preductor = left;
561                 } else if (b <= c) {
562                     preductor = above;
563                 } else {
564                     preductor = aboveLeft;
565                 }
566                 
567                 int currentValue = row[i] & 0xFF;
568                 row[i] = (byte) ((currentValue + preductor) & 0xFF);
569             }
570             break;
571     }
572 }
573
574 }
Popular Tags