Mercurial > projects > dwt2
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 } |