Last active
April 3, 2021 03:47
-
-
Save sevaa/2d543fd9a5bc8582548a2a0aae3644e8 to your computer and use it in GitHub Desktop.
Animated GIF support on Android.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.