Reputation: 9842
I am making an app like Snapchat where you can take a photo, preview it, and during the preview draw on it. To implement this, I am using a surface view.
Here is my code for taking a photo and drawing to it
CameraActivity.java:
public class CameraActivity extends Activity {
public static final String TAG = CameraActivity.class.getSimpleName();
private android.hardware.Camera mCamera;
private CameraPreview mCameraPreview;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_camera);
mCamera = getCameraInstance();
mCameraPreview = new CameraPreview(this, mCamera);
FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
preview.addView(mCameraPreview);
Button captureButton = (Button) findViewById(R.id.button_capture);
captureButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mCamera.takePicture(null, null, mPicture);
}
});
}
/**
* Helper method to access the camera returns null if it cannot get the
* camera or does not exist
*
* @return
*/
private Camera getCameraInstance() {
Camera camera = null;
try {
camera = Camera.open();
} catch (Exception e) {
// cannot get camera or does not exist
}
return camera;
}
PictureCallback mPicture = new PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
File pictureFile = getOutputMediaFile();
if (pictureFile == null) {
return;
}
try {
FileOutputStream fos = new FileOutputStream(pictureFile);
fos.write(data);
fos.close();
} catch (FileNotFoundException e) {
} catch (IOException e) {
}
}
};
private static File getOutputMediaFile() {
File mediaStorageDir = new File(
Environment
.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
"MyCameraApp");
if (!mediaStorageDir.exists()) {
if (!mediaStorageDir.mkdirs()) {
Log.d("MyCameraApp", "failed to create directory");
return null;
}
}
// Create a media file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss")
.format(new Date());
File mediaFile;
mediaFile = new File(mediaStorageDir.getPath() + File.separator
+ "IMG_" + timeStamp + ".jpg");
return mediaFile;
}
}
CameraPreview.java:
public class CameraPreview extends SurfaceView implements
SurfaceHolder.Callback {
public static final String TAG = CameraPreview.class.getSimpleName();
private SurfaceHolder mSurfaceHolder;
private android.hardware.Camera mCamera;
// Constructor that obtains context and camera
@SuppressWarnings("deprecation")
public CameraPreview(Context context, Camera camera) {
super(context);
this.mCamera = camera;
this.mSurfaceHolder = this.getHolder();
this.mSurfaceHolder.addCallback(this);
this.mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
try {
mCamera.setPreviewDisplay(surfaceHolder);
mCamera.startPreview();
tryDrawing(surfaceHolder);
} catch (IOException e) {
// left blank for now
}
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
mCamera.stopPreview();
mCamera.release();
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int format,
int width, int height) {
// start preview with new settings
try {
mCamera.setPreviewDisplay(surfaceHolder);
mCamera.startPreview();
tryDrawing(surfaceHolder);
} catch (Exception e) {
// intentionally left blank for a test
}
}
private void tryDrawing(SurfaceHolder surfaceHolder) {
Log.i(TAG, "Trying to draw...");
Canvas canvas = surfaceHolder.lockCanvas();
if (canvas == null) {
Log.e(TAG, "Cannot draw onto the canvas as it's null");
} else {
drawMyStuff(canvas);
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
private void drawMyStuff(final Canvas canvas) {
Random random = new Random();
Log.i(TAG, "Drawing...");
canvas.drawRGB(255, 128, 128);
}
}
This works great for taking a picture and previewing it, however, when I try to actually draw on the canvas (aka, run the CameraPreview.trydrawing() method), I get this error:
02-22 15:36:59.861 4261-4261/com.johncorser.selfiesnap I/CameraPreview﹕ Trying to draw...
02-22 15:36:59.861 4261-4261/com.johncorser.selfiesnap E/SurfaceHolder﹕ Exception locking surface
java.lang.IllegalArgumentException
at android.view.Surface.nativeLockCanvas(Native Method)
at android.view.Surface.lockCanvas(Surface.java:243)
at android.view.SurfaceView$4.internalLockCanvas(SurfaceView.java:814)
at android.view.SurfaceView$4.lockCanvas(SurfaceView.java:782)
at com.johncorser.selfiesnap.CameraPreview.tryDrawing(CameraPreview.java:63)
at com.johncorser.selfiesnap.CameraPreview.surfaceChanged(CameraPreview.java:54)
at android.view.SurfaceView.updateWindow(SurfaceView.java:583)
at android.view.SurfaceView.access$000(SurfaceView.java:86)
at android.view.SurfaceView$3.onPreDraw(SurfaceView.java:175)
at android.view.ViewTreeObserver.dispatchOnPreDraw(ViewTreeObserver.java:847)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1867)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:996)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5600)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:761)
at android.view.Choreographer.doCallbacks(Choreographer.java:574)
at android.view.Choreographer.doFrame(Choreographer.java:544)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:747)
at android.os.Handler.handleCallback(Handler.java:733)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5001)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
at dalvik.system.NativeStart.main(Native Method)
02-22 15:36:59.961 4261-4261/com.johncorser.selfiesnap E/CameraPreview﹕ Cannot draw onto the canvas as it's null
I've looked this up here on stack overflow and basically found that the problem has something to do with the fact that camera locks up the surface view, so I can't lock the canvas (thus it is null). So what I want to know is, is there some way I can overcome this issue and be able to draw on the image?
Ideally anything drawn on the image would also be saved in the file, but I could also just add a "save" button to handle that logic most likely?
Upvotes: 1
Views: 1466
Reputation: 3621
Put another CustomView on top and draw on it, then you can merge that two images by this method
public Bitmap mergeBitmaps(Bitmapclass bitmap1,RenderView Bitmapextends bitmap2)View {
Bitmap mergedBitmap =private Bitmap.createBitmap(bitmap1.getWidth(), bitmap1.getHeight(), bitmap1.getConfig());
Canvas canvas = newprivate Canvas(mergedBitmap);
canvas.drawBitmap(bitmap1, 0, 0, null);
canvas.drawBitmap(bitmap2, 0, 0, null);
return mergedBitmap;
}
And this is an implementation of the custom view
public class RenderView extends View {
private Bitmap cachedBitmap;
private Canvas cachedCanvas;
private Paint linePaint;
private boolean isClicked;
private float lastX;
private float lastY;
public RenderView(Context context) {
super(context);
}
public RenderView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public RenderView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onDraw(Canvas canvas) {
int width = getWidth();
int height = getHeight();
if(cachedBitmap == null && width > 0 && height > 0) {
cachedBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
cachedCanvas = new Canvas(cachedBitmap);
linePaint = new Paint();
linePaint.setStyle(Paint.Style.STROKE);
linePaint.setStrokeWidth(2);
linePaint.setColor(Color.parseColor("#000000"));
}
canvas.drawBitmap(cachedBitmap, 0, 0, null);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
isClicked = true;
break;
case MotionEvent.ACTION_MOVE:
if(isClicked && cachedCanvas != null) {
cachedCanvas.drawLine(lastX, lastY, event.getX(), event.getY(), linePaint);
invalidate();
}
break;
case MotionEvent.ACTION_UP:
isClicked = false;
break;
}
lastX = event.getX();
lastY = event.getY();
return true;
}
}
To add this view via XML in this way
<com.myapp.RenderView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/renderView"
android:clickable="true"/>
After capturing a bitmap from the camera, you can get cachedBitmap from this class, and merge that two bitmaps with mergeBitmaps method.
Upvotes: 1