001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.harmony.awt.gl.image;
019:
020: import java.awt.image.ColorModel;
021: import java.awt.image.ImageConsumer;
022: import java.awt.image.IndexColorModel;
023: import java.io.IOException;
024: import java.io.InputStream;
025: import java.util.ArrayList;
026: import java.util.Arrays;
027: import java.util.Hashtable;
028: import java.util.List;
029:
030: public class GifDecoder extends ImageDecoder {
031: // initializes proper field IDs
032: private static native void initIDs();
033:
034: static {
035: System.loadLibrary("gl"); //$NON-NLS-1$
036: initIDs();
037: }
038:
039: // ImageConsumer hints: common
040: private static final int baseHints = ImageConsumer.SINGLEPASS
041: | ImageConsumer.COMPLETESCANLINES
042: | ImageConsumer.SINGLEFRAME;
043: // ImageConsumer hints: interlaced
044: private static final int interlacedHints = baseHints
045: | ImageConsumer.RANDOMPIXELORDER;
046:
047: // Impossible color value - no translucent pixels allowed
048: static final int IMPOSSIBLE_VALUE = 0x0FFFFFFF;
049:
050: // I/O buffer
051: private static final int MIN_BUFFER_SIZE = 1024;
052: private static final int MAX_BUFFER_SIZE = 2097152;
053: private int buffer_size;
054: private byte buffer[];
055:
056: GifDataStream gifDataStream = new GifDataStream();
057: GifGraphicBlock currBlock;
058:
059: // Pointer to native structure which store decoding state
060: // between subsequent decoding/IO-suspension cycles
061: private long hNativeDecoder; // NULL initially
062:
063: // Number of bytes eaten by the native decoder
064: private int bytesConsumed;
065:
066: private boolean consumersPrepared;
067: private Hashtable<String, String> properties = new Hashtable<String, String>();
068:
069: // Could be set up by java code or native method when
070: // transparent pixel index changes or local color table encountered
071: private boolean forceRGB;
072:
073: private byte screenBuffer[];
074: private int screenRGBBuffer[];
075:
076: ColorModel gcm;
077:
078: public GifDecoder(DecodingImageSource src, InputStream is) {
079: super (src, is);
080: try {
081: int available_bytes = is.available();
082: if (available_bytes < MIN_BUFFER_SIZE) {
083: buffer_size = MIN_BUFFER_SIZE;
084: } else if (available_bytes > MAX_BUFFER_SIZE) {
085: buffer_size = MAX_BUFFER_SIZE;
086: } else {
087: buffer_size = available_bytes;
088: }
089: } catch (IOException e) {
090: buffer_size = MIN_BUFFER_SIZE;
091: }
092: buffer = new byte[buffer_size];
093: }
094:
095: private static native int[] toRGB(byte imageData[],
096: byte colormap[], int transparentColor);
097:
098: private static native void releaseNativeDecoder(long hDecoder);
099:
100: private native int decode(byte input[], int bytesInBuffer,
101: long hDecoder, GifDataStream dataStream,
102: GifGraphicBlock currBlock);
103:
104: private int[] getScreenRGBBuffer() {
105: if (screenRGBBuffer == null) {
106: if (screenBuffer != null) {
107: int transparentColor = gifDataStream.logicalScreen.globalColorTable.cm
108: .getTransparentPixel();
109: transparentColor = transparentColor > 0 ? transparentColor
110: : IMPOSSIBLE_VALUE;
111: screenRGBBuffer = toRGB(
112: screenBuffer,
113: gifDataStream.logicalScreen.globalColorTable.colors,
114: transparentColor);
115: } else {
116: int size = gifDataStream.logicalScreen.logicalScreenHeight
117: * gifDataStream.logicalScreen.logicalScreenWidth;
118: screenRGBBuffer = new int[size];
119: }
120: }
121:
122: return screenRGBBuffer;
123: }
124:
125: private void prepareConsumers() {
126: GifLogicalScreen gls = gifDataStream.logicalScreen;
127: setDimensions(gls.logicalScreenWidth, gls.logicalScreenHeight);
128: setProperties(properties);
129:
130: currBlock = gifDataStream.graphicBlocks.get(0);
131: if (forceRGB) {
132: setColorModel(ColorModel.getRGBdefault());
133: } else {
134: gcm = gls.globalColorTable
135: .getColorModel(currBlock.transparentColor);
136: setColorModel(gcm);
137: }
138:
139: // Fill screen buffer with the background or transparent color
140: if (forceRGB) {
141: int fillColor = 0xFF000000;
142: if (gls.backgroundColor != IMPOSSIBLE_VALUE) {
143: fillColor = gls.backgroundColor;
144: }
145:
146: Arrays.fill(getScreenRGBBuffer(), fillColor);
147: } else {
148: int fillColor = 0;
149:
150: if (gls.backgroundColor != IMPOSSIBLE_VALUE) {
151: fillColor = gls.backgroundColor;
152: } else {
153: fillColor = gls.globalColorTable.cm
154: .getTransparentPixel();
155: }
156:
157: screenBuffer = new byte[gls.logicalScreenHeight
158: * gls.logicalScreenWidth];
159: Arrays.fill(screenBuffer, (byte) fillColor);
160: }
161:
162: setHints(interlacedHints); // XXX - always random pixel order
163: }
164:
165: @Override
166: public void decodeImage() throws IOException {
167: try {
168: int bytesRead = 0;
169: int needBytes, offset, bytesInBuffer = 0;
170: boolean eosReached = false;
171: GifGraphicBlock blockToDispose = null;
172:
173: // Create new graphic block
174: if (currBlock == null) {
175: currBlock = new GifGraphicBlock();
176: gifDataStream.graphicBlocks.add(currBlock);
177: }
178:
179: // Read from the input stream
180: for (;;) {
181: needBytes = buffer_size - bytesInBuffer;
182: offset = bytesInBuffer;
183:
184: bytesRead = inputStream.read(buffer, offset, needBytes);
185:
186: if (bytesRead < 0) {
187: eosReached = true;
188: bytesRead = 0;
189: } // Don't break, maybe something left in buffer
190:
191: // Keep track on how much bytes left in buffer
192: bytesInBuffer += bytesRead;
193:
194: // Here we pass number of new bytes read from the input stream (bytesRead)
195: // since native decoder uses java buffer and doesn't have its own
196: // buffer. So it adds this number to the number of bytes left
197: // in buffer from the previous call.
198: int numLines = decode(buffer, bytesRead,
199: hNativeDecoder, gifDataStream, currBlock);
200:
201: // Keep track on how much bytes left in buffer
202: bytesInBuffer -= bytesConsumed;
203:
204: if (!consumersPrepared
205: && gifDataStream.logicalScreen.completed
206: && gifDataStream.logicalScreen.globalColorTable.completed
207: && (currBlock.imageData != null || // Have transparent pixel filled
208: currBlock.rgbImageData != null)) {
209: prepareConsumers();
210: consumersPrepared = true;
211: }
212:
213: if (bytesConsumed < 0) {
214: break; // Error exit
215: }
216:
217: if (currBlock != null) {
218: if (numLines != 0) {
219: // Dispose previous image only before showing next
220: if (blockToDispose != null) {
221: blockToDispose.dispose();
222: blockToDispose = null;
223: }
224:
225: currBlock.sendNewData(this , numLines);
226: }
227:
228: if (currBlock.completed && hNativeDecoder != 0) {
229: blockToDispose = currBlock; // Dispose only before showing new pixels
230: currBlock = new GifGraphicBlock();
231: gifDataStream.graphicBlocks.add(currBlock);
232: }
233: }
234:
235: if (hNativeDecoder == 0) {
236: break;
237: }
238:
239: if (eosReached && numLines == 0) { // Maybe image is truncated...
240: releaseNativeDecoder(hNativeDecoder);
241: break;
242: }
243: }
244: } finally {
245: closeStream();
246: }
247:
248: // Here all animation goes
249: // Repeat image loopCount-1 times or infinitely if loopCount = 0
250: if (gifDataStream.loopCount != 1) {
251: if (currBlock.completed == false) {
252: gifDataStream.graphicBlocks.remove(currBlock);
253: }
254:
255: int numFrames = gifDataStream.graphicBlocks.size();
256: // At first last block will be disposed
257: GifGraphicBlock gb = gifDataStream.graphicBlocks
258: .get(numFrames - 1);
259:
260: ImageLoader.beginAnimation();
261:
262: while (gifDataStream.loopCount != 1) {
263: if (gifDataStream.loopCount != 0) {
264: gifDataStream.loopCount--;
265: }
266:
267: // Show all frames
268: for (int i = 0; i < numFrames; i++) {
269: gb.dispose();
270: gb = gifDataStream.graphicBlocks.get(i);
271:
272: // Show one frame
273: if (forceRGB) {
274: setPixels(gb.imageLeft, gb.imageTop,
275: gb.imageWidth, gb.imageHeight,
276: ColorModel.getRGBdefault(), gb
277: .getRgbImageData(), 0,
278: gb.imageWidth);
279: } else {
280: setPixels(gb.imageLeft, gb.imageTop,
281: gb.imageWidth, gb.imageHeight, gcm,
282: gb.imageData, 0, gb.imageWidth);
283: }
284: }
285: }
286: ImageLoader.endAnimation();
287: }
288:
289: imageComplete(ImageConsumer.STATICIMAGEDONE);
290: }
291:
292: void setComment(String newComment) {
293: Object currComment = properties.get("comment"); //$NON-NLS-1$
294:
295: if (currComment == null) {
296: properties.put("comment", newComment); //$NON-NLS-1$
297: } else {
298: properties
299: .put(
300: "comment", (String) currComment + "\n" + newComment); //$NON-NLS-1$ //$NON-NLS-2$
301: }
302:
303: setProperties(properties);
304: }
305:
306: class GifDataStream {
307: // Indicates that reading of the whole data stream accomplished
308: boolean completed = false;
309:
310: // Added to support Netscape 2.0 application
311: // extension block.
312: int loopCount = 1;
313:
314: GifLogicalScreen logicalScreen = new GifLogicalScreen();
315: List<GifGraphicBlock> graphicBlocks = new ArrayList<GifGraphicBlock>(
316: 10); // Of GifGraphicBlocks
317:
318: // Comments from the image
319: String comments[];
320: }
321:
322: class GifLogicalScreen {
323: // Indicates that reading of this block accomplished
324: boolean completed = false;
325:
326: int logicalScreenWidth;
327: int logicalScreenHeight;
328:
329: int backgroundColor = IMPOSSIBLE_VALUE;
330:
331: GifColorTable globalColorTable = new GifColorTable();
332: }
333:
334: class GifGraphicBlock {
335: // Indicates that reading of this block accomplished
336: boolean completed = false;
337:
338: final static int DISPOSAL_NONE = 0;
339: final static int DISPOSAL_NODISPOSAL = 1;
340: final static int DISPOSAL_BACKGROUND = 2;
341: final static int DISPOSAL_RESTORE = 3;
342:
343: int disposalMethod;
344: int delayTime; // Multiplied by 10 already
345: int transparentColor = IMPOSSIBLE_VALUE;
346:
347: int imageLeft;
348: int imageTop;
349: int imageWidth;
350: int imageHeight;
351:
352: // Auxilliary variables to minimize computations
353: int imageRight;
354: int imageBottom;
355:
356: boolean interlace;
357:
358: // Don't need local color table - if it is specified
359: // image data are converted to RGB in the native code
360:
361: byte imageData[] = null;
362: int rgbImageData[] = null;
363:
364: private int currY = 0; // Current output scanline
365:
366: int[] getRgbImageData() {
367: if (rgbImageData == null) {
368: rgbImageData = toRGB(
369: imageData,
370: gifDataStream.logicalScreen.globalColorTable.colors,
371: transparentColor);
372: if (transparentColor != IMPOSSIBLE_VALUE) {
373: transparentColor = gifDataStream.logicalScreen.globalColorTable.cm
374: .getRGB(transparentColor);
375: transparentColor &= 0x00FFFFFF;
376: }
377: }
378: return rgbImageData;
379: }
380:
381: private void replaceTransparentPixels(int numLines) {
382: List<GifGraphicBlock> graphicBlocks = gifDataStream.graphicBlocks;
383: int prevBlockIndex = graphicBlocks.indexOf(this ) - 1;
384:
385: if (prevBlockIndex >= 0) {
386: int maxY = currY + numLines + imageTop;
387: int offset = currY * imageWidth;
388:
389: // Update right and bottom coordinates
390: imageRight = imageLeft + imageWidth;
391: imageBottom = imageTop + imageHeight;
392:
393: int globalWidth = gifDataStream.logicalScreen.logicalScreenWidth;
394: int pixelValue, imageOffset;
395: int rgbData[] = forceRGB ? getRgbImageData() : null;
396:
397: for (int y = currY + imageTop; y < maxY; y++) {
398: imageOffset = globalWidth * y + imageLeft;
399: for (int x = imageLeft; x < imageRight; x++) {
400: pixelValue = forceRGB ? rgbData[offset]
401: : imageData[offset] & 0xFF;
402: if (pixelValue == transparentColor) {
403: if (forceRGB) {
404: pixelValue = getScreenRGBBuffer()[imageOffset];
405: rgbData[offset] = pixelValue;
406: } else {
407: pixelValue = screenBuffer[imageOffset];
408: imageData[offset] = (byte) pixelValue;
409: }
410: }
411: offset++;
412: imageOffset++;
413: } // for
414: } // for
415:
416: } // if (prevBlockIndex >= 0)
417: }
418:
419: public void sendNewData(GifDecoder decoder, int numLines) {
420: // Get values for transparent pixels
421: // from the perevious frames
422: if (transparentColor != IMPOSSIBLE_VALUE) {
423: replaceTransparentPixels(numLines);
424: }
425:
426: if (forceRGB) {
427: decoder.setPixels(imageLeft, imageTop + currY,
428: imageWidth, numLines, ColorModel
429: .getRGBdefault(), getRgbImageData(),
430: currY * imageWidth, imageWidth);
431: } else {
432: decoder.setPixels(imageLeft, imageTop + currY,
433: imageWidth, numLines, gcm, imageData, currY
434: * imageWidth, imageWidth);
435: }
436:
437: currY += numLines;
438: }
439:
440: public void dispose() {
441: imageComplete(ImageConsumer.SINGLEFRAMEDONE);
442:
443: // Show current frame until delayInterval will not elapse
444: if (delayTime > 0) {
445: try {
446: Thread.sleep(delayTime);
447: } catch (InterruptedException e) {
448: e.printStackTrace();
449: }
450: } else {
451: Thread.yield(); // Allow consumers to consume data
452: }
453:
454: // Don't dispose if image is outside of the visible area
455: if (imageLeft > gifDataStream.logicalScreen.logicalScreenWidth
456: || imageTop > gifDataStream.logicalScreen.logicalScreenHeight) {
457: disposalMethod = DISPOSAL_NONE;
458: }
459:
460: switch (disposalMethod) {
461: case DISPOSAL_BACKGROUND: {
462: if (forceRGB) {
463: getRgbImageData(); // Ensure that transparentColor is RGB, not index
464:
465: int data[] = new int[imageWidth * imageHeight];
466:
467: // Compatibility: Fill with transparent color if we have one
468: if (transparentColor != IMPOSSIBLE_VALUE) {
469: Arrays.fill(data, transparentColor);
470: } else {
471: Arrays
472: .fill(
473: data,
474: gifDataStream.logicalScreen.backgroundColor);
475: }
476:
477: setPixels(imageLeft, imageTop, imageWidth,
478: imageHeight, ColorModel.getRGBdefault(),
479: data, 0, imageWidth);
480:
481: sendToScreenBuffer(data);
482: } else {
483: byte data[] = new byte[imageWidth * imageHeight];
484:
485: // Compatibility: Fill with transparent color if we have one
486: if (transparentColor != IMPOSSIBLE_VALUE) {
487: Arrays.fill(data, (byte) transparentColor);
488: } else {
489: Arrays
490: .fill(
491: data,
492: (byte) gifDataStream.logicalScreen.backgroundColor);
493: }
494:
495: setPixels(imageLeft, imageTop, imageWidth,
496: imageHeight, gcm, data, 0, imageWidth);
497:
498: sendToScreenBuffer(data);
499: }
500: break;
501: }
502: case DISPOSAL_RESTORE: {
503: screenBufferToScreen();
504: break;
505: }
506: case DISPOSAL_NONE:
507: case DISPOSAL_NODISPOSAL:
508: default: {
509: // Copy transmitted data to the screen buffer
510: Object data = forceRGB ? (Object) getRgbImageData()
511: : imageData;
512: sendToScreenBuffer(data);
513: break;
514: }
515: }
516: }
517:
518: private void sendToScreenBuffer(Object data) {
519: int dataInt[];
520: byte dataByte[];
521:
522: int width = gifDataStream.logicalScreen.logicalScreenWidth;
523:
524: if (forceRGB) {
525: dataInt = (int[]) data;
526:
527: if (imageWidth == width) {
528: System.arraycopy(dataInt, 0, getScreenRGBBuffer(),
529: imageLeft + imageTop * width,
530: dataInt.length);
531: } else { // Each scanline
532: copyScanlines(dataInt, getScreenRGBBuffer(), width);
533: }
534: } else {
535: dataByte = (byte[]) data;
536:
537: if (imageWidth == width) {
538: System.arraycopy(dataByte, 0, screenBuffer,
539: imageLeft + imageTop * width,
540: dataByte.length);
541: } else { // Each scanline
542: copyScanlines(dataByte, screenBuffer, width);
543: }
544: }
545: } // sendToScreenBuffer
546:
547: private void copyScanlines(Object src, Object dst, int width) {
548: for (int i = 0; i < imageHeight; i++) {
549: System.arraycopy(src, i * imageWidth, dst, imageLeft
550: + i * width + imageTop * width, imageWidth);
551: } // for
552: }
553:
554: private void screenBufferToScreen() {
555: int width = gifDataStream.logicalScreen.logicalScreenWidth;
556:
557: Object dst = forceRGB ? (Object) new int[imageWidth
558: * imageHeight] : new byte[imageWidth * imageHeight];
559:
560: Object src = forceRGB ? getScreenRGBBuffer()
561: : (Object) screenBuffer;
562:
563: int offset = 0;
564: Object toSend;
565:
566: if (width == imageWidth) {
567: offset = imageWidth * imageTop;
568: toSend = src;
569: } else {
570: for (int i = 0; i < imageHeight; i++) {
571: System.arraycopy(src, imageLeft + i * width
572: + imageTop * width, dst, i * imageWidth,
573: imageWidth);
574: } // for
575: toSend = dst;
576: }
577:
578: if (forceRGB) {
579: setPixels(imageLeft, imageTop, imageWidth, imageHeight,
580: ColorModel.getRGBdefault(), (int[]) toSend,
581: offset, imageWidth);
582: } else {
583: setPixels(imageLeft, imageTop, imageWidth, imageHeight,
584: gcm, (byte[]) toSend, offset, imageWidth);
585: }
586: }
587: }
588:
589: class GifColorTable {
590: // Indicates that reading of this block accomplished
591: boolean completed = false;
592:
593: IndexColorModel cm = null;
594: int size = 0; // Actual number of colors in the color table
595: byte colors[] = new byte[256 * 3];
596:
597: IndexColorModel getColorModel(int transparentColor) {
598: if (cm != null) {
599: if (transparentColor != cm.getTransparentPixel()) {
600: return cm = null; // Force default ARGB color model
601: }
602: return cm;
603: } else if (completed && size > 0) {
604: if (transparentColor == IMPOSSIBLE_VALUE) {
605: return cm = new IndexColorModel(8, size, colors, 0,
606: false);
607: }
608:
609: if (transparentColor > size) {
610: size = transparentColor + 1;
611: }
612: return cm = new IndexColorModel(8, size, colors, 0,
613: false, transparentColor);
614: }
615:
616: return cm = null; // Force default ARGB color model
617: }
618: }
619: }
|