1/*
2*  Licensed to the Apache Software Foundation (ASF) under one or more
3*  contributor license agreements.  See the NOTICE file distributed with
4*  this work for additional information regarding copyright ownership.
5*  The ASF licenses this file to You under the Apache License, Version 2.0
6*  (the "License"); you may not use this file except in compliance with
7*  the License.  You may obtain a copy of the License at
8*
9*     http://www.apache.org/licenses/LICENSE-2.0
10*
11*  Unless required by applicable law or agreed to in writing, software
12*  distributed under the License is distributed on an "AS IS" BASIS,
13*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14*  See the License for the specific language governing permissions and
15*  limitations under the License.
16*/
17/**
18 * @author Oleg V. Khaschansky
19 * @version $Revision$
20 */
21package org.apache.harmony.awt.gl.image;
22
23import java.awt.image.*;
24import java.awt.color.ColorSpace;
25import java.awt.*;
26import java.io.IOException;
27import java.io.InputStream;
28import java.util.Hashtable;
29
30import org.apache.harmony.awt.internal.nls.Messages;
31
32public class JpegDecoder extends ImageDecoder {
33    // Only 2 output colorspaces expected. Others are converted into
34    // these ones.
35    // 1. Grayscale
36    public static final int JCS_GRAYSCALE = 1;
37    // 2. RGB
38    public static final int JCS_RGB = 2;
39
40    // Flags for the consumer, progressive JPEG
41    private static final int hintflagsProgressive =
42            ImageConsumer.SINGLEFRAME | // JPEG is a static image
43            ImageConsumer.TOPDOWNLEFTRIGHT | // This order is only one possible
44            ImageConsumer.COMPLETESCANLINES; // Don't deliver incomplete scanlines
45    // Flags for the consumer, singlepass JPEG
46    private static final int hintflagsSingle =
47            ImageConsumer.SINGLEPASS |
48            hintflagsProgressive;
49
50    // Buffer for the stream
51    private static final int BUFFER_SIZE = 1024;
52    private byte buffer[] = new byte[BUFFER_SIZE];
53
54    // 3 possible color models only
55    private static ColorModel cmRGB;
56    private static ColorModel cmGray;
57
58    // initializes proper field IDs
59    private static native void initIDs();
60
61    // Pointer to native structure which store decoding state
62    // between subsequent decoding/IO-suspension cycles
63    private long hNativeDecoder = 0; // NULL initially
64
65    private boolean headerDone = false;
66
67    // Next 4 members are filled by the native method (decompress).
68    // We can simply check if imageWidth is still negative to find
69    // out if they are already filled.
70    private int imageWidth = -1;
71    private int imageHeight = -1;
72    private boolean progressive = false;
73    private int jpegColorSpace = 0;
74
75    // Stores number of bytes consumed by the native decoder
76    private int bytesConsumed = 0;
77    // Stores current scanline returned by the decoder
78    private int currScanline = 0;
79
80    private ColorModel cm = null;
81
82    static {
83        System.loadLibrary("jpegdecoder"); //$NON-NLS-1$
84
85        cmGray = new ComponentColorModel(
86                ColorSpace.getInstance(ColorSpace.CS_GRAY),
87                false, false,
88                Transparency.OPAQUE, DataBuffer.TYPE_BYTE
89        );
90
91        // Create RGB color model
92        cmRGB = new DirectColorModel(24, 0xFF0000, 0xFF00, 0xFF);
93
94        initIDs();
95    }
96
97    public JpegDecoder(DecodingImageSource src, InputStream is) {
98        super(src, is);
99    }
100
101    /*
102    public JpegDecoder(InputStream iStream, ImageConsumer iConsumer) {
103    inputStream = iStream;
104    consumer = iConsumer;
105    }
106    */
107
108    /**
109     * @return - not NULL if call is successful
110     */
111    private native Object decode(
112            byte[] input,
113            int bytesInBuffer,
114            long hDecoder);
115
116    private static native void releaseNativeDecoder(long hDecoder);
117
118    @Override
119    public void decodeImage() throws IOException {
120        try {
121            int bytesRead = 0, dataLength = 0;
122            boolean eosReached = false;
123            int needBytes, offset, bytesInBuffer = 0;
124            byte byteOut[] = null;
125            int intOut[] = null;
126            // Read from the input stream
127            for (;;) {
128                needBytes = BUFFER_SIZE - bytesInBuffer;
129                offset = bytesInBuffer;
130
131                bytesRead = inputStream.read(buffer, offset, needBytes);
132
133                if (bytesRead < 0) {
134                    bytesRead = 0;//break;
135                    eosReached = true;
136                } // Don't break, maybe something left in buffer
137
138                // Keep track on how much bytes left in buffer
139                bytesInBuffer += bytesRead;
140
141                // Here we pass overall number of bytes left in the java buffer
142                // (bytesInBuffer) since jpeg decoder has its own buffer and consumes
143                // as many bytes as it can. If there are any unconsumed bytes
144                // it didn't add them to its buffer...
145                Object arr = decode(
146                        buffer,
147                        bytesInBuffer,
148                        hNativeDecoder);
149
150                // Keep track on how much bytes left in buffer
151                bytesInBuffer -= bytesConsumed;
152
153                if (!headerDone && imageWidth != -1) {
154                    returnHeader();
155                    headerDone = true;
156                }
157
158                if (bytesConsumed < 0) {
159                    break; // Error exit
160                }
161
162                if (arr instanceof byte[]) {
163                    byteOut = (byte[]) arr;
164                    dataLength = byteOut.length;
165                    returnData(byteOut, currScanline);
166                } else if (arr instanceof int[]) {
167                    intOut = (int[]) arr;
168                    dataLength = intOut.length;
169                    returnData(intOut, currScanline);
170                } else {
171                    dataLength = 0;
172                }
173
174                if (hNativeDecoder == 0) {
175                    break;
176                }
177
178                if (dataLength == 0 && eosReached) {
179                    releaseNativeDecoder(hNativeDecoder);
180                    break; // Probably image is truncated
181                }
182            }
183            imageComplete(ImageConsumer.STATICIMAGEDONE);
184        } catch (IOException e) {
185            throw e;
186        } finally {
187            closeStream();
188        }
189    }
190
191    public void returnHeader() {
192        setDimensions(imageWidth, imageHeight);
193
194        switch (jpegColorSpace) {
195            case JCS_GRAYSCALE: cm = cmGray; break;
196            case JCS_RGB: cm = cmRGB; break;
197            default:
198                // awt.3D=Unknown colorspace
199                throw new IllegalArgumentException(Messages.getString("awt.3D")); //$NON-NLS-1$
200        }
201        setColorModel(cm);
202
203        setHints(progressive ? hintflagsProgressive : hintflagsSingle);
204
205        setProperties(new Hashtable<Object, Object>()); // Empty
206    }
207
208    // Send the data to the consumer
209    public void returnData(int data[], int currScanLine) {
210        // Send 1 or more scanlines to the consumer.
211        int numScanlines = data.length / imageWidth;
212        if (numScanlines > 0) {
213            setPixels(
214                    0, currScanLine - numScanlines,
215                    imageWidth, numScanlines,
216                    cm, data, 0, imageWidth
217            );
218        }
219    }
220
221    public void returnData(byte data[], int currScanLine) {
222        int numScanlines = data.length / imageWidth;
223        if (numScanlines > 0) {
224            setPixels(
225                    0, currScanLine - numScanlines,
226                    imageWidth, numScanlines,
227                    cm, data, 0, imageWidth
228            );
229        }
230    }
231}
232