From be2c4ddbdd4c2e1ac7fe59bdf55f080832a43fc0 Mon Sep 17 00:00:00 2001 From: Andrey Kamaev Date: Sat, 16 Jul 2011 22:31:47 +0000 Subject: [PATCH] Android: * Added manual port for getTextSize; * Fixed bugs in "native camera" sample; * Added 15-puzzle sample. --- modules/imgproc/doc/filtering.rst | 2 +- modules/java/gen_java.py | 68 ++++++- samples/android/15-puzzle/.classpath | 8 + samples/android/15-puzzle/.project | 40 ++++ .../15-puzzle/.settings/org.eclipse.jdt.core.prefs | 5 + samples/android/15-puzzle/AndroidManifest.xml | 24 +++ samples/android/15-puzzle/default.properties | 3 + samples/android/15-puzzle/res/drawable/icon.png | Bin 0 -> 5760 bytes samples/android/15-puzzle/res/values/strings.xml | 4 + .../opencv/samples/puzzle15/SampleViewBase.java | 108 ++++++++++ .../opencv/samples/puzzle15/puzzle15Activity.java | 50 +++++ .../org/opencv/samples/puzzle15/puzzle15View.java | 221 +++++++++++++++++++++ .../opencv/samples/tutorial2/SampleViewBase.java | 58 +++--- 13 files changed, 560 insertions(+), 31 deletions(-) create mode 100644 samples/android/15-puzzle/.classpath create mode 100644 samples/android/15-puzzle/.project create mode 100644 samples/android/15-puzzle/.settings/org.eclipse.jdt.core.prefs create mode 100644 samples/android/15-puzzle/AndroidManifest.xml create mode 100644 samples/android/15-puzzle/default.properties create mode 100644 samples/android/15-puzzle/res/drawable/icon.png create mode 100644 samples/android/15-puzzle/res/values/strings.xml create mode 100644 samples/android/15-puzzle/src/org/opencv/samples/puzzle15/SampleViewBase.java create mode 100644 samples/android/15-puzzle/src/org/opencv/samples/puzzle15/puzzle15Activity.java create mode 100644 samples/android/15-puzzle/src/org/opencv/samples/puzzle15/puzzle15View.java diff --git a/modules/imgproc/doc/filtering.rst b/modules/imgproc/doc/filtering.rst index 845e35b..819514b 100644 --- a/modules/imgproc/doc/filtering.rst +++ b/modules/imgproc/doc/filtering.rst @@ -1332,7 +1332,7 @@ pyrMeanShiftFiltering --------------------- Performs initial step of meanshift segmentation of an image. -.. ocv:function: void pyrMeanShiftFiltering( InputArray src, OutputArray dst, double sp, double sr, int maxLevel=1, TermCriteria termcrit=TermCriteria(TermCriteria::MAX_ITER+TermCriteria::EPS,5,1) ) +.. ocv:function:: void pyrMeanShiftFiltering( InputArray src, OutputArray dst, double sp, double sr, int maxLevel=1, TermCriteria termcrit=TermCriteria(TermCriteria::MAX_ITER+TermCriteria::EPS,5,1) ) .. ocv:pyfunction:: cv2.pyrMeanShiftFiltering(src, sp, sr[, dst[, maxLevel[, termcrit]]]) -> dst diff --git a/modules/java/gen_java.py b/modules/java/gen_java.py index ad73314..3d3cd00 100644 --- a/modules/java/gen_java.py +++ b/modules/java/gen_java.py @@ -176,7 +176,7 @@ type_dict = { } -setManualFunctions=set(['minMaxLoc']) +setManualFunctions=set(['minMaxLoc', 'getTextSize']) class ConstInfo(object): def __init__(self, cname, name, val): @@ -420,7 +420,14 @@ class JavaWrapperGenerator(object): return minMaxLoc(src, null); } private static native double[] n_minMaxLocManual(long src_nativeObj, long mask_nativeObj); - + + //javadoc:getTextSize(text, fontFace, fontScale, thickness, baseLine) + public static Size getTextSize(String text, int fontFace, double fontScale, int thickness, int[] baseLine) { + assert(baseLine == null || baseLine.length == 1); + Size retVal = new Size(n_getTextSize(text, fontFace, fontScale, thickness, baseLine)); + return retVal; + } + private static native double[] n_getTextSize(String text, int fontFace, double fontScale, int thickness, int[] baseLine); """ ) @@ -558,7 +565,62 @@ JNIEXPORT jdoubleArray JNICALL Java_org_opencv_core_n_1minMaxLocManual LOGD("core::n_1minMaxLoc() catched unknown exception (...)"); #endif // DEBUG jclass je = env->FindClass("java/lang/Exception"); - env->ThrowNew(je, "Unknown exception in JNI code {$module::$fname()}"); + env->ThrowNew(je, "Unknown exception in JNI code {core::minMaxLoc()}"); + return NULL; + } +} + +JNIEXPORT jdoubleArray JNICALL Java_org_opencv_core_n_1getTextSize + (JNIEnv* env, jclass cls, jstring text, jint fontFace, jdouble fontScale, jint thickness, jintArray baseLine) +{ + try { +#ifdef DEBUG + LOGD("core::n_1getTextSize()"); +#endif // DEBUG + + jdoubleArray result; + result = env->NewDoubleArray(2); + if (result == NULL) { + return NULL; /* out of memory error thrown */ + } + + const char* utf_text = env->GetStringUTFChars(text, 0); + std::string n_text( utf_text ? utf_text : "" ); + env->ReleaseStringUTFChars(text, utf_text); + + int _baseLine; + int* pbaseLine = 0; + + if (baseLine != NULL) + pbaseLine = &_baseLine; + + cv::Size rsize = cv::getTextSize(n_text, (int)fontFace, (double)fontScale, (int)thickness, pbaseLine); + + jdouble fill[2]; + fill[0]=rsize.width; + fill[1]=rsize.height; + + env->SetDoubleArrayRegion(result, 0, 2, fill); + + if (baseLine != NULL) + env->SetIntArrayRegion(baseLine, 0, 1, pbaseLine); + + return result; + + } catch(cv::Exception e) { +#ifdef DEBUG + LOGD("core::n_1getTextSize() catched cv::Exception: %s", e.what()); +#endif // DEBUG + jclass je = env->FindClass("org/opencv/CvException"); + if(!je) je = env->FindClass("java/lang/Exception"); + env->ThrowNew(je, e.what()); + return NULL; + } catch (...) { +#ifdef DEBUG + LOGD("core::n_1getTextSize() catched unknown exception (...)"); +#endif // DEBUG + jclass je = env->FindClass("java/lang/Exception"); + env->ThrowNew(je, "Unknown exception in JNI code {core::getTextSize()}"); return NULL; } } diff --git a/samples/android/15-puzzle/.classpath b/samples/android/15-puzzle/.classpath new file mode 100644 index 0000000..87ce3fa --- /dev/null +++ b/samples/android/15-puzzle/.classpath @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/samples/android/15-puzzle/.project b/samples/android/15-puzzle/.project new file mode 100644 index 0000000..415bed2 --- /dev/null +++ b/samples/android/15-puzzle/.project @@ -0,0 +1,40 @@ + + + 15-puzzle + + + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + + + + OpenCV-2.3.1_src + 2 + _android_OpenCV_2_3_1_df28900a/src + + + diff --git a/samples/android/15-puzzle/.settings/org.eclipse.jdt.core.prefs b/samples/android/15-puzzle/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..d995784 --- /dev/null +++ b/samples/android/15-puzzle/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,5 @@ +#Wed Jun 29 04:36:40 MSD 2011 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 +org.eclipse.jdt.core.compiler.compliance=1.5 +org.eclipse.jdt.core.compiler.source=1.5 diff --git a/samples/android/15-puzzle/AndroidManifest.xml b/samples/android/15-puzzle/AndroidManifest.xml new file mode 100644 index 0000000..30003c1 --- /dev/null +++ b/samples/android/15-puzzle/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/android/15-puzzle/default.properties b/samples/android/15-puzzle/default.properties new file mode 100644 index 0000000..fb3ea1f --- /dev/null +++ b/samples/android/15-puzzle/default.properties @@ -0,0 +1,3 @@ +android.library.reference.1=../../../android/build +# Project target. +target=android-8 diff --git a/samples/android/15-puzzle/res/drawable/icon.png b/samples/android/15-puzzle/res/drawable/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4e828bafd86aae740c6d4da0af6cc67653b535a5 GIT binary patch literal 5760 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 V9}r2j|9cq#=xN{7s@8Of{2yTISoHt^ literal 0 HcmV?d00001 diff --git a/samples/android/15-puzzle/res/values/strings.xml b/samples/android/15-puzzle/res/values/strings.xml new file mode 100644 index 0000000..99e7315 --- /dev/null +++ b/samples/android/15-puzzle/res/values/strings.xml @@ -0,0 +1,4 @@ + + + 15-puzzle + diff --git a/samples/android/15-puzzle/src/org/opencv/samples/puzzle15/SampleViewBase.java b/samples/android/15-puzzle/src/org/opencv/samples/puzzle15/SampleViewBase.java new file mode 100644 index 0000000..b85325e --- /dev/null +++ b/samples/android/15-puzzle/src/org/opencv/samples/puzzle15/SampleViewBase.java @@ -0,0 +1,108 @@ +package org.opencv.samples.puzzle15; + +import java.util.List; + +import org.opencv.*; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.util.Log; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +public abstract class SampleViewBase extends SurfaceView implements SurfaceHolder.Callback, Runnable { + private static final String TAG = "Sample::SurfaceView"; + + private SurfaceHolder mHolder; + private VideoCapture mCamera; + + public SampleViewBase(Context context) { + super(context); + mHolder = getHolder(); + mHolder.addCallback(this); + Log.i(TAG, "Instantiated new " + this.getClass()); + } + + public void surfaceChanged(SurfaceHolder _holder, int format, int width, int height) { + Log.i(TAG, "surfaceCreated"); + synchronized (this) { + if (mCamera != null && mCamera.isOpened()) { + Log.i(TAG, "before mCamera.getSupportedPreviewSizes()"); + List sizes = mCamera.getSupportedPreviewSizes(); + Log.i(TAG, "after mCamera.getSupportedPreviewSizes()"); + int mFrameWidth = width; + int mFrameHeight = height; + + // selecting optimal camera preview size + { + double minDiff = Double.MAX_VALUE; + for (Size size : sizes) { + if (Math.abs(size.height - height) < minDiff) { + mFrameWidth = (int) size.width; + mFrameHeight = (int) size.height; + minDiff = Math.abs(size.height - height); + } + } + } + + mCamera.set(highgui.CV_CAP_PROP_FRAME_WIDTH, mFrameWidth); + mCamera.set(highgui.CV_CAP_PROP_FRAME_HEIGHT, mFrameHeight); + } + } + } + + public void surfaceCreated(SurfaceHolder holder) { + Log.i(TAG, "surfaceCreated"); + mCamera = new VideoCapture(highgui.CV_CAP_ANDROID); + if (mCamera.isOpened()) { + (new Thread(this)).start(); + } else { + mCamera.release(); + mCamera = null; + Log.e(TAG, "Failed to open native camera"); + } + } + + public void surfaceDestroyed(SurfaceHolder holder) { + Log.i(TAG, "surfaceDestroyed"); + if (mCamera != null) { + synchronized (this) { + mCamera.release(); + mCamera = null; + } + } + } + + protected abstract Bitmap processFrame(VideoCapture capture); + + public void run() { + Log.i(TAG, "Starting processing thread"); + while (true) { + Bitmap bmp = null; + + synchronized (this) { + if (mCamera == null) + break; + + if (!mCamera.grab()) { + Log.e(TAG, "mCamera.grab() failed"); + break; + } + + bmp = processFrame(mCamera); + } + + if (bmp != null) { + Canvas canvas = mHolder.lockCanvas(); + if (canvas != null) { + canvas.drawBitmap(bmp, (canvas.getWidth() - bmp.getWidth()) / 2, (canvas.getHeight() - bmp.getHeight()) / 2, null); + mHolder.unlockCanvasAndPost(canvas); + } + bmp.recycle(); + } + } + + Log.i(TAG, "Finishing processing thread"); + } +} \ No newline at end of file diff --git a/samples/android/15-puzzle/src/org/opencv/samples/puzzle15/puzzle15Activity.java b/samples/android/15-puzzle/src/org/opencv/samples/puzzle15/puzzle15Activity.java new file mode 100644 index 0000000..2444f57 --- /dev/null +++ b/samples/android/15-puzzle/src/org/opencv/samples/puzzle15/puzzle15Activity.java @@ -0,0 +1,50 @@ +package org.opencv.samples.puzzle15; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.Window; + +public class puzzle15Activity extends Activity { + private static final String TAG = "Sample::Activity"; + + private MenuItem mItemNewGame; + private MenuItem mItemToggleNumbers; + private puzzle15View mView; + + public puzzle15Activity() { + Log.i(TAG, "Instantiated new " + this.getClass()); + } + + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + Log.i(TAG, "onCreate"); + super.onCreate(savedInstanceState); + requestWindowFeature(Window.FEATURE_NO_TITLE); + mView = new puzzle15View(this); + setContentView(mView); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + Log.i(TAG, "onCreateOptionsMenu"); + mItemNewGame = menu.add("Start new game"); + mItemToggleNumbers = menu.add("Show/hide tile numbers"); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + Log.i(TAG, "Menu Item selected " + item); + if (item == mItemNewGame) { + synchronized (mView) { + mView.startNewGame(); + } + } else if (item == mItemToggleNumbers) + mView.tolggleTileNumbers(); + return true; + } +} \ No newline at end of file diff --git a/samples/android/15-puzzle/src/org/opencv/samples/puzzle15/puzzle15View.java b/samples/android/15-puzzle/src/org/opencv/samples/puzzle15/puzzle15View.java new file mode 100644 index 0000000..1f6abbc --- /dev/null +++ b/samples/android/15-puzzle/src/org/opencv/samples/puzzle15/puzzle15View.java @@ -0,0 +1,221 @@ +package org.opencv.samples.puzzle15; + +import org.opencv.*; + +import android.content.Context; +import android.graphics.Bitmap; +import android.view.MotionEvent; +import android.view.SurfaceHolder; +import android.view.View; +import android.view.View.OnTouchListener; + +public class puzzle15View extends SampleViewBase implements OnTouchListener { + private Mat mRgba; + private Mat mRgba15; + private Mat[] mCells; + private Mat[] mCells15; + private int[] mIndexses; + private int[] mTextWidths; + private int[] mTextHeights; + private boolean mShowTileNumbers = true; + + int gridSize = 4; + int gridArea = gridSize * gridSize; + int gridEmptyIdx = gridArea - 1; + + public puzzle15View(Context context) { + super(context); + setOnTouchListener(this); + + mTextWidths = new int[gridArea]; + mTextHeights = new int[gridArea]; + for (int i = 0; i < gridArea; i++) { + Size s = core.getTextSize(Integer.toString(i + 1), 3/* CV_FONT_HERSHEY_COMPLEX */, 1, 2, null); + mTextHeights[i] = (int) s.height; + mTextWidths[i] = (int) s.width; + } + } + + @Override + public void surfaceChanged(SurfaceHolder _holder, int format, int width, int height) { + super.surfaceChanged(_holder, format, width, height); + synchronized (this) { + // initialize Mat before usage + mRgba = new Mat(); + } + } + + public static void shuffle(int[] array) { + for (int i = array.length; i > 1; i--) { + int temp = array[i - 1]; + int randIx = (int) (Math.random() * i); + array[i - 1] = array[randIx]; + array[randIx] = temp; + } + } + + public boolean isPuzzleSolvable() { + if (gridSize != 4) + return true; + + int sum = 0; + for (int i = 0; i < gridArea; i++) { + if (mIndexses[i] == gridEmptyIdx) + sum += (i / gridSize) + 1; + else { + int smaller = 0; + for (int j = i + 1; j < gridArea; j++) { + if (mIndexses[j] < mIndexses[i]) + smaller++; + } + sum += smaller; + } + } + + return sum % 2 == 0; + } + + private void createPuzzle(int cols, int rows) { + mCells = new Mat[gridArea]; + mCells15 = new Mat[gridArea]; + + mRgba15 = new Mat(rows, cols, mRgba.type()); + mIndexses = new int[gridArea]; + + for (int i = 0; i < gridSize; i++) { + for (int j = 0; j < gridSize; j++) { + int k = i * gridSize + j; + mIndexses[k] = k; + mCells[k] = mRgba.submat(i * rows / gridSize, (i + 1) * rows / gridSize, j * cols / gridSize, (j + 1) * cols / gridSize); + mCells15[k] = mRgba15.submat(i * rows / gridSize, (i + 1) * rows / gridSize, j * cols / gridSize, (j + 1) * cols / gridSize); + } + } + + startNewGame(); + } + + public void startNewGame() { + do { + shuffle(mIndexses); + } while (!isPuzzleSolvable()); + } + + public void tolggleTileNumbers() { + mShowTileNumbers = !mShowTileNumbers; + } + + @Override + protected Bitmap processFrame(VideoCapture capture) { + capture.retrieve(mRgba, highgui.CV_CAP_ANDROID_COLOR_FRAME_RGBA); + int cols = mRgba.cols(); + int rows = mRgba.rows(); + + if (mCells == null) + createPuzzle(cols, rows); + + // copy shuffled tiles + for (int i = 0; i < gridArea; i++) { + int idx = mIndexses[i]; + if (idx == gridEmptyIdx) + mCells15[i].setTo(new Scalar(0x33, 0x33, 0x33, 0xFF)); + else { + mCells[idx].copyTo(mCells15[i]); + if (mShowTileNumbers) { + core.putText(mCells15[i], Integer.toString(1 + idx), new Point((cols / gridSize - mTextWidths[idx]) / 2, + (rows / gridSize + mTextHeights[idx]) / 2), 3/* CV_FONT_HERSHEY_COMPLEX */, 1, new Scalar(255, 0, 0, 255), 2); + } + } + } + + drawGrid(cols, rows); + + Bitmap bmp = Bitmap.createBitmap(cols, rows, Bitmap.Config.ARGB_8888); + if (android.MatToBitmap(mRgba15, bmp)) + return bmp; + + bmp.recycle(); + return null; + } + + private void drawGrid(int cols, int rows) { + for (int i = 1; i < gridSize; i++) { + core.line(mRgba15, new Point(0, i * rows / gridSize), new Point(cols, i * rows / gridSize), new Scalar(0, 255, 0, 255), 3); + core.line(mRgba15, new Point(i * cols / gridSize, 0), new Point(i * cols / gridSize, rows), new Scalar(0, 255, 0, 255), 3); + } + } + + @Override + public void run() { + super.run(); + + synchronized (this) { + // Explicitly deallocate Mats + if (mCells != null) { + for (Mat m : mCells) + m.dispose(); + } + if (mCells15 != null) { + for (Mat m : mCells15) + m.dispose(); + } + if (mRgba != null) + mRgba.dispose(); + if (mRgba15 != null) + mRgba15.dispose(); + + mRgba = null; + mRgba15 = null; + mCells = null; + mCells15 = null; + mIndexses = null; + } + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + int cols = mRgba.cols(); + int rows = mRgba.rows(); + float xoffset = (getWidth() - cols) / 2; + float yoffset = (getHeight() - rows) / 2; + + float x = event.getX() - xoffset; + float y = event.getY() - yoffset; + + int row = (int) Math.floor(y * gridSize / rows); + int col = (int) Math.floor(x * gridSize / cols); + + if (row < 0 || row >= gridSize || col < 0 || col >= gridSize) + return false; + + int idx = row * gridSize + col; + int idxtoswap = -1; + + // left + if (idxtoswap < 0 && col > 0) + if (mIndexses[idx - 1] == gridEmptyIdx) + idxtoswap = idx - 1; + // right + if (idxtoswap < 0 && col < gridSize - 1) + if (mIndexses[idx + 1] == gridEmptyIdx) + idxtoswap = idx + 1; + // top + if (idxtoswap < 0 && row > 0) + if (mIndexses[idx - gridSize] == gridEmptyIdx) + idxtoswap = idx - gridSize; + // bottom + if (idxtoswap < 0 && row < gridSize - 1) + if (mIndexses[idx + gridSize] == gridEmptyIdx) + idxtoswap = idx + gridSize; + + // swap + if (idxtoswap >= 0) { + synchronized (this) { + int touched = mIndexses[idx]; + mIndexses[idx] = mIndexses[idxtoswap]; + mIndexses[idxtoswap] = touched; + } + } + + return false;// don't need subsequent touch events + } +} diff --git a/samples/android/tutorial-2-opencvcamera/src/org/opencv/samples/tutorial2/SampleViewBase.java b/samples/android/tutorial-2-opencvcamera/src/org/opencv/samples/tutorial2/SampleViewBase.java index 2f5c03a..4725844 100644 --- a/samples/android/tutorial-2-opencvcamera/src/org/opencv/samples/tutorial2/SampleViewBase.java +++ b/samples/android/tutorial-2-opencvcamera/src/org/opencv/samples/tutorial2/SampleViewBase.java @@ -16,7 +16,6 @@ public abstract class SampleViewBase extends SurfaceView implements SurfaceHolde private SurfaceHolder mHolder; private VideoCapture mCamera; - private boolean mThreadRun; public SampleViewBase(Context context) { super(context); @@ -27,27 +26,29 @@ public abstract class SampleViewBase extends SurfaceView implements SurfaceHolde public void surfaceChanged(SurfaceHolder _holder, int format, int width, int height) { Log.i(TAG, "surfaceCreated"); - if (mCamera != null && mCamera.isOpened()) { - Log.i(TAG, "before mCamera.getSupportedPreviewSizes()"); - List sizes = mCamera.getSupportedPreviewSizes(); - Log.i(TAG, "after mCamera.getSupportedPreviewSizes()"); - int mFrameWidth = width; - int mFrameHeight = height; - - // selecting optimal camera preview size - { - double minDiff = Double.MAX_VALUE; - for (Size size : sizes) { - if (Math.abs(size.height - height) < minDiff) { - mFrameWidth = (int) size.width; - mFrameHeight = (int) size.height; - minDiff = Math.abs(size.height - height); + synchronized (this) { + if (mCamera != null && mCamera.isOpened()) { + Log.i(TAG, "before mCamera.getSupportedPreviewSizes()"); + List sizes = mCamera.getSupportedPreviewSizes(); + Log.i(TAG, "after mCamera.getSupportedPreviewSizes()"); + int mFrameWidth = width; + int mFrameHeight = height; + + // selecting optimal camera preview size + { + double minDiff = Double.MAX_VALUE; + for (Size size : sizes) { + if (Math.abs(size.height - height) < minDiff) { + mFrameWidth = (int) size.width; + mFrameHeight = (int) size.height; + minDiff = Math.abs(size.height - height); + } } } - } - mCamera.set(highgui.CV_CAP_PROP_FRAME_WIDTH, mFrameWidth); - mCamera.set(highgui.CV_CAP_PROP_FRAME_HEIGHT, mFrameHeight); + mCamera.set(highgui.CV_CAP_PROP_FRAME_WIDTH, mFrameWidth); + mCamera.set(highgui.CV_CAP_PROP_FRAME_HEIGHT, mFrameHeight); + } } } @@ -65,7 +66,6 @@ public abstract class SampleViewBase extends SurfaceView implements SurfaceHolde public void surfaceDestroyed(SurfaceHolder holder) { Log.i(TAG, "surfaceDestroyed"); - mThreadRun = false; if (mCamera != null) { synchronized (this) { mCamera.release(); @@ -77,17 +77,19 @@ public abstract class SampleViewBase extends SurfaceView implements SurfaceHolde protected abstract Bitmap processFrame(VideoCapture capture); public void run() { - mThreadRun = true; Log.i(TAG, "Starting processing thread"); - while (mThreadRun) { + while (true) { Bitmap bmp = null; - if (!mCamera.grab()) { - Log.e(TAG, "mCamera.grab() failed"); - break; - } - synchronized (this) { + if (mCamera == null) + break; + + if (!mCamera.grab()) { + Log.e(TAG, "mCamera.grab() failed"); + break; + } + bmp = processFrame(mCamera); } @@ -100,5 +102,7 @@ public abstract class SampleViewBase extends SurfaceView implements SurfaceHolde bmp.recycle(); } } + + Log.i(TAG, "Finishing processing thread"); } } \ No newline at end of file -- 2.7.4