comparison dwt/internal/image/PNGFileFormat.d @ 0:380af2bdd8e5

Upload of whole dwt tree
author Jacob Carlborg <doob@me.com> <jacob.carlborg@gmail.com>
date Sat, 09 Aug 2008 17:00:02 +0200
parents
children b903c16b6f48
comparison
equal deleted inserted replaced
-1:000000000000 0:380af2bdd8e5
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 module dwt.internal.image;
12
13
14 import java.io.IOException;
15 import java.io.InputStream;
16
17 import dwt.DWT;
18 import dwt.graphics.ImageData;
19 import dwt.graphics.ImageLoader;
20 import dwt.graphics.ImageLoaderEvent;
21 import dwt.graphics.PaletteData;
22 import dwt.internal.Compatibility;
23
24 public final class PNGFileFormat : FileFormat {
25 static final int SIGNATURE_LENGTH = 8;
26 static final int PRIME = 65521;
27 PngIhdrChunk headerChunk;
28 PngPlteChunk paletteChunk;
29 ImageData imageData;
30 byte[] data;
31 byte[] alphaPalette;
32 byte headerByte1;
33 byte headerByte2;
34 int adler;
35
36 /**
37 * Skip over signature data. This has already been
38 * verified in isFileFormat().
39 */
40 void readSignature() throws IOException {
41 byte[] signature = new byte[SIGNATURE_LENGTH];
42 inputStream.read(signature);
43 }
44 /**
45 * Load the PNG image from the byte stream.
46 */
47 ImageData[] loadFromByteStream() {
48 try {
49 readSignature();
50 PngChunkReader chunkReader = new PngChunkReader(inputStream);
51 headerChunk = chunkReader.getIhdrChunk();
52 int width = headerChunk.getWidth(), height = headerChunk.getHeight();
53 if (width <= 0 || height <= 0) DWT.error(DWT.ERROR_INVALID_IMAGE);
54 int imageSize = getAlignedBytesPerRow() * height;
55 data = new byte[imageSize];
56 imageData = ImageData.internal_new(
57 width,
58 height,
59 headerChunk.getSwtBitsPerPixel(),
60 new PaletteData(0, 0, 0),
61 4,
62 data,
63 0,
64 null,
65 null,
66 -1,
67 -1,
68 DWT.IMAGE_PNG,
69 0,
70 0,
71 0,
72 0);
73
74 if (headerChunk.usesDirectColor()) {
75 imageData.palette = headerChunk.getPaletteData();
76 }
77
78 // Read and process chunks until the IEND chunk is encountered.
79 while (chunkReader.hasMoreChunks()) {
80 readNextChunk(chunkReader);
81 }
82
83 return new ImageData[] {imageData};
84 } catch (IOException e) {
85 DWT.error(DWT.ERROR_INVALID_IMAGE);
86 return null;
87 }
88 }
89 /**
90 * Read and handle the next chunk of data from the
91 * PNG file.
92 */
93 void readNextChunk(PngChunkReader chunkReader) throws IOException {
94 PngChunk chunk = chunkReader.readNextChunk();
95 switch (chunk.getChunkType()) {
96 case PngChunk.CHUNK_IEND:
97 break;
98 case PngChunk.CHUNK_PLTE:
99 if (!headerChunk.usesDirectColor()) {
100 paletteChunk = (PngPlteChunk) chunk;
101 imageData.palette = paletteChunk.getPaletteData();
102 }
103 break;
104 case PngChunk.CHUNK_tRNS:
105 PngTrnsChunk trnsChunk = (PngTrnsChunk) chunk;
106 if (trnsChunk.getTransparencyType(headerChunk) is
107 PngTrnsChunk.TRANSPARENCY_TYPE_PIXEL)
108 {
109 imageData.transparentPixel =
110 trnsChunk.getSwtTransparentPixel(headerChunk);
111 } else {
112 alphaPalette = trnsChunk.getAlphaValues(headerChunk, paletteChunk);
113 int transparentCount = 0, transparentPixel = -1;
114 for (int i = 0; i < alphaPalette.length; i++) {
115 if ((alphaPalette[i] & 0xFF) !is 255) {
116 transparentCount++;
117 transparentPixel = i;
118 }
119 }
120 if (transparentCount is 0) {
121 alphaPalette = null;
122 } else if (transparentCount is 1 && alphaPalette[transparentPixel] is 0) {
123 alphaPalette = null;
124 imageData.transparentPixel = transparentPixel;
125 }
126 }
127 break;
128 case PngChunk.CHUNK_IDAT:
129 if (chunkReader.readPixelData()) {
130 // All IDAT chunks in an image file must be
131 // sequential. If the pixel data has already
132 // been read and another IDAT block is encountered,
133 // then this is an invalid image.
134 DWT.error(DWT.ERROR_INVALID_IMAGE);
135 } else {
136 // Read in the pixel data for the image. This should
137 // go through all the image's IDAT chunks.
138 PngIdatChunk dataChunk = (PngIdatChunk) chunk;
139 readPixelData(dataChunk, chunkReader);
140 }
141 break;
142 default:
143 if (chunk.isCritical()) {
144 // All critical chunks must be supported.
145 DWT.error(DWT.ERROR_NOT_IMPLEMENTED);
146 }
147 }
148 }
149 void unloadIntoByteStream(ImageLoader loader) {
150 PngEncoder encoder = new PngEncoder(loader);
151 encoder.encode(outputStream);
152 }
153 bool isFileFormat(LEDataInputStream stream) {
154 try {
155 byte[] signature = new byte[SIGNATURE_LENGTH];
156 stream.read(signature);
157 stream.unread(signature);
158 if ((signature[0] & 0xFF) !is 137) return false; //137
159 if ((signature[1] & 0xFF) !is 80) return false; //P
160 if ((signature[2] & 0xFF) !is 78) return false; //N
161 if ((signature[3] & 0xFF) !is 71) return false; //G
162 if ((signature[4] & 0xFF) !is 13) return false; //<RETURN>
163 if ((signature[5] & 0xFF) !is 10) return false; //<LINEFEED>
164 if ((signature[6] & 0xFF) !is 26) return false; //<CTRL/Z>
165 if ((signature[7] & 0xFF) !is 10) return false; //<LINEFEED>
166 return true;
167 } catch (Exception e) {
168 return false;
169 }
170 }
171 /**
172 * DWT does not support 16-bit depths. If this image uses
173 * 16-bit depths, convert the data to an 8-bit depth.
174 */
175 byte[] validateBitDepth(byte[] data) {
176 if (headerChunk.getBitDepth() > 8) {
177 byte[] result = new byte[data.length / 2];
178 compress16BitDepthTo8BitDepth(data, 0, result, 0, result.length);
179 return result;
180 } else {
181 return data;
182 }
183 }
184 /**
185 * DWT does not support greyscale as a color type. For
186 * plain grayscale, we create a palette. For Grayscale
187 * with Alpha, however, we need to convert the pixels
188 * to use RGB values.
189 * Note: This method assumes that the bit depth of the
190 * data has already been restricted to 8 or less.
191 */
192 void setPixelData(byte[] data, ImageData imageData) {
193 switch (headerChunk.getColorType()) {
194 case PngIhdrChunk.COLOR_TYPE_GRAYSCALE_WITH_ALPHA:
195 {
196 int width = imageData.width;
197 int height = imageData.height;
198 int destBytesPerLine = imageData.bytesPerLine;
199 /*
200 * If the image uses 16-bit depth, it is converted
201 * to an 8-bit depth image.
202 */
203 int srcBytesPerLine = getAlignedBytesPerRow();
204 if (headerChunk.getBitDepth() > 8) srcBytesPerLine /= 2;
205
206 byte[] rgbData = new byte[destBytesPerLine * height];
207 byte[] alphaData = new byte[width * height];
208 for (int y = 0; y < height; y++) {
209 int srcIndex = srcBytesPerLine * y;
210 int destIndex = destBytesPerLine * y;
211 int destAlphaIndex = width * y;
212 for (int x = 0; x < width; x++) {
213 byte grey = data[srcIndex];
214 byte alpha = data[srcIndex + 1];
215 rgbData[destIndex + 0] = grey;
216 rgbData[destIndex + 1] = grey;
217 rgbData[destIndex + 2] = grey;
218 alphaData[destAlphaIndex] = alpha;
219 srcIndex += 2;
220 destIndex += 3;
221 destAlphaIndex++;
222 }
223 }
224 imageData.data = rgbData;
225 imageData.alphaData = alphaData;
226 break;
227 }
228 case PngIhdrChunk.COLOR_TYPE_RGB_WITH_ALPHA:
229 {
230 int width = imageData.width;
231 int height = imageData.height;
232 int destBytesPerLine = imageData.bytesPerLine;
233 int srcBytesPerLine = getAlignedBytesPerRow();
234 /*
235 * If the image uses 16-bit depth, it is converted
236 * to an 8-bit depth image.
237 */
238 if (headerChunk.getBitDepth() > 8) srcBytesPerLine /= 2;
239
240 byte[] rgbData = new byte[destBytesPerLine * height];
241 byte[] alphaData = new byte[width * height];
242 for (int y = 0; y < height; y++) {
243 int srcIndex = srcBytesPerLine * y;
244 int destIndex = destBytesPerLine * y;
245 int destAlphaIndex = width * y;
246 for (int x = 0; x < width; x++) {
247 rgbData[destIndex + 0] = data[srcIndex + 0];
248 rgbData[destIndex + 1] = data[srcIndex + 1];
249 rgbData[destIndex + 2] = data[srcIndex + 2];
250 alphaData[destAlphaIndex] = data[srcIndex + 3];
251 srcIndex += 4;
252 destIndex += 3;
253 destAlphaIndex++;
254 }
255 }
256 imageData.data = rgbData;
257 imageData.alphaData = alphaData;
258 break;
259 }
260 case PngIhdrChunk.COLOR_TYPE_RGB:
261 imageData.data = data;
262 break;
263 case PngIhdrChunk.COLOR_TYPE_PALETTE:
264 imageData.data = data;
265 if (alphaPalette !is null) {
266 int size = imageData.width * imageData.height;
267 byte[] alphaData = new byte[size];
268 byte[] pixelData = new byte[size];
269 imageData.getPixels(0, 0, size, pixelData, 0);
270 for (int i = 0; i < pixelData.length; i++) {
271 alphaData[i] = alphaPalette[pixelData[i] & 0xFF];
272 }
273 imageData.alphaData = alphaData;
274 }
275 break;
276 default:
277 imageData.data = data;
278 break;
279 }
280 }
281 /**
282 * PNG supports some color types and bit depths that are
283 * unsupported by DWT. If the image uses an unsupported
284 * color type (either of the gray scale types) or bit
285 * depth (16), convert the data to an DWT-supported
286 * format. Then assign the data into the ImageData given.
287 */
288 void setImageDataValues(byte[] data, ImageData imageData) {
289 byte[] result = validateBitDepth(data);
290 setPixelData(result, imageData);
291 }
292 /**
293 * Read the image data from the data stream. This must handle
294 * decoding the data, filtering, and interlacing.
295 */
296 void readPixelData(PngIdatChunk chunk, PngChunkReader chunkReader) throws IOException {
297 InputStream stream = new PngInputStream(chunk, chunkReader);
298 //TEMPORARY CODE
299 bool use3_2 = System.getProperty("dwt.internal.image.PNGFileFormat_3.2") !is null;
300 InputStream inflaterStream = use3_2 ? null : Compatibility.newInflaterInputStream(stream);
301 if (inflaterStream !is null) {
302 stream = inflaterStream;
303 } else {
304 stream = new PngDecodingDataStream(stream);
305 }
306 int interlaceMethod = headerChunk.getInterlaceMethod();
307 if (interlaceMethod is PngIhdrChunk.INTERLACE_METHOD_NONE) {
308 readNonInterlacedImage(stream);
309 } else {
310 readInterlacedImage(stream);
311 }
312 /*
313 * InflaterInputStream does not consume all bytes in the stream
314 * when it is closed. This may leave unread IDAT chunks. The fix
315 * is to read all available bytes before closing it.
316 */
317 while (stream.available() > 0) stream.read();
318 stream.close();
319 }
320 /**
321 * Answer the number of bytes in a word-aligned row of pixel data.
322 */
323 int getAlignedBytesPerRow() {
324 return ((getBytesPerRow(headerChunk.getWidth()) + 3) / 4) * 4;
325 }
326 /**
327 * Answer the number of bytes in each row of the image
328 * data. Each PNG row is byte-aligned, so images with bit
329 * depths less than a byte may have unused bits at the
330 * end of each row. The value of these bits is undefined.
331 */
332 int getBytesPerRow() {
333 return getBytesPerRow(headerChunk.getWidth());
334 }
335 /**
336 * Answer the number of bytes needed to represent a pixel.
337 * This value depends on the image's color type and bit
338 * depth.
339 * Note that this method rounds up if an image's pixel size
340 * isn't byte-aligned.
341 */
342 int getBytesPerPixel() {
343 int bitsPerPixel = headerChunk.getBitsPerPixel();
344 return (bitsPerPixel + 7) / 8;
345 }
346 /**
347 * Answer the number of bytes in a row of the given pixel
348 * width. Each row is byte-aligned, so images with bit
349 * depths less than a byte may have unused bits at the
350 * end of each row. The value of these bits is undefined.
351 */
352 int getBytesPerRow(int rowWidthInPixels) {
353 int bitsPerPixel = headerChunk.getBitsPerPixel();
354 int bitsPerRow = bitsPerPixel * rowWidthInPixels;
355 int bitsPerByte = 8;
356 return (bitsPerRow + (bitsPerByte - 1)) / bitsPerByte;
357 }
358 /**
359 * 1. Read one of the seven frames of interlaced data.
360 * 2. Update the imageData.
361 * 3. Notify the image loader's listeners of the frame load.
362 */
363 void readInterlaceFrame(
364 InputStream inputStream,
365 int rowInterval,
366 int columnInterval,
367 int startRow,
368 int startColumn,
369 int frameCount) throws IOException
370 {
371 int width = headerChunk.getWidth();
372 int alignedBytesPerRow = getAlignedBytesPerRow();
373 int height = headerChunk.getHeight();
374 if (startRow >= height || startColumn >= width) return;
375
376 int pixelsPerRow = (width - startColumn + columnInterval - 1) / columnInterval;
377 int bytesPerRow = getBytesPerRow(pixelsPerRow);
378 byte[] row1 = new byte[bytesPerRow];
379 byte[] row2 = new byte[bytesPerRow];
380 byte[] currentRow = row1;
381 byte[] lastRow = row2;
382 for (int row = startRow; row < height; row += rowInterval) {
383 byte filterType = (byte)inputStream.read();
384 int read = 0;
385 while (read !is bytesPerRow) {
386 read += inputStream.read(currentRow, read, bytesPerRow - read);
387 }
388 filterRow(currentRow, lastRow, filterType);
389 if (headerChunk.getBitDepth() >= 8) {
390 int bytesPerPixel = getBytesPerPixel();
391 int dataOffset = (row * alignedBytesPerRow) + (startColumn * bytesPerPixel);
392 for (int rowOffset = 0; rowOffset < currentRow.length; rowOffset += bytesPerPixel) {
393 for (int byteOffset = 0; byteOffset < bytesPerPixel; byteOffset++) {
394 data[dataOffset + byteOffset] = currentRow[rowOffset + byteOffset];
395 }
396 dataOffset += (columnInterval * bytesPerPixel);
397 }
398 } else {
399 int bitsPerPixel = headerChunk.getBitDepth();
400 int pixelsPerByte = 8 / bitsPerPixel;
401 int column = startColumn;
402 int rowBase = row * alignedBytesPerRow;
403 int valueMask = 0;
404 for (int i = 0; i < bitsPerPixel; i++) {
405 valueMask <<= 1;
406 valueMask |= 1;
407 }
408 int maxShift = 8 - bitsPerPixel;
409 for (int byteOffset = 0; byteOffset < currentRow.length; byteOffset++) {
410 for (int bitOffset = maxShift; bitOffset >= 0; bitOffset -= bitsPerPixel) {
411 if (column < width) {
412 int dataOffset = rowBase + (column * bitsPerPixel / 8);
413 int value = (currentRow[byteOffset] >> bitOffset) & valueMask;
414 int dataShift = maxShift - (bitsPerPixel * (column % pixelsPerByte));
415 data[dataOffset] |= value << dataShift;
416 }
417 column += columnInterval;
418 }
419 }
420 }
421 currentRow = (currentRow is row1) ? row2 : row1;
422 lastRow = (lastRow is row1) ? row2 : row1;
423 }
424 setImageDataValues(data, imageData);
425 fireInterlacedFrameEvent(frameCount);
426 }
427 /**
428 * Read the pixel data for an interlaced image from the
429 * data stream.
430 */
431 void readInterlacedImage(InputStream inputStream) throws IOException {
432 readInterlaceFrame(inputStream, 8, 8, 0, 0, 0);
433 readInterlaceFrame(inputStream, 8, 8, 0, 4, 1);
434 readInterlaceFrame(inputStream, 8, 4, 4, 0, 2);
435 readInterlaceFrame(inputStream, 4, 4, 0, 2, 3);
436 readInterlaceFrame(inputStream, 4, 2, 2, 0, 4);
437 readInterlaceFrame(inputStream, 2, 2, 0, 1, 5);
438 readInterlaceFrame(inputStream, 2, 1, 1, 0, 6);
439 }
440 /**
441 * Fire an event to let listeners know that an interlaced
442 * frame has been loaded.
443 * finalFrame should be true if the image has finished
444 * loading, false if there are more frames to come.
445 */
446 void fireInterlacedFrameEvent(int frameCount) {
447 if (loader.hasListeners()) {
448 ImageData image = (ImageData) imageData.clone();
449 bool finalFrame = frameCount is 6;
450 loader.notifyListeners(new ImageLoaderEvent(loader, image, frameCount, finalFrame));
451 }
452 }
453 /**
454 * Read the pixel data for a non-interlaced image from the
455 * data stream.
456 * Update the imageData to reflect the new data.
457 */
458 void readNonInterlacedImage(InputStream inputStream) throws IOException {
459 int dataOffset = 0;
460 int alignedBytesPerRow = getAlignedBytesPerRow();
461 int bytesPerRow = getBytesPerRow();
462 byte[] row1 = new byte[bytesPerRow];
463 byte[] row2 = new byte[bytesPerRow];
464 byte[] currentRow = row1;
465 byte[] lastRow = row2;
466 int height = headerChunk.getHeight();
467 for (int row = 0; row < height; row++) {
468 byte filterType = (byte)inputStream.read();
469 int read = 0;
470 while (read !is bytesPerRow) {
471 read += inputStream.read(currentRow, read, bytesPerRow - read);
472 }
473 filterRow(currentRow, lastRow, filterType);
474 System.arraycopy(currentRow, 0, data, dataOffset, bytesPerRow);
475 dataOffset += alignedBytesPerRow;
476 currentRow = (currentRow is row1) ? row2 : row1;
477 lastRow = (lastRow is row1) ? row2 : row1;
478 }
479 setImageDataValues(data, imageData);
480 }
481 /**
482 * DWT does not support 16-bit depth color formats.
483 * Convert the 16-bit data to 8-bit data.
484 * The correct way to do this is to multiply each
485 * 16 bit value by the value:
486 * (2^8 - 1) / (2^16 - 1).
487 * The fast way to do this is just to drop the low
488 * byte of the 16-bit value.
489 */
490 static void compress16BitDepthTo8BitDepth(
491 byte[] source,
492 int sourceOffset,
493 byte[] destination,
494 int destinationOffset,
495 int numberOfValues)
496 {
497 //double multiplier = (Compatibility.pow2(8) - 1) / (Compatibility.pow2(16) - 1);
498 for (int i = 0; i < numberOfValues; i++) {
499 int sourceIndex = sourceOffset + (2 * i);
500 int destinationIndex = destinationOffset + i;
501 //int value = (source[sourceIndex] << 8) | source[sourceIndex + 1];
502 //byte compressedValue = (byte)(value * multiplier);
503 byte compressedValue = source[sourceIndex];
504 destination[destinationIndex] = compressedValue;
505 }
506 }
507 /**
508 * DWT does not support 16-bit depth color formats.
509 * Convert the 16-bit data to 8-bit data.
510 * The correct way to do this is to multiply each
511 * 16 bit value by the value:
512 * (2^8 - 1) / (2^16 - 1).
513 * The fast way to do this is just to drop the low
514 * byte of the 16-bit value.
515 */
516 static int compress16BitDepthTo8BitDepth(int value) {
517 //double multiplier = (Compatibility.pow2(8) - 1) / (Compatibility.pow2(16) - 1);
518 //byte compressedValue = (byte)(value * multiplier);
519 return value >> 8;
520 }
521 /**
522 * PNG supports four filtering types. These types are applied
523 * per row of image data. This method unfilters the given row
524 * based on the filterType.
525 */
526 void filterRow(byte[] row, byte[] previousRow, int filterType) {
527 int byteOffset = headerChunk.getFilterByteOffset();
528 switch (filterType) {
529 case PngIhdrChunk.FILTER_NONE:
530 break;
531 case PngIhdrChunk.FILTER_SUB:
532 for (int i = byteOffset; i < row.length; i++) {
533 int current = row[i] & 0xFF;
534 int left = row[i - byteOffset] & 0xFF;
535 row[i] = (byte)((current + left) & 0xFF);
536 }
537 break;
538 case PngIhdrChunk.FILTER_UP:
539 for (int i = 0; i < row.length; i++) {
540 int current = row[i] & 0xFF;
541 int above = previousRow[i] & 0xFF;
542 row[i] = (byte)((current + above) & 0xFF);
543 }
544 break;
545 case PngIhdrChunk.FILTER_AVERAGE:
546 for (int i = 0; i < row.length; i++) {
547 int left = (i < byteOffset) ? 0 : row[i - byteOffset] & 0xFF;
548 int above = previousRow[i] & 0xFF;
549 int current = row[i] & 0xFF;
550 row[i] = (byte)((current + ((left + above) / 2)) & 0xFF);
551 }
552 break;
553 case PngIhdrChunk.FILTER_PAETH:
554 for (int i = 0; i < row.length; i++) {
555 int left = (i < byteOffset) ? 0 : row[i - byteOffset] & 0xFF;
556 int aboveLeft = (i < byteOffset) ? 0 : previousRow[i - byteOffset] & 0xFF;
557 int above = previousRow[i] & 0xFF;
558
559 int a = Math.abs(above - aboveLeft);
560 int b = Math.abs(left - aboveLeft);
561 int c = Math.abs(left - aboveLeft + above - aboveLeft);
562
563 int preductor = 0;
564 if (a <= b && a <= c) {
565 preductor = left;
566 } else if (b <= c) {
567 preductor = above;
568 } else {
569 preductor = aboveLeft;
570 }
571
572 int currentValue = row[i] & 0xFF;
573 row[i] = (byte) ((currentValue + preductor) & 0xFF);
574 }
575 break;
576 }
577 }
578
579 }