1 /******************************************************************************* 2 * Copyright (c) 2000, 2012 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 *******************************************************************************/ 14 package org.eclipse.swt.internal.image; 15 16 17 import org.eclipse.swt.*; 18 import org.eclipse.swt.graphics.*; 19 import java.io.*; 20 21 public final class GIFFileFormat extends FileFormat { 22 String signature; 23 int screenWidth, screenHeight, backgroundPixel, bitsPerPixel, defaultDepth; 24 int disposalMethod = 0; 25 int delayTime = 0; 26 int transparentPixel = -1; 27 int repeatCount = 1; 28 29 static final int GIF_APPLICATION_EXTENSION_BLOCK_ID = 0xFF; 30 static final int GIF_GRAPHICS_CONTROL_BLOCK_ID = 0xF9; 31 static final int GIF_PLAIN_TEXT_BLOCK_ID = 0x01; 32 static final int GIF_COMMENT_BLOCK_ID = 0xFE; 33 static final int GIF_EXTENSION_BLOCK_ID = 0x21; 34 static final int GIF_IMAGE_BLOCK_ID = 0x2C; 35 static final int GIF_TRAILER_ID = 0x3B; 36 static final byte [] GIF89a = new byte[] { (byte)'G', (byte)'I', (byte)'F', (byte)'8', (byte)'9', (byte)'a' }; 37 static final byte [] NETSCAPE2_0 = new byte[] { (byte)'N', (byte)'E', (byte)'T', (byte)'S', (byte)'C', (byte)'A', (byte)'P', (byte)'E', (byte)'2', (byte)'.', (byte)'0' }; 38 39 /** 40 * Answer a palette containing numGrays 41 * shades of gray, ranging from black to white. 42 */ grayRamp(int numGrays)43 static PaletteData grayRamp(int numGrays) { 44 int n = numGrays - 1; 45 RGB[] colors = new RGB[numGrays]; 46 for (int i = 0; i < numGrays; i++) { 47 int intensity = (byte)((i * 3) * 256 / n); 48 colors[i] = new RGB(intensity, intensity, intensity); 49 } 50 return new PaletteData(colors); 51 } 52 53 @Override isFileFormat(LEDataInputStream stream)54 boolean isFileFormat(LEDataInputStream stream) { 55 try { 56 byte[] signature = new byte[3]; 57 stream.read(signature); 58 stream.unread(signature); 59 return signature[0] == 'G' && signature[1] == 'I' && signature[2] == 'F'; 60 } catch (Exception e) { 61 return false; 62 } 63 } 64 65 /** 66 * Load the GIF image(s) stored in the input stream. 67 * Return an array of ImageData representing the image(s). 68 */ 69 @Override loadFromByteStream()70 ImageData[] loadFromByteStream() { 71 byte[] signature = new byte[3]; 72 byte[] versionBytes = new byte[3]; 73 byte[] block = new byte[7]; 74 try { 75 inputStream.read(signature); 76 if (!(signature[0] == 'G' && signature[1] == 'I' && signature[2] == 'F')) 77 SWT.error(SWT.ERROR_INVALID_IMAGE); 78 79 inputStream.read(versionBytes); 80 81 inputStream.read(block); 82 } catch (IOException e) { 83 SWT.error(SWT.ERROR_IO, e); 84 } 85 screenWidth = (block[0] & 0xFF) | ((block[1] & 0xFF) << 8); 86 loader.logicalScreenWidth = screenWidth; 87 screenHeight = (block[2] & 0xFF) | ((block[3] & 0xFF) << 8); 88 loader.logicalScreenHeight = screenHeight; 89 byte bitField = block[4]; 90 backgroundPixel = block[5] & 0xFF; 91 //aspect = block[6] & 0xFF; 92 bitsPerPixel = ((bitField >> 4) & 0x07) + 1; 93 defaultDepth = (bitField & 0x7) + 1; 94 PaletteData palette = null; 95 if ((bitField & 0x80) != 0) { 96 // Global palette. 97 //sorted = (bitField & 0x8) != 0; 98 palette = readPalette(1 << defaultDepth); 99 } else { 100 // No global palette. 101 //sorted = false; 102 backgroundPixel = -1; 103 defaultDepth = bitsPerPixel; 104 } 105 loader.backgroundPixel = backgroundPixel; 106 107 ImageData[] images = new ImageData[0]; 108 int id = readID(); 109 while (id != GIF_TRAILER_ID && id != -1) { 110 if (id == GIF_IMAGE_BLOCK_ID) { 111 ImageData image = readImageBlock(palette); 112 if (loader.hasListeners()) { 113 loader.notifyListeners(new ImageLoaderEvent(loader, image, 3, true)); 114 } 115 ImageData[] oldImages = images; 116 images = new ImageData[oldImages.length + 1]; 117 System.arraycopy(oldImages, 0, images, 0, oldImages.length); 118 images[images.length - 1] = image; 119 } else if (id == GIF_EXTENSION_BLOCK_ID) { 120 /* Read the extension block. Currently, only the 121 * interesting parts of certain extensions are kept, 122 * and the rest is discarded. In future, if we want 123 * to keep extensions, they should be grouped with 124 * the image data before which they appear. 125 */ 126 readExtension(); 127 } else { 128 /* The GIF is not to spec, but try to salvage it 129 * if we read at least one image. */ 130 if (images.length > 0) break; 131 SWT.error(SWT.ERROR_INVALID_IMAGE); 132 } 133 id = readID(); // block terminator (0) 134 if (id == 0) id = readID(); // next block ID (unless we just read it) 135 } 136 return images; 137 } 138 139 /** 140 * Read and return the next block or extension identifier from the file. 141 */ readID()142 int readID() { 143 try { 144 return inputStream.read(); 145 } catch (IOException e) { 146 SWT.error(SWT.ERROR_IO, e); 147 } 148 return -1; 149 } 150 151 /** 152 * Read a control extension. 153 * Return the extension block data. 154 */ readExtension()155 byte[] readExtension() { 156 int extensionID = readID(); 157 if (extensionID == GIF_COMMENT_BLOCK_ID) 158 return readCommentExtension(); 159 if (extensionID == GIF_PLAIN_TEXT_BLOCK_ID) 160 return readPlainTextExtension(); 161 if (extensionID == GIF_GRAPHICS_CONTROL_BLOCK_ID) 162 return readGraphicsControlExtension(); 163 if (extensionID == GIF_APPLICATION_EXTENSION_BLOCK_ID) 164 return readApplicationExtension(); 165 // Otherwise, we don't recognize the block. If the 166 // field size is correct, we can just skip over 167 // the block contents. 168 try { 169 int extSize = inputStream.read(); 170 if (extSize < 0) { 171 SWT.error(SWT.ERROR_INVALID_IMAGE); 172 } 173 byte[] ext = new byte[extSize]; 174 inputStream.read(ext, 0, extSize); 175 return ext; 176 } catch (IOException e) { 177 SWT.error(SWT.ERROR_IO, e); 178 return null; 179 } 180 } 181 182 /** 183 * We have just read the Comment extension identifier 184 * from the input stream. Read in the rest of the comment 185 * and return it. GIF comment blocks are variable size. 186 */ readCommentExtension()187 byte[] readCommentExtension() { 188 try { 189 byte[] comment = new byte[0]; 190 byte[] block = new byte[255]; 191 int size = inputStream.read(); 192 while ((size > 0) && (inputStream.read(block, 0, size) != -1)) { 193 byte[] oldComment = comment; 194 comment = new byte[oldComment.length + size]; 195 System.arraycopy(oldComment, 0, comment, 0, oldComment.length); 196 System.arraycopy(block, 0, comment, oldComment.length, size); 197 size = inputStream.read(); 198 } 199 return comment; 200 } catch (Exception e) { 201 SWT.error(SWT.ERROR_IO, e); 202 return null; 203 } 204 } 205 206 /** 207 * We have just read the PlainText extension identifier 208 * from the input stream. Read in the plain text info and text, 209 * and return the text. GIF plain text blocks are variable size. 210 */ readPlainTextExtension()211 byte[] readPlainTextExtension() { 212 try { 213 // Read size of block = 0x0C. 214 inputStream.read(); 215 // Read the text information (x, y, width, height, colors). 216 byte[] info = new byte[12]; 217 inputStream.read(info); 218 // Read the text. 219 byte[] text = new byte[0]; 220 byte[] block = new byte[255]; 221 int size = inputStream.read(); 222 while ((size > 0) && (inputStream.read(block, 0, size) != -1)) { 223 byte[] oldText = text; 224 text = new byte[oldText.length + size]; 225 System.arraycopy(oldText, 0, text, 0, oldText.length); 226 System.arraycopy(block, 0, text, oldText.length, size); 227 size = inputStream.read(); 228 } 229 return text; 230 } catch (Exception e) { 231 SWT.error(SWT.ERROR_IO, e); 232 return null; 233 } 234 } 235 236 /** 237 * We have just read the GraphicsControl extension identifier 238 * from the input stream. Read in the control information, store 239 * it, and return it. 240 */ readGraphicsControlExtension()241 byte[] readGraphicsControlExtension() { 242 try { 243 // Read size of block = 0x04. 244 inputStream.read(); 245 // Read the control block. 246 byte[] controlBlock = new byte[4]; 247 inputStream.read(controlBlock); 248 byte bitField = controlBlock[0]; 249 // Store the user input field. 250 //userInput = (bitField & 0x02) != 0; 251 // Store the disposal method. 252 disposalMethod = (bitField >> 2) & 0x07; 253 // Store the delay time. 254 delayTime = (controlBlock[1] & 0xFF) | ((controlBlock[2] & 0xFF) << 8); 255 // Store the transparent color. 256 if ((bitField & 0x01) != 0) { 257 transparentPixel = controlBlock[3] & 0xFF; 258 } else { 259 transparentPixel = -1; 260 } 261 return controlBlock; 262 } catch (Exception e) { 263 SWT.error(SWT.ERROR_IO, e); 264 return null; 265 } 266 } 267 268 /** 269 * We have just read the Application extension identifier 270 * from the input stream. Read in the rest of the extension, 271 * look for and store 'number of repeats', and return the data. 272 */ readApplicationExtension()273 byte[] readApplicationExtension() { 274 try { 275 // Read block data. 276 int blockSize = inputStream.read(); 277 byte[] blockData = new byte[blockSize]; 278 inputStream.read(blockData); 279 // Read application data. 280 byte[] data = new byte[0]; 281 byte[] block = new byte[255]; 282 int size = inputStream.read(); 283 while ((size > 0) && (inputStream.read(block, 0, size) != -1)) { 284 byte[] oldData = data; 285 data = new byte[oldData.length + size]; 286 System.arraycopy(oldData, 0, data, 0, oldData.length); 287 System.arraycopy(block, 0, data, oldData.length, size); 288 size = inputStream.read(); 289 } 290 // Look for the NETSCAPE 'repeat count' field for an animated GIF. 291 boolean netscape = 292 blockSize > 7 && 293 blockData[0] == 'N' && 294 blockData[1] == 'E' && 295 blockData[2] == 'T' && 296 blockData[3] == 'S' && 297 blockData[4] == 'C' && 298 blockData[5] == 'A' && 299 blockData[6] == 'P' && 300 blockData[7] == 'E'; 301 boolean authentic = 302 blockSize > 10 && 303 blockData[8] == '2' && 304 blockData[9] == '.' && 305 blockData[10] == '0'; 306 if (netscape && authentic && data[0] == 01) { //$NON-NLS-1$ //$NON-NLS-2$ 307 repeatCount = (data[1] & 0xFF) | ((data[2] & 0xFF) << 8); 308 loader.repeatCount = repeatCount; 309 } 310 return data; 311 } catch (Exception e) { 312 SWT.error(SWT.ERROR_IO, e); 313 return null; 314 } 315 } 316 317 /** 318 * Return a DeviceIndependentImage representing the 319 * image block at the current position in the input stream. 320 * Throw an error if an error occurs. 321 */ readImageBlock(PaletteData defaultPalette)322 ImageData readImageBlock(PaletteData defaultPalette) { 323 int depth; 324 PaletteData palette; 325 byte[] block = new byte[9]; 326 try { 327 inputStream.read(block); 328 } catch (IOException e) { 329 SWT.error(SWT.ERROR_IO, e); 330 } 331 int left = (block[0] & 0xFF) | ((block[1] & 0xFF) << 8); 332 int top = (block[2] & 0xFF) | ((block[3] & 0xFF) << 8); 333 int width = (block[4] & 0xFF) | ((block[5] & 0xFF) << 8); 334 int height = (block[6] & 0xFF) | ((block[7] & 0xFF) << 8); 335 byte bitField = block[8]; 336 boolean interlaced = (bitField & 0x40) != 0; 337 //boolean sorted = (bitField & 0x20) != 0; 338 if ((bitField & 0x80) != 0) { 339 // Local palette. 340 depth = (bitField & 0x7) + 1; 341 palette = readPalette(1 << depth); 342 } else { 343 // No local palette. 344 depth = defaultDepth; 345 palette = defaultPalette; 346 } 347 /* Work around: Ignore the case where a GIF specifies an 348 * invalid index for the transparent pixel that is larger 349 * than the number of entries in the palette. */ 350 if (transparentPixel > 1 << depth) { 351 transparentPixel = -1; 352 } 353 // Promote depth to next highest supported value. 354 if (!(depth == 1 || depth == 4 || depth == 8)) { 355 if (depth < 4) 356 depth = 4; 357 else 358 depth = 8; 359 } 360 if (palette == null) { 361 palette = grayRamp(1 << depth); 362 } 363 int initialCodeSize = -1; 364 try { 365 initialCodeSize = inputStream.read(); 366 } catch (IOException e) { 367 SWT.error(SWT.ERROR_IO, e); 368 } 369 if (initialCodeSize < 0) { 370 SWT.error(SWT.ERROR_INVALID_IMAGE); 371 } 372 ImageData image = ImageData.internal_new( 373 width, 374 height, 375 depth, 376 palette, 377 4, 378 null, 379 0, 380 null, 381 null, 382 -1, 383 transparentPixel, 384 SWT.IMAGE_GIF, 385 left, 386 top, 387 disposalMethod, 388 delayTime); 389 LZWCodec codec = new LZWCodec(); 390 codec.decode(inputStream, loader, image, interlaced, initialCodeSize); 391 return image; 392 } 393 394 /** 395 * Read a palette from the input stream. 396 */ readPalette(int numColors)397 PaletteData readPalette(int numColors) { 398 byte[] bytes = new byte[numColors * 3]; 399 try { 400 if (inputStream.read(bytes) != bytes.length) 401 SWT.error(SWT.ERROR_INVALID_IMAGE); 402 } catch (IOException e) { 403 SWT.error(SWT.ERROR_IO, e); 404 } 405 RGB[] colors = new RGB[numColors]; 406 for (int i = 0; i < numColors; i++) 407 colors[i] = new RGB(bytes[i*3] & 0xFF, 408 bytes[i*3+1] & 0xFF, bytes[i*3+2] & 0xFF); 409 return new PaletteData(colors); 410 } 411 412 @Override unloadIntoByteStream(ImageLoader loader)413 void unloadIntoByteStream(ImageLoader loader) { 414 415 /* Step 1: Acquire GIF parameters. */ 416 ImageData[] data = loader.data; 417 int frameCount = data.length; 418 boolean multi = frameCount > 1; 419 ImageData firstImage = data[0]; 420 int logicalScreenWidth = multi ? loader.logicalScreenWidth : firstImage.width; 421 int logicalScreenHeight = multi ? loader.logicalScreenHeight : firstImage.height; 422 int backgroundPixel = loader.backgroundPixel; 423 int depth = firstImage.depth; 424 PaletteData palette = firstImage.palette; 425 RGB[] colors = palette.getRGBs(); 426 short globalTable = 1; 427 428 /* Step 2: Check for validity and global/local color map. */ 429 if (!(depth == 1 || depth == 4 || depth == 8)) { 430 SWT.error(SWT.ERROR_UNSUPPORTED_DEPTH); 431 } 432 for (int i=0; i<frameCount; i++) { 433 if (data[i].palette.isDirect) { 434 SWT.error(SWT.ERROR_INVALID_IMAGE); 435 } 436 if (multi) { 437 if (!(data[i].height <= logicalScreenHeight && data[i].width <= logicalScreenWidth && data[i].depth == depth)) { 438 SWT.error(SWT.ERROR_INVALID_IMAGE); 439 } 440 if (globalTable == 1) { 441 RGB rgbs[] = data[i].palette.getRGBs(); 442 if (rgbs.length != colors.length) { 443 globalTable = 0; 444 } else { 445 for (int j=0; j<colors.length; j++) { 446 if (!(rgbs[j].red == colors[j].red && 447 rgbs[j].green == colors[j].green && 448 rgbs[j].blue == colors[j].blue)) 449 globalTable = 0; 450 } 451 } 452 } 453 } 454 } 455 456 try { 457 /* Step 3: Write the GIF89a Header and Logical Screen Descriptor. */ 458 outputStream.write(GIF89a); 459 int bitField = globalTable*128 + (depth-1)*16 + depth-1; 460 outputStream.writeShort((short)logicalScreenWidth); 461 outputStream.writeShort((short)logicalScreenHeight); 462 outputStream.write(bitField); 463 outputStream.write(backgroundPixel); 464 outputStream.write(0); // Aspect ratio is 1:1 465 } catch (IOException e) { 466 SWT.error(SWT.ERROR_IO, e); 467 } 468 469 /* Step 4: Write Global Color Table if applicable. */ 470 if (globalTable == 1) { 471 writePalette(palette, depth); 472 } 473 474 /* Step 5: Write Application Extension if applicable. */ 475 if (multi) { 476 int repeatCount = loader.repeatCount; 477 try { 478 outputStream.write(GIF_EXTENSION_BLOCK_ID); 479 outputStream.write(GIF_APPLICATION_EXTENSION_BLOCK_ID); 480 outputStream.write(NETSCAPE2_0.length); 481 outputStream.write(NETSCAPE2_0); 482 outputStream.write(3); // Three bytes follow 483 outputStream.write(1); // Extension type 484 outputStream.writeShort((short) repeatCount); 485 outputStream.write(0); // Block terminator 486 } catch (IOException e) { 487 SWT.error(SWT.ERROR_IO, e); 488 } 489 } 490 491 for (int frame=0; frame<frameCount; frame++) { 492 493 /* Step 6: Write Graphics Control Block for each frame if applicable. */ 494 if (multi || data[frame].transparentPixel != -1) { 495 writeGraphicsControlBlock(data[frame]); 496 } 497 498 /* Step 7: Write Image Header for each frame. */ 499 int x = data[frame].x; 500 int y = data[frame].y; 501 int width = data[frame].width; 502 int height = data[frame].height; 503 try { 504 outputStream.write(GIF_IMAGE_BLOCK_ID); 505 byte[] block = new byte[9]; 506 block[0] = (byte)(x & 0xFF); 507 block[1] = (byte)((x >> 8) & 0xFF); 508 block[2] = (byte)(y & 0xFF); 509 block[3] = (byte)((y >> 8) & 0xFF); 510 block[4] = (byte)(width & 0xFF); 511 block[5] = (byte)((width >> 8) & 0xFF); 512 block[6] = (byte)(height & 0xFF); 513 block[7] = (byte)((height >> 8) & 0xFF); 514 block[8] = (byte)(globalTable == 0 ? (depth-1) | 0x80 : 0x00); 515 outputStream.write(block); 516 } catch (IOException e) { 517 SWT.error(SWT.ERROR_IO, e); 518 } 519 520 /* Step 8: Write Local Color Table for each frame if applicable. */ 521 if (globalTable == 0) { 522 writePalette(data[frame].palette, depth); 523 } 524 525 /* Step 9: Write the actual data for each frame. */ 526 try { 527 outputStream.write(depth); // Minimum LZW Code size 528 } catch (IOException e) { 529 SWT.error(SWT.ERROR_IO, e); 530 } 531 new LZWCodec().encode(outputStream, data[frame]); 532 } 533 534 /* Step 10: Write GIF terminator. */ 535 try { 536 outputStream.write(0x3B); 537 } catch (IOException e) { 538 SWT.error(SWT.ERROR_IO, e); 539 } 540 } 541 542 /** 543 * Write out a GraphicsControlBlock to describe 544 * the specified device independent image. 545 */ writeGraphicsControlBlock(ImageData image)546 void writeGraphicsControlBlock(ImageData image) { 547 try { 548 outputStream.write(GIF_EXTENSION_BLOCK_ID); 549 outputStream.write(GIF_GRAPHICS_CONTROL_BLOCK_ID); 550 byte[] gcBlock = new byte[4]; 551 gcBlock[0] = 0; 552 gcBlock[1] = 0; 553 gcBlock[2] = 0; 554 gcBlock[3] = 0; 555 if (image.transparentPixel != -1) { 556 gcBlock[0] = (byte)0x01; 557 gcBlock[3] = (byte)image.transparentPixel; 558 } 559 if (image.disposalMethod != 0) { 560 gcBlock[0] |= (byte)((image.disposalMethod & 0x07) << 2); 561 } 562 if (image.delayTime != 0) { 563 gcBlock[1] = (byte)(image.delayTime & 0xFF); 564 gcBlock[2] = (byte)((image.delayTime >> 8) & 0xFF); 565 } 566 outputStream.write((byte)gcBlock.length); 567 outputStream.write(gcBlock); 568 outputStream.write(0); // Block terminator 569 } catch (IOException e) { 570 SWT.error(SWT.ERROR_IO, e); 571 } 572 } 573 574 /** 575 * Write the specified palette to the output stream. 576 */ writePalette(PaletteData palette, int depth)577 void writePalette(PaletteData palette, int depth) { 578 byte[] bytes = new byte[(1 << depth) * 3]; 579 int offset = 0; 580 for (RGB color : palette.colors) { 581 bytes[offset] = (byte)color.red; 582 bytes[offset + 1] = (byte)color.green; 583 bytes[offset + 2] = (byte)color.blue; 584 offset += 3; 585 } 586 try { 587 outputStream.write(bytes); 588 } catch (IOException e) { 589 SWT.error(SWT.ERROR_IO, e); 590 } 591 } 592 } 593