johncorser
johncorser

Reputation: 9842

Cannot draw on surfaceview because Canvas is null

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

Answers (1)

mes
mes

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"/>

RenderView

After capturing a bitmap from the camera, you can get cachedBitmap from this class, and merge that two bitmaps with mergeBitmaps method.

Upvotes: 1

Related Questions