GifImageView使用

这里写图片描述

GifDecoder.java

/**
 * Copyright (c) 2013 Xcellent Creations, Inc.
 * Copyright 2014 Google, Inc. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 * associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package com.lenovo.cava.widget.GifImageView;

import android.annotation.TargetApi;
import android.graphics.Bitmap;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;

import static com.lenovo.cava.utils.Log.TAG_WITH_CLASS_NAME;
import static com.lenovo.cava.utils.Log.TAG_CAVA;

/**
 * Reads frame data from a GIF image source and decodes it into individual frames
 * for animation purposes. Image data can be read from either and InputStream source
 * or a byte[].
 *
 * This class is optimized for running animations with the frames, there
 * are no methods to get individual frame images, only to decode the next frame in the
 * animation sequence. Instead, it lowers its memory footprint by only housing the minimum
 * data necessary to decode the next frame in the animation sequence.
 *
 * The animation must be manually moved forward using {@link #advance()} before requesting the next
 * frame. This method must also be called before you request the first frame or an error will
 * occur.
 *
 * Implementation adapted from sample code published in Lyons. (2004). <em>Java for Programmers</em>,
 * republished under the MIT Open Source License
 */
class GifDecoder {
  private static final String TAG = TAG_WITH_CLASS_NAME ? "GifDecoder" : TAG_CAVA;

  /**
   * File read status: No errors.
   */
  static final int STATUS_OK = 0;
  /**
   * File read status: Error decoding file (may be partially decoded).
   */
  static final int STATUS_FORMAT_ERROR = 1;
  /**
   * File read status: Unable to open source.
   */
  static final int STATUS_OPEN_ERROR = 2;
  /**
   * Unable to fully decode the current frame.
   */
  static final int STATUS_PARTIAL_DECODE = 3;
  /**
   * max decoder pixel stack size.
   */
  private static final int MAX_STACK_SIZE = 4096;

  /**
   * GIF Disposal Method meaning take no action.
   */
  private static final int DISPOSAL_UNSPECIFIED = 0;
  /**
   * GIF Disposal Method meaning leave canvas from previous frame.
   */
  private static final int DISPOSAL_NONE = 1;
  /**
   * GIF Disposal Method meaning clear canvas to background color.
   */
  private static final int DISPOSAL_BACKGROUND = 2;
  /**
   * GIF Disposal Method meaning clear canvas to frame before last.
   */
  private static final int DISPOSAL_PREVIOUS = 3;

  private static final int NULL_CODE = -1;

  private static final int INITIAL_FRAME_POINTER = -1;

  static final int LOOP_FOREVER = -1;

  private static final int BYTES_PER_INTEGER = 4;

  // Global File Header values and parsing flags.
  // Active color table.
  private int[] act;
  // Private color table that can be modified if needed.
  private final int[] pct = new int[256];

  // Raw GIF data from input source.
  private ByteBuffer rawData;

  // Raw data read working array.
  private byte[] block;

  // Temporary buffer for block reading. Reads 16k chunks from the native buffer for processing,
  // to greatly reduce JNI overhead.
  private static final int WORK_BUFFER_SIZE = 16384;
  @Nullable
  private byte[] workBuffer;
  private int workBufferSize = 0;
  private int workBufferPosition = 0;

  private GifHeaderParser parser;

  // LZW decoder working arrays.
  private short[] prefix;
  private byte[] suffix;
  private byte[] pixelStack;
  private byte[] mainPixels;
  private int[] mainScratch;

  private int framePointer;
  private int loopIndex;
  private GifHeader header;
  private BitmapProvider bitmapProvider;
  private Bitmap previousImage;
  private boolean savePrevious;
  private int status;
  private int sampleSize;
  private int downsampledHeight;
  private int downsampledWidth;
  private boolean isFirstFrameTransparent;

  /**
   * An interface that can be used to provide reused {@link Bitmap}s to avoid GCs
   * from constantly allocating {@link Bitmap}s for every frame.
   */
  interface BitmapProvider {
    /**
     * Returns an {@link Bitmap} with exactly the given dimensions and config.
     *
     * @param width  The width in pixels of the desired {@link Bitmap}.
     * @param height The height in pixels of the desired {@link Bitmap}.
     * @param config The {@link Bitmap.Config} of the desired {@link
     *               Bitmap}.
     */
    @NonNull
    Bitmap obtain(int width, int height, Bitmap.Config config);

    /**
     * Releases the given Bitmap back to the pool.
     */
    void release(Bitmap bitmap);

    /**
     * Returns a byte array used for decoding and generating the frame bitmap.
     *
     * @param size the size of the byte array to obtain
     */
    byte[] obtainByteArray(int size);

    /**
     * Releases the given byte array back to the pool.
     */
    void release(byte[] bytes);

    /**
     * Returns an int array used for decoding/generating the frame bitmaps.
     * @param size
     */
    int[] obtainIntArray(int size);

    /**
     * Release the given array back to the pool.
     * @param array
     */
    void release(int[] array);
  }

  GifDecoder(BitmapProvider provider, GifHeader gifHeader, ByteBuffer rawData) {
    this(provider, gifHeader, rawData, 1 /*sampleSize*/);
  }

  GifDecoder(BitmapProvider provider, GifHeader gifHeader, ByteBuffer rawData,
      int sampleSize) {
    this(provider);
    setData(gifHeader, rawData, sampleSize);
  }

  GifDecoder(BitmapProvider provider) {
    this.bitmapProvider = provider;
    header = new GifHeader();
  }

  GifDecoder() {
    this(new SimpleBitmapProvider());
  }

  int getWidth() {
    return header.width;
  }

  int getHeight() {
    return header.height;
  }

  ByteBuffer getData() {
    return rawData;
  }

  /**
   * Returns the current status of the decoder.
   *
   * <p> Status will update per frame to allow the caller to tell whether or not the current frame
   * was decoded successfully and/or completely. Format and open failures persist across frames.
   * </p>
   */
  int getStatus() {
    return status;
  }

