android 开发 解码gif图片,获取每帧bitmap
环境:android 4.3 (注意对于android4.4版本解码出来不正确,除了第一帧正确外,其余的都是显示不同的地方) 通用版本见: android 开发对gif解码(适配android 4.2、4.3、4.4版本)
使用方法:
void showGif2() { gifDecoder = new GifImageDecoder(); try { gifDecoder.read(this.getResources().openRawResource(R.drawable.b17)); //这是Gif图片资源 int size =gifDecoder.getFrameCount(); for(int i=0;i<size;i++) { ImageView iv_image = new ImageView(CustomActivity.this); iv_image.setPadding(5, 5, 5, 5); LayoutParams lparams = new LayoutParams(100,100); iv_image.setLayoutParams(lparams); iv_image.setImageBitmap(gifDecoder.getFrame(i)); ll_decodeimages.addView(iv_image); // gifFrame.nextFrame(); } } catch (NotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
见效果:
全部代码:
package com.xlm.testgif; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.app.Activity; import android.content.Context; import android.content.res.Resources.NotFoundException; import android.graphics.Bitmap; import android.view.Menu; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.LinearLayout.LayoutParams; public class CustomActivity extends Activity { LinearLayout ll_decodeimages; // GifFrame gifFrame = null; GifImageDecoder gifDecoder; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_custom); ll_decodeimages = (LinearLayout) findViewById(R.id.ll_decodeimages); showGif2(); } // void showGif() // { // // 解析gif动画 // gifFrame = GifFrame.createGifImage(fileConnect(this.getResources().openRawResource(R.drawable.b10))); // int size = gifFrame.size(); // for(int i=0;i<size;i++) // { // // ImageView iv_image = new ImageView(CustomActivity.this); // iv_image.setPadding(5, 5, 5, 5); // LayoutParams lparams = new LayoutParams(100,100); // iv_image.setLayoutParams(lparams); // iv_image.setImageBitmap(gifFrame.getImage()); // ll_decodeimages.addView(iv_image); // gifFrame.nextFrame(); // } // } void showGif2() { gifDecoder = new GifImageDecoder(); try { gifDecoder.read(this.getResources().openRawResource(R.drawable.b17)); int size =gifDecoder.getFrameCount(); for(int i=0;i<size;i++) { ImageView iv_image = new ImageView(CustomActivity.this); iv_image.setPadding(5, 5, 5, 5); LayoutParams lparams = new LayoutParams(100,100); iv_image.setLayoutParams(lparams); iv_image.setImageBitmap(gifDecoder.getFrame(i)); ll_decodeimages.addView(iv_image); // gifFrame.nextFrame(); } } catch (NotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } // Handler handler = new Handler() // { // // @Override // public void handleMessage(Message msg) { // switch(msg.what) // { // case 1: // Bitmap bmp = (Bitmap) msg.obj; // if(bmp!=null) // { // ImageView iv_image = new ImageView(CustomActivity.this); // iv_image.setImageBitmap(bmp); // ll_decodeimages.addView(iv_image); // } // break; // } // } // // }; /** * 独立线程解码gif图片 * @author huqiang * */ // class DecodeGif implements Runnable // { // Context mContext ; // Handler mHandler; // public DecodeGif(Context context,Handler handler) // { // this.mContext = context; // this.mHandler = handler; // } // @Override // public void run() { // //开始解析gif // // 解析gif动画 //// gifFrame = GifFrame.createGifImage(fileConnect(this.getResources().openRawResource(R.drawable.test))); // } // public void start() // { // new Thread(this).start(); // } // } // 读取文件 public byte[] fileConnect(InputStream is) { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); int ch = 0; while ((ch = is.read()) != -1) { baos.write(ch); } byte[] b = baos.toByteArray(); baos.close(); baos = null; is.close(); is = null; return b; } catch (Exception e) { return null; } } }
package com.xlm.testgif; import android.annotation.SuppressLint; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; public class GifImageDecoder { private static final String TAG = GifImageDecoder.class.getSimpleName(); private final GifImageDecoder self = this; // File read status: No errors. public static final int STATUS_OK = 0; // File read status: Error decoding file (may be partially decoded) public static final int STATUS_FORMAT_ERROR = 1; // File read status: Unable to open source. public static final int STATUS_OPEN_ERROR = 2; // Trailer private static final byte TRR_CODE = (byte) 0x3B; // Image Block private static final byte IMG_CODE = (byte) 0x2C; // Extension private static final byte EXT_CODE = (byte) 0x21; // Graphic Control Extension private static final byte GC_EXT = (byte) 0xF9; // Application Extension private static final byte APP_EXT = (byte) 0xFF; // Comment Extension private static final byte CMT_EXT = (byte) 0xFE; // Plain Text Extension private static final byte TXT_EXT = (byte) 0x01; private static final int MIN_DELAY = 100; private static final int MIN_DELAY_ENFORCE_THRESHOLD = 20; protected int mStatus; protected int mWidth; // full mCurrentImage mWidth protected int mHeight; // full mCurrentImage mHeight protected Bitmap mCurrentImage; // current frame protected Bitmap mLastImage; // previous frame protected int mDispose = 0; // 0=no action; 1=leave in place; 2=restore to bg; 3=restore to prev protected int mLastDispose = 0; protected int mDelay = 0; // mDelay in milliseconds protected ArrayList<GifFrame> mGifFrames; // mGifFrames read from current file protected int mFrameCount; private int mOffset = 0; private GifHeader mGifHeader; private GraphicControlExtension mGcExt; private ImageBlock mImageBlock; private static class GifFrame { public GifFrame(Bitmap im, int del) { image = im; delay = del; } public Bitmap image; public int delay; } /** * Gets display duration for specified frame. * * @param n int index of frame * @return delay in milliseconds */ public int getDelay(int n) { mDelay = -1; if ((n >= 0) && (n < mFrameCount)) { mDelay = mGifFrames.get(n).delay; if (mDelay < MIN_DELAY_ENFORCE_THRESHOLD) { mDelay = MIN_DELAY; } } return mDelay; } /** * Gets the number of GifFrames read from file. * * @return frame count */ public int getFrameCount() { return mFrameCount; } /** * Gets the first (or only) image read. * * @return BufferedBitmap containing first frame, or null if none. */ public Bitmap getBitmap() { return getFrame(0); } /** * Gets the image contents of frame n. * * @return BufferedBitmap representation of frame, or null if n is invalid. */ public Bitmap getFrame(int n) { if (mFrameCount <= 0) return null; n = n % mFrameCount; return (mGifFrames.get(n)).image; } /** * Reads GIF image from stream * * @param is containing GIF file. * @return read status code (0 = no errors) */ public int read(InputStream is) throws IOException { init(); if (is != null) { byte[] buffer = Utils.streamToBytes(is); mGifHeader = new GifHeader(buffer, mOffset); mOffset += mGifHeader.size; mWidth = mGifHeader.getWidth(); mHeight = mGifHeader.getHeight(); if (!mGifHeader.getSignature().equals("GIF")) { return STATUS_FORMAT_ERROR; } while (buffer[mOffset] != TRR_CODE) { if (buffer[mOffset] == IMG_CODE) { // ImageBlock mImageBlock = new ImageBlock(buffer, mOffset); mOffset += mImageBlock.size; mFrameCount++; // create new image to receive frame data mCurrentImage = extractImage(); if (mLastDispose > 0) { if (mLastDispose == 3) { // use image before last int n = mFrameCount - 2; if (n > 0) { mLastImage = getFrame(n - 1); } else { mLastImage = null; } } } mGifFrames.add(new GifFrame(mCurrentImage, mDelay)); // add image to frame resetFrame(); } else if (buffer[mOffset] == EXT_CODE) { if (buffer[mOffset + 1] == GC_EXT) { //GraphicControlExtension mGcExt = new GraphicControlExtension(buffer, mOffset); mOffset += mGcExt.size; mDispose = mGcExt.getDisposalMothod(); // disposal method if (mDispose == 0) { mDispose = 1; // elect to keep old image if discretionary } mDelay = mGcExt.getDelayTime() * 10; // delay in milliseconds } else if (buffer[mOffset + 1] == APP_EXT) { //ApplicationExtension ApplicationExtension appExt = new ApplicationExtension(buffer, mOffset); mOffset += appExt.size; } else if (buffer[mOffset + 1] == CMT_EXT) { //CommentExtension CommentExtension cmtExt = new CommentExtension(buffer, mOffset); mOffset += cmtExt.size; } else if (buffer[mOffset + 1] == TXT_EXT) { //PlainTextExtension PlainTextExtension txtExt = new PlainTextExtension(buffer, mOffset); mOffset += txtExt.size; } else { throw new IOException(); } } else { throw new IOException(); } } } else { mStatus = STATUS_OPEN_ERROR; } return mStatus; } /** * Initializes or re-initializes reader */ protected void init() { mStatus = STATUS_OK; mFrameCount = 0; mGifFrames = new ArrayList<GifFrame>(); } /** * Resets frame state for reading next image. */ protected void resetFrame() { mLastDispose = mDispose; mLastImage = mCurrentImage; mDispose = 0; mDelay = 0; } /** * Extract new image * * @return image */ @SuppressLint("NewApi") private Bitmap extractImage() { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try { outputStream.write(mGifHeader.bytes); if (mGcExt != null) { if ((mWidth != mImageBlock.getImageWidth() || mHeight != mImageBlock.getImageHeight()) && mGcExt.getTransparentColorFlag() == 0) { mGcExt.setTransparentColorFlagTrue(); } outputStream.write(mGcExt.bytes); } outputStream.write(mImageBlock.bytes); outputStream.write((byte) 0x3B); outputStream.flush(); BitmapFactory.Options options = new BitmapFactory.Options(); options.inMutable = true; options.inPreferredConfig = Bitmap.Config.RGB_565; Bitmap newBitmap = BitmapFactory.decodeStream(new BufferedInputStream(new ByteArrayInputStream(outputStream.toByteArray()))); if (newBitmap != null) { if (mLastImage == null) { return newBitmap; } else { Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.RGB_565); Canvas canvas = new Canvas(bitmap); canvas.drawBitmap(mLastImage, 0, 0, null); canvas.drawBitmap(newBitmap, 0, 0, null); return bitmap; } } else { if (mLastImage != null) { return mLastImage; } } } catch (IOException e) { e.printStackTrace(); } finally { try { outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } return null; } private class GifHeader { public byte[] bytes; public int size; public GifHeader(byte[] bytes, int offset) { boolean globalColorTableFlag = (bytes[offset + 0x0A] & 0x80) != 0x00; int globalColorTableSize = (bytes[offset + 0x0A] & 0x07); // get size size = 0x0D; if (globalColorTableFlag) { size += Math.pow(2, (globalColorTableSize + 1)) * 3; } this.bytes = new byte[size]; System.arraycopy(bytes, offset, this.bytes, 0, size); } public String getSignature() { return new String(bytes, 0, 3); } public String getVersion() { return new String(bytes, 3, 3); } public int getWidth() { return (bytes[6] & 0xFF) + ((bytes[7] & 0xFF) << 8); } public int getHeight() { return (bytes[8] & 0xFF) + ((bytes[9] & 0xFF) << 8); } public int getGlobalColorTableFlag() { return (bytes[10] & 0x80) >> 7; } public int getColorResolution() { return (bytes[10] & 0x70) >> 4; } public int getSortFlag() { return (bytes[10] & 0x08) >> 3; } public int getSizeOfGlobalColorTable() { return (bytes[10] & 0x07); } public int getBackgroundColorIndex() { return bytes[11] & 0xFF; } public int getPixelAspectRatio() { return bytes[12]; } public int[] getGlobalColorTable() { if (getGlobalColorTableFlag() == 0) { return new int[0]; } int[] colors = new int[(int) Math.pow(2, getSizeOfGlobalColorTable() + 1)]; for (int i = 0; i < colors.length; i++) { colors[i] = ((bytes[13 + (i * 3)] & 0xFF) << 16) + ((bytes[13 + (i * 3) + 1] & 0xFF) << 8) + (bytes[13 + (i * 3) + 2] & 0xFF); } return colors; } } private class ImageBlock { public byte[] bytes; public int size; public ImageBlock(byte[] bytes, int offset) { int blockSize; boolean localColorTableFlag = (bytes[offset + 0x09] & 0x80) != 0x00; int localColorTableSize = (bytes[offset + 0x09] & 0x07); //get size size = 0x0A; if (localColorTableFlag) { size += Math.pow(2, (localColorTableSize + 1)) * 3; } size += 1; //LZW Minimum Code Size //ImageData blockSize = bytes[offset + size] & 0xFF; size += 1; while (blockSize != 0x00) { size += blockSize; blockSize = bytes[offset + size] & 0xFF; size += 1; } this.bytes = new byte[size]; System.arraycopy(bytes, offset, this.bytes, 0, size); } public int getImageSeparator() { return bytes[0] & 0xFF; } public int ImageLeftPosition() { return (bytes[1] & 0xFF) + ((bytes[2] & 0xFF) << 8); } public int getImageTopPosition() { return (bytes[3] & 0xFF) + ((bytes[4] & 0xFF) << 8); } public int getImageWidth() { return (bytes[5] & 0xFF) + ((bytes[6] & 0xFF) << 8); } public int getImageHeight() { return (bytes[7] & 0xFF) + ((bytes[8] & 0xFF) << 8); } public int getLocalColorTableFlag() { return (bytes[9] & 0x80) >> 7; } public int getInterlaceFlag() { return (bytes[9] & 0x40) >> 6; } public int getSortFlag() { return (bytes[9] & 0x20) >> 5; } public int getReserved() { return (bytes[9] & 0x18) >> 2; } public int getSizeOfLocalColorTable() { return bytes[9] & 0x03; } public int[] getLocalColorTable() { if (getLocalColorTableFlag() == 0) { return new int[0]; } int[] colors = new int[(int) Math.pow(2, getSizeOfLocalColorTable() + 1)]; for (int i = 0; i < colors.length; i++) { colors[i] = ((bytes[10 + (i * 3)] & 0xFF) << 16) + ((bytes[10 + (i * 3) + 1] & 0xFF) << 8) + (bytes[10 + (i * 3) + 2] & 0xFF); } return colors; } public int getLZWMinimumCodeSize() { if (getLocalColorTableFlag() == 0) { return bytes[10] & 0xFF; } else { return bytes[10 + (int) Math.pow(2, getSizeOfLocalColorTable() + 1) * 3] & 0xFF; } } } private class ApplicationExtension { public byte[] bytes; public int size; public ApplicationExtension(byte[] bytes, int offset) { int blockSize; // get size size = 0x0E; blockSize = bytes[offset + size] & 0xFF; size += 1; while (blockSize != 0x00) { size += blockSize; blockSize = bytes[offset + size] & 0xFF; size += 1; } this.bytes = new byte[size]; System.arraycopy(bytes, offset, this.bytes, 0, size); } public int getExtensionIntroducer() { return bytes[0] & 0xFF; } public int getExtensionLabel() { return bytes[1] & 0xFF; } public int getBlockSize1() { return bytes[2] & 0xFF; } public String getApplicationIdentifier() { return new String(bytes, 3, 8); } public String getApplicationAuthenticationCode() { return new String(bytes, 11, 3); } } private class GraphicControlExtension { public byte[] bytes; public int size; public GraphicControlExtension(byte[] bytes, int offset) { size = 8; this.bytes = new byte[size]; System.arraycopy(bytes, offset, this.bytes, 0, size); } public int getExtensionIntroducer() { return bytes[0] & 0xFF; } public int getGraphicControlLabel() { return bytes[1] & 0xFF; } public int getBlockSize() { return bytes[2] & 0xFF; } public int getReserved() { return (bytes[3] & 0xE0) >> 5; } public int getDisposalMothod() { return (bytes[3] & 0x1C) >> 2; } public int getUserInputFlag() { return (bytes[3] & 0x02) >> 1; } public int getTransparentColorFlag() { return (bytes[3] & 0x01); } public int getDelayTime() { return (bytes[4] & 0xFF) + ((bytes[5] & 0xFF) << 8); } public int getTransparentColorIndex() { return bytes[6]; } public void setTransparentColorFlagTrue() { int value = getReserved() | getDisposalMothod() | getUserInputFlag() | 0x01; bytes[3] = (byte) Integer.parseInt(Utils.toHex(value, 2), 16); } } private class CommentExtension { public byte[] bytes; public int size; public CommentExtension(byte[] bytes, int offset) { int blockSize; // get size size = 0x02; blockSize = bytes[offset + size] & 0xFF; size += 1; while (blockSize != 0x00) { size += blockSize; blockSize = bytes[offset + size] & 0xFF; size += 1; } this.bytes = new byte[size]; System.arraycopy(bytes, offset, this.bytes, 0, size); } } private class PlainTextExtension { public byte[] bytes; public int size; public PlainTextExtension(byte[] bytes, int offset) { int blockSize; // get size size = 0x0F; blockSize = bytes[offset + size] & 0xFF; size += 1; while (blockSize != 0x00) { size += blockSize; blockSize = bytes[offset + size] & 0xFF; size += 1; } this.bytes = new byte[size]; System.arraycopy(bytes, offset, this.bytes, 0, size); } } }
import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; public class Utils { public static String toHex(int value, int length) { String hex = Integer.toHexString(value); hex = hex.toUpperCase(); if (hex.length() < length) { while (hex.length() < length) hex = "0" + hex; } else if (hex.length() > length) { hex = hex.substring(hex.length() - length); } return hex; } public static byte[] streamToBytes(InputStream stream) throws IOException, OutOfMemoryError { byte[] buff = new byte[1024]; int read; ByteArrayOutputStream bao = new ByteArrayOutputStream(); while ((read = stream.read(buff)) != -1) { bao.write(buff, 0, read); } try { stream.close(); } catch (IOException e) { e.printStackTrace(); } return bao.toByteArray(); } }