/* * 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$ */ package org.apache.harmony.awt.gl.image; import java.awt.image.*; import java.awt.color.ColorSpace; import java.awt.*; import java.io.IOException; import java.io.InputStream; import java.util.Hashtable; import org.apache.harmony.awt.internal.nls.Messages; public class JpegDecoder extends ImageDecoder { // Only 2 output colorspaces expected. Others are converted into // these ones. // 1. Grayscale public static final int JCS_GRAYSCALE = 1; // 2. RGB public static final int JCS_RGB = 2; // Flags for the consumer, progressive JPEG private static final int hintflagsProgressive = ImageConsumer.SINGLEFRAME | // JPEG is a static image ImageConsumer.TOPDOWNLEFTRIGHT | // This order is only one possible ImageConsumer.COMPLETESCANLINES; // Don't deliver incomplete scanlines // Flags for the consumer, singlepass JPEG private static final int hintflagsSingle = ImageConsumer.SINGLEPASS | hintflagsProgressive; // Buffer for the stream private static final int BUFFER_SIZE = 1024; private byte buffer[] = new byte[BUFFER_SIZE]; // 3 possible color models only private static ColorModel cmRGB; private static ColorModel cmGray; // initializes proper field IDs private static native void initIDs(); // Pointer to native structure which store decoding state // between subsequent decoding/IO-suspension cycles private long hNativeDecoder = 0; // NULL initially private boolean headerDone = false; // Next 4 members are filled by the native method (decompress). // We can simply check if imageWidth is still negative to find // out if they are already filled. private int imageWidth = -1; private int imageHeight = -1; private boolean progressive = false; private int jpegColorSpace = 0; // Stores number of bytes consumed by the native decoder private int bytesConsumed = 0; // Stores current scanline returned by the decoder private int currScanline = 0; private ColorModel cm = null; static { System.loadLibrary("jpegdecoder"); //$NON-NLS-1$ cmGray = new ComponentColorModel( ColorSpace.getInstance(ColorSpace.CS_GRAY), false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE ); // Create RGB color model cmRGB = new DirectColorModel(24, 0xFF0000, 0xFF00, 0xFF); initIDs(); } public JpegDecoder(DecodingImageSource src, InputStream is) { super(src, is); } /* public JpegDecoder(InputStream iStream, ImageConsumer iConsumer) { inputStream = iStream; consumer = iConsumer; } */ /** * @return - not NULL if call is successful */ private native Object decode( byte[] input, int bytesInBuffer, long hDecoder); private static native void releaseNativeDecoder(long hDecoder); @Override public void decodeImage() throws IOException { try { int bytesRead = 0, dataLength = 0; boolean eosReached = false; int needBytes, offset, bytesInBuffer = 0; byte byteOut[] = null; int intOut[] = null; // Read from the input stream for (;;) { needBytes = BUFFER_SIZE - bytesInBuffer; offset = bytesInBuffer; bytesRead = inputStream.read(buffer, offset, needBytes); if (bytesRead < 0) { bytesRead = 0;//break; eosReached = true; } // Don't break, maybe something left in buffer // Keep track on how much bytes left in buffer bytesInBuffer += bytesRead; // Here we pass overall number of bytes left in the java buffer // (bytesInBuffer) since jpeg decoder has its own buffer and consumes // as many bytes as it can. If there are any unconsumed bytes // it didn't add them to its buffer... Object arr = decode( buffer, bytesInBuffer, hNativeDecoder); // Keep track on how much bytes left in buffer bytesInBuffer -= bytesConsumed; if (!headerDone && imageWidth != -1) { returnHeader(); headerDone = true; } if (bytesConsumed < 0) { break; // Error exit } if (arr instanceof byte[]) { byteOut = (byte[]) arr; dataLength = byteOut.length; returnData(byteOut, currScanline); } else if (arr instanceof int[]) { intOut = (int[]) arr; dataLength = intOut.length; returnData(intOut, currScanline); } else { dataLength = 0; } if (hNativeDecoder == 0) { break; } if (dataLength == 0 && eosReached) { releaseNativeDecoder(hNativeDecoder); break; // Probably image is truncated } } imageComplete(ImageConsumer.STATICIMAGEDONE); } catch (IOException e) { throw e; } finally { closeStream(); } } public void returnHeader() { setDimensions(imageWidth, imageHeight); switch (jpegColorSpace) { case JCS_GRAYSCALE: cm = cmGray; break; case JCS_RGB: cm = cmRGB; break; default: // awt.3D=Unknown colorspace throw new IllegalArgumentException(Messages.getString("awt.3D")); //$NON-NLS-1$ } setColorModel(cm); setHints(progressive ? hintflagsProgressive : hintflagsSingle); setProperties(new Hashtable<Object, Object>()); // Empty } // Send the data to the consumer public void returnData(int data[], int currScanLine) { // Send 1 or more scanlines to the consumer. int numScanlines = data.length / imageWidth; if (numScanlines > 0) { setPixels( 0, currScanLine - numScanlines, imageWidth, numScanlines, cm, data, 0, imageWidth ); } } public void returnData(byte data[], int currScanLine) { int numScanlines = data.length / imageWidth; if (numScanlines > 0) { setPixels( 0, currScanLine - numScanlines, imageWidth, numScanlines, cm, data, 0, imageWidth ); } } }