Mercurial > projects > dwt2
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.swt.win32.win32.x86/src/org/eclipse/swt/internal/image/PNGFileFormat.d Mon Mar 02 14:44:16 2009 +0100 @@ -0,0 +1,595 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module org.eclipse.swt.internal.image.PNGFileFormat; + + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.ImageLoaderEvent; +import org.eclipse.swt.graphics.ImageLoader; +import org.eclipse.swt.graphics.PaletteData; +import org.eclipse.swt.internal.Compatibility; +import org.eclipse.swt.internal.image.FileFormat; +import org.eclipse.swt.internal.image.PngIhdrChunk; +import org.eclipse.swt.internal.image.PngPlteChunk; +import org.eclipse.swt.internal.image.PngChunkReader; +import org.eclipse.swt.internal.image.PngChunk; +import org.eclipse.swt.internal.image.PngTrnsChunk; +import org.eclipse.swt.internal.image.PngIdatChunk; +import org.eclipse.swt.internal.image.PngEncoder; +import org.eclipse.swt.internal.image.PngInputStream; +import org.eclipse.swt.internal.image.PngDecodingDataStream; +import java.lang.all; + +import java.io.BufferedInputStream; + +import tango.core.Exception; + +final class PNGFileFormat : FileFormat { + static final int SIGNATURE_LENGTH = 8; + static final int PRIME = 65521; + PngIhdrChunk headerChunk; + PngPlteChunk paletteChunk; + ImageData imageData; + byte[] data; + byte[] alphaPalette; + byte headerByte1; + byte headerByte2; + int adler; + +/** + * Skip over signature data. This has already been + * verified in isFileFormat(). + */ +void readSignature() { + byte[] signature = new byte[SIGNATURE_LENGTH]; + inputStream.read(signature); +} +/** + * Load the PNG image from the byte stream. + */ +override ImageData[] loadFromByteStream() { + try { + readSignature(); + PngChunkReader chunkReader = new PngChunkReader(inputStream); + headerChunk = chunkReader.getIhdrChunk(); + int width = headerChunk.getWidth(), height = headerChunk.getHeight(); + if (width <= 0 || height <= 0) SWT.error(SWT.ERROR_INVALID_IMAGE); + int imageSize = getAlignedBytesPerRow() * height; + data = new byte[imageSize]; + imageData = ImageData.internal_new( + width, + height, + headerChunk.getSwtBitsPerPixel(), + new PaletteData(0, 0, 0), + 4, + data, + 0, + null, + null, + -1, + -1, + SWT.IMAGE_PNG, + 0, + 0, + 0, + 0); + + if (headerChunk.usesDirectColor()) { + imageData.palette = headerChunk.getPaletteData(); + } + + // Read and process chunks until the IEND chunk is encountered. + while (chunkReader.hasMoreChunks()) { + readNextChunk(chunkReader); + } + + return [imageData]; + } catch (IOException e) { + SWT.error(SWT.ERROR_INVALID_IMAGE); + return null; + } +} +/** + * Read and handle the next chunk of data from the + * PNG file. + */ +void readNextChunk(PngChunkReader chunkReader) { + PngChunk chunk = chunkReader.readNextChunk(); + switch (chunk.getChunkType()) { + case PngChunk.CHUNK_IEND: + break; + case PngChunk.CHUNK_PLTE: + if (!headerChunk.usesDirectColor()) { + paletteChunk = cast(PngPlteChunk) chunk; + imageData.palette = paletteChunk.getPaletteData(); + } + break; + case PngChunk.CHUNK_tRNS: + PngTrnsChunk trnsChunk = cast(PngTrnsChunk) chunk; + if (trnsChunk.getTransparencyType(headerChunk) is + PngTrnsChunk.TRANSPARENCY_TYPE_PIXEL) + { + imageData.transparentPixel = + trnsChunk.getSwtTransparentPixel(headerChunk); + } else { + alphaPalette = trnsChunk.getAlphaValues(headerChunk, paletteChunk); + int transparentCount = 0, transparentPixel = -1; + for (int i = 0; i < alphaPalette.length; i++) { + if ((alphaPalette[i] & 0xFF) !is 255) { + transparentCount++; + transparentPixel = i; + } + } + if (transparentCount is 0) { + alphaPalette = null; + } else if (transparentCount is 1 && alphaPalette[transparentPixel] is 0) { + alphaPalette = null; + imageData.transparentPixel = transparentPixel; + } + } + break; + case PngChunk.CHUNK_IDAT: + if (chunkReader.readPixelData()) { + // All IDAT chunks in an image file must be + // sequential. If the pixel data has already + // been read and another IDAT block is encountered, + // then this is an invalid image. + SWT.error(SWT.ERROR_INVALID_IMAGE); + } else { + // Read in the pixel data for the image. This should + // go through all the image's IDAT chunks. + PngIdatChunk dataChunk = cast(PngIdatChunk) chunk; + readPixelData(dataChunk, chunkReader); + } + break; + default: + if (chunk.isCritical()) { + // All critical chunks must be supported. + SWT.error(SWT.ERROR_NOT_IMPLEMENTED); + } + } +} +override void unloadIntoByteStream(ImageLoader loader) { + PngEncoder encoder = new PngEncoder(loader); + encoder.encode(outputStream); +} +override bool isFileFormat(LEDataInputStream stream) { + try { + byte[] signature = new byte[SIGNATURE_LENGTH]; + stream.read(signature); + stream.unread(signature); + if ((signature[0] & 0xFF) !is 137) return false; //137 + if ((signature[1] & 0xFF) !is 80) return false; //P + if ((signature[2] & 0xFF) !is 78) return false; //N + if ((signature[3] & 0xFF) !is 71) return false; //G + if ((signature[4] & 0xFF) !is 13) return false; //<RETURN> + if ((signature[5] & 0xFF) !is 10) return false; //<LINEFEED> + if ((signature[6] & 0xFF) !is 26) return false; //<CTRL/Z> + if ((signature[7] & 0xFF) !is 10) return false; //<LINEFEED> + return true; + } catch (Exception e) { + return false; + } +} +/** + * SWT does not support 16-bit depths. If this image uses + * 16-bit depths, convert the data to an 8-bit depth. + */ +byte[] validateBitDepth(byte[] data) { + if (headerChunk.getBitDepth() > 8) { + byte[] result = new byte[data.length / 2]; + compress16BitDepthTo8BitDepth(data, 0, result, 0, result.length); + return result; + } else { + return data; + } +} +/** + * SWT does not support greyscale as a color type. For + * plain grayscale, we create a palette. For Grayscale + * with Alpha, however, we need to convert the pixels + * to use RGB values. + * Note: This method assumes that the bit depth of the + * data has already been restricted to 8 or less. + */ +void setPixelData(byte[] data, ImageData imageData) { + switch (headerChunk.getColorType()) { + case PngIhdrChunk.COLOR_TYPE_GRAYSCALE_WITH_ALPHA: + { + int width = imageData.width; + int height = imageData.height; + int destBytesPerLine = imageData.bytesPerLine; + /* + * If the image uses 16-bit depth, it is converted + * to an 8-bit depth image. + */ + int srcBytesPerLine = getAlignedBytesPerRow(); + if (headerChunk.getBitDepth() > 8) srcBytesPerLine /= 2; + + byte[] rgbData = new byte[destBytesPerLine * height]; + byte[] alphaData = new byte[width * height]; + for (int y = 0; y < height; y++) { + int srcIndex = srcBytesPerLine * y; + int destIndex = destBytesPerLine * y; + int destAlphaIndex = width * y; + for (int x = 0; x < width; x++) { + byte grey = data[srcIndex]; + byte alpha = data[srcIndex + 1]; + rgbData[destIndex + 0] = grey; + rgbData[destIndex + 1] = grey; + rgbData[destIndex + 2] = grey; + alphaData[destAlphaIndex] = alpha; + srcIndex += 2; + destIndex += 3; + destAlphaIndex++; + } + } + imageData.data = rgbData; + imageData.alphaData = alphaData; + break; + } + case PngIhdrChunk.COLOR_TYPE_RGB_WITH_ALPHA: + { + int width = imageData.width; + int height = imageData.height; + int destBytesPerLine = imageData.bytesPerLine; + int srcBytesPerLine = getAlignedBytesPerRow(); + /* + * If the image uses 16-bit depth, it is converted + * to an 8-bit depth image. + */ + if (headerChunk.getBitDepth() > 8) srcBytesPerLine /= 2; + + byte[] rgbData = new byte[destBytesPerLine * height]; + byte[] alphaData = new byte[width * height]; + for (int y = 0; y < height; y++) { + int srcIndex = srcBytesPerLine * y; + int destIndex = destBytesPerLine * y; + int destAlphaIndex = width * y; + for (int x = 0; x < width; x++) { + rgbData[destIndex + 0] = data[srcIndex + 0]; + rgbData[destIndex + 1] = data[srcIndex + 1]; + rgbData[destIndex + 2] = data[srcIndex + 2]; + alphaData[destAlphaIndex] = data[srcIndex + 3]; + srcIndex += 4; + destIndex += 3; + destAlphaIndex++; + } + } + imageData.data = rgbData; + imageData.alphaData = alphaData; + break; + } + case PngIhdrChunk.COLOR_TYPE_RGB: + imageData.data = data; + break; + case PngIhdrChunk.COLOR_TYPE_PALETTE: + imageData.data = data; + if (alphaPalette !is null) { + int size = imageData.width * imageData.height; + byte[] alphaData = new byte[size]; + byte[] pixelData = new byte[size]; + imageData.getPixels(0, 0, size, pixelData, 0); + for (int i = 0; i < pixelData.length; i++) { + alphaData[i] = alphaPalette[pixelData[i] & 0xFF]; + } + imageData.alphaData = alphaData; + } + break; + default: + imageData.data = data; + break; + } +} +/** + * PNG supports some color types and bit depths that are + * unsupported by SWT. If the image uses an unsupported + * color type (either of the gray scale types) or bit + * depth (16), convert the data to an SWT-supported + * format. Then assign the data into the ImageData given. + */ +void setImageDataValues(byte[] data, ImageData imageData) { + byte[] result = validateBitDepth(data); + setPixelData(result, imageData); +} +/** + * Read the image data from the data stream. This must handle + * decoding the data, filtering, and interlacing. + */ +void readPixelData(PngIdatChunk chunk, PngChunkReader chunkReader) { + InputStream stream = new PngInputStream(chunk, chunkReader); + //TEMPORARY CODE + //PORTING_FIXME + bool use3_2 = true;//System.getProperty("org.eclipse.swt.internal.image.PNGFileFormat_3.2") !is null; + InputStream inflaterStream = use3_2 ? null : Compatibility.newInflaterInputStream(stream); + if (inflaterStream !is null) { + stream = inflaterStream; + } else { + stream = new PngDecodingDataStream(stream); + } + int interlaceMethod = headerChunk.getInterlaceMethod(); + if (interlaceMethod is PngIhdrChunk.INTERLACE_METHOD_NONE) { + readNonInterlacedImage(stream); + } else { + readInterlacedImage(stream); + } + /* + * InflaterInputStream does not consume all bytes in the stream + * when it is closed. This may leave unread IDAT chunks. The fix + * is to read all available bytes before closing it. + */ + while (stream.available() > 0) stream.read(); + stream.close(); +} +/** + * Answer the number of bytes in a word-aligned row of pixel data. + */ +int getAlignedBytesPerRow() { + return ((getBytesPerRow(headerChunk.getWidth()) + 3) / 4) * 4; +} +/** + * Answer the number of bytes in each row of the image + * data. Each PNG row is byte-aligned, so images with bit + * depths less than a byte may have unused bits at the + * end of each row. The value of these bits is undefined. + */ +int getBytesPerRow() { + return getBytesPerRow(headerChunk.getWidth()); +} +/** + * Answer the number of bytes needed to represent a pixel. + * This value depends on the image's color type and bit + * depth. + * Note that this method rounds up if an image's pixel size + * isn't byte-aligned. + */ +int getBytesPerPixel() { + int bitsPerPixel = headerChunk.getBitsPerPixel(); + return (bitsPerPixel + 7) / 8; +} +/** + * Answer the number of bytes in a row of the given pixel + * width. Each row is byte-aligned, so images with bit + * depths less than a byte may have unused bits at the + * end of each row. The value of these bits is undefined. + */ +int getBytesPerRow(int rowWidthInPixels) { + int bitsPerPixel = headerChunk.getBitsPerPixel(); + int bitsPerRow = bitsPerPixel * rowWidthInPixels; + int bitsPerByte = 8; + return (bitsPerRow + (bitsPerByte - 1)) / bitsPerByte; +} +/** + * 1. Read one of the seven frames of interlaced data. + * 2. Update the imageData. + * 3. Notify the image loader's listeners of the frame load. + */ +void readInterlaceFrame( + InputStream inputStream, + int rowInterval, + int columnInterval, + int startRow, + int startColumn, + int frameCount) +{ + int width = headerChunk.getWidth(); + int alignedBytesPerRow = getAlignedBytesPerRow(); + int height = headerChunk.getHeight(); + if (startRow >= height || startColumn >= width) return; + + int pixelsPerRow = (width - startColumn + columnInterval - 1) / columnInterval; + int bytesPerRow = getBytesPerRow(pixelsPerRow); + byte[] row1 = new byte[bytesPerRow]; + byte[] row2 = new byte[bytesPerRow]; + byte[] currentRow = row1; + byte[] lastRow = row2; + for (int row = startRow; row < height; row += rowInterval) { + byte filterType = cast(byte)inputStream.read(); + int read = 0; + while (read !is bytesPerRow) { + read += inputStream.read(currentRow, read, bytesPerRow - read); + } + filterRow(currentRow, lastRow, filterType); + if (headerChunk.getBitDepth() >= 8) { + int bytesPerPixel = getBytesPerPixel(); + int dataOffset = (row * alignedBytesPerRow) + (startColumn * bytesPerPixel); + for (int rowOffset = 0; rowOffset < currentRow.length; rowOffset += bytesPerPixel) { + for (int byteOffset = 0; byteOffset < bytesPerPixel; byteOffset++) { + data[dataOffset + byteOffset] = currentRow[rowOffset + byteOffset]; + } + dataOffset += (columnInterval * bytesPerPixel); + } + } else { + int bitsPerPixel = headerChunk.getBitDepth(); + int pixelsPerByte = 8 / bitsPerPixel; + int column = startColumn; + int rowBase = row * alignedBytesPerRow; + int valueMask = 0; + for (int i = 0; i < bitsPerPixel; i++) { + valueMask <<= 1; + valueMask |= 1; + } + int maxShift = 8 - bitsPerPixel; + for (int byteOffset = 0; byteOffset < currentRow.length; byteOffset++) { + for (int bitOffset = maxShift; bitOffset >= 0; bitOffset -= bitsPerPixel) { + if (column < width) { + int dataOffset = rowBase + (column * bitsPerPixel / 8); + int value = (currentRow[byteOffset] >> bitOffset) & valueMask; + int dataShift = maxShift - (bitsPerPixel * (column % pixelsPerByte)); + data[dataOffset] |= value << dataShift; + } + column += columnInterval; + } + } + } + currentRow = (currentRow is row1) ? row2 : row1; + lastRow = (lastRow is row1) ? row2 : row1; + } + setImageDataValues(data, imageData); + fireInterlacedFrameEvent(frameCount); +} +/** + * Read the pixel data for an interlaced image from the + * data stream. + */ +void readInterlacedImage(InputStream inputStream) { + readInterlaceFrame(inputStream, 8, 8, 0, 0, 0); + readInterlaceFrame(inputStream, 8, 8, 0, 4, 1); + readInterlaceFrame(inputStream, 8, 4, 4, 0, 2); + readInterlaceFrame(inputStream, 4, 4, 0, 2, 3); + readInterlaceFrame(inputStream, 4, 2, 2, 0, 4); + readInterlaceFrame(inputStream, 2, 2, 0, 1, 5); + readInterlaceFrame(inputStream, 2, 1, 1, 0, 6); +} +/** + * Fire an event to let listeners know that an interlaced + * frame has been loaded. + * finalFrame should be true if the image has finished + * loading, false if there are more frames to come. + */ +void fireInterlacedFrameEvent(int frameCount) { + if (loader.hasListeners()) { + ImageData image = cast(ImageData) imageData.clone(); + bool finalFrame = frameCount is 6; + loader.notifyListeners(new ImageLoaderEvent(loader, image, frameCount, finalFrame)); + } +} +/** + * Read the pixel data for a non-interlaced image from the + * data stream. + * Update the imageData to reflect the new data. + */ +void readNonInterlacedImage(InputStream inputStream) { + int dataOffset = 0; + int alignedBytesPerRow = getAlignedBytesPerRow(); + int bytesPerRow = getBytesPerRow(); + byte[] row1 = new byte[bytesPerRow]; + byte[] row2 = new byte[bytesPerRow]; + byte[] currentRow = row1; + byte[] lastRow = row2; + int height = headerChunk.getHeight(); + for (int row = 0; row < height; row++) { + byte filterType = cast(byte)inputStream.read(); + int read = 0; + while (read !is bytesPerRow) { + read += inputStream.read(currentRow, read, bytesPerRow - read); + } + filterRow(currentRow, lastRow, filterType); + System.arraycopy(currentRow, 0, data, dataOffset, bytesPerRow); + dataOffset += alignedBytesPerRow; + currentRow = (currentRow is row1) ? row2 : row1; + lastRow = (lastRow is row1) ? row2 : row1; + } + setImageDataValues(data, imageData); +} +/** + * SWT does not support 16-bit depth color formats. + * Convert the 16-bit data to 8-bit data. + * The correct way to do this is to multiply each + * 16 bit value by the value: + * (2^8 - 1) / (2^16 - 1). + * The fast way to do this is just to drop the low + * byte of the 16-bit value. + */ +static void compress16BitDepthTo8BitDepth( + byte[] source, + int sourceOffset, + byte[] destination, + int destinationOffset, + int numberOfValues) +{ + //double multiplier = (Compatibility.pow2(8) - 1) / (Compatibility.pow2(16) - 1); + for (int i = 0; i < numberOfValues; i++) { + int sourceIndex = sourceOffset + (2 * i); + int destinationIndex = destinationOffset + i; + //int value = (source[sourceIndex] << 8) | source[sourceIndex + 1]; + //byte compressedValue = (byte)(value * multiplier); + byte compressedValue = source[sourceIndex]; + destination[destinationIndex] = compressedValue; + } +} +/** + * SWT does not support 16-bit depth color formats. + * Convert the 16-bit data to 8-bit data. + * The correct way to do this is to multiply each + * 16 bit value by the value: + * (2^8 - 1) / (2^16 - 1). + * The fast way to do this is just to drop the low + * byte of the 16-bit value. + */ +static int compress16BitDepthTo8BitDepth(int value) { + //double multiplier = (Compatibility.pow2(8) - 1) / (Compatibility.pow2(16) - 1); + //byte compressedValue = (byte)(value * multiplier); + return value >> 8; +} +/** + * PNG supports four filtering types. These types are applied + * per row of image data. This method unfilters the given row + * based on the filterType. + */ +void filterRow(byte[] row, byte[] previousRow, int filterType) { + int byteOffset = headerChunk.getFilterByteOffset(); + switch (filterType) { + case PngIhdrChunk.FILTER_NONE: + break; + case PngIhdrChunk.FILTER_SUB: + for (int i = byteOffset; i < row.length; i++) { + int current = row[i] & 0xFF; + int left = row[i - byteOffset] & 0xFF; + row[i] = cast(byte)((current + left) & 0xFF); + } + break; + case PngIhdrChunk.FILTER_UP: + for (int i = 0; i < row.length; i++) { + int current = row[i] & 0xFF; + int above = previousRow[i] & 0xFF; + row[i] = cast(byte)((current + above) & 0xFF); + } + break; + case PngIhdrChunk.FILTER_AVERAGE: + for (int i = 0; i < row.length; i++) { + int left = (i < byteOffset) ? 0 : row[i - byteOffset] & 0xFF; + int above = previousRow[i] & 0xFF; + int current = row[i] & 0xFF; + row[i] = cast(byte)((current + ((left + above) / 2)) & 0xFF); + } + break; + case PngIhdrChunk.FILTER_PAETH: + for (int i = 0; i < row.length; i++) { + int left = (i < byteOffset) ? 0 : row[i - byteOffset] & 0xFF; + int aboveLeft = (i < byteOffset) ? 0 : previousRow[i - byteOffset] & 0xFF; + int above = previousRow[i] & 0xFF; + + int a = Math.abs(above - aboveLeft); + int b = Math.abs(left - aboveLeft); + int c = Math.abs(left - aboveLeft + above - aboveLeft); + + int preductor = 0; + if (a <= b && a <= c) { + preductor = left; + } else if (b <= c) { + preductor = above; + } else { + preductor = aboveLeft; + } + + int currentValue = row[i] & 0xFF; + row[i] = cast(byte) ((currentValue + preductor) & 0xFF); + } + break; + default: + } +} + +}