Hi guys I was trying to make a QRCode reader so I used the used the QRCodeReaderView library provided by dlzaaro66 which provides easy implementation of Zxing library. The code is scanning the qrcode but i wanted to make sort of a reference box so as to indicate the whereabouts of where the code is being scanned from on the camera surface view I tried to use the normal draw technique. Its not giving any error but its not drawing either could you help me with where the problem might be occurring.
This my activity class.
import android.app.Activity;
import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.CompoundButton;
import android.widget.Switch;
import android.widget.Toast;
import com.dlazaro66.qrcodereaderview.QRCodeReaderView;
import com.dlazaro66.qrcodereaderview.QRCodeReaderView.OnQRCodeReadListener;
public class MyActivity extends Activity implements OnQRCodeReadListener{
QRCodeReaderView decoder;
Switch start_stop;
Paint paint;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
decoder = (QRCodeReaderView) findViewById(R.id.view2);
decoder.setOnQRCodeReadListener(this);
start_stop=(Switch) findViewById(R.id.switch1);
start_stop.setChecked(true);
start_stop.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
if(b){
decoder.getCameraManager().startPreview();
}
else{
decoder.getCameraManager().stopPreview();
}
}
});
paint= new Paint();
paint.setColor(Color.RED);
paint.setStrokeWidth(100);
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.my, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
public void onQRCodeRead(String text, PointF[] points) {
start_stop.setChecked(false);
if(text.startsWith("http")){
Toast.makeText(getApplicationContext(),text,Toast.LENGTH_SHORT).show();
final Intent intent = new Intent(Intent.ACTION_VIEW).setData(Uri.parse(text));
startActivity(intent);
}
else{
Toast.makeText(getApplicationContext(),text,Toast.LENGTH_SHORT).show();
}
Canvas canvas=new Canvas();
for(int i=0;i<points.length-1;i++){
canvas.drawLine(points[i].x,points[i].y,points[i+1].x,points[i+1].y,paint);
}
}
#Override
public void cameraNotFound() {
}
#Override
public void QRCodeNotFoundOnCamImage() {
}
}
This is the library project class from where I am getting the methods and the custom surfaceview
public class QRCodeReaderView extends SurfaceView implements SurfaceHolder.Callback,Camera.PreviewCallback {
public interface OnQRCodeReadListener {
public void onQRCodeRead(String text, PointF[] points);
public void cameraNotFound();
public void QRCodeNotFoundOnCamImage();
}
private OnQRCodeReadListener mOnQRCodeReadListener;
private static final String TAG = QRCodeReaderView.class.getName();
private QRCodeReader mQRCodeReader;
private int mPreviewWidth;
private int mPreviewHeight;
private SurfaceHolder mHolder;
private CameraManager mCameraManager;
public QRCodeReaderView(Context context) {
super(context);
init();
}
public QRCodeReaderView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public void setOnQRCodeReadListener(OnQRCodeReadListener onQRCodeReadListener) {
mOnQRCodeReadListener = onQRCodeReadListener;
}
public CameraManager getCameraManager() {
return mCameraManager;
}
#SuppressWarnings("deprecation")
private void init() {
if (checkCameraHardware(getContext())){
mCameraManager = new CameraManager(getContext());
mHolder = this.getHolder();
mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); // Need to set this flag despite it's deprecated
} else {
Log.e(TAG, "Error: Camera not found");
mOnQRCodeReadListener.cameraNotFound();
}
}
/****************************************************
* SurfaceHolder.Callback,Camera.PreviewCallback
****************************************************/
#Override
public void surfaceCreated(SurfaceHolder holder) {
try {
// Indicate camera, our View dimensions
mCameraManager.openDriver(holder,this.getWidth(),this.getHeight());
} catch (IOException e) {
Log.w(TAG, "Can not openDriver: "+e.getMessage());
mCameraManager.closeDriver();
}
try {
mQRCodeReader = new QRCodeReader();
mCameraManager.startPreview();
} catch (Exception e) {
Log.e(TAG, "Exception: " + e.getMessage());
mCameraManager.closeDriver();
}
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d(TAG, "surfaceDestroyed");
mCameraManager.getCamera().setPreviewCallback(null);
mCameraManager.getCamera().stopPreview();
mCameraManager.getCamera().release();
mCameraManager.closeDriver();
}
// Called when camera take a frame
#Override
public void onPreviewFrame(byte[] data, Camera camera) {
PlanarYUVLuminanceSource source = mCameraManager.buildLuminanceSource(data, mPreviewWidth, mPreviewHeight);
HybridBinarizer hybBin = new HybridBinarizer(source);
BinaryBitmap bitmap = new BinaryBitmap(hybBin);
try {
Result result = mQRCodeReader.decode(bitmap);
// Notify We're found a QRCode
if (mOnQRCodeReadListener != null) {
// Transform resultPoints to View coordinates
PointF[] transformedPoints = transformToViewCoordinates(result.getResultPoints());
mOnQRCodeReadListener.onQRCodeRead(result.getText(), transformedPoints);
}
} catch (ChecksumException e) {
Log.d(TAG, "ChecksumException");
e.printStackTrace();
} catch (NotFoundException e) {
// Notify QR not found
if (mOnQRCodeReadListener != null) {
mOnQRCodeReadListener.QRCodeNotFoundOnCamImage();
}
} catch (FormatException e) {
Log.d(TAG, "FormatException");
e.printStackTrace();
} finally {
mQRCodeReader.reset();
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.d(TAG, "surfaceChanged");
if (mHolder.getSurface() == null){
Log.e(TAG, "Error: preview surface does not exist");
return;
}
//preview_width = width;
//preview_height = height;
mPreviewWidth = mCameraManager.getPreviewSize().x;
mPreviewHeight = mCameraManager.getPreviewSize().y;
mCameraManager.stopPreview();
mCameraManager.getCamera().setPreviewCallback(this);
mCameraManager.getCamera().setDisplayOrientation(90); // Portrait mode
mCameraManager.startPreview();
}
/**
* Transform result to surfaceView coordinates
*
* This method is needed because coordinates are given in landscape camera coordinates.
* Now is working but transform operations aren't very explained
*
* TODO re-write this method explaining each single value
*
* #return a new PointF array with transformed points
*/
private PointF[] transformToViewCoordinates(ResultPoint[] resultPoints) {
PointF[] transformedPoints = new PointF[resultPoints.length];
int index = 0;
if (resultPoints != null){
float previewX = mCameraManager.getPreviewSize().x;
float previewY = mCameraManager.getPreviewSize().y;
float scaleX = this.getWidth()/previewY;
float scaleY = this.getHeight()/previewX;
for (ResultPoint point :resultPoints){
PointF tmppoint = new PointF((previewY- point.getY())*scaleX, point.getX()*scaleY);
transformedPoints[index] = tmppoint;
index++;
}
}
return transformedPoints;
}
/** Check if this device has a camera */
private boolean checkCameraHardware(Context context) {
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){
// this device has a camera
return true;
}
else if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT)){
// this device has a front camera
return true;
}
else if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)){
// this device has any camera
return true;
}
else {
// no camera on this device
return false;
}
}
}
A Surface is part of a producer-consumer buffer queue arrangement. Your application is on the producer end, and for a SurfaceView the system compositor (SurfaceFlinger) is on the consumer end.
A surface can have only one producer at a time. You've established the camera preview as the producer, so it's not possible to also connect a Canvas to perform drawing. You're not seeing failures because you're using new Canvas to create a Canvas in a vacuum -- it's not connected to anything. (Normally you'd use Surface#lockCanvas() to get the Canvas associated with the Surface.)
The surface is a completely separate layer, composited behind everything else by default, which means you can draw on top of it with a custom View. I don't think you need an additional view object though -- I believe you can do it with the 'view' part of the SurfaceView itself, which should have a transparent background. See the "custom drawing" documentation.
If you want to get fancy you can feed the camera preview to OpenGL ES, but that's probably excessive for what you need. (Some examples here.) Also, if you want to learn more about the Android graphics architecture, see this document.
Related
I'm developing an android app which allows user to check a QR Code content and execute something according read result.
In order to improve the performance i'd like to implement 2 methods:
onClickFocus (which allows user to focus the camera when screen is clicked)
turnOn/OFF flash (which allows user to turn on/off the flash)
I've done some digging and figured out that for manage camera and flash I need to be able to manage the Camera as object itself.
And here is where the nightmare begin.
I'm using the follow code to show camera result and track QR codes.
import android.app.FragmentTransaction;
import android.content.Context;
import android.os.Bundle;
import android.os.Vibrator;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.util.SparseArray;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.widget.TextView;
import com.google.android.gms.vision.Detector;
import com.google.android.gms.vision.barcode.Barcode;
import com.google.android.gms.vision.barcode.BarcodeDetector;
import java.io.IOException;
public class MainReadActivity extends AppCompatActivity {
public SurfaceView cameraView;
private TextView barcodeInfo;
public BarcodeDetector barcodeDetector;
public CameraSource cameraSource;
public Vibrator v;
public String textInfo;
public DrawerLayout mDrawerLayout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_read);
v = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout_main);
getSupportFragmentManager().findFragmentById(R.id.drawer_layout_main);
cameraView = (SurfaceView) findViewById(R.id.camera_view);
//barcodeInfo = (TextView) findViewById(R.id.code_info);
barcodeDetector = new BarcodeDetector.Builder(this)
.setBarcodeFormats(Barcode.QR_CODE)
.build();
cameraSource = new CameraSource.Builder(this, barcodeDetector).build();
cameraView.getHolder().addCallback(new SurfaceHolder.Callback() {
#Override
public void surfaceCreated(SurfaceHolder holder) {
try {
cameraSource.start(cameraView.getHolder());
} catch (IOException ie) {
Log.e("CAMERA SOURCE", ie.getMessage());
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
cameraSource.stop();
}
});
barcodeDetector.setProcessor(new Detector.Processor<Barcode>() {
#Override
public void release() {
}
#Override
public void receiveDetections(Detector.Detections<Barcode> detections) {
final SparseArray<Barcode> barcodes = detections.getDetectedItems();
if (barcodes.size() != 0) {
new Runnable() { // Use the post method of the TextView
public void run() {
v.vibrate(500);
// textInfo = barcodes.valueAt(0).displayValue;
MyFragmentDialog newf = new MyFragmentDialog();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, newf);
transaction.addToBackStack("tag");
transaction.commit();
}
};
}
}
});
}
public void onBackPressed() {
// do nothing
}
}
So, I need to get access to Camera, from CameraSource (am I right?!)
Once it is not possible, I tryed to use this CameraSource class from GoogleSamples's git which allows to use setFocusMode method... But unfortunately I wasn't successful.
I also tryed to use API 21, since API 22 no longer supports Camera and CameraPreferences.
I'm pretty sure this is not only my problem, but couldn't find a way to fix it.
Anyone can help?
FIXED:
Just use this CameraSource (github.com/googlesamples/android-vision/blob/master/visionSamples/barcode-reader/app/src/main/java/com/google/android/gms/samples/vision/barcodereader/ui/camera/CameraSource.java) . Yeah, I know, I've suggested it... But this time i fixed my problem! So, if you're going to use this, make sure your compile looks like this:
compile 'com.google.android.gms:play-services:8.1.0'
Initialize these and define them in OnCreate
Camera.Parameters params;
Camera camera;
CameraSource cameraSource;
SurfaceView cameraView;
boolean isFlash = false;
Call changeFlashStatus() method to turn flash ON and call it again to turn flash OFF
public void changeFlashStatus() {
Field[] declaredFields = CameraSource.class.getDeclaredFields();
for (Field field : declaredFields) {
if (field.getType() == Camera.class) {
field.setAccessible(true);
try {
camera = (Camera) field.get(cameraSource);
if (camera != null) {
params = camera.getParameters();
if (!isFlash) {
params.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
flashImage.setColorFilter(getResources().getColor(R.color.yellow));
isFlash = true;
} else {
params.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
flashImage.setColorFilter(getResources().getColor(R.color.greyLight));
isFlash = false;
}
camera.setParameters(params);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
break;
}
}
}
To get the camera to focus, you need a specific area ( Rect ) to pass it to Camera to make focus on that area. So we have to implement onTouchListener() for surfaceView so when we touch the surfaceView we create MotionEvent which is determine where exactly you touch the surfaceView then we can extract Rect from MotionEvent.
Call initCameraFocusListener() in your OnCreate. Safely Call it after the start of the Camera
private void initCameraFocusListener() {
cameraView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
cameraFocus(event, cameraSource, Camera.Parameters.FOCUS_MODE_AUTO);
return false;
}
});
}
private boolean cameraFocus(MotionEvent event, #NonNull CameraSource cameraSource, #NonNull String focusMode) {
Field[] declaredFields = CameraSource.class.getDeclaredFields();
int pointerId = event.getPointerId(0);
int pointerIndex = event.findPointerIndex(pointerId);
// Get the pointer's current position
float x = event.getX(pointerIndex);
float y = event.getY(pointerIndex);
float touchMajor = event.getTouchMajor();
float touchMinor = event.getTouchMinor();
Rect touchRect = new Rect((int)(x - touchMajor / 2), (int)(y - touchMinor / 2), (int)(x + touchMajor / 2), (int)(y + touchMinor / 2));
Rect focusArea = new Rect();
focusArea.set(touchRect.left * 2000 / cameraView.getWidth() - 1000,
touchRect.top * 2000 / cameraView.getHeight() - 1000,
touchRect.right * 2000 / cameraView.getWidth() - 1000,
touchRect.bottom * 2000 / cameraView.getHeight() - 1000);
// Submit focus area to camera
ArrayList<Camera.Area> focusAreas = new ArrayList<Camera.Area>();
focusAreas.add(new Camera.Area(focusArea, 1000));
for (Field field : declaredFields) {
if (field.getType() == Camera.class) {
field.setAccessible(true);
try {
camera = (Camera) field.get(cameraSource);
if (camera != null) {
params = camera.getParameters();
params.setFocusMode(focusMode);
params.setFocusAreas(focusAreas);
camera.setParameters(params);
// Start the autofocus operation
camera.autoFocus(new Camera.AutoFocusCallback() {
#Override
public void onAutoFocus(boolean b, Camera camera) {
// currently set to auto-focus on single touch
}
});
return true;
}
return false;
} catch (IllegalAccessException e) {
e.printStackTrace();
}
break;
}
}
return false;
}
I use this library and it works really well, and its easy to implement
https://github.com/dm77/barcodescanner
Answered here: Google Vision API Samples: Get the CameraSource to Focus
To autofocus use
.setAutoFocusEnabled(true) on your CameraSource.Builder()
In order to display a view with moving objects (from bitmaps) and touch events, I've been using the following code for a SurfaceView in Android. It has alwas worked fine on my development devices, but it turned out that lots of users just see a black box in place of that View. After quite a long time of (unsuccessful) debugging, I've come to the conclusion that it must be Android 4.1 which causes the SurfaceView to stop working correctly.
My development devices are Android 4.0 but users complaining about the black-only SurfaceView have Android 4.1. Checked that with a Android 4.1 emulator - and it's not working there, either.
Can you see what is wrong with the code? Is it caused by the "Project Butter" things in Android 4.1, perhaps?
Of course, I've checked that the Bitmap objects are valid (saved them to SD card in appropriate lines) and all methods for drawing are periodically called as well - everything's normal there.
package com.my.package.util;
import java.util.ArrayList;
import java.util.List;
import com.my.package.Card;
import com.my.package.MyApp;
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.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class MySurface extends SurfaceView implements SurfaceHolder.Callback {
private MyRenderThread mRenderThread;
private volatile List<Card> mGameObjects;
private volatile int mGameObjectsCount;
private int mScreenWidth;
private int mScreenHeight;
private int mGameObjectWidth;
private int mGameObjectHeight;
private int mHighlightedObject = -1;
private Paint mGraphicsPaint;
private Paint mShadowPaint;
private Rect mDrawingRect;
private int mTouchEventAction;
private Bitmap bitmapToDraw;
private int mOnDrawX1;
private BitmapFactory.Options bitmapOptions;
// ...
public MySurface(Context activityContext, AttributeSet attributeSet) {
super(activityContext, attributeSet);
getHolder().addCallback(this);
setFocusable(true); // touch events should be processed by this class
mGameObjects = new ArrayList<Card>();
mGraphicsPaint = new Paint();
mGraphicsPaint.setAntiAlias(true);
mGraphicsPaint.setFilterBitmap(true);
mShadowPaint = new Paint();
mShadowPaint.setARGB(160, 20, 20, 20);
mShadowPaint.setAntiAlias(true);
bitmapOptions = new BitmapFactory.Options();
bitmapOptions.inInputShareable = true;
bitmapOptions.inPurgeable = true;
mDrawingRect = new Rect();
}
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) { }
public void surfaceCreated(SurfaceHolder arg0) {
mScreenWidth = getWidth();
mScreenHeight = getHeight();
mGameObjectHeight = mScreenHeight;
mGameObjectWidth = mGameObjectHeight*99/150;
mCurrentSpacing = mGameObjectWidth;
setDrawingCacheEnabled(true);
mRenderThread = new MyRenderThread(getHolder(), this);
mRenderThread.setRunning(true);
mRenderThread.start();
}
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
mRenderThread.setRunning(false); // stop thread
while (retry) { // wait for thread to close
try {
mRenderThread.join();
retry = false;
}
catch (InterruptedException e) { }
}
}
public void stopThread() {
if (mRenderThread != null) {
mRenderThread.setRunning(false);
}
}
#Override
public void onDraw(Canvas canvas) {
if (canvas != null) {
synchronized (mGameObjects) {
mGameObjectsCount = mGameObjects.size();
canvas.drawColor(Color.BLACK);
if (mGameObjectsCount > 0) {
mCurrentSpacing = Math.min(mScreenWidth/mGameObjectsCount, mGameObjectWidth);
for (int c = 0; c < mGameObjectsCount; c++) {
if (c != mHighlightedObject) {
try {
drawGameObject(canvas, mGameObjects.get(c).getDrawableID(), false, c*mCurrentSpacing, c*mCurrentSpacing+mGameObjectWidth);
}
catch (Exception e) { }
}
}
if (mHighlightedObject > -1) {
mOnDrawX1 = Math.min(mHighlightedObject*mCurrentSpacing, mScreenWidth-mGameObjectWidth);
try {
drawGameObject(canvas, mGameObjects.get(mHighlightedObject).getDrawableID(), true, mOnDrawX1, mOnDrawX1+mGameObjectWidth);
}
catch (Exception e) { }
}
}
}
}
}
private void drawGameObject(Canvas canvas, int resourceID, boolean highlighted, int xLeft, int xRight) {
if (canvas != null && resourceID != 0) {
try {
if (highlighted) {
canvas.drawRect(0, 0, mScreenWidth, mScreenHeight, mShadowPaint);
}
bitmapToDraw = MyApp.gameObjectCacheGet(resourceID);
if (bitmapToDraw == null) {
bitmapToDraw = BitmapFactory.decodeResource(getResources(), resourceID, bitmapOptions);
MyApp.gameObjectCachePut(resourceID, bitmapToDraw);
}
mDrawingRect.set(xLeft, 0, xRight, mGameObjectHeight);
canvas.drawBitmap(bitmapToDraw, null, mDrawingRect, mGraphicsPaint);
}
catch (Exception e) { }
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
synchronized (mRenderThread.getSurfaceHolder()) { // synchronized so that there are no concurrent accesses
mTouchEventAction = event.getAction();
if (mTouchEventAction == MotionEvent.ACTION_DOWN || mTouchEventAction == MotionEvent.ACTION_MOVE) {
if (event.getY() >= 0 && event.getY() < mScreenHeight) {
mTouchEventObject = (int) event.getX()/mCurrentSpacing;
if (mTouchEventObject > -1 && mTouchEventObject < mGameObjectsCount) {
mHighlightedObject = mTouchEventObject;
}
else {
mHighlightedObject = -1;
}
}
else {
mHighlightedObject = -1;
}
}
else if (mTouchEventAction == MotionEvent.ACTION_UP) {
if (mActivityCallback != null && mHighlightedObject > -1 && mHighlightedObject < mGameObjectsCount) {
try {
mActivityCallback.placeObject(mGameObjects.get(mHighlightedObject));
}
catch (Exception e) { }
}
mHighlightedObject = -1;
}
}
return true;
}
// ...
}
And this is the code for the thread that periodically calls the SurfaceView's onDraw():
package com.my.package.util;
import android.graphics.Canvas;
import android.view.SurfaceHolder;
public class MyRenderThread extends Thread {
private SurfaceHolder mSurfaceHolder;
private MySurface mSurface;
private boolean mRunning = false;
public MyRenderThread(SurfaceHolder surfaceHolder, MySurface surface) {
mSurfaceHolder = surfaceHolder;
mSurface = surface;
}
public SurfaceHolder getSurfaceHolder() {
return mSurfaceHolder;
}
public void setRunning(boolean run) {
mRunning = run;
}
#Override
public void run() {
Canvas c;
while (mRunning) {
c = null;
try {
c = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
if (c != null) {
mSurface.onDraw(c);
}
}
}
finally { // when exception is thrown above we may not leave the surface in an inconsistent state
if (c != null) {
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
The SurfaceView is included in my Activity's layout XML:
<com.my.package.util.MySurface
android:id="#+id/my_surface"
android:layout_width="fill_parent"
android:layout_height="#dimen/my_surface_height" />
Then in code it is accessed like this:
MySurface mySurface = (MySurface) findViewById(R.id.my_surface);
Rename your draw method to onDraw2(). Change the thread code to call onDraw2. This way you are not overidding the base class's ondraw. I think you might be getting 2 hits in your onDraw. One from the base class override and one from the thread.
This would explain why setting the z-order helps. You will reverse the order the 2 windows draw therefore avoiding the problem. As to the "why now" part of the question. Since you have the 2 pathways to onDraw I suspect this is unsupported android behavior, so no telling what might happen.
Also I saw you called setDrawingCache enabled. I don't think that is helping you. Usually you would call getDrawingCache at some point. Try removing it if it is not important.
The only other thing I see is that you create the thread and pass the holder in surface created. You might want to take action when surfaceChanged occurs, or at
Least verify that nothing important has changed.
I am trying to learn game development in android. First I am trying to appear and disappear an object on screen using game loop for every five second. But I did not get succeed. I have read different tutorials and forums. I applied all things as in tutorials but still object is drawing continuously. It is not disappearing. I a not getting what I am missing? Please guide me.
The complete code is here:
MainGameActivity.java
package com.example.showandhideobject;
import android.app.Activity;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;
public class MainGameActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(new MainGamePanel(this));
}
}
MainGamePanel .java
package com.example.showandhideobject;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class MainGamePanel extends SurfaceView implements
SurfaceHolder.Callback {
private MainGameThread thread;
private ImageObject image;
// private long gameStartTime;
public MainGamePanel(Context context) {
super(context);
// adding the callback (this) to the surface holder to intercept events
getHolder().addCallback(this);
// create the game loop thread
thread = new MainGameThread(getHolder(), this);
Bitmap imageBitMap = BitmapFactory.decodeResource(getResources(),
R.drawable.rose);
image = new ImageObject(imageBitMap, 100, 150);
image.setAppeared(false);
image.setDisappearTime(System.currentTimeMillis());
// make the GamePanel focusable so it can handle events
setFocusable(true);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
// at this point the surface is created and
// we can safely start the game loop
thread.setRunning(true);
thread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
public void update() {
Log.i("Image Status::::::::::::: ",
Boolean.valueOf(image.isAppeared()).toString());
if (!image.isAppeared()
&& System.currentTimeMillis() - image.getDisappearTime() >= 5000) {
Log.i("Image Object::::::: ", "Showing");
image.setAppeared(true);
image.setAppearTime(System.currentTimeMillis());
}
if (image.isAppeared()
&& (System.currentTimeMillis() - image.getAppearTime() >= 5000)) {
Log.i("Image Object::::::: ", "Not Showing");
image.setAppeared(false);
image.setDisappearTime(System.currentTimeMillis());
}
}
public void render(Canvas canvas) {
if (image.isAppeared()) {
image.draw(canvas);
}
}
}
MainGameThread.java
package com.example.showandhideobject;
import android.graphics.Canvas;
import android.util.Log;
import android.view.SurfaceHolder;
public class MainGameThread extends Thread {
// Surface holder that can access the physical surface
private SurfaceHolder surfaceHolder;
// The actual view that handles inputs
// and draws to the surface
private MainGamePanel gamePanel;
// flag to hold game state
private boolean running;
public boolean isRunning() {
return running;
}
public void setRunning(boolean running) {
this.running = running;
}
public MainGameThread(SurfaceHolder surfaceHolder, MainGamePanel gamePanel) {
super();
this.surfaceHolder = surfaceHolder;
this.gamePanel = gamePanel;
}
#Override
public void run() {
Canvas canvas;
while (isRunning()) {
canvas = null;
// try locking the canvas for exclusive pixel editing
// in the surface
try {
canvas = this.surfaceHolder.lockCanvas();
synchronized (surfaceHolder) {
Log.i("With in :::::::::", "Game Loop");
// update game state
gamePanel.update();
// render state to the screen and draw the canvas on the
// panel
gamePanel.render(canvas);
// gamePanel.onDraw(canvas);
}
} finally {
// in case of an exception the surface is not left in an
// inconsistent state
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
} // end finally
}
}
}
ImageObject.java
package com.example.showandhideobject;
import android.graphics.Bitmap;
import android.graphics.Canvas;
public class ImageObject {
private Bitmap bitmap; // the actual bitmap
private int x; // the X coordinate
private int y; // the Y coordinate
private boolean isAppeared;
private long appearTime;
private long disappearTime;
// Constructor for this class
public ImageObject(Bitmap bitmap, int x, int y) {
this.bitmap = bitmap;
this.x = x;
this.y = y;
}
public Bitmap getBitmap() {
return bitmap;
}
public void setBitmap(Bitmap bitmap) {
this.bitmap = bitmap;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public boolean isAppeared() {
return isAppeared;
}
public void setAppeared(boolean isAppeared) {
this.isAppeared = isAppeared;
}
public long getAppearTime() {
return appearTime;
}
public void setAppearTime(long appearTime) {
this.appearTime = appearTime;
}
public long getDisappearTime() {
return disappearTime;
}
public void setDisappearTime(long disappearTime) {
this.disappearTime = disappearTime;
}
/* Method to draw images on Canvas */
public void draw(Canvas canvas) {
canvas.drawBitmap(bitmap, x - (bitmap.getWidth() / 2),
y - (bitmap.getHeight() / 2), null);
}
}
in this part
if (image.isAppeared()) {
image.draw(canvas);
}
you never clear your canvas. What you are doing is actually drawing your image over and over on the same spot.
You probably need to redraw a background in cas isAppeared() is false
Edit
you can also use canvas.save() before drawing the image, and canvas.restore() when you don't want the image anymore.
Don't try to optimise too early, game rendering is usually inefficient as almost always most of the screen is expected to change.
Loop should be:
always draw the background to canvas
always draw all game objects to the canvas, let them decide if they are visible or not which will simplify the MainGamePanel class
finally always display canvas (by copying to the image as you are doing)
To expand on point 2:
/* Method to draw images on Canvas */
public void draw(Canvas canvas) {
if(!isAppeared) return; //let the object decide when it should be drawn
canvas.drawBitmap(bitmap, x - (bitmap.getWidth() / 2),
y - (bitmap.getHeight() / 2), null);
}
Change the method render in MainGamePanel.java to
if (image.isAppeared() && canvas != null) {
image.draw(canvas);
}
I am using:
Android 4.0.3
OpenCV 2.4.2
Samsung Galaxy S2
The face-detection example (from the opencv 2.4.2) is working perfectly.
But now, I would like to create a custom layout and actually work with just the data extracted from face detection and build a game on it. Not necessarily having the FdView surface taking the entire screen.
I have done these modifications below, but just a black screen is displayed. Nothing appears on the screen.
Added a fd.xml layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<org.opencv.samples.fd.FdView android:id="#+id/FdView"
android:layout_width="640dp"
android:layout_height="480dp"
android:visibility="visible"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FF0000"
android:text="hi"/>
Modified the baseLoaderCallback of FdActivity.java:
private BaseLoaderCallback mOpenCVCallBack = new BaseLoaderCallback(this) {
#Override
public void onManagerConnected(int status) {
switch (status) {
case LoaderCallbackInterface.SUCCESS:
{
Log.i(TAG, "OpenCV loaded successfully");
// Load native libs after OpenCV initialization
System.loadLibrary("detection_based_tracker");
//EXPERIMENT
setContentView(R.layout.fd);
FdView surface = (FdView) (findViewById(R.id.FdView));
surface = mView;
// Create and set View
mView = new FdView(mAppContext);
mView.setDetectorType(mDetectorType);
mView.setMinFaceSize(0.2f);
//setContentView(mView);
// Check native OpenCV camera
if( !mView.openCamera() ) {
AlertDialog ad = new AlertDialog.Builder(mAppContext).create();
ad.setCancelable(false); // This blocks the 'BACK' button
ad.setMessage("Fatal error: can't open camera!");
ad.setButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
finish();
}
});
ad.show();
}
} break;
default:
{
super.onManagerConnected(status);
} break;
}
}
};
Added constructors in FdView.java:
public FdView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
public FdView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
Added constructors in SampleCvViewBase.java:
public SampleCvViewBase(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
public SampleCvViewBase(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
I have precisely the same issue. Also trying to figure it out. I'm trying to display the image on a SurfaceView that doesn't take the whole screen. And with that I read that you can't have your Camera handler class and linked SurfaceView in different classes. So smashed everything into one.
So, at the moment I have the camera displaying on the SurfaceView, and copying the frame data to a mFrame variable. Basically just struggling to get the mFrame processed (in the multi-threading, Run(), function) and showing the result on the SurfaceView.
This is the code I have, if you think it would help: (excuse the formatting as my code is also a work in progress)
package org.opencv.samples.tutorial3;
import java.io.IOException;
import java.util.List;
import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ImageFormat;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.hardware.Camera;
import android.hardware.Camera.PreviewCallback;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.Window;
import android.widget.TextView;
public class Sample3Native extends Activity implements SurfaceHolder.Callback,Runnable{
//Camera variables
private Camera cam;
private boolean previewing = false;
private SurfaceHolder mHolder;
private SurfaceView mViewer;
private int mFrameWidth;
private int mFrameHeight;
private byte[] mFrame;
private boolean mThreadRun;
private byte[] mBuffer;
Sample3View viewclass;
TextView text;
int value = 0;
//==========
int framecount = 0;
private BaseLoaderCallback mOpenCVCallBack = new BaseLoaderCallback(this) {
#Override
public void onManagerConnected(int status) {
switch (status) {
case LoaderCallbackInterface.SUCCESS:
{
// Load native library after(!) OpenCV initialization
System.loadLibrary("native_sample");
//constructor for viewclass that works on frames
viewclass = new Sample3View();
//setContentView(mView);
//OpenCam();
//setContentView(R.layout.main);
// Create and set View
CameraConstruct();
Camopen();
} break;
default:
{
super.onManagerConnected(status);
} break;
}
}
};
public Sample3Native()
{}
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.main);
OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_2, this, mOpenCVCallBack);
}
//Camera construction
public void CameraConstruct()
{
mViewer = (SurfaceView)findViewById(R.id.camera_view);
text = (TextView)findViewById(R.id.text);
mHolder = mViewer.getHolder();
mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
//calls camera screen setup when screen surface changes
public void surfaceChanged(SurfaceHolder holder, int format, int width,int height)
{
CamStartDisplay();
}
public void Camclose()
{
if(cam != null && previewing)
{
cam.setPreviewCallback(null);
cam.stopPreview();
cam.release();
cam = null;
previewing = false;
}
mThreadRun = false;
viewclass.PreviewStopped();
}
//only open camera, and get frame data
public void Camopen()
{
if(!previewing){
cam = Camera.open();
//rotate display
cam.setDisplayOrientation(90);
if (cam != null)
{
//copy viewed frame
cam.setPreviewCallbackWithBuffer(new PreviewCallback()
{
public void onPreviewFrame(byte[] data, Camera camera)
{
synchronized (this)
{
System.arraycopy(data, 0, mFrame, 0, data.length);
this.notify();
}
//text.setText(Integer.toString(value++));
camera.addCallbackBuffer(mBuffer);
}
});
}
}//if not previewing
}
//start preview
public void CamStartDisplay()
{
synchronized (this)
{
if(cam != null)
{
//stop previewing till after settings is changed
if(previewing == true)
{
cam.stopPreview();
previewing = false;
}
Camera.Parameters p = cam.getParameters();
for(Camera.Size s : p.getSupportedPreviewSizes())
{
p.setPreviewSize(s.width, s.height);
mFrameWidth = s.width;
mFrameHeight = s.height;
break;
}
p.setPreviewSize(mFrameWidth, mFrameHeight);
List<String> FocusModes = p.getSupportedFocusModes();
if (FocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO))
{
p.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
}
cam.setParameters(p);
//set the width and height for processing
viewclass.setFrame(mFrameWidth, mFrameHeight);
int size = mFrameWidth*mFrameHeight;
size = size * ImageFormat.getBitsPerPixel(p.getPreviewFormat()) / 8;
mBuffer = new byte[size];
mFrame = new byte [size];
cam.addCallbackBuffer(mBuffer);
viewclass.PreviewStarted(mFrameWidth, mFrameHeight);
//start display streaming
try
{
//cam.setPreviewDisplay(null);
cam.setPreviewDisplay(mHolder);
cam.startPreview();
previewing = true;
}
catch (IOException e)
{
e.printStackTrace();
}
}//end of if cam != null
}//synchronising
}
//thread gets started when the screen surface is created
public void surfaceCreated(SurfaceHolder holder) {
//Camopen();
//CamStartDisplay();
(new Thread(this)).start();
}
//called when the screen surface is stopped
public void surfaceDestroyed(SurfaceHolder holder)
{
Camclose();
}
//this is function that is run by thread
public void run()
{
mThreadRun = true;
while (mThreadRun)
{
//text.setText(Integer.toString(value++));
Bitmap bmp = null;
synchronized (this)
{
try
{
this.wait();
bmp = viewclass.processFrame(mFrame);
}
catch (InterruptedException e) {}
}
if (bmp != null)
{
Canvas canvas = mHolder.lockCanvas();
if (canvas != null)
{
canvas.drawBitmap(bmp, (canvas.getWidth() - mFrameWidth) / 2, (canvas.getHeight() - mFrameHeight) / 2, null);
mHolder.unlockCanvasAndPost(canvas);
}
}//if bmp != null
}// while thread in run
}
}//end class
Sample3View as used in this class just includes the processFrame function as such:
package org.opencv.samples.tutorial3;
import android.content.Context;
import android.graphics.Bitmap;
import android.widget.TextView;
class Sample3View {
private int mFrameSize;
private Bitmap mBitmap;
private int[] mRGBA;
private int frameWidth;
private int frameHeight;
private int count = 0;
Sample3Native samp;
//constructor
public Sample3View()
{
}
public void setFrame(int width,int height)
{
frameWidth = width;
frameHeight = height;
}
public void PreviewStarted(int previewWidtd, int previewHeight) {
mFrameSize = previewWidtd * previewHeight;
mRGBA = new int[mFrameSize];
mBitmap = Bitmap.createBitmap(previewWidtd, previewHeight, Bitmap.Config.ARGB_8888);
}
public void PreviewStopped() {
if(mBitmap != null) {
mBitmap.recycle();
mBitmap = null;
}
mRGBA = null;
}
public Bitmap processFrame(byte[] data) {
int[] rgba = mRGBA;
FindFeatures(frameWidth, frameHeight, data, rgba);
Bitmap bmp = mBitmap;
bmp.setPixels(rgba, 0, frameWidth, 0, 0, frameWidth, frameHeight);
//samp.setValue(count++);
return bmp;
}
public native void FindFeatures(int width, int height, byte yuv[], int[] rgba);
}
So yeah, hope this helps. If I get the complete solution working, I'll post that also. Also post your stuff if you get the solution first please! Enjoy
Sry not a real answer (yet) but also tried to make a custom layout in opencv 2.4.2
i have this perfectly working solution for 2.4.0 if i remember it right it was enough to add the instructors.. but it doesn't work with 2.4.2
i'll try to figure smthg out and let you guys know.
I met the same problem that I wanted to create a custom view using layout. OpenCV 2.4.2 seems not to offer this function.
OpenCV 2.4.3 has the function, but its tutorial doesn't say so (it uses the old example from OpenCV2.4.2). Its Android samples provide some insights. Finally I found the instruction in OpenCV 2.4.9 documentation.
Hope it helps.
Hah, I figured out one way. You could just simply separate the OpenCV Loader and the custom layout.
Define BaseLoaderCallback mOpenCVCallBack.
private BaseLoaderCallback mOpenCVCallBack = new BaseLoaderCallback(this) {
#Override
public void onManagerConnected(int status) {
switch (status) {
case LoaderCallbackInterface.SUCCESS: {
Log.i(TAG, "OpenCV loaded successfully");
// Load native library after(!) OpenCV initialization
System.loadLibrary("native_sample");
}
break;
default: {
super.onManagerConnected(status);
}
break;
}
}
};
In OnCreat, build your custom layout, load the OpenCv Loader,
public void onCreate(Bundle savedInstanceState) {
Log.i(TAG, "onCreate");
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
// /////////////////////////////////////////////////////////////////////
// // begin:
// // Create and set View
setContentView(R.layout.main);
mView = (Sample3View) findViewById(R.id.sample3view);
mcameraButton = (ImageView) findViewById(R.id.cameraButton);
if (!OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_2, this, mOpenCVCallBack)) {
Log.e(TAG, "Cannot connect to OpenCV Manager");
}
}
Just that!
I did that, and it worked very well.
It's a bit cheeky - but I was wondering if anyone could tell me what's wrong below.
This is messing around trying to understand android - not "real" code.
It's a surfaceView which is laid out in the main activity layout.
It works - until the phone's "off" button is tapped (sleep) and woken up again. Upon waking up, it goes crazy and android produces a "Force Close" diaglog.
I've been trying to follow the path with LogCat, but for some reason, some messages get dropped - OR - the path I think is being followed, isn't.
eg - on putting the phone to sleep, I will get surfaceDestroyed called (seems reasonable) but on waking, I do not get a surfaceCreated().
The basic logic is: the surfaceView creates a thread which paints the system time in seconds as text. That's it.
I've got a real app I'd like to write - but until I really understand the basics, that won't happen. I've been through a fair number of tutorials too.
Any pointers most gratefully recieved :)
Cheers
Tim
package net.dionic.android.bouncingsquid;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.lang.System;
public class WidgetSeconds extends SurfaceView implements SurfaceHolder.Callback {
private class CanvasThread extends Thread {
private SurfaceHolder _surfaceHolder;
private WidgetSeconds _surfaceView;
private boolean _run = false;
public CanvasThread(SurfaceHolder surfaceHolder, WidgetSeconds surfaceView) {
Log.i("WidgetSecs.CanvasThread", "constructor");
_surfaceHolder = surfaceHolder;
_surfaceView = surfaceView;
}
public void setRunning(boolean run) {
_run = run;
}
#Override
public void run() {
Canvas c;
while (_run) {
c = null;
try {
c = _surfaceHolder.lockCanvas(null);
synchronized (_surfaceHolder) {
_surfaceView.onDraw(c);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} finally {
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the Surface in an
// inconsistent state
if (c != null) {
_surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
private CanvasThread canvasthread;
public void Initalise() {
Log.i("WidgetSecs", "Initialise");
}
public WidgetSeconds(Context context, AttributeSet attrs) {
super(context, attrs);
Log.i("WidgetSecs", "constructor");
this.Initalise();
getHolder().addCallback(this);
setFocusable(true);
}
#Override
public void onDraw(Canvas canvas) {
Paint textPaint;
canvas.drawColor(Color.GRAY);
textPaint = new Paint();
textPaint.setTextSize(32);
canvas.drawText(System.currentTimeMillis()/1000 + " S", 10, 50, textPaint);
canvas.restore();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
Log.i("WidgetSecs", "surfaceChanged");
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
Log.i("WidgetSecs", "surfaceCreated");
Log.i("WidgetSecs.CanvasThread", "Thread create");
canvasthread = new CanvasThread(getHolder(), this);
canvasthread.setRunning(true);
canvasthread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.i("WidgetSecs", "surfaceDestroyed");
boolean retry = true;
while (retry) {
try {
Log.i("WidgetSecs", "Thread destroyed");
canvasthread.join();
canvasthread = null;
retry = false;
} catch (InterruptedException e) {
Log.i("WidgetSecs", "Thread join failed");
// we will try it again and again...
}
}
}
}