  /**
   * Move the animation frame counter forward.
   *
   * @return boolean specifying if animation should continue or if loopCount has been fulfilled.
   */
  boolean advance() {
    if (header.frameCount <= 0) {
      return false;
    }

    if(framePointer == getFrameCount() - 1) {
      loopIndex++;
    }

    if(header.loopCount != LOOP_FOREVER && loopIndex > header.loopCount) {
      return false;
    }

    framePointer = (framePointer + 1) % header.frameCount;
    return true;
  }

  /**
   * Gets display duration for specified frame.
   *
   * @param n int index of frame.
   * @return delay in milliseconds.
   */
  int getDelay(int n) {
    int delay = -1;
    if ((n >= 0) && (n < header.frameCount)) {
      delay = header.frames.get(n).delay;
    }
    return delay;
  }

  /**
   * Gets display duration for the upcoming frame in ms.
   */
  int getNextDelay() {
    if (header.frameCount <= 0 || framePointer < 0) {
      return 0;
    }

    return getDelay(framePointer);
  }

  /**
   * Gets the number of frames read from file.
   *
   * @return frame count.
   */
  int getFrameCount() {
    return header.frameCount;
  }

  /**
   * Gets the current index of the animation frame, or -1 if animation hasn't not yet started.
   *
   * @return frame index.
   */
  int getCurrentFrameIndex() {
    return framePointer;
  }

  /**
   * Sets the frame pointer to a specific frame
   *
   * @return boolean true if the move was successful
   */
  boolean setFrameIndex(int frame) {
    if(frame < INITIAL_FRAME_POINTER || frame >= getFrameCount()) {
      return false;
    }
    framePointer = frame;
    return true;
  }

  /**
   * Resets the frame pointer to before the 0th frame, as if we'd never used this decoder to
   * decode any frames.
   */
  void resetFrameIndex() {
    framePointer = INITIAL_FRAME_POINTER;
  }

  /**
   * Resets the loop index to the first loop.
   */
  void resetLoopIndex() { loopIndex = 0; }

  /**
   * Gets the "Netscape" iteration count, if any. A count of 0 means repeat indefinitely.
   *
   * @return iteration count if one was specified, else 1.
   */
  int getLoopCount() { return header.loopCount; }

  /**
   * Gets the number of loops that have been shown.
   *
   * @return iteration count.
   */
  int getLoopIndex() {
    return loopIndex;
  }

  /**
   * Returns an estimated byte size for this decoder based on the data provided to {@link
   * #setData(GifHeader, byte[])}, as well as internal buffers.
   */
  int getByteSize() {
    return rawData.limit() + mainPixels.length + (mainScratch.length * BYTES_PER_INTEGER);
  }

