1 /*
   2  * Copyright (c) 1995, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 /*-
  27  *      Reads GIF images from an InputStream and reports the
  28  *      image data to an InputStreamImageSource object.
  29  *
  30  * The algorithm is copyright of CompuServe.
  31  */
  32 package sun.awt.image;
  33 
  34 import java.util.Vector;
  35 import java.util.Hashtable;
  36 import java.io.InputStream;
  37 import java.io.IOException;
  38 import java.awt.image.*;
  39 
  40 /**
  41  * Gif Image converter
  42  *
  43  * @author Arthur van Hoff
  44  * @author Jim Graham
  45  */
  46 public class GifImageDecoder extends ImageDecoder {
  47     private static final boolean verbose = false;
  48 
  49     private static final int IMAGESEP           = 0x2c;
  50     private static final int EXBLOCK            = 0x21;
  51     private static final int EX_GRAPHICS_CONTROL= 0xf9;
  52     private static final int EX_COMMENT         = 0xfe;
  53     private static final int EX_APPLICATION     = 0xff;
  54     private static final int TERMINATOR         = 0x3b;
  55     private static final int TRANSPARENCYMASK   = 0x01;
  56     private static final int INTERLACEMASK      = 0x40;
  57     private static final int COLORMAPMASK       = 0x80;
  58 
  59     int num_global_colors;
  60     byte[] global_colormap;
  61     int trans_pixel = -1;
  62     IndexColorModel global_model;
  63 
  64     Hashtable<String, Object> props = new Hashtable<>();
  65 
  66     byte[] saved_image;
  67     IndexColorModel saved_model;
  68 
  69     int global_width;
  70     int global_height;
  71     int global_bgpixel;
  72 
  73     GifFrame curframe;
  74 
  75     public GifImageDecoder(InputStreamImageSource src, InputStream is) {
  76         super(src, is);
  77     }
  78 
  79     /**
  80      * An error has occurred. Throw an exception.
  81      */
  82     private static void error(String s1) throws ImageFormatException {
  83         throw new ImageFormatException(s1);
  84     }
  85 
  86     /**
  87      * Read a number of bytes into a buffer.
  88      * @return number of bytes that were not read due to EOF or error
  89      */
  90     private int readBytes(byte buf[], int off, int len) {
  91         while (len > 0) {
  92             try {
  93                 int n = input.read(buf, off, len);
  94                 if (n < 0) {
  95                     break;
  96                 }
  97                 off += n;
  98                 len -= n;
  99             } catch (IOException e) {
 100                 break;
 101             }
 102         }
 103         return len;
 104     }
 105 
 106     private static final int ExtractByte(byte buf[], int off) {
 107         return (buf[off] & 0xFF);
 108     }
 109 
 110     private static final int ExtractWord(byte buf[], int off) {
 111         return (buf[off] & 0xFF) | ((buf[off + 1] & 0xFF) << 8);
 112     }
 113 
 114     /**
 115      * produce an image from the stream.
 116      */
 117     @SuppressWarnings({"fallthrough", "deprecation"})
 118     public void produceImage() throws IOException, ImageFormatException {
 119         try {
 120             readHeader();
 121 
 122             int totalframes = 0;
 123             int frameno = 0;
 124             int nloops = -1;
 125             int disposal_method = 0;
 126             int delay = -1;
 127             boolean loopsRead = false;
 128             boolean isAnimation = false;
 129 
 130             while (!aborted) {
 131                 int code;
 132 
 133                 switch (code = input.read()) {
 134                   case EXBLOCK:
 135                     switch (code = input.read()) {
 136                       case EX_GRAPHICS_CONTROL: {
 137                         byte buf[] = new byte[6];
 138                         if (readBytes(buf, 0, 6) != 0) {
 139                             return;//error("corrupt GIF file");
 140                         }
 141                         if ((buf[0] != 4) || (buf[5] != 0)) {
 142                             return;//error("corrupt GIF file (GCE size)");
 143                         }
 144                         // Get the index of the transparent color
 145                         delay = ExtractWord(buf, 2) * 10;
 146                         if (delay > 0 && !isAnimation) {
 147                             isAnimation = true;
 148                             ImageFetcher.startingAnimation();
 149                         }
 150                         disposal_method = (buf[1] >> 2) & 7;
 151                         if ((buf[1] & TRANSPARENCYMASK) != 0) {
 152                             trans_pixel = ExtractByte(buf, 4);
 153                         } else {
 154                             trans_pixel = -1;
 155                         }
 156                         break;
 157                       }
 158 
 159                       case EX_COMMENT:
 160                       case EX_APPLICATION:
 161                       default:
 162                         boolean loop_tag = false;
 163                         String comment = "";
 164                         while (true) {
 165                             int n = input.read();
 166                             if (n <= 0) {
 167                                 break;
 168                             }
 169                             byte buf[] = new byte[n];
 170                             if (readBytes(buf, 0, n) != 0) {
 171                                 return;//error("corrupt GIF file");
 172                             }
 173                             if (code == EX_COMMENT) {
 174                                 comment += new String(buf, 0);
 175                             } else if (code == EX_APPLICATION) {
 176                                 if (loop_tag) {
 177                                     if (n == 3 && buf[0] == 1) {
 178                                         if (loopsRead) {
 179                                             ExtractWord(buf, 1);
 180                                         }
 181                                         else {
 182                                             nloops = ExtractWord(buf, 1);
 183                                             loopsRead = true;
 184                                         }
 185                                     } else {
 186                                         loop_tag = false;
 187                                     }
 188                                 }
 189                                 if ("NETSCAPE2.0".equals(new String(buf, 0))) {
 190                                     loop_tag = true;
 191                                 }
 192                             }
 193                         }
 194                         if (code == EX_COMMENT) {
 195                             props.put("comment", comment);
 196                         }
 197                         if (loop_tag && !isAnimation) {
 198                             isAnimation = true;
 199                             ImageFetcher.startingAnimation();
 200                         }
 201                         break;
 202 
 203                       case -1:
 204                         return; //error("corrupt GIF file");
 205                     }
 206                     break;
 207 
 208                   case IMAGESEP:
 209                     if (!isAnimation) {
 210                         input.mark(0); // we don't need the mark buffer
 211                     }
 212                     try {
 213                         if (!readImage(totalframes == 0,
 214                                        disposal_method,
 215                                        delay)) {
 216                             return;
 217                         }
 218                     } catch (Exception e) {
 219                         if (verbose) {
 220                             e.printStackTrace();
 221                         }
 222                         return;
 223                     }
 224                     frameno++;
 225                     totalframes++;
 226                     break;
 227 
 228                   default:
 229                   case -1:
 230                     if (verbose) {
 231                         if (code == -1) {
 232                             System.err.println("Premature EOF in GIF file," +
 233                                                " frame " + frameno);
 234                         } else {
 235                             System.err.println("corrupt GIF file (parse) ["
 236                                                + code + "].");
 237                         }
 238                     }
 239                     if (frameno == 0) {
 240                         return;
 241                     }
 242                     // Fall through
 243 
 244                   case TERMINATOR:
 245                     if (nloops == 0 || nloops-- >= 0) {
 246                         try {
 247                             if (curframe != null) {
 248                                 curframe.dispose();
 249                                 curframe = null;
 250                             }
 251                             input.reset();
 252                             saved_image = null;
 253                             saved_model = null;
 254                             frameno = 0;
 255                             break;
 256                         } catch (IOException e) {
 257                             return; // Unable to reset input buffer
 258                         }
 259                     }
 260                     if (verbose && frameno != 1) {
 261                         System.out.println("processing GIF terminator,"
 262                                            + " frames: " + frameno
 263                                            + " total: " + totalframes);
 264                     }
 265                     imageComplete(ImageConsumer.STATICIMAGEDONE, true);
 266                     return;
 267                 }
 268             }
 269         } finally {
 270             close();
 271         }
 272     }
 273 
 274     /**
 275      * Read Image header
 276      */
 277     private void readHeader() throws IOException, ImageFormatException {
 278         // Create a buffer
 279         byte buf[] = new byte[13];
 280 
 281         // Read the header
 282         if (readBytes(buf, 0, 13) != 0) {
 283             throw new IOException();
 284         }
 285 
 286         // Check header
 287         if ((buf[0] != 'G') || (buf[1] != 'I') || (buf[2] != 'F')) {
 288             error("not a GIF file.");
 289         }
 290 
 291         // Global width&height
 292         global_width = ExtractWord(buf, 6);
 293         global_height = ExtractWord(buf, 8);
 294 
 295         // colormap info
 296         int ch = ExtractByte(buf, 10);
 297         if ((ch & COLORMAPMASK) == 0) {
 298             // no global colormap so make up our own
 299             // If there is a local colormap, it will override what we
 300             // have here.  If there is not a local colormap, the rules
 301             // for GIF89 say that we can use whatever colormap we want.
 302             // This means that we should probably put in a full 256 colormap
 303             // at some point.  REMIND!
 304             num_global_colors = 2;
 305             global_bgpixel = 0;
 306             global_colormap = new byte[2*3];
 307             global_colormap[0] = global_colormap[1] = global_colormap[2] = (byte)0;
 308             global_colormap[3] = global_colormap[4] = global_colormap[5] = (byte)255;
 309 
 310         }
 311         else {
 312             num_global_colors = 1 << ((ch & 0x7) + 1);
 313 
 314             global_bgpixel = ExtractByte(buf, 11);
 315 
 316             if (buf[12] != 0) {
 317                 props.put("aspectratio", ""+((ExtractByte(buf, 12) + 15) / 64.0));
 318             }
 319 
 320             // Read colors
 321             global_colormap = new byte[num_global_colors * 3];
 322             if (readBytes(global_colormap, 0, num_global_colors * 3) != 0) {
 323                 throw new IOException();
 324             }
 325         }
 326         input.mark(Integer.MAX_VALUE); // set this mark in case this is an animated GIF
 327     }
 328 
 329     /**
 330      * The ImageConsumer hints flag for a non-interlaced GIF image.
 331      */
 332     private static final int normalflags =
 333         ImageConsumer.TOPDOWNLEFTRIGHT | ImageConsumer.COMPLETESCANLINES |
 334         ImageConsumer.SINGLEPASS | ImageConsumer.SINGLEFRAME;
 335 
 336     /**
 337      * The ImageConsumer hints flag for an interlaced GIF image.
 338      */
 339     private static final int interlaceflags =
 340         ImageConsumer.RANDOMPIXELORDER | ImageConsumer.COMPLETESCANLINES |
 341         ImageConsumer.SINGLEPASS | ImageConsumer.SINGLEFRAME;
 342 
 343     private short prefix[]  = new short[4096];
 344     private byte  suffix[]  = new byte[4096];
 345     private byte  outCode[] = new byte[4097];
 346 
 347     private static native void initIDs();
 348 
 349     static {
 350         /* ensure that the necessary native libraries are loaded */
 351         NativeLibLoader.loadLibraries();
 352         initIDs();
 353     }
 354 
 355     private native boolean parseImage(int x, int y, int width, int height,
 356                                       boolean interlace, int initCodeSize,
 357                                       byte block[], byte rasline[],
 358                                       IndexColorModel model);
 359 
 360     private int sendPixels(int x, int y, int width, int height,
 361                            byte rasline[], ColorModel model) {
 362         int rasbeg, rasend, x2;
 363         if (y < 0) {
 364             height += y;
 365             y = 0;
 366         }
 367         if (y + height > global_height) {
 368             height = global_height - y;
 369         }
 370         if (height <= 0) {
 371             return 1;
 372         }
 373         // rasline[0]     == pixel at coordinate (x,y)
 374         // rasline[width] == pixel at coordinate (x+width, y)
 375         if (x < 0) {
 376             rasbeg = -x;
 377             width += x;         // same as (width -= rasbeg)
 378             x2 = 0;             // same as (x2     = x + rasbeg)
 379         } else {
 380             rasbeg = 0;
 381             // width -= 0;      // same as (width -= rasbeg)
 382             x2 = x;             // same as (x2     = x + rasbeg)
 383         }
 384         // rasline[rasbeg]          == pixel at coordinate (x2,y)
 385         // rasline[width]           == pixel at coordinate (x+width, y)
 386         // rasline[rasbeg + width]  == pixel at coordinate (x2+width, y)
 387         if (x2 + width > global_width) {
 388             width = global_width - x2;
 389         }
 390         if (width <= 0) {
 391             return 1;
 392         }
 393         rasend = rasbeg + width;
 394         // rasline[rasbeg] == pixel at coordinate (x2,y)
 395         // rasline[rasend] == pixel at coordinate (x2+width, y)
 396         int off = y * global_width + x2;
 397         boolean save = (curframe.disposal_method == GifFrame.DISPOSAL_SAVE);
 398         if (trans_pixel >= 0 && !curframe.initialframe) {
 399             if (saved_image != null && model.equals(saved_model)) {
 400                 for (int i = rasbeg; i < rasend; i++, off++) {
 401                     byte pixel = rasline[i];
 402                     if ((pixel & 0xff) == trans_pixel) {
 403                         rasline[i] = saved_image[off];
 404                     } else if (save) {
 405                         saved_image[off] = pixel;
 406                     }
 407                 }
 408             } else {
 409                 // We have to do this the hard way - only transmit
 410                 // the non-transparent sections of the line...
 411                 // Fix for 6301050: the interlacing is ignored in this case
 412                 // in order to avoid artefacts in case of animated images.
 413                 int runstart = -1;
 414                 int count = 1;
 415                 for (int i = rasbeg; i < rasend; i++, off++) {
 416                     byte pixel = rasline[i];
 417                     if ((pixel & 0xff) == trans_pixel) {
 418                         if (runstart >= 0) {
 419                             count = setPixels(x + runstart, y,
 420                                               i - runstart, 1,
 421                                               model, rasline,
 422                                               runstart, 0);
 423                             if (count == 0) {
 424                                 break;
 425                             }
 426                         }
 427                         runstart = -1;
 428                     } else {
 429                         if (runstart < 0) {
 430                             runstart = i;
 431                         }
 432                         if (save) {
 433                             saved_image[off] = pixel;
 434                         }
 435                     }
 436                 }
 437                 if (runstart >= 0) {
 438                     count = setPixels(x + runstart, y,
 439                                       rasend - runstart, 1,
 440                                       model, rasline,
 441                                       runstart, 0);
 442                 }
 443                 return count;
 444             }
 445         } else if (save) {
 446             System.arraycopy(rasline, rasbeg, saved_image, off, width);
 447         }
 448         int count = setPixels(x2, y, width, height, model,
 449                               rasline, rasbeg, 0);
 450         return count;
 451     }
 452 
 453     /**
 454      * Read Image data
 455      */
 456     private boolean readImage(boolean first, int disposal_method, int delay)
 457         throws IOException
 458     {
 459         if (curframe != null && !curframe.dispose()) {
 460             abort();
 461             return false;
 462         }
 463 
 464         long tm = 0;
 465 
 466         if (verbose) {
 467             tm = System.currentTimeMillis();
 468         }
 469 
 470         // Allocate the buffer
 471         byte block[] = new byte[256 + 3];
 472 
 473         // Read the image descriptor
 474         if (readBytes(block, 0, 10) != 0) {
 475             throw new IOException();
 476         }
 477         int x = ExtractWord(block, 0);
 478         int y = ExtractWord(block, 2);
 479         int width = ExtractWord(block, 4);
 480         int height = ExtractWord(block, 6);
 481 
 482         /*
 483          * Majority of gif images have
 484          * same logical screen and frame dimensions.
 485          * Also, Photoshop and Mozilla seem to use the logical
 486          * screen dimension (from the global stream header)
 487          * if frame dimension is invalid.
 488          *
 489          * We use similar heuristic and trying to recover
 490          * frame width from logical screen dimension and
 491          * frame offset.
 492          */
 493         if (width == 0 && global_width != 0) {
 494             width = global_width - x;
 495         }
 496         if (height == 0 && global_height != 0) {
 497             height = global_height - y;
 498         }
 499 
 500         boolean interlace = (block[8] & INTERLACEMASK) != 0;
 501 
 502         IndexColorModel model = global_model;
 503 
 504         if ((block[8] & COLORMAPMASK) != 0) {
 505             // We read one extra byte above so now when we must
 506             // transfer that byte as the first colormap byte
 507             // and manually read the code size when we are done
 508             int num_local_colors = 1 << ((block[8] & 0x7) + 1);
 509 
 510             // Read local colors
 511             byte[] local_colormap = new byte[num_local_colors * 3];
 512             local_colormap[0] = block[9];
 513             if (readBytes(local_colormap, 1, num_local_colors * 3 - 1) != 0) {
 514                 throw new IOException();
 515             }
 516 
 517             // Now read the "real" code size byte which follows
 518             // the local color table
 519             if (readBytes(block, 9, 1) != 0) {
 520                 throw new IOException();
 521             }
 522             if (trans_pixel >= num_local_colors) {
 523                 // Fix for 4233748: extend colormap to contain transparent pixel
 524                 num_local_colors = trans_pixel + 1;
 525                 local_colormap = grow_colormap(local_colormap, num_local_colors);
 526             }
 527             model = new IndexColorModel(8, num_local_colors, local_colormap,
 528                                         0, false, trans_pixel);
 529         } else if (model == null
 530                    || trans_pixel != model.getTransparentPixel()) {
 531             if (trans_pixel >= num_global_colors) {
 532                 // Fix for 4233748: extend colormap to contain transparent pixel
 533                 num_global_colors = trans_pixel + 1;
 534                 global_colormap = grow_colormap(global_colormap, num_global_colors);
 535             }
 536             model = new IndexColorModel(8, num_global_colors, global_colormap,
 537                                         0, false, trans_pixel);
 538             global_model = model;
 539         }
 540 
 541         // Notify the consumers
 542         if (first) {
 543             if (global_width == 0) global_width = width;
 544             if (global_height == 0) global_height = height;
 545 
 546             setDimensions(global_width, global_height);
 547             setProperties(props);
 548             setColorModel(model);
 549             headerComplete();
 550         }
 551 
 552         if (disposal_method == GifFrame.DISPOSAL_SAVE && saved_image == null) {
 553             saved_image = new byte[global_width * global_height];
 554             /*
 555              * If height of current image is smaller than the global height,
 556              * fill the gap with transparent pixels.
 557              */
 558             if ((height < global_height) && (model != null)) {
 559                 byte tpix = (byte)model.getTransparentPixel();
 560                 if (tpix >= 0) {
 561                     byte trans_rasline[] = new byte[global_width];
 562                     for (int i=0; i<global_width;i++) {
 563                         trans_rasline[i] = tpix;
 564                     }
 565 
 566                     setPixels(0, 0, global_width, y,
 567                               model, trans_rasline, 0, 0);
 568                     setPixels(0, y+height, global_width,
 569                               global_height-height-y, model, trans_rasline,
 570                               0, 0);
 571                 }
 572             }
 573         }
 574 
 575         int hints = (interlace ? interlaceflags : normalflags);
 576         setHints(hints);
 577 
 578         curframe = new GifFrame(this, disposal_method, delay,
 579                                 (curframe == null), model,
 580                                 x, y, width, height);
 581 
 582         // allocate the raster data
 583         byte rasline[] = new byte[width];
 584 
 585         if (verbose) {
 586             System.out.print("Reading a " + width + " by " + height + " " +
 587                       (interlace ? "" : "non-") + "interlaced image...");
 588         }
 589         int initCodeSize = ExtractByte(block, 9);
 590         if (initCodeSize >= 12) {
 591             if (verbose) {
 592                 System.out.println("Invalid initial code size: " +
 593                                    initCodeSize);
 594             }
 595             return false;
 596         }
 597         boolean ret = parseImage(x, y, width, height,
 598                                  interlace, initCodeSize,
 599                                  block, rasline, model);
 600 
 601         if (!ret) {
 602             abort();
 603         }
 604 
 605         if (verbose) {
 606             System.out.println("done in "
 607                                + (System.currentTimeMillis() - tm)
 608                                + "ms");
 609         }
 610 
 611         return ret;
 612     }
 613 
 614     public static byte[] grow_colormap(byte[] colormap, int newlen) {
 615         byte[] newcm = new byte[newlen * 3];
 616         System.arraycopy(colormap, 0, newcm, 0, colormap.length);
 617         return newcm;
 618     }
 619 }
 620 
 621 class GifFrame {
 622     private static final boolean verbose = false;
 623     private static IndexColorModel trans_model;
 624 
 625     static final int DISPOSAL_NONE      = 0x00;
 626     static final int DISPOSAL_SAVE      = 0x01;
 627     static final int DISPOSAL_BGCOLOR   = 0x02;
 628     static final int DISPOSAL_PREVIOUS  = 0x03;
 629 
 630     GifImageDecoder decoder;
 631 
 632     int disposal_method;
 633     int delay;
 634 
 635     IndexColorModel model;
 636 
 637     int x;
 638     int y;
 639     int width;
 640     int height;
 641 
 642     boolean initialframe;
 643 
 644     public GifFrame(GifImageDecoder id, int dm, int dl, boolean init,
 645                     IndexColorModel cm, int x, int y, int w, int h) {
 646         this.decoder = id;
 647         this.disposal_method = dm;
 648         this.delay = dl;
 649         this.model = cm;
 650         this.initialframe = init;
 651         this.x = x;
 652         this.y = y;
 653         this.width = w;
 654         this.height = h;
 655     }
 656 
 657     private void setPixels(int x, int y, int w, int h,
 658                            ColorModel cm, byte[] pix, int off, int scan) {
 659         decoder.setPixels(x, y, w, h, cm, pix, off, scan);
 660     }
 661 
 662     public boolean dispose() {
 663         if (decoder.imageComplete(ImageConsumer.SINGLEFRAMEDONE, false) == 0) {
 664             return false;
 665         } else {
 666             if (delay > 0) {
 667                 try {
 668                     if (verbose) {
 669                         System.out.println("sleeping: "+delay);
 670                     }
 671                     Thread.sleep(delay);
 672                 } catch (InterruptedException e) {
 673                     return false;
 674                 }
 675             } else {
 676                 Thread.yield();
 677             }
 678 
 679             if (verbose && disposal_method != 0) {
 680                 System.out.println("disposal method: "+disposal_method);
 681             }
 682 
 683             int global_width = decoder.global_width;
 684             int global_height = decoder.global_height;
 685 
 686             if (x < 0) {
 687                 width += x;
 688                 x = 0;
 689             }
 690             if (x + width > global_width) {
 691                 width = global_width - x;
 692             }
 693             if (width <= 0) {
 694                 disposal_method = DISPOSAL_NONE;
 695             } else {
 696                 if (y < 0) {
 697                     height += y;
 698                     y = 0;
 699                 }
 700                 if (y + height > global_height) {
 701                     height = global_height - y;
 702                 }
 703                 if (height <= 0) {
 704                     disposal_method = DISPOSAL_NONE;
 705                 }
 706             }
 707 
 708             switch (disposal_method) {
 709             case DISPOSAL_PREVIOUS:
 710                 byte[] saved_image = decoder.saved_image;
 711                 IndexColorModel saved_model = decoder.saved_model;
 712                 if (saved_image != null) {
 713                     setPixels(x, y, width, height,
 714                               saved_model, saved_image,
 715                               y * global_width + x, global_width);
 716                 }
 717                 break;
 718             case DISPOSAL_BGCOLOR:
 719                 byte tpix;
 720                 if (model.getTransparentPixel() < 0) {
 721                     model = trans_model;
 722                     if (model == null) {
 723                         model = new IndexColorModel(8, 1,
 724                                                     new byte[4], 0, true);
 725                         trans_model = model;
 726                     }
 727                     tpix = 0;
 728                 } else {
 729                     tpix = (byte) model.getTransparentPixel();
 730                 }
 731                 byte[] rasline = new byte[width];
 732                 if (tpix != 0) {
 733                     for (int i = 0; i < width; i++) {
 734                         rasline[i] = tpix;
 735                     }
 736                 }
 737 
 738                 // clear saved_image using transparent pixels
 739                 // this will be used as the background in the next display
 740                 if( decoder.saved_image != null ) {
 741                     for( int i = 0; i < global_width * global_height; i ++ )
 742                         decoder.saved_image[i] = tpix;
 743                 }
 744 
 745                 setPixels(x, y, width, height, model, rasline, 0, 0);
 746                 break;
 747             case DISPOSAL_SAVE:
 748                 decoder.saved_model = model;
 749                 break;
 750             }
 751         }
 752         return true;
 753     }
 754 }