Tuesday, April 1, 2014

Splitting GIF into frames on Android via giflib

PREAMBLE: the app where this technique originated is no longer using it. I've integrated GifFileDecoder by Google and never looked back. Had to patch it somewhat, though - my GIFs are small, makes more sense to read them into memory rather than display progressively.

UPDATE: it's now a Gist.

This is a followup to my answer at StackOverflow regarding animated GIFs on Android. Folks want code - I've got some. The general idea is - use giflib to get RGB(A) pixel data for each frame in a format that's compatible with Android's, feed the pixels to bitmaps, display the bitmaps.

Naturally, a starting point is a Java Android project with a native library in it. The first step is including several files from giflib 4.1.4. They're sitting in an archive, attached to a comment on a Gist. Also in that Gist there is a native wrapper called MyClass.cpp. Unzip the archive into your native library's folder, and list the following sources in the Android.mk:
  • dgif_lib.c
  • gif_err.c
  • gifalloc.c
  • MyClass.cpp
Also, insert the following line into Android.mk: 


LOCAL_CFLAGS := -D HAVE_SYS_STAT_H -D HAVE_SYS_TYPES_H -D HAVE_FCNTL_H -D HAVE_INTTYPES_H -D HAVE_UNISTD_H -D HAVE_STDLIB_H -D HAVE_STDINT_H -D UINT32=uint32_t 


Without that, giflib won't compile. Finally, rename the Java_..._LoadGIF function in MyClass.cpp to match your package and class. The function LoadGIF() logically belongs to class MyClass; we'll discuss its Java side later. LoadGIF takes two arguments - a local file name for the GIF file (not a URL!), and a boolean called HighColor that specifies the generated pixel format. With HighColor=false, it generates ARGB_4444 bitmaps; with true, it's ARGB_8888.

ARGB_4444 used to work for me for a while, but then it was phased out in recent versions of Android, so go with high color, unless you're targeting old, low memory devices.

Now to the Java world. There are two classes there - MovieView and the second one, the one I've called MyClass for genericity's sake. Feel free to rename.

MovieView is in the same Gist.It's fairly simple; it just displays bitmaps driven by a timer. It's a view, so it can be placed into a layout file.

MyClass is where the action takes place. In my project, it's a subclass of Dialog and it's quite involved. The key part is that it holds a reference to a MovieView instance and feeds a GIF to it. I'll paste just the relevant parts here:

class MyClass
{
    private MovieView m_mv; //initialized on loading

    private static s_bHighColor = true;
    //Format; hard-coded here
    
    private native boolean LoadGIF(String FileName, boolean bHighColor); 

    //loadLibrary() is called elsewhere
    //Called from JNI - do not mess with it.
    private void AddFrame(int Delay, int l, int t, int w, int h, int Disp, byte [] Bits)
    {
        Bitmap bm = Bitmap.createBitmap(w, h,

        s_bHighColor ?
            Bitmap.Config.ARGB_8888 :
            Bitmap.Config.ARGB_4444);
        bm.copyPixelsFromBuffer(ByteBuffer.wrap(Bits));
        m_mv.AddFrame(bm, l, t, Delay, Disp);
    }

    //Called from JNI - do not mess with it.
    private void StartImage(int FrameCount)
    {
        m_mv.Reset(FrameCount);
    }

//////////////////////////////// The animation starts here
    public void StartMovie(File f)
    {
        if(LoadGIF(f.getAbsolutePath()
, s_bHighColor))
        //This will call Reset(), AddFrames()
            m_mv.Start();
    }




The flow is: you call MyClass.StartMovie(). StartMovie() calls LoadGIF() which calls StartImage() once and AddFrame() in a loop. If everything goes fine, then MovieView.Start() is invoked.

To terminate the animation, call MovieView.Stop(). This code assumes infinite loop, but feel free to follow the Disposition parameter from the GIF.

The official home of giflib 4.1.4 is here at SourceForge.

3 comments:

  1. Hi. I have used your code and find that the bitmap color is wrong. I set the s_bHighColor to true.

    ReplyDelete
  2. I find out why. In Strokes.cpp, line 77 to 79, I changed to c.Blue << 16 | c.Green << 8| c.Red (I omit (unsigned int) here), and the bitmap color is right. But I don't know why. Now the order Alpha, Blue, Green, Red (ABGR) makes right for ARGB.8888. Wired..

    ReplyDelete
  3. Weird indeed. My own project would only deal with greyscale images, so this was not an issue I'd face.

    ReplyDelete