http://divided-games.com/drawing-with-canvas-on-top-of-a-camera-preview/
In this tutorial I’m going to describe how you can draw on top of a Camera Preview with Canvas.
Part 1: Make an Android Project
To start make an Android project in your work space. I’m using Android version 2.1 for this tutorial, but later versions should work as well.
First the following two lines will need to be added to the Android Manifest of the project.
1
2
|
|
These two lines are necessary in order to use the camera on the phone.
Part 2: Making the camera preview class
Next Lets make a camera preview class. I’m using the preview class directly from the API Guides on Google found here. The code posted below is the same code. With one addition. Add the following method to the class.
1
2
3
4
|
public
void
onPause() {
mCamera.release();
mCamera =
null
;
}
|
This method will release the camera and then nullify it when we are done using it.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
public
class
CameraPreview
extends
SurfaceView
implements
SurfaceHolder.Callback {
private
SurfaceHolder mHolder;
private
Camera mCamera;
public
CameraPreview(Context context, Camera camera) {
super
(context);
mCamera = camera;
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
mHolder = getHolder();
mHolder.addCallback(
this
);
// deprecated setting, but required on Android versions prior to 3.0
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
public
void
surfaceCreated(SurfaceHolder holder) {
// The Surface has been created, now tell the camera where to draw the preview.
try
{
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
}
catch
(IOException e) {
Log.d(
"CameraView"
,
"Error setting camera preview: "
+ e.getMessage());
}
}
public
void
surfaceDestroyed(SurfaceHolder holder) {
// empty. Take care of releasing the Camera preview in your activity.
}
public
void
surfaceChanged(SurfaceHolder holder,
int
format,
int
w,
int
h) {
// If your preview can change or rotate, take care of those events here.
// Make sure to stop the preview before resizing or reformatting it.
if
(mHolder.getSurface() ==
null
){
// preview surface does not exist
return
;
}
// stop preview before making changes
try
{
mCamera.stopPreview();
}
catch
(Exception e){
// ignore: tried to stop a non-existent preview
}
// set preview size and make any resize, rotate or
// reformatting changes here
// start preview with new settings
try
{
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
}
catch
(Exception e){
Log.d(
"CameraView"
,
"Error starting camera preview: "
+ e.getMessage());
}
}
public
void
onPause() {
mCamera.release();
mCamera =
null
;
}
}
|
This code is off the API Guides as I mentioned above, so if you have trouble understanding it, please refer to the link above as they do a very good job in explaining it. Or if you are still having trouble, just post a comment below.
Part 3: Making the draw class
Next lets make our draw class. Create a new class called “DrawView” and have it extend “SurfaceView” so that we can use its “onDraw” method.
1
2
|
public
class
DrawView
extends
SurfaceView{
}
|
Next lets make a paint variable so that we can test our app.
1
|
private
Paint textPaint =
new
Paint();
|
And now make the constructor if it has not already been made. It should look like this.
1
2
3
4
|
public
DrawView(Context context) {
super
(context);
}
|
And add the following two lines to the constructor. These lines just tell our paint what color we want it to be and what text size.
1
2
|
textPaint.setARGB(
255
,
200
,
0
,
0
);
textPaint.setTextSize(
60
);
|
And now for the most important line of the constructor.
1
|
setWillNotDraw(
false
);
|
Without this line, onDraw will not be called at all. So we need to make sure it is included.
Now lets make the draw method.
1
2
3
4
|
@Override
protected
void
onDraw(Canvas canvas){
canvas.drawText(
"Hello World!"
,
50
,
50
, textPaint);
}
|
This is just a simple override of the onDraw method that SurfaceView has, with a call added to canvas that draws the text “Hello World”.
The following is it all thrown together.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public
class
DrawView
extends
SurfaceView{
private
Paint textPaint =
new
Paint();
public
DrawView(Context context) {
super
(context);
// Create out paint to use for drawing
textPaint.setARGB(
255
,
200
,
0
,
0
);
textPaint.setTextSize(
60
);
// This call is necessary, or else the
// draw method will not be called.
setWillNotDraw(
false
);
}
@Override
protected
void
onDraw(Canvas canvas){
// A Simple Text Render to test the display
canvas.drawText(
"Hello World!"
,
50
,
50
, textPaint);
}
}
|
Part 4: Putting it all together
Finally lets make our activity class. If you are using eclipse, this class will be generated so I will just tell you what needs to be added.
Lets add some variables that we will need for our activity. The first two here are for keeping track of our views, and the third one is going to be our content view. We are using a FrameLayout because it allows us to layer views on the Z axis and therefore we can put our DrawView on top of the CameraPreview.
1
2
3
|
CameraPreview cv;
DrawView dv;
FrameLayout alParent;
|
Next add these lines to the onCreate method.
1
2
3
4
|
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
The first line here tells Android that we want our application to be in landscape and to stay in landscape. Without this line your camera will switch orientations if the user turns their phone, and the portrait orientation will skew the image from camera preview.
The next two lines here just tell android that we don’t want a title bar and that we want our application to be full screen.
Now add these two methods to the class.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@Override
protected
void
onPause() {
super
.onPause();
if
(cv !=
null
){
cv.onPause();
cv =
null
;
}
}
@Override
protected
void
onResume(){
super
.onResume();
Load();
}
|
If you are familiar with the states off an Android app, then you will know that onPause will be called each time the app closes and onResume will be called each time it is opened. Because of this we will call the CameraPreview’s onPause method in this method to release the camera each time we close the app.
Next is the onResume method. There is a call to a Load method here, we have not created this method yet but we will shortly. The load method we are going to create will make our apps layout and initialize the views. Instead of adding it in the onCreate method like you would normally do, we are going to be calling it from the onResume method. This is because in certain circumstances the app will not load correctly if it is just in the onCreate method.
Before we make the Load method we need to make a quick method to fetch the camera.
1
2
3
4
5
6
7
8
9
10
|
public
static
Camera getCameraInstance(){
Camera c =
null
;
try
{
c = Camera.open();
}
catch
(Exception e){
e.printStackTrace();
}
return
c;
}
|
This method simply tries to get a camera reference, and returns null if it is unable to.
Here is the load method, I will explain it below.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public
void
Load(){
Camera c = getCameraInstance();
if
(c !=
null
){
alParent =
new
FrameLayout(
this
);
alParent.setLayoutParams(
new
LayoutParams(
LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT));
cv =
new
CameraPreview(
this
,c);
alParent.addView(cv);
dv =
new
DrawView(
this
);
alParent.addView(dv);
setContentView(alParent);
}
else
{
Toast toast = Toast.makeText(getApplicationContext(),
"Unable to find camera. Closing."
, Toast.LENGTH_SHORT);
toast.show();
finish();
}
}
|
The first thing that happens here is we try and get the camera. If the call to get the camera returns null then we display a message telling the user that the app was unable to find the camera, and then we tell the app to close. But if the call to get the camera does not return null, then we create the layout.
The first part in creating the layout is to initialize our FrameLayout object.
1
2
3
4
|
alParent =
new
FrameLayout(
this
);
alParent.setLayoutParams(
new
LayoutParams(
LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT));
|
That is what these lines did. We first created our object and then we told it to fill the parent.
Next we just initialized our CameraPreview and DrawView. The important part here is to add the CameraPreview first so that the Draw View is on top of it.
1
2
3
4
5
|
cv =
new
CameraPreview(
this
,c);
alParent.addView(cv);
dv =
new
DrawView(
this
);
alParent.addView(dv);
|
And finally we just tell our app that our frame layout is the content view.
1
|
setContentView(alParent);
|
Here is it all added together with some comments.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
public
class
CameraTestActivity
extends
Activity {
// Our variables
CameraPreview cv;
DrawView dv;
FrameLayout alParent;
@Override
public
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
// Set the screen orientation to landscape, because
// the camera preview will be in landscape, and if we
// don't do this, then we will get a streached image.
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
// requesting to turn the title OFF
requestWindowFeature(Window.FEATURE_NO_TITLE);
// making it full screen
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
public
void
Load(){
// Try to get the camera
Camera c = getCameraInstance();
// If the camera was received, create the app
if
(c !=
null
){
// Create our layout in order to layer the
// draw view on top of the camera preview.
alParent =
new
FrameLayout(
this
);
alParent.setLayoutParams(
new
LayoutParams(
LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT));
// Create a new camera view and add it to the layout
cv =
new
CameraPreview(
this
,c);
alParent.addView(cv);
// Create a new draw view and add it to the layout
dv =
new
DrawView(
this
);
alParent.addView(dv);
// Set the layout as the apps content view
setContentView(alParent);
}
// If the camera was not received, close the app
else
{
Toast toast = Toast.makeText(getApplicationContext(),
"Unable to find camera. Closing."
, Toast.LENGTH_SHORT);
toast.show();
finish();
}
}
// This method is strait for the Android API
// A safe way to get an instance of the Camera object.
public
static
Camera getCameraInstance(){
Camera c =
null
;
try
{
c = Camera.open();
// attempt to get a Camera instance
}
catch
(Exception e){
// Camera is not available (in use or does not exist)
e.printStackTrace();
}
return
c;
// returns null if camera is unavailable
}
// Override the onPause method so that we
// can release the camera when the app is closing.
@Override
protected
void
onPause() {
super
.onPause();
if
(cv !=
null
){
cv.onPause();
cv =
null
;
}
}
// We call Load in our Resume method, because
// the app will close if we call it in onCreate
@Override
protected
void
onResume(){
super
.onResume();
Load();
}
}
|