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