  /**
   * Get the next frame in the animation sequence.
   *
   * @return Bitmap representation of frame.
   */
  synchronized Bitmap getNextFrame() {
    if (header.frameCount <= 0 || framePointer < 0) {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "unable to decode frame, frameCount=" + header.frameCount + " framePointer="
            + framePointer);
      }
      status = STATUS_FORMAT_ERROR;
    }
    if (status == STATUS_FORMAT_ERROR || status == STATUS_OPEN_ERROR) {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "Unable to decode frame, status=" + status);
      }
      return null;
    }
    status = STATUS_OK;

    GifFrame currentFrame = header.frames.get(framePointer);
    GifFrame previousFrame = null;
    int previousIndex = framePointer - 1;
    if (previousIndex >= 0) {
      previousFrame = header.frames.get(previousIndex);
    }

    // Set the appropriate color table.
    act = currentFrame.lct != null ? currentFrame.lct : header.gct;
    if (act == null) {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "No Valid Color Table for frame #" + framePointer);
      }
      // No color table defined.
      status = STATUS_FORMAT_ERROR;
      return null;
    }

    // Reset the transparent pixel in the color table
    if (currentFrame.transparency) {
      // Prepare local copy of color table ("pct = act"), see #1068
      System.arraycopy(act, 0, pct, 0, act.length);
      // Forget about act reference from shared header object, use copied version
      act = pct;
      // Set transparent color if specified.
      act[currentFrame.transIndex] = 0;
    }

    // Transfer pixel data to image.
    return setPixels(currentFrame, previousFrame);
  }

  /**
   * Reads GIF image from stream.
   *
   * @param is containing GIF file.
   * @return read status code (0 = no errors).
   */
  int read(InputStream is, int contentLength) {
    if (is != null) {
      try {
        int capacity = (contentLength > 0) ? (contentLength + 4096) : 16384;
        ByteArrayOutputStream buffer = new ByteArrayOutputStream(capacity);
        int nRead;
        byte[] data = new byte[16384];
        while ((nRead = is.read(data, 0, data.length)) != -1) {
          buffer.write(data, 0, nRead);
        }
        buffer.flush();

        read(buffer.toByteArray());
      } catch (IOException e) {
        Log.w(TAG, "Error reading data from stream", e);
      }
    } else {
      status = STATUS_OPEN_ERROR;
    }

    try {
      if (is != null) {
        is.close();
      }
    } catch (IOException e) {
      Log.w(TAG, "Error closing stream", e);
    }

    return status;
  }

  void clear() {
    header = null;
    if (mainPixels != null) {
      bitmapProvider.release(mainPixels);
    }
    if (mainScratch != null) {
      bitmapProvider.release(mainScratch);
    }
    if (previousImage != null) {
      bitmapProvider.release(previousImage);
    }
    previousImage = null;
    rawData = null;
    isFirstFrameTransparent = false;
    if (block != null) {
      bitmapProvider.release(block);
    }
    if (workBuffer != null) {
      bitmapProvider.release(workBuffer);
    }
  }

  synchronized void setData(GifHeader header, byte[] data) {
    setData(header, ByteBuffer.wrap(data));
  }

  synchronized void setData(GifHeader header, ByteBuffer buffer) {
    setData(header, buffer, 1);
  }

  synchronized void setData(GifHeader header, ByteBuffer buffer, int sampleSize) {
    if (sampleSize <= 0) {
      throw new IllegalArgumentException("Sample size must be >=0, not: " + sampleSize);
    }
    // Make sure sample size is a power of 2.
    sampleSize = Integer.highestOneBit(sampleSize);
    this.status = STATUS_OK;
    this.header = header;
    isFirstFrameTransparent = false;
    framePointer = INITIAL_FRAME_POINTER;
    resetLoopIndex();
    // Initialize the raw data buffer.
    rawData = buffer.asReadOnlyBuffer();
    rawData.position(0);
    rawData.order(ByteOrder.LITTLE_ENDIAN);

    // No point in specially saving an old frame if we're never going to use it.
    savePrevious = false;
    for (GifFrame frame : header.frames) {
      if (frame.dispose == DISPOSAL_PREVIOUS) {
        savePrevious = true;
        break;
      }
    }

    this.sampleSize = sampleSize;
    downsampledWidth = header.width / sampleSize;
    downsampledHeight = header.height / sampleSize;
    // Now that we know the size, init scratch arrays.
    // TODO Find a way to avoid this entirely or at least downsample it (either should be possible).
    mainPixels = bitmapProvider.obtainByteArray(header.width * header.height);
    mainScratch = bitmapProvider.obtainIntArray(downsampledWidth * downsampledHeight);
  }

  private GifHeaderParser getHeaderParser() {
    if (parser == null) {
      parser = new GifHeaderParser();
    }
    return parser;
  }

  /**
   * Reads GIF image from byte array.
   *
   * @param data containing GIF file.
   * @return read status code (0 = no errors).
   */
  synchronized int read(byte[] data) {
    this.header = getHeaderParser().setData(data).parseHeader();
    if (data != null) {
      setData(header, data);
    }

    return status;
  }

  /**
   * Creates new frame image from current data (and previous frames as specified by their
   * disposition codes).
   */
  private Bitmap setPixels(GifFrame currentFrame, GifFrame previousFrame) {
    // Final location of blended pixels.
    final int[] dest = mainScratch;

    // clear all pixels when meet first frame
    if (previousFrame == null) {
      Arrays.fill(dest, 0);
    }

    // fill in starting image contents based on last image's dispose code
    if (previousFrame != null && previousFrame.dispose > DISPOSAL_UNSPECIFIED) {
      // We don't need to do anything for DISPOSAL_NONE, if it has the correct pixels so will our
      // mainScratch and therefore so will our dest array.
      if (previousFrame.dispose == DISPOSAL_BACKGROUND) {
        // Start with a canvas filled with the background color
        int c = 0;
        if (!currentFrame.transparency) {
          c = header.bgColor;
          if (currentFrame.lct != null && header.bgIndex == currentFrame.transIndex) {
            c = 0;
          }
        } else if (framePointer == 0) {
          // TODO: We should check and see if all individual pixels are replaced. If they are, the
          // first frame isn't actually transparent. For now, it's simpler and safer to assume
          // drawing a transparent background means the GIF contains transparency.
          isFirstFrameTransparent = true;
        }
        fillRect(dest, previousFrame, c);
      } else if (previousFrame.dispose == DISPOSAL_PREVIOUS) {
        if (previousImage == null) {
          fillRect(dest, previousFrame, 0);
        } else {
          // Start with the previous frame
          int downsampledIH = previousFrame.ih / sampleSize;
          int downsampledIY = previousFrame.iy / sampleSize;
          int downsampledIW = previousFrame.iw / sampleSize;
          int downsampledIX = previousFrame.ix / sampleSize;
          int topLeft = downsampledIY * downsampledWidth + downsampledIX;
          previousImage.getPixels(dest, topLeft, downsampledWidth,
              downsampledIX, downsampledIY, downsampledIW, downsampledIH);
        }
      }
    }

    // Decode pixels for this frame into the global pixels[] scratch.
    decodeBitmapData(currentFrame);

    int downsampledIH = currentFrame.ih / sampleSize;
    int downsampledIY = currentFrame.iy / sampleSize;
    int downsampledIW = currentFrame.iw / sampleSize;
    int downsampledIX = currentFrame.ix / sampleSize;
    // Copy each source line to the appropriate place in the destination.
    int pass = 1;
    int inc = 8;
    int iline = 0;
    boolean isFirstFrame = framePointer == 0;
    for (int i = 0; i < downsampledIH; i++) {
      int line = i;
      if (currentFrame.interlace) {
        if (iline >= downsampledIH) {
          pass++;
          switch (pass) {
            case 2:
              iline = 4;
              break;
            case 3:
              iline = 2;
              inc = 4;
              break;
            case 4:
              iline = 1;
              inc = 2;
              break;
            default:
              break;
          }
        }
        line = iline;
        iline += inc;
      }
      line += downsampledIY;
      if (line < downsampledHeight) {
        int k = line * downsampledWidth;
        // Start of line in dest.
        int dx = k + downsampledIX;
        // End of dest line.
        int dlim = dx + downsampledIW;
        if (k + downsampledWidth < dlim) {
          // Past dest edge.
          dlim = k + downsampledWidth;
        }
        // Start of line in source.
        int sx = i * sampleSize * currentFrame.iw;
        int maxPositionInSource = sx + ((dlim - dx) * sampleSize);
        while (dx < dlim) {
          // Map color and insert in destination.
          int averageColor;
          if (sampleSize == 1) {
            int currentColorIndex = ((int) mainPixels[sx]) & 0x000000ff;
            averageColor = act[currentColorIndex];
          } else {
            // TODO: This is substantially slower (up to 50ms per frame) than just grabbing the
            // current color index above, even with a sample size of 1.
            averageColor = averageColorsNear(sx, maxPositionInSource, currentFrame.iw);
          }
          if (averageColor != 0) {
            dest[dx] = averageColor;
          } else if (!isFirstFrameTransparent && isFirstFrame) {
            isFirstFrameTransparent = true;
          }
          sx += sampleSize;
          dx++;
        }
      }
    }

    // Copy pixels into previous image
    if (savePrevious && (currentFrame.dispose == DISPOSAL_UNSPECIFIED
        || currentFrame.dispose == DISPOSAL_NONE)) {
      if (previousImage == null) {
        previousImage = getNextBitmap();
      }
      previousImage.setPixels(dest, 0, downsampledWidth, 0, 0, downsampledWidth,
          downsampledHeight);
    }

    // Set pixels for current image.
    Bitmap result = getNextBitmap();
    result.setPixels(dest, 0, downsampledWidth, 0, 0, downsampledWidth, downsampledHeight);
    return result;
  }

  private void fillRect(int[] dest, GifFrame frame, int bgColor) {
    // The area used by the graphic must be restored to the background color.
    int downsampledIH = frame.ih / sampleSize;
    int downsampledIY = frame.iy / sampleSize;
    int downsampledIW = frame.iw / sampleSize;
    int downsampledIX = frame.ix / sampleSize;
    int topLeft = downsampledIY * downsampledWidth + downsampledIX;
    int bottomLeft = topLeft + downsampledIH * downsampledWidth;
    for (int left = topLeft; left < bottomLeft; left += downsampledWidth) {
      int right = left + downsampledIW;
      for (int pointer = left; pointer < right; pointer++) {
        dest[pointer] = bgColor;
      }
    }
  }

  private int averageColorsNear(int positionInMainPixels, int maxPositionInMainPixels,
      int currentFrameIw) {
    int alphaSum = 0;
    int redSum = 0;
    int greenSum = 0;
    int blueSum = 0;

    int totalAdded = 0;
    // Find the pixels in the current row.
    for (int i = positionInMainPixels;
        i < positionInMainPixels + sampleSize && i < mainPixels.length
            && i < maxPositionInMainPixels; i++) {
      int currentColorIndex = ((int) mainPixels[i]) & 0xff;
      int currentColor = act[currentColorIndex];
      if (currentColor != 0) {
        alphaSum += currentColor >> 24 & 0x000000ff;
        redSum += currentColor >> 16 & 0x000000ff;
        greenSum += currentColor >> 8 & 0x000000ff;
        blueSum += currentColor & 0x000000ff;
        totalAdded++;
      }
    }
    // Find the pixels in the next row.
    for (int i = positionInMainPixels + currentFrameIw;
        i < positionInMainPixels + currentFrameIw + sampleSize && i < mainPixels.length
            && i < maxPositionInMainPixels; i++) {
      int currentColorIndex = ((int) mainPixels[i]) & 0xff;
      int currentColor = act[currentColorIndex];
      if (currentColor != 0) {
        alphaSum += currentColor >> 24 & 0x000000ff;
        redSum += currentColor >> 16 & 0x000000ff;
        greenSum += currentColor >> 8 & 0x000000ff;
        blueSum += currentColor & 0x000000ff;
        totalAdded++;
      }
    }
    if (totalAdded == 0) {
      return 0;
    } else {
      return ((alphaSum / totalAdded) << 24)
          | ((redSum / totalAdded) << 16)
          | ((greenSum / totalAdded) << 8)
          | (blueSum / totalAdded);
    }
  }

  /**
   * Decodes LZW image data into pixel array. Adapted from John Cristy's BitmapMagick.
   */
  private void decodeBitmapData(GifFrame frame) {
    workBufferSize = 0;
    workBufferPosition = 0;
    if (frame != null) {
      // Jump to the frame start position.
      rawData.position(frame.bufferFrameStart);
    }

    int npix = (frame == null) ? header.width * header.height : frame.iw * frame.ih;
    int available, clear, codeMask, codeSize, endOfInformation, inCode, oldCode, bits, code, count,
        i, datum,
        dataSize, first, top, bi, pi;

    if (mainPixels == null || mainPixels.length < npix) {
      // Allocate new pixel array.
      mainPixels = bitmapProvider.obtainByteArray(npix);
    }
    if (prefix == null) {
      prefix = new short[MAX_STACK_SIZE];
    }
    if (suffix == null) {
      suffix = new byte[MAX_STACK_SIZE];
    }
    if (pixelStack == null) {
      pixelStack = new byte[MAX_STACK_SIZE + 1];
    }

    // Initialize GIF data stream decoder.
    dataSize = readByte();
    clear = 1 << dataSize;
    endOfInformation = clear + 1;
    available = clear + 2;
    oldCode = NULL_CODE;
    codeSize = dataSize + 1;
    codeMask = (1 << codeSize) - 1;
    for (code = 0; code < clear; code++) {
      // XXX ArrayIndexOutOfBoundsException.
      prefix[code] = 0;
      suffix[code] = (byte) code;
    }

    // Decode GIF pixel stream.
    datum = bits = count = first = top = pi = bi = 0;
    for (i = 0; i < npix; ) {
      // Load bytes until there are enough bits for a code.
      if (count == 0) {
        // Read a new data block.
        count = readBlock();
        if (count <= 0) {
          status = STATUS_PARTIAL_DECODE;
          break;
        }
        bi = 0;
      }

      datum += (((int) block[bi]) & 0xff) << bits;
      bits += 8;
      bi++;
      count--;

      while (bits >= codeSize) {
        // Get the next code.
        code = datum & codeMask;
        datum >>= codeSize;
        bits -= codeSize;

        // Interpret the code.
        if (code == clear) {
          // Reset decoder.
          codeSize = dataSize + 1;
          codeMask = (1 << codeSize) - 1;
          available = clear + 2;
          oldCode = NULL_CODE;
          continue;
        }

        if (code > available) {
          status = STATUS_PARTIAL_DECODE;
          break;
        }

        if (code == endOfInformation) {
          break;
        }

        if (oldCode == NULL_CODE) {
          pixelStack[top++] = suffix[code];
          oldCode = code;
          first = code;
          continue;
        }
        inCode = code;
        if (code >= available) {
          pixelStack[top++] = (byte) first;
          code = oldCode;
        }
        while (code >= clear) {
          pixelStack[top++] = suffix[code];
          code = prefix[code];
        }
        first = ((int) suffix[code]) & 0xff;
        pixelStack[top++] = (byte) first;

        // Add a new string to the string table.
        if (available < MAX_STACK_SIZE) {
          prefix[available] = (short) oldCode;
          suffix[available] = (byte) first;
          available++;
          if (((available & codeMask) == 0) && (available < MAX_STACK_SIZE)) {
            codeSize++;
            codeMask += available;
          }
        }
        oldCode = inCode;

        while (top > 0) {
          // Pop a pixel off the pixel stack.
          mainPixels[pi++] = pixelStack[--top];
          i++;
        }
      }
    }

    // Clear missing pixels.
    for (i = pi; i < npix; i++) {
      mainPixels[i] = 0;
    }
  }

  /**
   * Reads the next chunk for the intermediate work buffer.
   */
  private void readChunkIfNeeded() {
    if (workBufferSize > workBufferPosition) {
      return;
    }
    if (workBuffer == null) {
      workBuffer = bitmapProvider.obtainByteArray(WORK_BUFFER_SIZE);
    }
    workBufferPosition = 0;
    workBufferSize = Math.min(rawData.remaining(), WORK_BUFFER_SIZE);
    rawData.get(workBuffer, 0, workBufferSize);
  }

  /**
   * Reads a single byte from the input stream.
   */
  private int readByte() {
    try {
      readChunkIfNeeded();
      return workBuffer[workBufferPosition++] & 0xFF;
    } catch (Exception e) {
      status = STATUS_FORMAT_ERROR;
      return 0;
    }
  }

  /**
   * Reads next variable length block from input.
   *
   * @return number of bytes stored in "buffer".
   */
  private int readBlock() {
    int blockSize = readByte();
    if (blockSize > 0) {
      try {
        if (block == null) {
          block = bitmapProvider.obtainByteArray(255);
        }
        final int remaining = workBufferSize - workBufferPosition;
        if (remaining >= blockSize) {
          // Block can be read from the current work buffer.
          System.arraycopy(workBuffer, workBufferPosition, block, 0, blockSize);
          workBufferPosition += blockSize;
        } else if (rawData.remaining() + remaining >= blockSize) {
          // Block can be read in two passes.
          System.arraycopy(workBuffer, workBufferPosition, block, 0, remaining);
          workBufferPosition = workBufferSize;
          readChunkIfNeeded();
          final int secondHalfRemaining = blockSize - remaining;
          System.arraycopy(workBuffer, 0, block, remaining, secondHalfRemaining);
          workBufferPosition += secondHalfRemaining;
        } else {
          status = STATUS_FORMAT_ERROR;
        }
      } catch (Exception e) {
        Log.w(TAG, "Error Reading Block", e);
        status = STATUS_FORMAT_ERROR;
      }
    }
    return blockSize;
  }

  private Bitmap getNextBitmap() {
    Bitmap.Config config = isFirstFrameTransparent
        ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
    Bitmap result = bitmapProvider.obtain(downsampledWidth, downsampledHeight, config);
    setAlpha(result);
    return result;
  }

  @TargetApi(12)
  private static void setAlpha(Bitmap bitmap) {
    if (Build.VERSION.SDK_INT >= 12) {
      bitmap.setHasAlpha(true);
    }
  }
}

