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 *
21 * @date: Jul 22, 2005
22 */
23
24package org.apache.harmony.awt.gl.image;
25
26import java.io.IOException;
27import java.io.InputStream;
28import java.util.Hashtable;
29import java.awt.color.ColorSpace;
30import java.awt.image.*;
31import java.awt.*;
32
33import org.apache.harmony.awt.internal.nls.Messages;
34
35public class PngDecoder extends ImageDecoder {
36    // initializes proper field IDs
37    private static native void initIDs();
38
39    static {
40        System.loadLibrary("gl"); //$NON-NLS-1$
41        initIDs();
42    }
43
44    private static final int hintflags =
45            ImageConsumer.SINGLEFRAME | // PNG is a static image
46            ImageConsumer.TOPDOWNLEFTRIGHT | // This order is only one possible
47            ImageConsumer.COMPLETESCANLINES; // Don't deliver incomplete scanlines
48
49    // Each pixel is a grayscale sample.
50    private static final int PNG_COLOR_TYPE_GRAY = 0;
51    // Each pixel is an R,G,B triple.
52    private static final int PNG_COLOR_TYPE_RGB = 2;
53    // Each pixel is a palette index, a PLTE chunk must appear.
54    private static final int PNG_COLOR_TYPE_PLTE = 3;
55    // Each pixel is a grayscale sample, followed by an alpha sample.
56    private static final int PNG_COLOR_TYPE_GRAY_ALPHA = 4;
57    // Each pixel is an R,G,B triple, followed by an alpha sample.
58    private static final int PNG_COLOR_TYPE_RGBA = 6;
59
60    private static final int INPUT_BUFFER_SIZE = 4096;
61    private byte buffer[] = new byte[INPUT_BUFFER_SIZE];
62
63    // Buffers for decoded image data
64    byte byteOut[];
65    int intOut[];
66
67    // Native pointer to png decoder data
68    private long hNativeDecoder;
69
70    int imageWidth, imageHeight;
71    int colorType;
72    int bitDepth;
73    byte cmap[];
74
75    boolean transferInts; // Is transfer type int?.. or byte?
76    int dataElementsPerPixel = 1;
77
78    ColorModel cm;
79
80    int updateFromScanline; // First scanline to update
81    int numScanlines; // Number of scanlines to update
82
83    private native long decode(byte[] input, int bytesInBuffer, long hDecoder);
84
85    private static native void releaseNativeDecoder(long hDecoder);
86
87    public PngDecoder(DecodingImageSource src, InputStream is) {
88        super(src, is);
89    }
90
91    @Override
92    public void decodeImage() throws IOException {
93        try {
94            int bytesRead = 0;
95            int needBytes, offset, bytesInBuffer = 0;
96            // Read from the input stream
97            for (;;) {
98                needBytes = INPUT_BUFFER_SIZE - bytesInBuffer;
99                offset = bytesInBuffer;
100
101                bytesRead = inputStream.read(buffer, offset, needBytes);
102
103                if (bytesRead < 0) { // Break, nothing to read from buffer, image truncated?
104                    releaseNativeDecoder(hNativeDecoder);
105                    break;
106                }
107
108                // Keep track on how much bytes left in buffer
109                bytesInBuffer += bytesRead;
110                hNativeDecoder = decode(buffer, bytesInBuffer, hNativeDecoder);
111                // PNG decoder always consumes all bytes at once
112                bytesInBuffer = 0;
113
114                // if (bytesConsumed < 0)
115                //break; // Error exit
116
117                returnData();
118
119                // OK, we decoded all the picture in the right way...
120                if (hNativeDecoder == 0) {
121                    break;
122                }
123            }
124
125            imageComplete(ImageConsumer.STATICIMAGEDONE);
126        } catch (IOException e) {
127            throw e;
128        } catch (RuntimeException e) {
129            imageComplete(ImageConsumer.IMAGEERROR);
130            throw e;
131        } finally {
132            closeStream();
133        }
134    }
135
136    @SuppressWarnings("unused")
137    private void returnHeader() { // Called from native code
138        setDimensions(imageWidth, imageHeight);
139
140        switch (colorType) {
141            case PNG_COLOR_TYPE_GRAY: {
142                if (bitDepth != 8 && bitDepth != 4 && bitDepth != 2 && bitDepth != 1) {
143                    // awt.3C=Unknown PNG color type
144                    throw new IllegalArgumentException(Messages.getString("awt.3C")); //$NON-NLS-1$
145                }
146
147                // Create gray color model
148                int numEntries = 1 << bitDepth;
149                int scaleFactor = 255 / (numEntries-1);
150                byte comps[] = new byte[numEntries];
151                for (int i = 0; i < numEntries; i++) {
152                    comps[i] = (byte) (i * scaleFactor);
153                }
154                cm = new IndexColorModel(/*bitDepth*/8, numEntries, comps, comps, comps);
155
156                transferInts = false;
157                break;
158            }
159
160            case PNG_COLOR_TYPE_RGB: {
161                if (bitDepth != 8) {
162                    // awt.3C=Unknown PNG color type
163                    throw new IllegalArgumentException(Messages.getString("awt.3C")); //$NON-NLS-1$
164                }
165
166                cm = new DirectColorModel(24, 0xFF0000, 0xFF00, 0xFF);
167
168                transferInts = true;
169                break;
170            }
171
172            case PNG_COLOR_TYPE_PLTE: {
173                if (bitDepth != 8 && bitDepth != 4 && bitDepth != 2 && bitDepth != 1) {
174                    // awt.3C=Unknown PNG color type
175                    throw new IllegalArgumentException(Messages.getString("awt.3C")); //$NON-NLS-1$
176                }
177
178                cm = new IndexColorModel(/*bitDepth*/8, cmap.length / 3, cmap, 0, false);
179
180                transferInts = false;
181                break;
182            }
183
184            case PNG_COLOR_TYPE_GRAY_ALPHA: {
185                if (bitDepth != 8) {
186                    // awt.3C=Unknown PNG color type
187                    throw new IllegalArgumentException(Messages.getString("awt.3C")); //$NON-NLS-1$
188                }
189
190                cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY),
191                        true, false,
192                        Transparency.TRANSLUCENT,
193                        DataBuffer.TYPE_BYTE);
194
195                transferInts = false;
196                dataElementsPerPixel = 2;
197                break;
198            }
199
200            case PNG_COLOR_TYPE_RGBA: {
201                if (bitDepth != 8) {
202                    // awt.3C=Unknown PNG color type
203                    throw new IllegalArgumentException(Messages.getString("awt.3C")); //$NON-NLS-1$
204                }
205
206                cm = ColorModel.getRGBdefault();
207
208                transferInts = true;
209                break;
210            }
211            default:
212                // awt.3C=Unknown PNG color type
213                throw new IllegalArgumentException(Messages.getString("awt.3C")); //$NON-NLS-1$
214        }
215
216        // Create output buffer
217        if (transferInts) {
218            intOut = new int[imageWidth * imageHeight];
219        } else {
220            byteOut = new byte[imageWidth * imageHeight * dataElementsPerPixel];
221        }
222
223        setColorModel(cm);
224
225        setHints(hintflags);
226        setProperties(new Hashtable<Object, Object>()); // Empty
227    }
228
229    // Send the data to the consumer
230    private void returnData() {
231        // Send 1 or more scanlines to the consumer.
232        if (numScanlines > 0) {
233            // Native decoder could have returned
234            // some data from the next pass, handle it here
235            int pass1, pass2;
236            if (updateFromScanline + numScanlines > imageHeight) {
237                pass1 = imageHeight - updateFromScanline;
238                pass2 = updateFromScanline + numScanlines - imageHeight;
239            } else {
240                pass1 = numScanlines;
241                pass2 = 0;
242            }
243
244            transfer(updateFromScanline, pass1);
245            if (pass2 != 0) {
246                transfer(0, pass2);
247            }
248        }
249    }
250
251    private void transfer(int updateFromScanline, int numScanlines) {
252        if (transferInts) {
253            setPixels(
254                    0, updateFromScanline,
255                    imageWidth, numScanlines,
256                    cm, intOut,
257                    updateFromScanline * imageWidth,
258                    imageWidth
259            );
260        } else {
261            setPixels(
262                    0, updateFromScanline,
263                    imageWidth, numScanlines,
264                    cm, byteOut,
265                    updateFromScanline * imageWidth * dataElementsPerPixel,
266                    imageWidth * dataElementsPerPixel
267            );
268        }
269    }
270}
271