comparison org.eclipse.swt.win32.win32.x86/src/org/eclipse/swt/internal/image/PNGFileFormat.d @ 0:6dd524f61e62

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