I have a piece of native code where I am mallocing (i.e. allocating) a buffer. I like to draw into this memory using Canvas draw operations. But Canvas code uses Bitmap as its backing plane. I am wondering if there is a way to wrap native block of memory with Android Bitmap.
Thanks
Videoguy
You can pass a Buffer from JAVA, fill it in Native code and then render it using Canvas. Done, works perfectly.
edited to add an example:
warning, Java bloat ahead
/*
* Copyright (C) 2009 The Android Open Source Project
*/
package com.example.hellojni;
import android.app.Activity;
import android.widget.TextView;
import android.os.Bundle;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.widget.Toast;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.MotionEvent;
import android.view.KeyEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.Window;
import android.view.WindowManager;
import android.graphics.PixelFormat;
import java.nio.ByteBuffer;
public class HelloJni extends Activity
{
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(new Panel(this));
}
public void onDestroy() {
super.onDestroy();
myEngineDestroy();
}
class Panel extends SurfaceView implements SurfaceHolder.Callback {
Bitmap renderbmp = null;
Paint paint = null;
public Panel(Context context) {
super(context);
getHolder().addCallback(this);
getHolder().setFormat(PixelFormat.RGB_565);
setFocusable(true);
setFocusableInTouchMode(true);
}
@Override
public boolean onKeyDown(int i, KeyEvent event) {
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if ((event.getAction() == MotionEvent.ACTION_DOWN) ||
(event.getAction() == MotionEvent.ACTION_MOVE) )
{
if( myEngineMouseInput( (int) event.getX(), (int) event.getY(), 0 ) == 1 )
drawFrame();
return true;
}
return false;
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// TODO Auto-generated method stub
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
if( renderbmp == null )
renderbmp = Bitmap.createBitmap( holder.getSurfaceFrame().right-holder.getSurfaceFrame().left, holder.getSurfaceFrame().bottom-holder.getSurfaceFrame().top,Bitmap.Config.RGB_565 );
if( paint == null )
paint = new Paint(Paint.FILTER_BITMAP_FLAG);
myEngineInit( renderbmp, renderbmp.getWidth(), renderbmp.getHeight(), PixelFormat.RGB_565 );
drawFrame();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
public void drawFrame() {
Canvas c;
c = getHolder().lockCanvas(null);
c.drawBitmap(renderbmp, 0, 0, paint);
if (c != null) getHolder().unlockCanvasAndPost(c);
}
}
/* A native method that is implemented by the
* 'hello-jni' native library, which is packaged
* with this application.
*/
public native void myEngineInit( Bitmap bmp, int w, int h, int pf );
public native int myEngineMouseInput( int x, int y, int mt );
public native void myEngineDestroy();
public native String unimplementedStringFromJNI();
static {
System.loadLibrary("hello-jni");
}
}
and now the NDK side
/*
* Copyright (C) 2010 The Android Open Source Project
*/
#include <android_native_app_glue.h>
#include <errno.h>
#include <jni.h>
#include <sys/time.h>
#include <time.h>
#include <android/log.h>
#include <android/bitmap.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define LOG_TAG "myapp"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,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
/* 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.;
}
jobject jbmp = NULL;
ANativeWindow_Buffer draw_buffer = { 0 };
static void cleanup_draw_buffer( JNIEnv* env )
{
if(jbmp) {
AndroidBitmap_unlockPixels( env, jbmp);
}
draw_buffer.bits = 0;
}
static int init_draw_buffer( JNIEnv* env, jobject jbitmap, int width, int height )
{
int res = 0, ret;
LOGI("init_draw_buffer");
LOGI("window w:%d, h:%d, format: %d", width, height, 4 );
if( draw_buffer.width != width ||
draw_buffer.height != height )
{
draw_buffer.width=width;
draw_buffer.height=height;
draw_buffer.stride = draw_buffer.width*2;
res = 1;
}
jbmp = NULL;
if ((ret = AndroidBitmap_lockPixels(env, jbitmap, &draw_buffer.bits)) < 0) {
LOGE("AndroidBitmap-lockPixels() failed ! error=%d", ret);
}
else {
LOGI("Successfully acquired bitmap pixels: %x", draw_buffer.bits );
jbmp = jbitmap;
}
return res;
}
/* simple stats management */
typedef struct {
double renderTime;
double frameTime;
} FrameStats;
#define MAX_FRAME_STATS 200
#define MAX_PERIOD_MS 1500
typedef struct {
double firstTime;
double lastTime;
double frameTime;
int firstFrame;
int numFrames;
FrameStats frames[ MAX_FRAME_STATS ];
} Stats;
static void
stats_init( Stats* s )
{
s->lastTime = now_ms();
s->firstTime = 0.;
s->firstFrame = 0;
s->numFrames = 0;
}
static void
stats_startFrame( Stats* s )
{
s->frameTime = now_ms();
}
static void
stats_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;
}
// ----------------------------------------------------------------------
struct engine {
struct android_app* app;
Stats stats;
int animating;
};
void
Java_com_example_hellojni_HelloJni_myEngineDestroy( JNIEnv* env,
jobject thiz )
{
LOGI("Java_com_example_hellojni_HelloJni_myEngineDestroy");
cleanup_draw_buffer(env);
}
void
Java_com_example_hellojni_HelloJni_myEngineInit( JNIEnv* env,
jobject thiz, jobject jbitmap, int w, int h, int pf )
{
LOGI("Java_com_example_hellojni_HelloJni_myEngineInit");
init_draw_buffer( env, jbitmap, w, h );
}
jint
Java_com_example_hellojni_HelloJni_myEngineMouseInput( JNIEnv* env,
jobject thiz, int x, int y, int mt )
{
if( menuvisible )
// LOGI("Java_com_example_hellojni_HelloJni_myEngineMouseInput, x:%d y:%d mt:%d", x, y, mt);
return do_the_drawing_stuff(params_ommited); //drawing buffer is unsigned char *dest = draw_buffer.bits;
else
return 0;
}
So the main rendering part is called a few lines above, and as stated, your buffer is located at draw_buffer structure.
Please examine the passing of a buffer technique and trace its path from the JAVA to C via JNI interface. I took care to make a final bitmap rendering operation without any conversions - fastest approach IMO.
You could ommit that frame counting part, as I took some kind of example as a base and just wanted to lay my hands on that new device I got :)
What you could do is pass the buffer through JNI into the java and create a bitmap from it. see this page. Then you can use Canvas.setBitmap to cause it to draw into the buffer.
Bitmaps use native memory by default.
来源:https://stackoverflow.com/questions/6631412/can-i-create-a-bitmap-that-uses-malloced-buffer-i-created-in-native-code