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