GifFrame.java

/**
 * Copyright 2014 Google, Inc. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 * associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package com.lenovo.cava.widget.GifImageView;

/**
 * Inner model class housing metadata for each frame.
 */
class GifFrame {
  int ix, iy, iw, ih;
  /**
   * Control Flag.
   */
  boolean interlace;
  /**
   * Control Flag.
   */
  boolean transparency;
  /**
   * Disposal Method.
   */
  int dispose;
  /**
   * Transparency Index.
   */
  int transIndex;
  /**
   * Delay, in ms, to next frame.
   */
  int delay;
  /**
   * Index in the raw buffer where we need to start reading to decode.
   */
  int bufferFrameStart;
  /**
   * Local Color Table.
   */
  int[] lct;
}

GifHeader.java

/**
 * Copyright 2014 Google, Inc. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 * associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package com.lenovo.cava.widget.GifImageView;

import java.util.ArrayList;
import java.util.List;

/**
 * A header object containing the number of frames in an animated GIF image as well as basic
 * metadata like width and height that can be used to decode each individual frame of the GIF. Can
 * be shared by one or more {@link GifDecoder}s to play the same animated GIF in multiple views.
 */
public class GifHeader {

  int[] gct = null;
  int status = GifDecoder.STATUS_OK;
  int frameCount = 0;

