Skip to content

Instantly share code, notes, and snippets.

@sevaa
Last active April 3, 2021 03:47
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sevaa/2d543fd9a5bc8582548a2a0aae3644e8 to your computer and use it in GitHub Desktop.
Save sevaa/2d543fd9a5bc8582548a2a0aae3644e8 to your computer and use it in GitHub Desktop.
Animated GIF support on Android.
package com.mypackage;
import java.util.ArrayList;
import java.util.Date;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.View;
//Shows bitmaps on timer. Can be a part of an Android layout.
public class MovieView extends View
{
long m_ts;
Handler m_h = new Handler();
Runnable m_Inval = new Runnable()
{
public void run()
{
invalidate();
}
};
static class Frame
{
private Bitmap bm;
private int l, t;
private int Delay;
@SuppressWarnings("unused")
private int Disp;
}
private ArrayList<Frame> m_Frames = null;
private int m_Pos;
private Paint m_Paint = new Paint();
private Canvas m_Canvas;
private Bitmap m_Surface;
private Rect m_SrcRc, m_DestRc;
public MovieView(Context Ctxt)
{
super(Ctxt);
}
public MovieView(Context Ctxt, AttributeSet a)
{
super(Ctxt, a);
}
@Override
public void draw(Canvas c)
{
super.draw(c);
if(m_Frames != null)
{
Frame f = m_Frames.get(m_Pos);
m_Canvas.drawBitmap(f.bm, f.l, f.t, m_Paint);
c.drawBitmap(m_Surface, m_SrcRc, m_DestRc, m_Paint);
m_Pos = (m_Pos + 1) % m_Frames.size();
if(f.Delay > 0)
{
m_h.removeCallbacks(m_Inval);
m_h.postDelayed(m_Inval, f.Delay);
}
else
invalidate();
}
}
public void Reset(int n)
{
m_Frames = new ArrayList<Frame>(n);
}
public void AddFrame(Bitmap bm, int l, int t, int Delay, int Disp)
{
Frame f = new Frame();
f.bm = bm;
f.Delay = Delay;
f.Disp = Disp;
f.l = l;
f.t = t;
m_Frames.add(f);
}
//Assumes that the frames have been built
public void Start()
{
m_Pos = 0;
Bitmap bm = m_Frames.get(0).bm; //First frame
m_Surface = bm.copy(bm.getConfig(), true);
m_Canvas = new Canvas(m_Surface);
int bw, bh;
m_SrcRc = new Rect(0, 0, (bw = m_Surface.getWidth())-1, (bh = m_Surface.getHeight())-1);
int w = getWidth(), h = getHeight();
//Letterboxing logic conditional on large size???
if(w*bh > bw*h) //Needs letterboxing on width
{
int dx = (w - (h*bw)/bh)/2;
m_DestRc = new Rect(dx, 0, w-2*dx - 1, h-1);
}
else
{
int dy = (h - (w*bh)/bw)/2;
m_DestRc = new Rect(0, dy, w-1, h-2*dy - 1);
}
m_ts = new Date().getTime();
invalidate();
}
public void Stop()
{
m_Frames = null;
m_Surface = null;
m_Canvas = null;
m_h.removeCallbacks(m_Inval);
invalidate();
}
}
#include <jni.h>
#include "gif_lib.h"
//Native counterpart to the MyClass class
//Requires giflib sources and headers! They're in the archive that's attached to the comment below.
extern "C"
{
//Replace "com_mypackage_MyClass" with your own package/class
jboolean JNIEXPORT Java_com_mypackage_MyClass_LoadGIF(JNIEnv *jni, jobject self, jstring jfile, jboolean bHighColor)
{
const char *File = jni->GetStringUTFChars(jfile, 0);
GifFileType *g = DGifOpenFileName(File);
jni->ReleaseStringUTFChars(jfile, File);
if (!g)
return JNI_FALSE;
if (DGifSlurp(g) != GIF_OK)
{
DGifCloseFile(g);
return JNI_FALSE;
}
jclass cl = jni->GetObjectClass(self);
jni->CallVoidMethod(self, jni->GetMethodID(cl, "StartImage", "(I)V"), (jint)g->ImageCount);
jmethodID afmid = jni->GetMethodID(cl, "AddFrame", "(IIIIII[B)V");
if (!afmid)
{
DGifCloseFile(g);
return JNI_FALSE;
}
int i, Frame;
unsigned int Colors[256];
//TODO: don't rebuild colors if there's a global color table only
for (Frame = 0; Frame<g->ImageCount; Frame++)
{
SavedImage &si = g->SavedImages[Frame];
int Delay = 0;
int TransColor = -1;
jint Disp = 0;
for (i = 0; i<si.ExtensionBlockCount; i++)
{
ExtensionBlock &eb = si.ExtensionBlocks[i];
if (eb.Function == 0xf9) //Animation!
{
char c = eb.Bytes[0];
Delay = (int)(unsigned char)(eb.Bytes[1]) | ((int)(unsigned char)(eb.Bytes[2]) << 8);
if (c & 1)
TransColor = (unsigned char)(eb.Bytes[3]);
Disp = (c >> 2) & 7;
}
}
//Now format the bits...
GifImageDesc &id = si.ImageDesc;
ColorMapObject *cm = id.ColorMap ? id.ColorMap : g->SColorMap;
int w = id.Width, h = id.Height;
int wh = w*h;
int Size = wh * (bHighColor ? 4 : 2); //Size in bytes - in RGBA-4444 format, 2 bytes per pixel, in RGBA-8888, 4
unsigned char *Buf = (unsigned char*)malloc(Size);
if (!Buf)
{
DGifCloseFile(g);
return JNI_FALSE;
}
//Translate the color map into RGBA-4444 or RGBA-8888 format, take alpha into account. 256 colors tops, static is OK
for (i = 0; i<cm->ColorCount; i++)
{
GifColorType &c = cm->Colors[i];
Colors[i] =
bHighColor ?
((unsigned int)c.Red << 16) |
((unsigned int)c.Green << 8) |
((unsigned int)c.Blue) |
0xff000000
:
((unsigned short)(c.Red & 0xf0) << 8) |
((unsigned short)(c.Green & 0xf0) << 4) |
c.Blue | 0xf;
}
if (TransColor != -1)
Colors[TransColor] = 0;
//Convert pixel by pixel...
unsigned char *pSrc = si.RasterBits;
if (bHighColor)
{
unsigned int *pDest = (unsigned int*)Buf;
for (i = 0; i<wh; i++)
*pDest++ = Colors[*pSrc++];
}
else
{
unsigned short *pDest = (unsigned short*)Buf;
for (i = 0; i<wh; i++)
*pDest++ = (unsigned short)(Colors[*pSrc++]);
}
jbyteArray ja = jni->NewByteArray(Size);
if (!ja)
{
free(Buf);
DGifCloseFile(g);
return JNI_FALSE;
}
jni->SetByteArrayRegion(ja, 0, Size, (jbyte*)Buf);
free(Buf);
jni->CallVoidMethod(self, afmid,
(jint)Delay * 10,
(jint)id.Left, (jint)id.Top,
(jint)w, (jint)h, Disp, ja);
jni->DeleteLocalRef(ja);
}
DGifCloseFile(g);
return JNI_TRUE;
}
}
package com.mypackage;
//This is a sample. In a real project, there'd be a Dialog or an Activity that hosts the MovieView.
class MyClass
{
private MovieView m_mv; //initialized on loading
private static s_bHighColor = true;
//Format; hard-coded here
//This native method's implementation in in MyClass.cpp
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();
}
}
@sevaa
Copy link
Author

sevaa commented Dec 28, 2016

The attached file contains the required giflib sources and headers. It's a zip archive, renamed to keep the Gist comment system happy.

Explanations for the whole thing are here.

giflib zip

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment