1 /*******************************************************************************
2 * Copyright (c) 2000, 2011 IBM Corporation and others.
3 *
4 * This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License 2.0
6 * which accompanies this distribution, and is available at
7 * https://www.eclipse.org/legal/epl-2.0/
8 *
9 * SPDX-License-Identifier: EPL-2.0
10 *
11 * Contributors:
12 * IBM Corporation - initial API and implementation
13 *******************************************************************************/
14 package org.eclipse.swt.internal.image;
15
16
17 import java.io.*;
18 import java.util.zip.*;
19
20 import org.eclipse.swt.*;
21 import org.eclipse.swt.graphics.*;
22
23 public final class PNGFileFormat extends FileFormat {
24 static final int SIGNATURE_LENGTH = 8;
25 static final int PRIME = 65521;
26 PngIhdrChunk headerChunk;
27 PngPlteChunk paletteChunk;
28 ImageData imageData;
29 byte[] data;
30 byte[] alphaPalette;
31 byte headerByte1;
32 byte headerByte2;
33 int adler;
34
35 /**
36 * Skip over signature data. This has already been
37 * verified in isFileFormat().
38 */
readSignature()39 void readSignature() throws IOException {
40 byte[] signature = new byte[SIGNATURE_LENGTH];
41 inputStream.read(signature);
42 }
43 /**
44 * Load the PNG image from the byte stream.
45 */
46 @Override
loadFromByteStream()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) SWT.error(SWT.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 SWT.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 SWT.error(SWT.ERROR_INVALID_IMAGE);
86 return null;
87 }
88 }
89 /**
90 * Read and handle the next chunk of data from the
91 * PNG file.
92 */
readNextChunk(PngChunkReader chunkReader)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) ==
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) != 255) {
116 transparentCount++;
117 transparentPixel = i;
118 }
119 }
120 if (transparentCount == 0) {
121 alphaPalette = null;
122 } else if (transparentCount == 1 && alphaPalette[transparentPixel] == 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 SWT.error(SWT.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 SWT.error(SWT.ERROR_NOT_IMPLEMENTED);
146 }
147 }
148 }
149 @Override
unloadIntoByteStream(ImageLoader loader)150 void unloadIntoByteStream(ImageLoader loader) {
151 PngEncoder encoder = new PngEncoder(loader);
152 encoder.encode(outputStream);
153 }
154 @Override
isFileFormat(LEDataInputStream stream)155 boolean isFileFormat(LEDataInputStream stream) {
156 try {
157 byte[] signature = new byte[SIGNATURE_LENGTH];
158 stream.read(signature);
159 stream.unread(signature);
160 if ((signature[0] & 0xFF) != 137) return false; //137
161 if ((signature[1] & 0xFF) != 80) return false; //P
162 if ((signature[2] & 0xFF) != 78) return false; //N
163 if ((signature[3] & 0xFF) != 71) return false; //G
164 if ((signature[4] & 0xFF) != 13) return false; //<RETURN>
165 if ((signature[5] & 0xFF) != 10) return false; //<LINEFEED>
166 if ((signature[6] & 0xFF) != 26) return false; //<CTRL/Z>
167 if ((signature[7] & 0xFF) != 10) return false; //<LINEFEED>
168 return true;
169 } catch (Exception e) {
170 return false;
171 }
172 }
173 /**
174 * SWT does not support 16-bit depths. If this image uses
175 * 16-bit depths, convert the data to an 8-bit depth.
176 */
validateBitDepth(byte[] data)177 byte[] validateBitDepth(byte[] data) {
178 if (headerChunk.getBitDepth() > 8) {
179 byte[] result = new byte[data.length / 2];
180 compress16BitDepthTo8BitDepth(data, 0, result, 0, result.length);
181 return result;
182 } else {
183 return data;
184 }
185 }
186 /**
187 * SWT does not support greyscale as a color type. For
188 * plain grayscale, we create a palette. For Grayscale
189 * with Alpha, however, we need to convert the pixels
190 * to use RGB values.
191 * Note: This method assumes that the bit depth of the
192 * data has already been restricted to 8 or less.
193 */
setPixelData(byte[] data, ImageData imageData)194 void setPixelData(byte[] data, ImageData imageData) {
195 switch (headerChunk.getColorType()) {
196 case PngIhdrChunk.COLOR_TYPE_GRAYSCALE_WITH_ALPHA:
197 {
198 int width = imageData.width;
199 int height = imageData.height;
200 int destBytesPerLine = imageData.bytesPerLine;
201 /*
202 * If the image uses 16-bit depth, it is converted
203 * to an 8-bit depth image.
204 */
205 int srcBytesPerLine = getAlignedBytesPerRow();
206 if (headerChunk.getBitDepth() > 8) srcBytesPerLine /= 2;
207
208 byte[] rgbData = new byte[destBytesPerLine * height];
209 byte[] alphaData = new byte[width * height];
210 for (int y = 0; y < height; y++) {
211 int srcIndex = srcBytesPerLine * y;
212 int destIndex = destBytesPerLine * y;
213 int destAlphaIndex = width * y;
214 for (int x = 0; x < width; x++) {
215 byte grey = data[srcIndex];
216 byte alpha = data[srcIndex + 1];
217 rgbData[destIndex + 0] = grey;
218 rgbData[destIndex + 1] = grey;
219 rgbData[destIndex + 2] = grey;
220 alphaData[destAlphaIndex] = alpha;
221 srcIndex += 2;
222 destIndex += 3;
223 destAlphaIndex++;
224 }
225 }
226 imageData.data = rgbData;
227 imageData.alphaData = alphaData;
228 break;
229 }
230 case PngIhdrChunk.COLOR_TYPE_RGB_WITH_ALPHA:
231 {
232 int width = imageData.width;
233 int height = imageData.height;
234 int destBytesPerLine = imageData.bytesPerLine;
235 int srcBytesPerLine = getAlignedBytesPerRow();
236 /*
237 * If the image uses 16-bit depth, it is converted
238 * to an 8-bit depth image.
239 */
240 if (headerChunk.getBitDepth() > 8) srcBytesPerLine /= 2;
241
242 byte[] rgbData = new byte[destBytesPerLine * height];
243 byte[] alphaData = new byte[width * height];
244 for (int y = 0; y < height; y++) {
245 int srcIndex = srcBytesPerLine * y;
246 int destIndex = destBytesPerLine * y;
247 int destAlphaIndex = width * y;
248 for (int x = 0; x < width; x++) {
249 rgbData[destIndex + 0] = data[srcIndex + 0];
250 rgbData[destIndex + 1] = data[srcIndex + 1];
251 rgbData[destIndex + 2] = data[srcIndex + 2];
252 alphaData[destAlphaIndex] = data[srcIndex + 3];
253 srcIndex += 4;
254 destIndex += 3;
255 destAlphaIndex++;
256 }
257 }
258 imageData.data = rgbData;
259 imageData.alphaData = alphaData;
260 break;
261 }
262 case PngIhdrChunk.COLOR_TYPE_PALETTE:
263 imageData.data = data;
264 if (alphaPalette != null) {
265 int size = imageData.width * imageData.height;
266 byte[] alphaData = new byte[size];
267 byte[] pixelData = new byte[size];
268 imageData.getPixels(0, 0, size, pixelData, 0);
269 for (int i = 0; i < pixelData.length; i++) {
270 alphaData[i] = alphaPalette[pixelData[i] & 0xFF];
271 }
272 imageData.alphaData = alphaData;
273 }
274 break;
275 case PngIhdrChunk.COLOR_TYPE_RGB:
276 default:
277 int height = imageData.height;
278 int destBytesPerLine = imageData.bytesPerLine;
279 int srcBytesPerLine = getAlignedBytesPerRow();
280 /*
281 * If the image uses 16-bit depth, it is converted
282 * to an 8-bit depth image.
283 */
284 if (headerChunk.getBitDepth() > 8) srcBytesPerLine /= 2;
285 if (destBytesPerLine != srcBytesPerLine) {
286 for (int y = 0; y < height; y++) {
287 System.arraycopy(data, y * srcBytesPerLine, imageData.data, y * destBytesPerLine, srcBytesPerLine);
288 }
289 } else {
290 imageData.data = data;
291 }
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 */
setImageDataValues(byte[] data, ImageData imageData)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 @SuppressWarnings("resource")
readPixelData(PngIdatChunk chunk, PngChunkReader chunkReader)311 void readPixelData(PngIdatChunk chunk, PngChunkReader chunkReader) throws IOException {
312 InputStream stream = new PngInputStream(chunk, chunkReader);
313 //TEMPORARY CODE
314 boolean use3_2 = System.getProperty("org.eclipse.swt.internal.image.PNGFileFormat_3.2") != null;
315 InputStream inflaterStream = use3_2 ? null : new BufferedInputStream(new InflaterInputStream(stream));
316 if (inflaterStream != null) {
317 stream = inflaterStream;
318 } else {
319 stream = new PngDecodingDataStream(stream);
320 }
321 int interlaceMethod = headerChunk.getInterlaceMethod();
322 if (interlaceMethod == 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 */
getAlignedBytesPerRow()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 */
getBytesPerRow()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 */
getBytesPerPixel()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 */
getBytesPerRow(int rowWidthInPixels)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 */
readInterlaceFrame( InputStream inputStream, int rowInterval, int columnInterval, int startRow, int startColumn, int frameCount)378 void readInterlaceFrame(
379 InputStream inputStream,
380 int rowInterval,
381 int columnInterval,
382 int startRow,
383 int startColumn,
384 int frameCount) throws IOException
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 = (byte)inputStream.read();
399 int read = 0;
400 while (read != 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 (byte element : currentRow) {
425 for (int bitOffset = maxShift; bitOffset >= 0; bitOffset -= bitsPerPixel) {
426 if (column < width) {
427 int dataOffset = rowBase + (column * bitsPerPixel / 8);
428 int value = (element >> bitOffset) & valueMask;
429 int dataShift = maxShift - (bitsPerPixel * (column % pixelsPerByte));
430 data[dataOffset] |= value << dataShift;
431 }
432 column += columnInterval;
433 }
434 }
435 }
436 currentRow = (currentRow == row1) ? row2 : row1;
437 lastRow = (lastRow == 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 */
readInterlacedImage(InputStream inputStream)446 void readInterlacedImage(InputStream inputStream) throws IOException {
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 */
fireInterlacedFrameEvent(int frameCount)461 void fireInterlacedFrameEvent(int frameCount) {
462 if (loader.hasListeners()) {
463 ImageData image = (ImageData) imageData.clone();
464 boolean finalFrame = frameCount == 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 */
readNonInterlacedImage(InputStream inputStream)473 void readNonInterlacedImage(InputStream inputStream) throws IOException {
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 = (byte)inputStream.read();
484 int read = 0;
485 while (read != 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 == row1) ? row2 : row1;
492 lastRow = (lastRow == 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 */
compress16BitDepthTo8BitDepth( byte[] source, int sourceOffset, byte[] destination, int destinationOffset, int numberOfValues)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 */
compress16BitDepthTo8BitDepth(int value)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 */
filterRow(byte[] row, byte[] previousRow, int filterType)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] = (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] = (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] = (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] = (byte) ((currentValue + preductor) & 0xFF);
589 }
590 break;
591 }
592 }
593
594 }