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/*
22* Created on 27.01.2005
23*/
24package org.apache.harmony.awt.gl.image;
25
26import java.awt.image.ColorModel;
27import java.awt.image.ImageConsumer;
28import java.awt.image.IndexColorModel;
29import java.io.IOException;
30import java.io.InputStream;
31import java.util.ArrayList;
32import java.util.Arrays;
33import java.util.Hashtable;
34import java.util.List;
35
36public class GifDecoder extends ImageDecoder {
37    // initializes proper field IDs
38    private static native void initIDs();
39
40    static {
41        System.loadLibrary("gl"); //$NON-NLS-1$
42        initIDs();
43    }
44
45    // ImageConsumer hints: common
46    private static final int baseHints =
47            ImageConsumer.SINGLEPASS | ImageConsumer.COMPLETESCANLINES |
48            ImageConsumer.SINGLEFRAME;
49    // ImageConsumer hints: interlaced
50    private static final int interlacedHints =
51            baseHints | ImageConsumer.RANDOMPIXELORDER;
52
53    // Impossible color value - no translucent pixels allowed
54    static final int IMPOSSIBLE_VALUE = 0x0FFFFFFF;
55
56    // I/O buffer
57    private static final int BUFFER_SIZE = 1024;
58    private byte buffer[] = new byte[BUFFER_SIZE];
59
60    GifDataStream gifDataStream = new GifDataStream();
61    GifGraphicBlock currBlock;
62
63    // Pointer to native structure which store decoding state
64    // between subsequent decoding/IO-suspension cycles
65    private long hNativeDecoder; // NULL initially
66
67    // Number of bytes eaten by the native decoder
68    private int bytesConsumed;
69
70    private boolean consumersPrepared;
71    private Hashtable<String, String> properties = new Hashtable<String, String>();
72
73    // Could be set up by java code or native method when
74    // transparent pixel index changes or local color table encountered
75    private boolean forceRGB;
76
77    private byte screenBuffer[];
78    private int screenRGBBuffer[];
79
80    public GifDecoder(DecodingImageSource src, InputStream is) {
81        super(src, is);
82    }
83
84    private static native int[] toRGB(byte imageData[], byte colormap[], int transparentColor);
85
86    private static native void releaseNativeDecoder(long hDecoder);
87
88    private native int decode(
89            byte input[],
90            int bytesInBuffer,
91            long hDecoder,
92            GifDataStream dataStream,
93            GifGraphicBlock currBlock
94            );
95
96    private int[] getScreenRGBBuffer() {
97        if (screenRGBBuffer == null) {
98            if (screenBuffer != null) {
99                int transparentColor =
100                        gifDataStream.logicalScreen.globalColorTable.cm.getTransparentPixel();
101                transparentColor = transparentColor > 0 ? transparentColor : IMPOSSIBLE_VALUE;
102                screenRGBBuffer =
103                        toRGB(
104                                screenBuffer,
105                                gifDataStream.logicalScreen.globalColorTable.colors,
106                                transparentColor
107                        );
108            } else {
109                int size = gifDataStream.logicalScreen.logicalScreenHeight *
110                        gifDataStream.logicalScreen.logicalScreenWidth;
111                screenRGBBuffer = new int[size];
112            }
113        }
114
115        return screenRGBBuffer;
116    }
117
118    private void prepareConsumers() {
119        GifLogicalScreen gls = gifDataStream.logicalScreen;
120        setDimensions(gls.logicalScreenWidth,
121                gls.logicalScreenHeight);
122        setProperties(properties);
123
124        currBlock = gifDataStream.graphicBlocks.get(0);
125        if (forceRGB) {
126            setColorModel(ColorModel.getRGBdefault());
127        } else {
128            setColorModel(gls.globalColorTable.getColorModel(currBlock.transparentColor));
129        }
130
131        // Fill screen buffer with the background or transparent color
132        if (forceRGB) {
133            int fillColor = 0xFF000000;
134            if (gls.backgroundColor != IMPOSSIBLE_VALUE) {
135                fillColor = gls.backgroundColor;
136            }
137
138            Arrays.fill(getScreenRGBBuffer(), fillColor);
139        } else {
140            int fillColor = 0;
141
142            if (gls.backgroundColor != IMPOSSIBLE_VALUE) {
143                fillColor = gls.backgroundColor;
144            } else {
145                fillColor = gls.globalColorTable.cm.getTransparentPixel();
146            }
147
148            screenBuffer = new byte[gls.logicalScreenHeight*gls.logicalScreenWidth];
149            Arrays.fill(screenBuffer, (byte) fillColor);
150        }
151
152        setHints(interlacedHints); // XXX - always random pixel order
153    }
154
155    @Override
156    public void decodeImage() throws IOException {
157        try {
158            int bytesRead = 0;
159            int needBytes, offset, bytesInBuffer = 0;
160            boolean eosReached = false;
161            GifGraphicBlock blockToDispose = null;
162
163            // Create new graphic block
164            if (currBlock == null) {
165                currBlock = new GifGraphicBlock();
166                gifDataStream.graphicBlocks.add(currBlock);
167            }
168
169            // Read from the input stream
170            for (;;) {
171                needBytes = BUFFER_SIZE - bytesInBuffer;
172                offset = bytesInBuffer;
173
174                bytesRead = inputStream.read(buffer, offset, needBytes);
175
176                if (bytesRead < 0) {
177                    eosReached = true;
178                    bytesRead = 0;
179                } // Don't break, maybe something left in buffer
180
181                // Keep track on how much bytes left in buffer
182                bytesInBuffer += bytesRead;
183
184                // Here we pass number of new bytes read from the input stream (bytesRead)
185                // since native decoder uses java buffer and doesn't have its own
186                // buffer. So it adds this number to the number of bytes left
187                // in buffer from the previous call.
188                int numLines = decode(
189                        buffer,
190                        bytesRead,
191                        hNativeDecoder,
192                        gifDataStream,
193                        currBlock);
194
195                // Keep track on how much bytes left in buffer
196                bytesInBuffer -= bytesConsumed;
197
198                if (
199                        !consumersPrepared &&
200                        gifDataStream.logicalScreen.completed &&
201                        gifDataStream.logicalScreen.globalColorTable.completed &&
202                        (currBlock.imageData != null || // Have transparent pixel filled
203                        currBlock.rgbImageData != null)
204                ) {
205                    prepareConsumers();
206                    consumersPrepared = true;
207                }
208
209                if (bytesConsumed < 0) {
210                    break; // Error exit
211                }
212
213                if (currBlock != null) {
214                    if (numLines != 0) {
215                        // Dispose previous image only before showing next
216                        if (blockToDispose != null) {
217                            blockToDispose.dispose();
218                            blockToDispose = null;
219                        }
220
221                        currBlock.sendNewData(this, numLines);
222                    }
223
224                    if (currBlock.completed && hNativeDecoder != 0) {
225                        blockToDispose = currBlock; // Dispose only before showing new pixels
226                        currBlock = new GifGraphicBlock();
227                        gifDataStream.graphicBlocks.add(currBlock);
228                    }
229                }
230
231                if (hNativeDecoder == 0) {
232                    break;
233                }
234
235                if (eosReached && numLines == 0) { // Maybe image is truncated...
236                    releaseNativeDecoder(hNativeDecoder);
237                    break;
238                }
239            }
240        } finally {
241            closeStream();
242        }
243
244        // Here all animation goes
245        // Repeat image loopCount-1 times or infinitely if loopCount = 0
246        if (gifDataStream.loopCount != 1) {
247            if (currBlock.completed == false) {
248                gifDataStream.graphicBlocks.remove(currBlock);
249            }
250
251            int numFrames = gifDataStream.graphicBlocks.size();
252            // At first last block will be disposed
253            GifGraphicBlock gb =
254                    gifDataStream.graphicBlocks.get(numFrames-1);
255
256            ImageLoader.beginAnimation();
257
258            while (gifDataStream.loopCount != 1) {
259                if (gifDataStream.loopCount != 0) {
260                    gifDataStream.loopCount--;
261                }
262
263                // Show all frames
264                for (int i=0; i<numFrames; i++) {
265                    gb.dispose();
266                    gb = gifDataStream.graphicBlocks.get(i);
267
268                    // Show one frame
269                    if (forceRGB) {
270                        setPixels(
271                                gb.imageLeft,
272                                gb.imageTop,
273                                gb.imageWidth,
274                                gb.imageHeight,
275                                ColorModel.getRGBdefault(),
276                                gb.getRgbImageData(),
277                                0,
278                                gb.imageWidth
279                        );
280                    } else {
281                        setPixels(
282                                gb.imageLeft,
283                                gb.imageTop,
284                                gb.imageWidth,
285                                gb.imageHeight,
286                                null,
287                                gb.imageData,
288                                0,
289                                gb.imageWidth
290                        );
291                    }
292                }
293            }
294            ImageLoader.endAnimation();
295        }
296
297        imageComplete(ImageConsumer.STATICIMAGEDONE);
298    }
299
300    void setComment(String newComment) {
301        Object currComment = properties.get("comment"); //$NON-NLS-1$
302
303        if (currComment == null) {
304            properties.put("comment", newComment); //$NON-NLS-1$
305        } else {
306            properties.put("comment", (String) currComment + "\n" + newComment); //$NON-NLS-1$ //$NON-NLS-2$
307        }
308
309        setProperties(properties);
310    }
311
312    class GifDataStream {
313        //  Indicates that reading of the whole data stream accomplished
314        boolean completed = false;
315
316        // Added to support Netscape 2.0 application
317        // extension block.
318        int loopCount = 1;
319
320        GifLogicalScreen logicalScreen = new GifLogicalScreen();
321        List<GifGraphicBlock> graphicBlocks = new ArrayList<GifGraphicBlock>(10); // Of GifGraphicBlocks
322
323        // Comments from the image
324        String comments[];
325    }
326
327    class GifLogicalScreen {
328        //  Indicates that reading of this block accomplished
329        boolean completed = false;
330
331        int logicalScreenWidth;
332        int logicalScreenHeight;
333
334        int backgroundColor = IMPOSSIBLE_VALUE;
335
336        GifColorTable globalColorTable = new GifColorTable();
337    }
338
339    class GifGraphicBlock {
340        //  Indicates that reading of this block accomplished
341        boolean completed = false;
342
343        final static int DISPOSAL_NONE = 0;
344        final static int DISPOSAL_NODISPOSAL = 1;
345        final static int DISPOSAL_BACKGROUND = 2;
346        final static int DISPOSAL_RESTORE = 3;
347
348        int disposalMethod;
349        int delayTime; // Multiplied by 10 already
350        int transparentColor = IMPOSSIBLE_VALUE;
351
352        int imageLeft;
353        int imageTop;
354        int imageWidth;
355        int imageHeight;
356
357        // Auxilliary variables to minimize computations
358        int imageRight;
359        int imageBottom;
360
361        boolean interlace;
362
363        // Don't need local color table - if it is specified
364        // image data are converted to RGB in the native code
365
366        byte imageData[] = null;
367        int rgbImageData[] = null;
368
369        private int currY = 0; // Current output scanline
370
371        int[] getRgbImageData() {
372            if (rgbImageData == null) {
373                rgbImageData =
374                        toRGB(
375                                imageData,
376                                gifDataStream.logicalScreen.globalColorTable.colors,
377                                transparentColor
378                        );
379                if (transparentColor != IMPOSSIBLE_VALUE) {
380                    transparentColor =
381                            gifDataStream.logicalScreen.globalColorTable.cm.getRGB(transparentColor);
382                    transparentColor &= 0x00FFFFFF;
383                }
384            }
385            return rgbImageData;
386        }
387
388        private void replaceTransparentPixels(int numLines) {
389            List<GifGraphicBlock> graphicBlocks = gifDataStream.graphicBlocks;
390            int prevBlockIndex = graphicBlocks.indexOf(this) - 1;
391
392            if (prevBlockIndex >= 0) {
393                int maxY = currY + numLines + imageTop;
394                int offset = currY * imageWidth;
395
396                // Update right and bottom coordinates
397                imageRight = imageLeft + imageWidth;
398                imageBottom = imageTop + imageHeight;
399
400                int globalWidth = gifDataStream.logicalScreen.logicalScreenWidth;
401                int pixelValue, imageOffset;
402                int rgbData[] = forceRGB ? getRgbImageData() : null;
403
404                for (int y = currY + imageTop; y < maxY; y++) {
405                    imageOffset = globalWidth * y + imageLeft;
406                    for (int x = imageLeft; x < imageRight; x++) {
407                        pixelValue = forceRGB ?
408                                rgbData[offset] :
409                                imageData[offset] & 0xFF;
410                        if (pixelValue == transparentColor) {
411                            if (forceRGB) {
412                                pixelValue = getScreenRGBBuffer() [imageOffset];
413                                rgbData[offset] = pixelValue;
414                            } else {
415                                pixelValue = screenBuffer [imageOffset];
416                                imageData[offset] = (byte) pixelValue;
417                            }
418                        }
419                        offset++;
420                        imageOffset++;
421                    } // for
422                } // for
423
424            } // if (prevBlockIndex >= 0)
425        }
426
427        public void sendNewData(GifDecoder decoder, int numLines) {
428            // Get values for transparent pixels
429            // from the perevious frames
430            if (transparentColor != IMPOSSIBLE_VALUE) {
431                replaceTransparentPixels(numLines);
432            }
433
434            if (forceRGB) {
435                decoder.setPixels(
436                        imageLeft,
437                        imageTop + currY,
438                        imageWidth,
439                        numLines,
440                        ColorModel.getRGBdefault(),
441                        getRgbImageData(),
442                        currY*imageWidth,
443                        imageWidth
444                );
445            } else {
446                decoder.setPixels(
447                        imageLeft,
448                        imageTop + currY,
449                        imageWidth,
450                        numLines,
451                        null,
452                        imageData,
453                        currY*imageWidth,
454                        imageWidth
455                );
456            }
457
458            currY += numLines;
459        }
460
461        public void dispose() {
462            imageComplete(ImageConsumer.SINGLEFRAMEDONE);
463
464            // Show current frame until delayInterval will not elapse
465            if (delayTime > 0) {
466                try {
467                    Thread.sleep(delayTime);
468                } catch (InterruptedException e) {
469                    e.printStackTrace();
470                }
471            } else {
472                Thread.yield(); // Allow consumers to consume data
473            }
474
475            // Don't dispose if image is outside of the visible area
476            if (imageLeft > gifDataStream.logicalScreen.logicalScreenWidth ||
477                    imageTop > gifDataStream.logicalScreen.logicalScreenHeight) {
478                disposalMethod = DISPOSAL_NONE;
479            }
480
481            switch(disposalMethod) {
482                case DISPOSAL_BACKGROUND: {
483                    if (forceRGB) {
484                        getRgbImageData(); // Ensure that transparentColor is RGB, not index
485
486                        int data[] = new int[imageWidth*imageHeight];
487
488                        // Compatibility: Fill with transparent color if we have one
489                        if (transparentColor != IMPOSSIBLE_VALUE) {
490                            Arrays.fill(
491                                    data,
492                                    transparentColor
493                            );
494                        } else {
495                            Arrays.fill(
496                                    data,
497                                    gifDataStream.logicalScreen.backgroundColor
498                            );
499                        }
500
501                        setPixels(
502                                imageLeft,
503                                imageTop,
504                                imageWidth,
505                                imageHeight,
506                                ColorModel.getRGBdefault(),
507                                data,
508                                0,
509                                imageWidth
510                        );
511
512                        sendToScreenBuffer(data);
513                    } else {
514                        byte data[] = new byte[imageWidth*imageHeight];
515
516                        // Compatibility: Fill with transparent color if we have one
517                        if (transparentColor != IMPOSSIBLE_VALUE) {
518                            Arrays.fill(
519                                    data,
520                                    (byte) transparentColor
521                            );
522                        } else {
523                            Arrays.fill(
524                                    data,
525                                    (byte) gifDataStream.logicalScreen.backgroundColor
526                            );
527                        }
528
529                        setPixels(
530                                imageLeft,
531                                imageTop,
532                                imageWidth,
533                                imageHeight,
534                                null,
535                                data,
536                                0,
537                                imageWidth
538                        );
539
540                        sendToScreenBuffer(data);
541                    }
542                    break;
543                }
544                case DISPOSAL_RESTORE: {
545                    screenBufferToScreen();
546                    break;
547                }
548                case DISPOSAL_NONE:
549                case DISPOSAL_NODISPOSAL:
550                default: {
551                    // Copy transmitted data to the screen buffer
552                    Object data = forceRGB ? (Object) getRgbImageData() : imageData;
553                    sendToScreenBuffer(data);
554                    break;
555                }
556            }
557        }
558
559        private void sendToScreenBuffer(Object data) {
560            int dataInt[];
561            byte dataByte[];
562
563            int width = gifDataStream.logicalScreen.logicalScreenWidth;
564
565
566            if (forceRGB) {
567                dataInt = (int[]) data;
568
569                if (imageWidth == width) {
570                    System.arraycopy(dataInt,
571                            0,
572                            getScreenRGBBuffer(),
573                            imageLeft + imageTop*width,
574                            dataInt.length
575                    );
576                } else { // Each scanline
577                    copyScanlines(dataInt, getScreenRGBBuffer(), width);
578                }
579            } else {
580                dataByte = (byte[]) data;
581
582                if (imageWidth == width) {
583                    System.arraycopy(dataByte,
584                            0,
585                            screenBuffer,
586                            imageLeft + imageTop*width,
587                            dataByte.length
588                    );
589                } else { // Each scanline
590                    copyScanlines(dataByte, screenBuffer, width);
591                }
592            }
593        } // sendToScreenBuffer
594
595        private void copyScanlines(Object src, Object dst, int width) {
596            for (int i=0; i<imageHeight; i++) {
597                System.arraycopy(src,
598                        i*imageWidth,
599                        dst,
600                        imageLeft + i*width + imageTop*width,
601                        imageWidth
602                );
603            } // for
604        }
605
606        private void screenBufferToScreen() {
607            int width = gifDataStream.logicalScreen.logicalScreenWidth;
608
609            Object dst = forceRGB ?
610                    (Object) new int[imageWidth*imageHeight] :
611                    new byte[imageWidth*imageHeight];
612
613            Object src = forceRGB ?
614                    getScreenRGBBuffer() :
615                    (Object) screenBuffer;
616
617            int offset = 0;
618            Object toSend;
619
620            if (width == imageWidth) {
621                offset = imageWidth * imageTop;
622                toSend = src;
623            } else {
624                for (int i=0; i<imageHeight; i++) {
625                    System.arraycopy(src,
626                            imageLeft + i*width + imageTop*width,
627                            dst,
628                            i*imageWidth,
629                            imageWidth
630                    );
631                } // for
632                toSend = dst;
633            }
634
635            if (forceRGB) {
636                setPixels(
637                        imageLeft,
638                        imageTop,
639                        imageWidth,
640                        imageHeight,
641                        ColorModel.getRGBdefault(),
642                        (int [])toSend,
643                        offset,
644                        imageWidth
645                );
646            } else {
647                setPixels(
648                        imageLeft,
649                        imageTop,
650                        imageWidth,
651                        imageHeight,
652                        null,
653                        (byte [])toSend,
654                        offset,
655                        imageWidth
656                );
657            }
658        }
659    }
660
661    class GifColorTable {
662        //  Indicates that reading of this block accomplished
663        boolean completed = false;
664
665        IndexColorModel cm = null;
666        int size = 0; // Actual number of colors in the color table
667        byte colors[] = new byte[256*3];
668
669        IndexColorModel getColorModel(int transparentColor) {
670            if (cm != null) {
671                if (transparentColor != cm.getTransparentPixel()) {
672                    return cm = null; // Force default ARGB color model
673                }
674                return cm;
675            } else
676                if (completed && size > 0) {
677                    if (transparentColor == IMPOSSIBLE_VALUE) {
678                        return cm =
679                                new IndexColorModel(8, size, colors, 0, false);
680                    }
681
682                    if (transparentColor > size) {
683                        size = transparentColor + 1;
684                    }
685                    return cm =
686                            new IndexColorModel(8, size, colors, 0, false, transparentColor);
687                }
688
689            return cm = null; // Force default ARGB color model
690        }
691    }
692}
693