blob: 2e64427f09c4611d42ef3128c115705b2188727c [file] [log] [blame]
/*
* 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
);
}
}
}