  GifFrame currentFrame;
  List<GifFrame> frames = new ArrayList<>();
  // Logical screen size.
  // Full image width.
  int width;
  // Full image height.
  int height;

  // 1 : global color table flag.
  boolean gctFlag;
  // 2-4 : color resolution.
  // 5 : gct sort flag.
  // 6-8 : gct size.
  int gctSize;
  // Background color index.
  int bgIndex;
  // Pixel aspect ratio.
  int pixelAspect;
  //TODO: this is set both during reading the header and while decoding frames...
  int bgColor;
  int loopCount = 0;

  public int getHeight() {
    return height;
  }

  public int getWidth() {
    return width;
  }

  public int getNumFrames() {
    return frameCount;
  }

  /**
   * Global status code of GIF data parsing.
   */
  public int getStatus() {
    return status;
  }
}

GifHeaderParser.java

/**
 * Copyright 2014 Google, Inc. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 * associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package com.lenovo.cava.widget.GifImageView;

import android.util.Log;

import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;

import static com.lenovo.cava.utils.Log.TAG_WITH_CLASS_NAME;
import static com.lenovo.cava.utils.Log.TAG_CAVA;

/**
 * A class responsible for creating {@link GifHeader}s from data
 * representing animated gifs.
 */
public class GifHeaderParser {
  public static final String TAG = TAG_WITH_CLASS_NAME ? "GifHeaderParser" : TAG_CAVA;

