1 /* 2 * Copyright (c) 2012, 2015, 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 package com.sun.javafx.iio.gif; 27 28 import com.sun.javafx.iio.ImageFrame; 29 import com.sun.javafx.iio.ImageMetadata; 30 import com.sun.javafx.iio.ImageStorage; 31 import com.sun.javafx.iio.common.ImageLoaderImpl; 32 import com.sun.javafx.iio.common.ImageTools; 33 import com.sun.javafx.iio.common.PushbroomScaler; 34 import com.sun.javafx.iio.common.ScalerFactory; 35 import java.io.EOFException; 36 import java.io.IOException; 37 import java.io.InputStream; 38 import java.nio.ByteBuffer; 39 import java.util.Arrays; 40 41 /* 42 * loader implementation for GIF89 file format 43 */ 44 45 public class GIFImageLoader2 extends ImageLoaderImpl { 46 47 static final byte FILE_SIG87[] = {'G', 'I', 'F', '8', '7', 'a'}; 48 static final byte FILE_SIG89[] = {'G', 'I', 'F', '8', '9', 'a'}; 49 static final byte NETSCAPE_SIG[] = {'N', 'E', 'T', 'S', 'C', 'A', 'P', 'E', '2', '.', '0'}; 50 static final int DEFAULT_FPS = 25; 51 52 InputStream stream = null; 53 int screenW, screenH, bgColor; 54 byte globalPalette[][]; // r,g,b,a 55 byte image[]; 56 int loopCount = 1; 57 58 public GIFImageLoader2(InputStream input) throws IOException { 59 super(GIFDescriptor.getInstance()); 60 this.stream = input; 61 readGlobalHeader(); 62 } 63 64 // read GIF file header 65 private void readGlobalHeader() throws IOException { 66 byte signature[] = readBytes(new byte[6]); 67 if (!Arrays.equals(FILE_SIG87, signature) && !Arrays.equals(FILE_SIG89, signature)) { 68 throw new IOException("Bad GIF signature!"); 69 } 70 screenW = readShort(); 71 screenH = readShort(); 72 int cInfo = readByte(); 73 bgColor = readByte(); 74 int aspectR = readByte(); 75 76 if ((cInfo & 0x80) != 0) { 77 globalPalette = readPalete(2 << (cInfo & 7), -1); 78 } 79 image = new byte[screenW * screenH * 4]; 80 } 81 82 // read palette data from the stream 83 private byte[][] readPalete(int size, int trnsIndex) throws IOException { 84 byte palette[][] = new byte[4][size]; 85 byte paletteData[] = readBytes(new byte[size*3]); 86 for (int i = 0, idx = 0; i != size; ++i) { 87 for (int k = 0; k != 3; ++k) { 88 palette[k][i] = paletteData[idx++]; 89 } 90 palette[3][i] = (i == trnsIndex) ? 0 : (byte)0xFF; 91 } 92 return palette; 93 } 94 95 // skip an extension 96 private void consumeAnExtension() throws IOException { 97 for (int blSize = readByte(); blSize != 0; blSize = readByte()) { 98 skipBytes(blSize); 99 } 100 } 101 102 private void readAppExtension() throws IOException { 103 int size = readByte(); 104 byte buf[] = readBytes(new byte[size]); 105 if (Arrays.equals(NETSCAPE_SIG, buf)) { 106 for (int subBlockSize = readByte(); subBlockSize != 0; subBlockSize = readByte()) { 107 byte subBlock[] = readBytes(new byte[subBlockSize]); 108 int subBlockId = subBlock[0]; 109 if (subBlockSize == 3 && subBlockId == 1) { // loop count extension 110 loopCount = (subBlock[1] & 0xff) | ((subBlock[2] & 0xff) << 8); 111 } 112 } 113 } else { 114 consumeAnExtension(); // read data sub-blocks 115 } 116 } 117 118 // reads Image Control extension information 119 // returns ((pField & 0x1F) << 24) + (trnsIndex << 16) + frameDelay; 120 private int readControlCode() throws IOException { 121 int size = readByte(); 122 int pField = readByte(); 123 int frameDelay = readShort(); 124 int trnsIndex = readByte(); 125 126 if (size != 4 || readByte() != 0) { 127 throw new IOException("Bad GIF GraphicControlExtension"); 128 } 129 return ((pField & 0x1F) << 24) + (trnsIndex << 16) + frameDelay; 130 } 131 132 // The method waits until image data in the stream 133 // The method also reads and return Image Control extension information 134 // returns -1 if EOF reached or the value of readControlCode 135 private int waitForImageFrame() throws IOException { 136 int controlData = 0; 137 while (true) { 138 int ch = stream.read(); 139 switch (ch) { 140 case 0x2C: 141 return controlData; 142 case 0x21: 143 switch (readByte()) { 144 case 0xF9: 145 controlData = readControlCode(); 146 break; 147 case 0xFF: 148 readAppExtension(); 149 break; 150 default: 151 consumeAnExtension(); 152 } 153 break; 154 case -1: case 0x3B: // EOF or end of GIF 155 return -1; 156 default: 157 throw new IOException("Unexpected GIF control characher 0x" 158 + String.format("%02X", ch)); 159 } 160 } 161 } 162 163 // Decode the one frame of GIF form the input stread using internal LZWDecoder class 164 private void decodeImage(byte image[], int w, int h, int interlace[]) throws IOException { 165 LZWDecoder dec = new LZWDecoder(); 166 byte data[] = dec.getString(); 167 int y = 0, iPos = 0, xr = w; 168 while (true) { 169 int len = dec.readString(); 170 if (len == -1) { // end of stream 171 dec.waitForTerminator(); 172 return; 173 } 174 for (int pos = 0; pos != len;) { 175 int ax = xr < (len - pos) ? xr : (len - pos); 176 System.arraycopy(data, pos, image, iPos, ax); 177 iPos += ax; 178 pos += ax; 179 if ((xr -= ax) == 0) { 180 if (++y == h) { // image is full 181 dec.waitForTerminator(); 182 return; 183 } 184 int iY = interlace == null ? y : interlace[y]; 185 iPos = iY * w; 186 xr = w; 187 } 188 } 189 } 190 } 191 192 // computes row re-index for interlaced case 193 private int[] computeInterlaceReIndex(int h) { 194 int data[] = new int[h], pos = 0; 195 for (int i = 0; i < h; i += 8) data[pos++] = i; 196 for (int i = 4; i < h; i += 8) data[pos++] = i; 197 for (int i = 2; i < h; i += 4) data[pos++] = i; 198 for (int i = 1; i < h; i += 2) data[pos++] = i; 199 return data; 200 } 201 202 // loads next image frame or null if no more 203 public ImageFrame load(int imageIndex, int width, int height, boolean preserveAspectRatio, boolean smooth) throws IOException { 204 int imageControlCode = waitForImageFrame(); 205 206 if (imageControlCode < 0) { 207 return null; 208 } 209 210 int left = readShort(), top = readShort(), w = readShort(), h = readShort(); 211 212 // check if the image is in the virtual screen boundaries 213 if (left + w > screenW || top + h > screenH) { 214 throw new IOException("Wrong GIF image frame size"); 215 } 216 217 int imgCtrl = readByte(); 218 219 boolean isTRNS = ((imageControlCode >>> 24) & 1) == 1; 220 int trnsIndex = isTRNS ? (imageControlCode >>> 16) & 0xFF : -1; 221 boolean localPalette = (imgCtrl & 0x80) != 0; 222 boolean isInterlaced = (imgCtrl & 0x40) != 0; 223 224 byte palette[][] = localPalette ? readPalete(2 << (imgCtrl & 7), trnsIndex) : globalPalette; 225 226 ImageMetadata metadata = updateMetadata(screenW, screenH, imageControlCode & 0xFFFF); 227 228 int disposalCode = (imageControlCode >>> 26) & 7; 229 byte pImage[] = new byte[w * h]; 230 decodeImage(pImage, w, h, isInterlaced ? computeInterlaceReIndex(h) : null); 231 232 ImageFrame imgGIF = decodePalette(pImage, palette, trnsIndex, 233 left, top, w, h, disposalCode, metadata); 234 235 // need to remove scaler from image decoder itself 236 int[] outWH = ImageTools.computeDimensions(screenW, screenH, width, height, preserveAspectRatio); 237 238 if (screenW != outWH[0] || screenH != outWH[1]) { 239 imgGIF = scaleImage(imgGIF, outWH[0], outWH[1], smooth); 240 } 241 242 return imgGIF; 243 } 244 245 // IO helpers 246 private int readByte() throws IOException { 247 int ch = stream.read(); 248 if (ch < 0) { 249 throw new EOFException(); 250 } 251 return ch; 252 } 253 254 private int readShort() throws IOException { 255 int lsb = readByte(), msb = readByte(); 256 return lsb + (msb << 8); 257 } 258 259 private byte[] readBytes(byte data[]) throws IOException { 260 return readBytes(data, 0, data.length); 261 } 262 263 private byte[] readBytes(byte data[], int offs, int size) throws IOException { 264 while (size > 0) { 265 int sz = stream.read(data, offs, size); 266 if (sz < 0) { 267 throw new EOFException(); 268 } 269 offs += sz; 270 size -= sz; 271 } 272 return data; 273 } 274 275 private void skipBytes(int n) throws IOException { 276 ImageTools.skipFully(stream, n); 277 } 278 279 public void dispose() {} 280 281 // GIF specification states that restore to background should fill the frame 282 // with background color, but actually all modern programs fill with transparent color. 283 private void restoreToBackground(byte img[], int left, int top, int w, int h) { 284 for (int y = 0; y != h; ++y) { 285 int iPos = ((top + y) * screenW + left) * 4; 286 for (int x = 0; x != w; iPos += 4, ++x) { 287 img[iPos + 3] = 0; 288 } 289 } 290 } 291 292 // decode palletized image into RGBA 293 private ImageFrame decodePalette(byte[] srcImage, byte[][] palette, int trnsIndex, 294 int left, int top, int w, int h, int disposalCode, ImageMetadata metadata) { 295 296 byte img[] = (disposalCode == 3) ? image.clone() : image; 297 298 for (int y = 0; y != h; ++y) { 299 int iPos = ((top + y) * screenW + left) * 4; 300 int i = y * w; 301 if (trnsIndex < 0) { 302 for (int x = 0; x != w; iPos += 4, ++x) { 303 int index = 0xFF & srcImage[i + x]; 304 img[iPos + 0] = palette[0][index]; 305 img[iPos + 1] = palette[1][index]; 306 img[iPos + 2] = palette[2][index]; 307 img[iPos + 3] = palette[3][index]; 308 } 309 } else { 310 for (int x = 0; x != w; iPos += 4, ++x) { 311 int index = 0xFF & srcImage[i + x]; 312 if (index != trnsIndex) { 313 img[iPos + 0] = palette[0][index]; 314 img[iPos + 1] = palette[1][index]; 315 img[iPos + 2] = palette[2][index]; 316 img[iPos + 3] = palette[3][index]; 317 } 318 } 319 } 320 } 321 322 if (disposalCode != 3) img = img.clone(); 323 if (disposalCode == 2) restoreToBackground(image, left, top, w, h); 324 325 return new ImageFrame(ImageStorage.ImageType.RGBA, ByteBuffer.wrap(img), 326 screenW, screenH, screenW * 4, null, metadata); 327 } 328 329 // copy from PNG, needs exctract refactoring later 330 // scales the image 331 private ImageFrame scaleImage(ImageFrame imgPNG, int rWidth, int rHeight, boolean smooth) { 332 byte image[] = ((ByteBuffer) imgPNG.getImageData()).array(); 333 int bpp = ImageStorage.getNumBands(imgPNG.getImageType()); 334 335 PushbroomScaler scaler = ScalerFactory.createScaler(screenW, screenH, bpp, 336 rWidth, rHeight, smooth); 337 338 for (int y = 0; y != screenH; ++y) { 339 scaler.putSourceScanline(image, y * screenW * bpp); 340 } 341 342 return new ImageFrame(imgPNG.getImageType(), scaler.getDestination(), 343 rWidth, rHeight, rWidth * bpp, null, imgPNG.getMetadata()); 344 } 345 346 // fill metadata 347 private ImageMetadata updateMetadata(int w, int h, int delayTime) { 348 ImageMetadata metaData = new ImageMetadata(null, true, null, null, null, 349 delayTime != 0 ? delayTime*10 : 1000/DEFAULT_FPS, loopCount, w, h, null, null, null); 350 updateImageMetadata(metaData); 351 return metaData; 352 } 353 354 class LZWDecoder { 355 private final int initCodeSize, clearCode, eofCode; 356 private int codeSize, codeMask, tableIndex, oldCode; 357 358 // input data buffer 359 private int blockLength = 0, blockPos = 0; 360 private byte block[] = new byte[255]; 361 private int inData = 0, inBits = 0; 362 363 // table 364 private int[] prefix = new int[4096]; 365 private byte[] suffix = new byte[4096]; 366 private byte[] initial = new byte[4096]; 367 private int[] length = new int[4096]; 368 private byte[] string = new byte[4096]; 369 370 public LZWDecoder() throws IOException { 371 initCodeSize = readByte(); 372 clearCode = 1 << initCodeSize; 373 eofCode = clearCode + 1; 374 initTable(); 375 } 376 377 // decode next string of data, which can be accessed by getString() method 378 public final int readString() throws IOException { 379 int code = getCode(); 380 if (code == eofCode) { 381 return -1; 382 } else if (code == clearCode) { 383 initTable(); 384 code = getCode(); 385 if (code == eofCode) { 386 return -1; 387 } 388 } else { 389 int newSuffixIndex; 390 int ti = tableIndex; 391 if (code < ti) { 392 newSuffixIndex = code; 393 } else { // code == tableIndex 394 newSuffixIndex = oldCode; 395 if (code != ti) { 396 throw new IOException("Bad GIF LZW: Out-of-sequence code!"); 397 } 398 } 399 400 int oc = oldCode; 401 402 prefix[ti] = oc; 403 suffix[ti] = initial[newSuffixIndex]; 404 initial[ti] = initial[oc]; 405 length[ti] = length[oc] + 1; 406 407 ++tableIndex; 408 if ((tableIndex == (1 << codeSize)) && (tableIndex < 4096)) { 409 ++codeSize; 410 codeMask = (1 << codeSize) - 1; 411 } 412 } 413 // Reverse code 414 int c = code; 415 int len = length[c]; 416 for (int i = len - 1; i >= 0; i--) { 417 string[i] = suffix[c]; 418 c = prefix[c]; 419 } 420 421 oldCode = code; 422 return len; 423 } 424 425 // data accessor, the data length returned by readString method 426 public final byte[] getString() { return string; } 427 428 // waits until data ends 429 public final void waitForTerminator() throws IOException { 430 consumeAnExtension(); 431 } 432 433 // initialize LZW dctionary 434 private void initTable() { 435 int numEntries = 1 << initCodeSize; 436 for (int i = 0; i < numEntries; i++) { 437 prefix[i] = -1; 438 suffix[i] = (byte) i; 439 initial[i] = (byte) i; 440 length[i] = 1; 441 } 442 443 // fill in the entire table for robustness against 444 // out-of-sequence codes. 445 for (int i = numEntries; i < 4096; i++) { 446 prefix[i] = -1; 447 length[i] = 1; 448 } 449 450 codeSize = initCodeSize + 1; 451 codeMask = (1 << codeSize) - 1; 452 tableIndex = numEntries + 2; 453 oldCode = 0; 454 } 455 456 // reads codeSize bits from the stream 457 private int getCode() throws IOException { 458 while (inBits < codeSize) { 459 inData |= nextByte() << inBits; 460 inBits += 8; 461 } 462 int code = inData & codeMask; 463 inBits -= codeSize; 464 inData >>>= codeSize; 465 return code; 466 } 467 468 // reads next in byte 469 private int nextByte() throws IOException { 470 if (blockPos == blockLength) { 471 readData(); 472 } 473 return (int)block[blockPos++] & 0xFF; 474 } 475 476 // reads next block if data 477 private void readData() throws IOException { 478 blockPos = 0; 479 blockLength = readByte(); 480 if (blockLength > 0) { 481 readBytes(block, 0, blockLength); 482 } else { 483 throw new EOFException(); 484 } 485 } 486 } 487 }