/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @author Oleg V. Khaschansky * @version $Revision$ * * @date: Jul 22, 2005 */ package org.apache.harmony.awt.gl.image; import java.io.IOException; import java.io.InputStream; import java.util.Hashtable; import java.awt.color.ColorSpace; import java.awt.image.*; import java.awt.*; import org.apache.harmony.awt.internal.nls.Messages; public class PngDecoder extends ImageDecoder { // initializes proper field IDs private static native void initIDs(); static { System.loadLibrary("gl"); //$NON-NLS-1$ initIDs(); } private static final int hintflags = ImageConsumer.SINGLEFRAME | // PNG is a static image ImageConsumer.TOPDOWNLEFTRIGHT | // This order is only one possible ImageConsumer.COMPLETESCANLINES; // Don't deliver incomplete scanlines // Each pixel is a grayscale sample. private static final int PNG_COLOR_TYPE_GRAY = 0; // Each pixel is an R,G,B triple. private static final int PNG_COLOR_TYPE_RGB = 2; // Each pixel is a palette index, a PLTE chunk must appear. private static final int PNG_COLOR_TYPE_PLTE = 3; // Each pixel is a grayscale sample, followed by an alpha sample. private static final int PNG_COLOR_TYPE_GRAY_ALPHA = 4; // Each pixel is an R,G,B triple, followed by an alpha sample. private static final int PNG_COLOR_TYPE_RGBA = 6; private static final int INPUT_BUFFER_SIZE = 4096; private byte buffer[] = new byte[INPUT_BUFFER_SIZE]; // Buffers for decoded image data byte byteOut[]; int intOut[]; // Native pointer to png decoder data private long hNativeDecoder; int imageWidth, imageHeight; int colorType; int bitDepth; byte cmap[]; boolean transferInts; // Is transfer type int?.. or byte? int dataElementsPerPixel = 1; ColorModel cm; int updateFromScanline; // First scanline to update int numScanlines; // Number of scanlines to update private native long decode(byte[] input, int bytesInBuffer, long hDecoder); private static native void releaseNativeDecoder(long hDecoder); public PngDecoder(DecodingImageSource src, InputStream is) { super(src, is); } @Override public void decodeImage() throws IOException { try { int bytesRead = 0; int needBytes, offset, bytesInBuffer = 0; // Read from the input stream for (;;) { needBytes = INPUT_BUFFER_SIZE - bytesInBuffer; offset = bytesInBuffer; bytesRead = inputStream.read(buffer, offset, needBytes); if (bytesRead < 0) { // Break, nothing to read from buffer, image truncated? releaseNativeDecoder(hNativeDecoder); break; } // Keep track on how much bytes left in buffer bytesInBuffer += bytesRead; hNativeDecoder = decode(buffer, bytesInBuffer, hNativeDecoder); // PNG decoder always consumes all bytes at once bytesInBuffer = 0; // if (bytesConsumed < 0) //break; // Error exit returnData(); // OK, we decoded all the picture in the right way... if (hNativeDecoder == 0) { break; } } imageComplete(ImageConsumer.STATICIMAGEDONE); } catch (IOException e) { throw e; } catch (RuntimeException e) { imageComplete(ImageConsumer.IMAGEERROR); throw e; } finally { closeStream(); } } @SuppressWarnings("unused") private void returnHeader() { // Called from native code setDimensions(imageWidth, imageHeight); switch (colorType) { case PNG_COLOR_TYPE_GRAY: { if (bitDepth != 8 && bitDepth != 4 && bitDepth != 2 && bitDepth != 1) { // awt.3C=Unknown PNG color type throw new IllegalArgumentException(Messages.getString("awt.3C")); //$NON-NLS-1$ } // Create gray color model int numEntries = 1 << bitDepth; int scaleFactor = 255 / (numEntries-1); byte comps[] = new byte[numEntries]; for (int i = 0; i < numEntries; i++) { comps[i] = (byte) (i * scaleFactor); } cm = new IndexColorModel(/*bitDepth*/8, numEntries, comps, comps, comps); transferInts = false; break; } case PNG_COLOR_TYPE_RGB: { if (bitDepth != 8) { // awt.3C=Unknown PNG color type throw new IllegalArgumentException(Messages.getString("awt.3C")); //$NON-NLS-1$ } cm = new DirectColorModel(24, 0xFF0000, 0xFF00, 0xFF); transferInts = true; break; } case PNG_COLOR_TYPE_PLTE: { if (bitDepth != 8 && bitDepth != 4 && bitDepth != 2 && bitDepth != 1) { // awt.3C=Unknown PNG color type throw new IllegalArgumentException(Messages.getString("awt.3C")); //$NON-NLS-1$ } cm = new IndexColorModel(/*bitDepth*/8, cmap.length / 3, cmap, 0, false); transferInts = false; break; } case PNG_COLOR_TYPE_GRAY_ALPHA: { if (bitDepth != 8) { // awt.3C=Unknown PNG color type throw new IllegalArgumentException(Messages.getString("awt.3C")); //$NON-NLS-1$ } cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE); transferInts = false; dataElementsPerPixel = 2; break; } case PNG_COLOR_TYPE_RGBA: { if (bitDepth != 8) { // awt.3C=Unknown PNG color type throw new IllegalArgumentException(Messages.getString("awt.3C")); //$NON-NLS-1$ } cm = ColorModel.getRGBdefault(); transferInts = true; break; } default: // awt.3C=Unknown PNG color type throw new IllegalArgumentException(Messages.getString("awt.3C")); //$NON-NLS-1$ } // Create output buffer if (transferInts) { intOut = new int[imageWidth * imageHeight]; } else { byteOut = new byte[imageWidth * imageHeight * dataElementsPerPixel]; } setColorModel(cm); setHints(hintflags); setProperties(new Hashtable<Object, Object>()); // Empty } // Send the data to the consumer private void returnData() { // Send 1 or more scanlines to the consumer. if (numScanlines > 0) { // Native decoder could have returned // some data from the next pass, handle it here int pass1, pass2; if (updateFromScanline + numScanlines > imageHeight) { pass1 = imageHeight - updateFromScanline; pass2 = updateFromScanline + numScanlines - imageHeight; } else { pass1 = numScanlines; pass2 = 0; } transfer(updateFromScanline, pass1); if (pass2 != 0) { transfer(0, pass2); } } } private void transfer(int updateFromScanline, int numScanlines) { if (transferInts) { setPixels( 0, updateFromScanline, imageWidth, numScanlines, cm, intOut, updateFromScanline * imageWidth, imageWidth ); } else { setPixels( 0, updateFromScanline, imageWidth, numScanlines, cm, byteOut, updateFromScanline * imageWidth * dataElementsPerPixel, imageWidth * dataElementsPerPixel ); } } }