  // The minimum frame delay in hundredths of a second.
  static final int MIN_FRAME_DELAY = 2;
  // The default frame delay in hundredths of a second for GIFs with frame delays less than the
  // minimum.
  static final int DEFAULT_FRAME_DELAY = 10;

  private static final int MAX_BLOCK_SIZE = 256;
  // Raw data read working array.
  private final byte[] block = new byte[MAX_BLOCK_SIZE];

  private ByteBuffer rawData;
  private GifHeader header;
  private int blockSize = 0;

  public GifHeaderParser setData(ByteBuffer data) {
    reset();
    rawData = data.asReadOnlyBuffer();
    rawData.position(0);
    rawData.order(ByteOrder.LITTLE_ENDIAN);
    return this;
  }

  public GifHeaderParser setData(byte[] data) {
    if (data != null) {
      setData(ByteBuffer.wrap(data));
    } else {
      rawData = null;
      header.status = GifDecoder.STATUS_OPEN_ERROR;
    }
    return this;
  }

  public void clear() {
    rawData = null;
    header = null;
  }

  private void reset() {
    rawData = null;
    Arrays.fill(block, (byte) 0);
    header = new GifHeader();
    blockSize = 0;
  }

  public GifHeader parseHeader() {
    if (rawData == null) {
      throw new IllegalStateException("You must call setData() before parseHeader()");
    }
    if (err()) {
      return header;
    }

    readHeader();
    if (!err()) {
      readContents();
      if (header.frameCount < 0) {
        header.status = GifDecoder.STATUS_FORMAT_ERROR;
      }
    }

    return header;
  }

  /**
   * Determines if the GIF is animated by trying to read in the first 2 frames
   * This method reparses the data even if the header has already been read.
   */
  public boolean isAnimated() {
    readHeader();
    if (!err()) {
      readContents(2 /* maxFrames */);
    }
    return header.frameCount > 1;
  }

  /**
   * Main file parser. Reads GIF content blocks.
   */
  private void readContents() {
    readContents(Integer.MAX_VALUE /* maxFrames */);
  }

  /**
   * Main file parser. Reads GIF content blocks. Stops after reading maxFrames
   */
  private void readContents(int maxFrames) {
    // Read GIF file content blocks.
    boolean done = false;
    while (!(done || err() || header.frameCount > maxFrames)) {
      int code = read();
      switch (code) {
        // Image separator.
        case 0x2C:
          // The graphics control extension is optional, but will always come first if it exists.
          // If one did
          // exist, there will be a non-null current frame which we should use. However if one
          // did not exist,
          // the current frame will be null and we must create it here. See issue #134.
          if (header.currentFrame == null) {
            header.currentFrame = new GifFrame();
          }
          readBitmap();
          break;
        // Extension.
        case 0x21:
          code = read();
          switch (code) {
            // Graphics control extension.
            case 0xf9:
              // Start a new frame.
              header.currentFrame = new GifFrame();
              readGraphicControlExt();
              break;
            // Application extension.
            case 0xff:
              readBlock();
              String app = "";
              for (int i = 0; i < 11; i++) {
                app += (char) block[i];
              }
              if (app.equals("NETSCAPE2.0")) {
                readNetscapeExt();
              } else {
                // Don't care.
                skip();
              }
              break;
            // Comment extension.
            case 0xfe:
              skip();
              break;
            // Plain text extension.
            case 0x01:
              skip();
              break;
            // Uninteresting extension.
            default:
              skip();
          }
          break;
        // Terminator.
        case 0x3b:
          done = true;
          break;
        // Bad byte, but keep going and see what happens break;
        case 0x00:
        default:
          header.status = GifDecoder.STATUS_FORMAT_ERROR;
      }
    }
  }

  /**
   * Reads Graphics Control Extension values.
   */
  private void readGraphicControlExt() {
    // Block size.
    read();
    // Packed fields.
    int packed = read();
    // Disposal method.
    header.currentFrame.dispose = (packed & 0x1c) >> 2;
    if (header.currentFrame.dispose == 0) {
      // Elect to keep old image if discretionary.
      header.currentFrame.dispose = 1;
    }
    header.currentFrame.transparency = (packed & 1) != 0;
    // Delay in milliseconds.
    int delayInHundredthsOfASecond = readShort();
    // TODO: consider allowing -1 to indicate show forever.
    if (delayInHundredthsOfASecond < MIN_FRAME_DELAY) {
      delayInHundredthsOfASecond = DEFAULT_FRAME_DELAY;
    }
    header.currentFrame.delay = delayInHundredthsOfASecond * 10;
    // Transparent color index
    header.currentFrame.transIndex = read();
    // Block terminator
    read();
  }

  /**
   * Reads next frame image.
   */
  private void readBitmap() {
    // (sub)image position & size.
    header.currentFrame.ix = readShort();
    header.currentFrame.iy = readShort();
    header.currentFrame.iw = readShort();
    header.currentFrame.ih = readShort();

    int packed = read();
    // 1 - local color table flag interlace
    boolean lctFlag = (packed & 0x80) != 0;
    int lctSize = (int) Math.pow(2, (packed & 0x07) + 1);
    // 3 - sort flag
    // 4-5 - reserved lctSize = 2 << (packed & 7); // 6-8 - local color
    // table size
    header.currentFrame.interlace = (packed & 0x40) != 0;
    if (lctFlag) {
      // Read table.
      header.currentFrame.lct = readColorTable(lctSize);
    } else {
      // No local color table.
      header.currentFrame.lct = null;
    }

    // Save this as the decoding position pointer.
    header.currentFrame.bufferFrameStart = rawData.position();

    // False decode pixel data to advance buffer.
    skipImageData();

    if (err()) {
      return;
    }

    header.frameCount++;
    // Add image to frame.
    header.frames.add(header.currentFrame);
  }

