google 官方ndk demo解析(二)——bitmap-plasma

来源:互联网 发布:dota 幽鬼攻略知乎 编辑:程序博客网 时间:2024/06/06 01:26

本文将分析bitmap-plasma这个demo。

这是一个使用c代码在bitmap上绘制细胞质效果的demo。

CMakeList.txt

cmake_minimum_required(VERSION 3.4.1)set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall")add_library(plasma SHARED            plasma.c)# Include libraries needed for plasma libtarget_link_libraries(plasma                      android                      jnigraphics                      log                      m)

文件很简单,不逐行解释。需要用到jnigraphics库,这是个很小的library,展示一个稳定的,基于C语言的,接口,使本机代码安全地访问Java对象的像素缓冲区的位图

Plasma.java

public class Plasma extends Activity{    // Called when the activity is first created.    @Override    public void onCreate(Bundle savedInstanceState)    {        super.onCreate(savedInstanceState);        Display display = getWindowManager().getDefaultDisplay();        setContentView(new PlasmaView(this, display.getWidth(), display.getHeight()));    }    // load our native library    static {        System.loadLibrary("plasma");    }}// Custom view for rendering plasma.//// Note: suppressing lint wrarning for ViewConstructor since it is//       manually set from the activity and not used in any layout.@SuppressLint("ViewConstructor")class PlasmaView extends View {    private Bitmap mBitmap;    private long mStartTime;    // implementend by libplasma.so    private static native void renderPlasma(Bitmap  bitmap, long time_ms);    public PlasmaView(Context context, int width, int height) {        super(context);        mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);        mStartTime = System.currentTimeMillis();    }    @Override protected void onDraw(Canvas canvas) {        renderPlasma(mBitmap, System.currentTimeMillis() - mStartTime);        canvas.drawBitmap(mBitmap, 0, 0, null);        // force a redraw, with a different time-based pattern.        invalidate();    }}

自定义PlasmaView扩展自View,onDraw()方法会调用native函数renderPlasma(),在Bitmap上进行绘制。

plasma.c

#define  LOG_TAG    "libplasma"#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)/* Set to 1 to enable debug log traces. */#define DEBUG 0/* Set to 1 to optimize memory stores when generating plasma. */#define OPTIMIZE_WRITES  1/* Return current time in milliseconds */static double now_ms(void){    struct timeval tv;    gettimeofday(&tv, NULL);    return tv.tv_sec*1000. + tv.tv_usec/1000.;}/* We're going to perform computations for every pixel of the target * bitmap. floating-point operations are very slow on ARMv5, and not * too bad on ARMv7 with the exception of trigonometric functions. * * For better performance on all platforms, we're going to use fixed-point * arithmetic and all kinds of tricks */typedef int32_t  Fixed;#define  FIXED_BITS           16#define  FIXED_ONE            (1 << FIXED_BITS)#define  FIXED_AVERAGE(x,y)   (((x) + (y)) >> 1)#define  FIXED_FROM_INT(x)    ((x) << FIXED_BITS)#define  FIXED_TO_INT(x)      ((x) >> FIXED_BITS)#define  FIXED_FROM_FLOAT(x)  ((Fixed)((x)*FIXED_ONE))#define  FIXED_TO_FLOAT(x)    ((x)/(1.*FIXED_ONE))#define  FIXED_MUL(x,y)       (((int64_t)(x) * (y)) >> FIXED_BITS)#define  FIXED_DIV(x,y)       (((int64_t)(x) * FIXED_ONE) / (y))#define  FIXED_DIV2(x)        ((x) >> 1)#define  FIXED_AVERAGE(x,y)   (((x) + (y)) >> 1)#define  FIXED_FRAC(x)        ((x) & ((1 << FIXED_BITS)-1))#define  FIXED_TRUNC(x)       ((x) & ~((1 << FIXED_BITS)-1))#define  FIXED_FROM_INT_FLOAT(x,f)   (Fixed)((x)*(FIXED_ONE*(f)))typedef int32_t  Angle;#define  ANGLE_BITS              9#if ANGLE_BITS < 8#  error ANGLE_BITS must be at least 8#endif#define  ANGLE_2PI               (1 << ANGLE_BITS)#define  ANGLE_PI                (1 << (ANGLE_BITS-1))#define  ANGLE_PI2               (1 << (ANGLE_BITS-2))#define  ANGLE_PI4               (1 << (ANGLE_BITS-3))#define  ANGLE_FROM_FLOAT(x)   (Angle)((x)*ANGLE_PI/M_PI)#define  ANGLE_TO_FLOAT(x)     ((x)*M_PI/ANGLE_PI)#if ANGLE_BITS <= FIXED_BITS#  define  ANGLE_FROM_FIXED(x)     (Angle)((x) >> (FIXED_BITS - ANGLE_BITS))#  define  ANGLE_TO_FIXED(x)       (Fixed)((x) << (FIXED_BITS - ANGLE_BITS))#else#  define  ANGLE_FROM_FIXED(x)     (Angle)((x) << (ANGLE_BITS - FIXED_BITS))#  define  ANGLE_TO_FIXED(x)       (Fixed)((x) >> (ANGLE_BITS - FIXED_BITS))#endifstatic Fixed  angle_sin_tab[ANGLE_2PI+1];static void init_angles(void){    int  nn;    for (nn = 0; nn < ANGLE_2PI+1; nn++) {        double  radians = nn*M_PI/ANGLE_PI;        angle_sin_tab[nn] = FIXED_FROM_FLOAT(sin(radians));    }}static __inline__ Fixed angle_sin( Angle  a ){    return angle_sin_tab[(uint32_t)a & (ANGLE_2PI-1)];}static __inline__ Fixed angle_cos( Angle  a ){    return angle_sin(a + ANGLE_PI2);}static __inline__ Fixed fixed_sin( Fixed  f ){    return angle_sin(ANGLE_FROM_FIXED(f));}static __inline__ Fixed  fixed_cos( Fixed  f ){    return angle_cos(ANGLE_FROM_FIXED(f));}/* Color palette used for rendering the plasma */#define  PALETTE_BITS   8#define  PALETTE_SIZE   (1 << PALETTE_BITS)#if PALETTE_BITS > FIXED_BITS#  error PALETTE_BITS must be smaller than FIXED_BITS #endifstatic uint16_t  palette[PALETTE_SIZE];static uint16_t  make565(int red, int green, int blue){    return (uint16_t)( ((red   << 8) & 0xf800) |                       ((green << 3) & 0x07e0) |                       ((blue  >> 3) & 0x001f) );}static void init_palette(void){    int  nn, mm = 0;    /* fun with colors */    for (nn = 0; nn < PALETTE_SIZE/4; nn++) {        int  jj = (nn-mm)*4*255/PALETTE_SIZE;        palette[nn] = make565(255, jj, 255-jj);    }    for ( mm = nn; nn < PALETTE_SIZE/2; nn++ ) {        int  jj = (nn-mm)*4*255/PALETTE_SIZE;        palette[nn] = make565(255-jj, 255, jj);    }    for ( mm = nn; nn < PALETTE_SIZE*3/4; nn++ ) {        int  jj = (nn-mm)*4*255/PALETTE_SIZE;        palette[nn] = make565(0, 255-jj, 255);    }    for ( mm = nn; nn < PALETTE_SIZE; nn++ ) {        int  jj = (nn-mm)*4*255/PALETTE_SIZE;        palette[nn] = make565(jj, 0, 255);    }}static __inline__ uint16_t  palette_from_fixed( Fixed  x ){    if (x < 0) x = -x;    if (x >= FIXED_ONE) x = FIXED_ONE-1;    int  idx = FIXED_FRAC(x) >> (FIXED_BITS - PALETTE_BITS);    return palette[idx & (PALETTE_SIZE-1)];}/* Angles expressed as fixed point radians */static void init_tables(void){    init_palette();    init_angles();}static void fill_plasma( AndroidBitmapInfo*  info, void*  pixels, double  t ){    Fixed yt1 = FIXED_FROM_FLOAT(t/1230.);    Fixed yt2 = yt1;    Fixed xt10 = FIXED_FROM_FLOAT(t/3000.);    Fixed xt20 = xt10;#define  YT1_INCR   FIXED_FROM_FLOAT(1/100.)#define  YT2_INCR   FIXED_FROM_FLOAT(1/163.)    int  yy;    for (yy = 0; yy < info->height; yy++) {        uint16_t*  line = (uint16_t*)pixels;        Fixed      base = fixed_sin(yt1) + fixed_sin(yt2);        Fixed      xt1 = xt10;        Fixed      xt2 = xt20;        yt1 += YT1_INCR;        yt2 += YT2_INCR;#define  XT1_INCR  FIXED_FROM_FLOAT(1/173.)#define  XT2_INCR  FIXED_FROM_FLOAT(1/242.)#if OPTIMIZE_WRITES        /* optimize memory writes by generating one aligned 32-bit store         * for every pair of pixels.         */        uint16_t*  line_end = line + info->width;        if (line < line_end) {            if (((uint32_t)(uintptr_t)line & 3) != 0) {                Fixed ii = base + fixed_sin(xt1) + fixed_sin(xt2);                xt1 += XT1_INCR;                xt2 += XT2_INCR;                line[0] = palette_from_fixed(ii >> 2);                line++;            }            while (line + 2 <= line_end) {                Fixed i1 = base + fixed_sin(xt1) + fixed_sin(xt2);                xt1 += XT1_INCR;                xt2 += XT2_INCR;                Fixed i2 = base + fixed_sin(xt1) + fixed_sin(xt2);                xt1 += XT1_INCR;                xt2 += XT2_INCR;                uint32_t  pixel = ((uint32_t)palette_from_fixed(i1 >> 2) << 16) |                                   (uint32_t)palette_from_fixed(i2 >> 2);                ((uint32_t*)line)[0] = pixel;                line += 2;            }            if (line < line_end) {                Fixed ii = base + fixed_sin(xt1) + fixed_sin(xt2);                line[0] = palette_from_fixed(ii >> 2);                line++;            }        }#else /* !OPTIMIZE_WRITES */        int xx;        for (xx = 0; xx < info->width; xx++) {            Fixed ii = base + fixed_sin(xt1) + fixed_sin(xt2);            xt1 += XT1_INCR;            xt2 += XT2_INCR;            line[xx] = palette_from_fixed(ii / 4);        }#endif /* !OPTIMIZE_WRITES */        // go to next line        pixels = (char*)pixels + info->stride;    }}/* simple stats management */typedef struct {    double  renderTime;    double  frameTime;} FrameStats;#define  MAX_FRAME_STATS  200#define  MAX_PERIOD_MS    1500typedef struct {    double  firstTime;    double  lastTime;    double  frameTime;    int         firstFrame;    int         numFrames;    FrameStats  frames[ MAX_FRAME_STATS ];} Stats;static voidstats_init( Stats*  s ){    s->lastTime = now_ms();    s->firstTime = 0.;    s->firstFrame = 0;    s->numFrames  = 0;}static voidstats_startFrame( Stats*  s ){    s->frameTime = now_ms();}static voidstats_endFrame( Stats*  s ){    double now = now_ms();    double renderTime = now - s->frameTime;    double frameTime  = now - s->lastTime;    int nn;    if (now - s->firstTime >= MAX_PERIOD_MS) {        if (s->numFrames > 0) {            double minRender, maxRender, avgRender;            double minFrame, maxFrame, avgFrame;            int count;            nn = s->firstFrame;            minRender = maxRender = avgRender = s->frames[nn].renderTime;            minFrame  = maxFrame  = avgFrame  = s->frames[nn].frameTime;            for (count = s->numFrames; count > 0; count-- ) {                nn += 1;                if (nn >= MAX_FRAME_STATS)                    nn -= MAX_FRAME_STATS;                double render = s->frames[nn].renderTime;                if (render < minRender) minRender = render;                if (render > maxRender) maxRender = render;                double frame = s->frames[nn].frameTime;                if (frame < minFrame) minFrame = frame;                if (frame > maxFrame) maxFrame = frame;                avgRender += render;                avgFrame  += frame;            }            avgRender /= s->numFrames;            avgFrame  /= s->numFrames;            LOGI("frame/s (avg,min,max) = (%.1f,%.1f,%.1f) "                 "render time ms (avg,min,max) = (%.1f,%.1f,%.1f)\n",                 1000./avgFrame, 1000./maxFrame, 1000./minFrame,                 avgRender, minRender, maxRender);        }        s->numFrames  = 0;        s->firstFrame = 0;        s->firstTime  = now;    }    nn = s->firstFrame + s->numFrames;    if (nn >= MAX_FRAME_STATS)        nn -= MAX_FRAME_STATS;    s->frames[nn].renderTime = renderTime;    s->frames[nn].frameTime  = frameTime;    if (s->numFrames < MAX_FRAME_STATS) {        s->numFrames += 1;    } else {        s->firstFrame += 1;        if (s->firstFrame >= MAX_FRAME_STATS)            s->firstFrame -= MAX_FRAME_STATS;    }    s->lastTime = now;}JNIEXPORT void JNICALL Java_com_example_plasma_PlasmaView_renderPlasma(JNIEnv * env, jobject  obj, jobject bitmap,  jlong  time_ms){    AndroidBitmapInfo  info;    void*              pixels;    int                ret;    static Stats       stats;    static int         init;    if (!init) {        init_tables();        stats_init(&stats);        init = 1;    }    if ((ret = AndroidBitmap_getInfo(env, bitmap, &info)) < 0) {        LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);        return;    }    if (info.format != ANDROID_BITMAP_FORMAT_RGB_565) {        LOGE("Bitmap format is not RGB_565 !");        return;    }    if ((ret = AndroidBitmap_lockPixels(env, bitmap, &pixels)) < 0) {        LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);    }    stats_startFrame(&stats);    /* Now fill the values with a nice little plasma */    fill_plasma(&info, pixels, time_ms );    AndroidBitmap_unlockPixels(env, bitmap);    stats_endFrame(&stats);}
首先分析函数renderPlasma()。

首先定义一个AndroidBitmapInfo的对象info。AndroidBitmapInfo的定义如下:

/** Bitmap info, see AndroidBitmap_getInfo(). */typedef struct {    /** The bitmap width in pixels. */    uint32_t    width;    /** The bitmap height in pixels. */    uint32_t    height;    /** The number of byte per row. */    uint32_t    stride;    /** The bitmap pixel format. See {@link AndroidBitmapFormat} */    int32_t     format;    /** Unused. */    uint32_t    flags;      // 0 for now} AndroidBitmapInfo;
AndroidBitmapInfo用来描述一个bitmap,width,height分别是bitmap的宽,高,stride是一行的字节数,format表示bitmap的format,flags目前没有用,恒为0。

继续分析renderPlasma()函数。如果init为false,即没有执行过初始化,那么首先调用方法init_tables()和stat_init()进行初始化。init_tables()会依次调用init_palette()和init_angles()。init_palette()初始化着色版,PALETTE_SIZE的值是256,init_palette分四段

进行着色,分别是0-63, 64-127, 128-192, 192-255。init_angles()初始化图像的角度。

接下来调用AndroidBitmap_getInfo()获取bitmap的info,如果返回<0,记录错误信息到log并返回。

如果获取信息成功,那么判断bitmap的格式,如果format!=ANDROID_BITMAP_FORMAT_RGB_565,记录错误信息到log并返回。

接下来,调用函数AndroidBitmap_lockPixels(),尝试锁定bitmap的像素地址,这个方法的目的是确保在AndroidBitmap_unlockPixels()调用之前,这些像素的内存不会被移除。

如果锁定成功,接着调用方法stats_startFrame(&stats)。stats_startFrame(&stats)会调用方法now_ms(),返回值赋值给参数stats的成员frameTime。now_ms()返回当前时间的毫秒数。

接下来,调用fill_plasma(&info, pixels, time_ms )进行填充。

之后,要记得调用AndroidBitmap_unlockPixels(env, bitmap)接触像素锁定。