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 }