  /**
   * Reads Netscape extension to obtain iteration count.
   */
  private void readNetscapeExt() {
    do {
      readBlock();
      if (block[0] == 1) {
        // Loop count sub-block.
        int b1 = ((int) block[1]) & 0xff;
        int b2 = ((int) block[2]) & 0xff;
        header.loopCount = (b2 << 8) | b1;
        if(header.loopCount == 0) {
          header.loopCount = GifDecoder.LOOP_FOREVER;
        }
      }
    } while ((blockSize > 0) && !err());
  }


  /**
   * Reads GIF file header information.
   */
  private void readHeader() {
    String id = "";
    for (int i = 0; i < 6; i++) {
      id += (char) read();
    }
    if (!id.startsWith("GIF")) {
      header.status = GifDecoder.STATUS_FORMAT_ERROR;
      return;
    }
    readLSD();
    if (header.gctFlag && !err()) {
      header.gct = readColorTable(header.gctSize);
      header.bgColor = header.gct[header.bgIndex];
    }
  }

  /**
   * Reads Logical Screen Descriptor.
   */
  private void readLSD() {
    // Logical screen size.
    header.width = readShort();
    header.height = readShort();
    // Packed fields
    int packed = read();
    // 1 : global color table flag.
    header.gctFlag = (packed & 0x80) != 0;
    // 2-4 : color resolution.
    // 5 : gct sort flag.
    // 6-8 : gct size.
    header.gctSize = 2 << (packed & 7);
    // Background color index.
    header.bgIndex = read();
    // Pixel aspect ratio
    header.pixelAspect = read();
  }

  /**
   * Reads color table as 256 RGB integer values.
   *
   * @param ncolors int number of colors to read.
   * @return int array containing 256 colors (packed ARGB with full alpha).
   */
  private int[] readColorTable(int ncolors) {
    int nbytes = 3 * ncolors;
    int[] tab = null;
    byte[] c = new byte[nbytes];

    try {
      rawData.get(c);

      // TODO: what bounds checks are we avoiding if we know the number of colors?
      // Max size to avoid bounds checks.
      tab = new int[MAX_BLOCK_SIZE];
      int i = 0;
      int j = 0;
      while (i < ncolors) {
        int r = ((int) c[j++]) & 0xff;
        int g = ((int) c[j++]) & 0xff;
        int b = ((int) c[j++]) & 0xff;
        tab[i++] = 0xff000000 | (r << 16) | (g << 8) | b;
      }
    } catch (BufferUnderflowException e) {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "Format Error Reading Color Table", e);
      }
      header.status = GifDecoder.STATUS_FORMAT_ERROR;
    }

    return tab;
  }

  /**
   * Skips LZW image data for a single frame to advance buffer.
   */
  private void skipImageData() {
    // lzwMinCodeSize
    read();
    // data sub-blocks
    skip();
  }

  /**
   * Skips variable length blocks up to and including next zero length block.
   */
  private void skip() {
    try {
      int blockSize;
      do {
        blockSize = read();
        rawData.position(rawData.position() + blockSize);
      } while (blockSize > 0);
    } catch (IllegalArgumentException ex) {
    }
  }

  /**
   * Reads next variable length block from input.
   *
   * @return number of bytes stored in "buffer"
   */
  private int readBlock() {
    blockSize = read();
    int n = 0;
    if (blockSize > 0) {
      int count = 0;
      try {
        while (n < blockSize) {
          count = blockSize - n;
          rawData.get(block, n, count);

          n += count;
        }
      } catch (Exception e) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
          Log.d(TAG,
              "Error Reading Block n: " + n + " count: " + count + " blockSize: " + blockSize, e);
        }
        header.status = GifDecoder.STATUS_FORMAT_ERROR;
      }
    }
    return n;
  }

  /**
   * Reads a single byte from the input stream.
   */
  private int read() {
    int curByte = 0;
    try {
      curByte = rawData.get() & 0xFF;
    } catch (Exception e) {
      header.status = GifDecoder.STATUS_FORMAT_ERROR;
    }
    return curByte;
  }

  /**
   * Reads next 16-bit value, LSB first.
   */
  private int readShort() {
    // Read 16-bit value.
    return rawData.getShort();
  }

  private boolean err() {
    return header.status != GifDecoder.STATUS_OK;
  }
}

GifImageView.java

package com.lenovo.cava.widget.GifImageView;

import android.content.Context;
import android.graphics.Bitmap;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.ImageView;

import static com.lenovo.cava.utils.Log.TAG_WITH_CLASS_NAME;
import static com.lenovo.cava.utils.Log.TAG_CAVA;

public class GifImageView extends ImageView implements Runnable {

  private static final String TAG = TAG_WITH_CLASS_NAME ? "GifDecoderView" : TAG_CAVA;
  private GifDecoder gifDecoder;
  private Bitmap tmpBitmap;
  private final Handler handler = new Handler(Looper.getMainLooper());
  private boolean animating;
  private boolean renderFrame;
  private boolean shouldClear;
  private Thread animationThread;
  private OnFrameAvailable frameCallback = null;
  private long framesDisplayDuration = -1L;
  private OnAnimationStop animationStopCallback = null;
  private OnAnimationStart animationStartCallback = null;

  private final Runnable updateResults = new Runnable() {
    @Override
    public void run() {
      if (tmpBitmap != null && !tmpBitmap.isRecycled()) {
        setImageBitmap(tmpBitmap);
      }
    }
  };

