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