  private final Runnable cleanupRunnable = new Runnable() {
    @Override
    public void run() {
      if(gifDecoder != null) {
        gifDecoder.clear();
      }
      tmpBitmap = null;
      gifDecoder = null;
      shouldClear = false;
    }
  };

  public GifImageView(final Context context, final AttributeSet attrs) {
    super(context, attrs);
  }

  public GifImageView(final Context context) {
    super(context);
  }

  public void setBytes(final byte[] bytes) {
    gifDecoder = new GifDecoder();
    try {
      gifDecoder.read(bytes);
    } catch (final Exception e) {
      gifDecoder = null;
      Log.e(TAG, e.getMessage(), e);
      return;
    }

    if(animating){
      startAnimationThread();
    } else {
      gotoFrame(0);
    }
  }

  public long getFramesDisplayDuration() {
    return framesDisplayDuration;
  }

  /**
   * Sets custom display duration in milliseconds for the all frames. Should be called before {@link
   * #startAnimation()}
   *
   * @param framesDisplayDuration Duration in milliseconds. Default value = -1, this property will
   *                              be ignored and default delay from gif file will be used.
   */
  public void setFramesDisplayDuration(long framesDisplayDuration) {
    this.framesDisplayDuration = framesDisplayDuration;
  }

  public void startAnimation() {
    animating = true;
    startAnimationThread();
  }

  public boolean isAnimating() {
    return animating;
  }

  public void stopAnimation() {
    animating = false;

    if (animationThread != null) {
      animationThread.interrupt();
      animationThread = null;
    }
    sleep(200);
  }

  private void sleep(long time){
      try {
          Thread.sleep(time);
      } catch (InterruptedException e) {
          e.printStackTrace();
      }
  }

  public void gotoFrame(int frame){
    if(gifDecoder.getCurrentFrameIndex() == frame) return;
    if(gifDecoder.setFrameIndex(frame-1) && !animating){
      renderFrame = true;
      startAnimationThread();
    }
  }

  public void resetAnimation(){
    gifDecoder.resetLoopIndex();
    gotoFrame(0);
  }

  public void clear() {
    animating = false;
    renderFrame = false;
    shouldClear = true;
    stopAnimation();
    handler.post(cleanupRunnable);
  }

  public boolean canStart() {
    return (animating || renderFrame) && gifDecoder != null && animationThread == null;
  }

  public int getGifWidth() {
    return gifDecoder.getWidth();
  }

  public int getGifHeight() {
    return gifDecoder.getHeight();
  }

  @Override public void run() {
    if (animationStartCallback != null) {
      animationStartCallback.onAnimationStart();
    }
    boolean callBack = true;
    do {
      callBack = true;
      if (!animating && !renderFrame) {
        callBack = false;
        break;
      }
        long frameDecodeTime = 0;
        long before = System.nanoTime();
        boolean advance = false;
        try {
            advance = gifDecoder.advance();
            //milliseconds spent on frame decode


            tmpBitmap = gifDecoder.getNextFrame();
            if (frameCallback != null) {
            tmpBitmap = frameCallback.onFrameAvailable(tmpBitmap);
            }
            frameDecodeTime = (System.nanoTime() - before) / 1000000;
            handler.post(updateResults);
        } catch (Exception e) {
            advance = false;
            callBack = false;
            Log.w(TAG, e);
        }

      renderFrame = false;
      if (!animating || !advance) {
        animating = false;
        break;
      }
      try {
        int delay = gifDecoder.getNextDelay();
        // Sleep for frame duration minus time already spent on frame decode
        // Actually we need next frame decode duration here,
        // but I use previous frame time to make code more readable
        delay -= frameDecodeTime;
        if (delay > 0) {
          Thread.sleep(framesDisplayDuration > 0 ? framesDisplayDuration : delay);
        }
      } catch (final Exception e) {
        // suppress exception
        break;
      }
    } while (animating);

    if (shouldClear) {
      handler.post(cleanupRunnable);
    }
    animationThread = null;

    if (animationStopCallback != null && callBack) {
      animationStopCallback.onAnimationStop();
    }
  }

  public OnFrameAvailable getOnFrameAvailable() {
    return frameCallback;
  }

  public void setOnFrameAvailable(OnFrameAvailable frameProcessor) {
    this.frameCallback = frameProcessor;
  }

  public interface OnFrameAvailable {
    Bitmap onFrameAvailable(Bitmap bitmap);
  }

  public OnAnimationStop getOnAnimationStop() {
    return animationStopCallback;
  }

  public void setOnAnimationStop(OnAnimationStop animationStop) {
    this.animationStopCallback = animationStop;
  }

  public void setOnAnimationStart(OnAnimationStart animationStart) {
    this.animationStartCallback = animationStart;
  }

  public interface OnAnimationStop {
    void onAnimationStop();
  }

  public interface OnAnimationStart {
    void onAnimationStart();
  }

  @Override
  protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    clear();
  }

  private void startAnimationThread() {
    if (canStart()) {
      animationThread = new Thread(this);
      animationThread.start();
    }
  }
}

SimpleBitmapProvider.java

package com.lenovo.cava.widget.GifImageView;

import android.graphics.Bitmap;
import android.support.annotation.NonNull;

final class SimpleBitmapProvider implements GifDecoder.BitmapProvider {
  @NonNull
  @Override public Bitmap obtain(int width, int height, Bitmap.Config config) {
    return Bitmap.createBitmap(width, height, config);
  }

  @Override public void release(Bitmap bitmap) {
    bitmap.recycle();
  }

  @Override public byte[] obtainByteArray(int size) {
    return new byte[size];
  }

  @Override public void release(byte[] bytes) {
    // no-op
  }

  @Override public int[] obtainIntArray(int size) {
    return new int[size];
  }

  @Override public void release(int[] array) {
    // no-op
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值