Puzzle15 implementation with use of the OpenCV surface framework

pull/80/head
Konstantin Bezruk 12 years ago committed by Andrey Kamaev
parent 5f1339b319
commit cd152c3a03
  1. 8
      samples/android/15-puzzle-framework/.classpath
  2. 33
      samples/android/15-puzzle-framework/.project
  3. 30
      samples/android/15-puzzle-framework/AndroidManifest.xml
  4. BIN
      samples/android/15-puzzle-framework/ic_launcher-web.png
  5. BIN
      samples/android/15-puzzle-framework/libs/android-support-v4.jar
  6. BIN
      samples/android/15-puzzle-framework/res/drawable-hdpi/ic_action_search.png
  7. BIN
      samples/android/15-puzzle-framework/res/drawable-hdpi/ic_launcher.png
  8. BIN
      samples/android/15-puzzle-framework/res/drawable-ldpi/ic_launcher.png
  9. BIN
      samples/android/15-puzzle-framework/res/drawable-mdpi/ic_action_search.png
  10. BIN
      samples/android/15-puzzle-framework/res/drawable-mdpi/ic_launcher.png
  11. BIN
      samples/android/15-puzzle-framework/res/drawable-xhdpi/ic_action_search.png
  12. BIN
      samples/android/15-puzzle-framework/res/drawable-xhdpi/ic_launcher.png
  13. 12
      samples/android/15-puzzle-framework/res/layout/activity_puzzle15.xml
  14. 6
      samples/android/15-puzzle-framework/res/menu/activity_puzzle15.xml
  15. 10
      samples/android/15-puzzle-framework/res/values/strings.xml
  16. 5
      samples/android/15-puzzle-framework/res/values/styles.xml
  17. 320
      samples/android/15-puzzle-framework/src/org/opencv/framework/OpenCvCameraBridgeViewBase.java
  18. 120
      samples/android/15-puzzle-framework/src/org/opencv/framework/OpenCvJavaCameraView.java
  19. 142
      samples/android/15-puzzle-framework/src/org/opencv/framework/OpenCvNativeCameraView.java
  20. 127
      samples/android/15-puzzle-framework/src/org/opencv/samples/puzzle15/Puzzle15Activity.java
  21. 203
      samples/android/15-puzzle-framework/src/org/opencv/samples/puzzle15/Puzzle15Processor.java

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
<classpathentry kind="output" path="bin/classes"/>
</classpath>

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>15-puzzle</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

@ -0,0 +1,30 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.opencv.samples.puzzle15"
android:versionCode="1"
android:versionName="1.0" >
<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="9" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".Puzzle15Activity"
android:label="@string/title_activity_puzzle15" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

@ -0,0 +1,12 @@
<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" >
<org.opencv.framework.OpenCvNativeCameraView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/puzzle_activity_surface_view"
/>
</LinearLayout>

@ -0,0 +1,6 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/menu_start_new_game"
android:title="@string/menu_start_new_game"
android:orderInCategory="100" />
<item android:id="@+id/menu_toggle_tile_numbers" android:title="@string/menu_toggle_tile_numbers"></item>
</menu>

@ -0,0 +1,10 @@
<resources>
<string name="app_name">15-puzzle</string>
<string name="hello_world">Hello world!</string>
<string name="menu_settings">Settings</string>
<string name="title_activity_puzzle15">Puzzle15Activity</string>
<string name="menu_toggle_tile_numbers">Show/hide tile numbers</string>
<string name="menu_start_new_game">Start new game</string>
</resources>

@ -0,0 +1,5 @@
<resources>
<style name="AppTheme" parent="android:Theme.Light" />
</resources>

@ -0,0 +1,320 @@
package org.opencv.framework;
import java.util.List;
import org.opencv.android.Utils;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.highgui.Highgui;
import org.opencv.highgui.VideoCapture;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
/**
* This is a basic class, implementing the interaction with Camera and OpenCV library.
* The main responsibility of it - is to control when camera can be enabled, process the frame,
* call external listener to make any adjustments to the frame and then draw the resulting
* frame to the screen.
* The clients shall implement CvCameraViewListener
* TODO: add method to control the format in which the frames will be delivered to CvCameraViewListener
*/
public abstract class OpenCvCameraBridgeViewBase extends SurfaceView implements SurfaceHolder.Callback {
private static final int MAX_UNSPECIFIED = -1;
protected int mFrameWidth;
protected int mFrameHeight;
protected int mMaxHeight;
protected int mMaxWidth;
private Bitmap mCacheBitmap;
public OpenCvCameraBridgeViewBase(Context context, AttributeSet attrs) {
super(context,attrs);
getHolder().addCallback(this);
mMaxWidth = MAX_UNSPECIFIED;
mMaxHeight = MAX_UNSPECIFIED;
}
public interface CvCameraViewListener {
/**
* This method is invoked when camera preview has started. After this method is invoked
* the frames will start to be delivered to client via the onCameraFrame() callback.
* @param width - the width of the frames that will be delivered
* @param height - the height of the frames that will be delivered
*/
public void onCameraViewStarted(int width, int height);
/**
* This method is invoked when camera preview has been stopped for some reason.
* No frames will be delivered via onCameraFrame() callback after this method is called.
*/
public void onCameraViewStopped();
/**
* This method is invoked when delivery of the frame needs to be done.
* The returned values - is a modified frame which needs to be displayed on the screen.
* TODO: pass the parameters specifying the format of the frame (BPP, YUV or RGB and etc)
*/
public Mat onCameraFrame(Mat inputFrame);
}
public class FrameSize {
public int width;
public int height;
public FrameSize(int w, int h) {
width = w;
height = h;
}
}
private static final int STOPPED = 0;
private static final int STARTED = 1;
private static final String TAG = "SampleCvBase";
private CvCameraViewListener mListener;
private int mState = STOPPED;
private boolean mEnabled;
private boolean mSurfaceExist;
private Object mSyncObject = new Object();
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
synchronized(mSyncObject) {
if (!mSurfaceExist) {
mSurfaceExist = true;
checkCurrentState();
} else {
/** Surface changed. We need to stop camera and restart with new parameters */
/* Pretend that old surface has been destroyed */
mSurfaceExist = false;
checkCurrentState();
/* Now use new surface. Say we have it now */
mSurfaceExist = true;
checkCurrentState();
}
}
}
public void surfaceCreated(SurfaceHolder holder) {
/* Do nothing. Wait until surfaceChanged delivered */
}
public void surfaceDestroyed(SurfaceHolder holder) {
synchronized(mSyncObject) {
mSurfaceExist = false;
checkCurrentState();
}
}
/**
* This method is provided for clients, so they can enable the camera connection.
* The actuall onCameraViewStarted callback will be delivered only after both this method is called and surface is available
*/
public void enableView() {
synchronized(mSyncObject) {
mEnabled = true;
checkCurrentState();
}
}
/**
* This method is provided for clients, so they can disable camera connection and stop
* the delivery of frames eventhough the surfaceview itself is not destroyed and still stays on the scren
*/
public void disableView() {
synchronized(mSyncObject) {
mEnabled = false;
checkCurrentState();
}
}
public void setCvCameraViewListener(CvCameraViewListener listener) {
mListener = listener;
}
/**
* This method sets the maximum size that camera frame is allowed to be. When selecting
* size - the biggest size which less or equal the size set will be selected.
* As an example - we set setMaxFrameSize(200,200) and we have 176x152 and 320x240 sizes. The
* preview frame will be selected with 176x152 size.
* This method is usefull when need to restrict the size of preview frame for some reason (for example for video recording)
* @param maxWidth - the maximum width allowed for camera frame.
* @param maxHeight - the maxumum height allowed for camera frame
*/
public void setMaxFrameSize(int maxWidth, int maxHeight) {
mMaxWidth = maxWidth;
mMaxHeight = maxHeight;
}
/**
* Called when mSyncObject lock is held
*/
private void checkCurrentState() {
int targetState;
if (mEnabled && mSurfaceExist) {
targetState = STARTED;
} else {
targetState = STOPPED;
}
if (targetState != mState) {
/* The state change detected. Need to exit the current state and enter target state */
processExitState(mState);
mState = targetState;
processEnterState(mState);
}
}
private void processEnterState(int state) {
switch(state) {
case STARTED:
onEnterStartedState();
if (mListener != null) {
mListener.onCameraViewStarted(mFrameWidth, mFrameHeight);
}
break;
case STOPPED:
onEnterStoppedState();
if (mListener != null) {
mListener.onCameraViewStopped();
}
break;
};
}
private void processExitState(int state) {
switch(state) {
case STARTED:
onExitStartedState();
break;
case STOPPED:
onExitStoppedState();
break;
};
}
private void onEnterStoppedState() {
/* nothing to do */
}
private void onExitStoppedState() {
/* nothing to do */
}
private void onEnterStartedState() {
connectCamera(getWidth(), getHeight());
/* Now create cahe Bitmap */
mCacheBitmap = Bitmap.createBitmap(mFrameWidth, mFrameHeight, Bitmap.Config.ARGB_8888);
}
private void onExitStartedState() {
disconnectCamera();
if (mCacheBitmap != null) {
mCacheBitmap.recycle();
}
}
/**
* This method shall be called by the subclasses when they have valid
* object and want it to be delivered to external client (via callback) and
* then displayed on the screen.
* @param frame - the current frame to be delivered
*/
protected void deliverAndDrawFrame(Mat frame) {
Mat modified;
if (mListener != null) {
modified = mListener.onCameraFrame(frame);
} else {
modified = frame;
}
if (modified != null) {
Utils.matToBitmap(modified, mCacheBitmap);
}
if (mCacheBitmap != null) {
Canvas canvas = getHolder().lockCanvas();
if (canvas != null) {
canvas.drawBitmap(mCacheBitmap, (canvas.getWidth() - mCacheBitmap.getWidth()) / 2, (canvas.getHeight() - mCacheBitmap.getHeight()) / 2, null);
getHolder().unlockCanvasAndPost(canvas);
}
}
}
/**
* This method is invoked shall perform concrete operation to initialize the camera.
* CONTRACT: as a result of this method variables mFrameWidth and mFrameHeight MUST be
* initialized with the size of the Camera frames that will be delivered to external processor.
* @param width - the width of this SurfaceView
* @param height - the height of this SurfaceView
*/
protected abstract void connectCamera(int width, int height);
/**
* Disconnects and release the particular camera object beeing connected to this surface view.
* Called when syncObject lock is held
*/
protected abstract void disconnectCamera();
public interface ListItemAccessor {
public int getWidth(Object obj);
public int getHeight(Object obj);
};
/**
* This helper method can be called by subclasses to select camera preview size.
* It goes over the list of the supported preview sizes and selects the maximum one which
* fits both values set via setMaxFrameSize() and surface frame allocated for this view
* @param supportedSizes
* @param surfaceWidth
* @param surfaceHeight
* @return
*/
protected FrameSize calculateCameraFrameSize(List<?> supportedSizes, ListItemAccessor accessor, int surfaceWidth, int surfaceHeight) {
int calcWidth = 0;
int calcHeight = 0;
int maxAllowedWidth = (mMaxWidth != MAX_UNSPECIFIED && mMaxWidth < surfaceWidth)? mMaxWidth : surfaceWidth;
int maxAllowedHeight = (mMaxHeight != MAX_UNSPECIFIED && mMaxHeight < surfaceHeight)? mMaxHeight : surfaceHeight;
for (Object size : supportedSizes) {
int width = accessor.getWidth(size);
int height = accessor.getHeight(size);
if (width <= maxAllowedWidth && height <= maxAllowedHeight) {
if (width >= calcWidth && height >= calcHeight) {
calcWidth = (int) width;
calcHeight = (int) height;
}
}
}
return new FrameSize(calcWidth, calcHeight);
}
}

@ -0,0 +1,120 @@
package org.opencv.framework;
import java.io.IOException;
import java.util.List;
import android.content.Context;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.hardware.Camera.PreviewCallback;
import android.util.AttributeSet;
import android.util.Log;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfByte;
import org.opencv.imgproc.Imgproc;
/**
* This class is an implementation of the Bridge View between OpenCv and JAVA Camera.
* This class relays on the functionality available in base class and only implements
* required functions:
* connectCamera - opens Java camera and sets the PreviewCallback to be delivered.
* disconnectCamera - closes the camera and stops preview.
* When frame is delivered via callback from Camera - it processed via OpenCV to be
* converted to RGBA32 and then passed to the external callback for modifications if required.
*/
public class OpenCvJavaCameraView extends OpenCvCameraBridgeViewBase implements PreviewCallback {
private static final int MAGIC_TEXTURE_ID = 10;
private static final String TAG = "OpenCvJavaCameraView";
private Mat mBaseMat;
public static class JavaCameraSizeAccessor implements ListItemAccessor {
@Override
public int getWidth(Object obj) {
Camera.Size size = (Camera.Size) obj;
return size.width;
}
@Override
public int getHeight(Object obj) {
Camera.Size size = (Camera.Size) obj;
return size.height;
}
}
private Camera mCamera;
public OpenCvJavaCameraView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void connectCamera(int width, int height) {
mCamera = Camera.open(0);
List<android.hardware.Camera.Size> sizes = mCamera.getParameters().getSupportedPreviewSizes();
/* Select the size that fits surface considering maximum size allowed */
FrameSize frameSize = calculateCameraFrameSize(sizes, new JavaCameraSizeAccessor(), width, height);
/* Now set camera parameters */
try {
Camera.Parameters params = mCamera.getParameters();
List<Integer> formats = params.getSupportedPictureFormats();
params.setPreviewFormat(ImageFormat.NV21);
params.setPreviewSize(frameSize.width, frameSize.height);
mCamera.setPreviewCallback(this);
mCamera.setParameters(params);
//mCamera.setPreviewTexture(new SurfaceTexture(MAGIC_TEXTURE_ID));
SurfaceTexture tex = new SurfaceTexture(MAGIC_TEXTURE_ID);
mCamera.setPreviewTexture(tex);
mFrameWidth = frameSize.width;
mFrameHeight = frameSize.height;
} catch (IOException e) {
e.printStackTrace();
}
mBaseMat = new Mat(mFrameHeight + (mFrameHeight/2), mFrameWidth, CvType.CV_8UC1);
/* Finally we are ready to start the preview */
mCamera.startPreview();
}
@Override
protected void disconnectCamera() {
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
mCamera.release();
}
@Override
public void onPreviewFrame(byte[] frame, Camera arg1) {
Log.i(TAG, "Preview Frame received. Need to create MAT and deliver it to clients");
Log.i(TAG, "Frame size is " + frame.length);
mBaseMat.put(0, 0, frame);
Mat frameMat = new Mat();
Imgproc.cvtColor(mBaseMat, frameMat, Imgproc.COLOR_YUV2RGBA_NV21, 4);
deliverAndDrawFrame(frameMat);
frameMat.release();
}
}

@ -0,0 +1,142 @@
package org.opencv.framework;
import org.opencv.android.Utils;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.highgui.Highgui;
import org.opencv.highgui.VideoCapture;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Log;
/**
* This class is an implementation of a bridge between SurfaceView and native OpenCV camera.
* Due to the big amount of work done, by the base class this child is only responsible
* for creating camera, destroying camera and delivering frames while camera is enabled
*/
public class OpenCvNativeCameraView extends OpenCvCameraBridgeViewBase {
public static final String TAG = "OpenCvNativeCameraView";
private boolean mStopThread;
private Thread mThread;
private VideoCapture mCamera;
public OpenCvNativeCameraView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void connectCamera(int width, int height) {
/* 1. We need to instantiate camera
* 2. We need to start thread which will be getting frames
*/
/* First step - initialize camera connection */
initializeCamera(getWidth(), getHeight());
/* now we can start update thread */
mThread = new Thread(new CameraWorker(getWidth(), getHeight()));
mThread.start();
}
@Override
protected void disconnectCamera() {
/* 1. We need to stop thread which updating the frames
* 2. Stop camera and release it
*/
try {
mStopThread = true;
mThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
mThread = null;
mStopThread = false;
}
/* Now release camera */
releaseCamera();
}
public static class OpenCvSizeAccessor implements ListItemAccessor {
@Override
public int getWidth(Object obj) {
Size size = (Size)obj;
return (int)size.width;
}
@Override
public int getHeight(Object obj) {
Size size = (Size)obj;
return (int)size.height;
}
}
private void initializeCamera(int width, int height) {
mCamera = new VideoCapture(Highgui.CV_CAP_ANDROID);
//TODO: improve error handling
java.util.List<Size> sizes = mCamera.getSupportedPreviewSizes();
/* Select the size that fits surface considering maximum size allowed */
FrameSize frameSize = calculateCameraFrameSize(sizes, new OpenCvSizeAccessor(), width, height);
double frameWidth = frameSize.width;
double frameHeight = frameSize.height;
mCamera.set(Highgui.CV_CAP_PROP_FRAME_WIDTH, frameWidth);
mCamera.set(Highgui.CV_CAP_PROP_FRAME_HEIGHT, frameHeight);
mFrameWidth = (int)frameWidth;
mFrameHeight = (int)frameHeight;
Log.i(TAG, "Selected camera frame size = (" + mFrameWidth + ", " + mFrameHeight + ")");
}
private void releaseCamera() {
if (mCamera != null) {
mCamera.release();
}
}
private class CameraWorker implements Runnable {
private Mat mRgba = new Mat();
private int mWidth;
private int mHeight;
CameraWorker(int w, int h) {
mWidth = w;
mHeight = h;
}
@Override
public void run() {
Mat modified;
do {
if (!mCamera.grab()) {
Log.e(TAG, "Camera frame grab failed");
break;
}
mCamera.retrieve(mRgba, Highgui.CV_CAP_ANDROID_COLOR_FRAME_RGBA);
deliverAndDrawFrame(mRgba);
} while (!mStopThread);
}
}
}

@ -0,0 +1,127 @@
package org.opencv.samples.puzzle15;
import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.core.Mat;
import org.opencv.framework.OpenCvCameraBridgeViewBase.CvCameraViewListener;
import org.opencv.framework.OpenCvNativeCameraView;
import android.os.Bundle;
import android.app.Activity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.Window;
import android.view.View;
public class Puzzle15Activity extends Activity implements CvCameraViewListener, View.OnTouchListener {
private static final String TAG = "Sample::Puzzle15::Activity";
private OpenCvNativeCameraView mOpenCvCameraView;
private Puzzle15Processor mPuzzle15;
private int mGameWidth;
private int mGameHeight;
private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
@Override
public void onManagerConnected(int status) {
switch (status) {
case LoaderCallbackInterface.SUCCESS:
{
Log.i(TAG, "OpenCV loaded successfully");
mPuzzle15 = new Puzzle15Processor();
mPuzzle15.prepareNewGame();
/* Now enable camera view to start receiving frames */
mOpenCvCameraView.setOnTouchListener(Puzzle15Activity.this);
mOpenCvCameraView.enableView();
} break;
default:
{
super.onManagerConnected(status);
} break;
}
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_puzzle15);
mOpenCvCameraView = (OpenCvNativeCameraView) findViewById(R.id.puzzle_activity_surface_view);
mOpenCvCameraView.setCvCameraViewListener(this);
OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_2, this, mLoaderCallback);
}
public void onDestroy() {
super.onDestroy();
mOpenCvCameraView.disableView();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_puzzle15, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
Log.i(TAG, "Menu Item selected " + item);
if (item.getItemId() == R.id.menu_start_new_game) {
/* We need to start new game */
mPuzzle15.prepareNewGame();
} else if (item.getItemId() == R.id.menu_toggle_tile_numbers) {
/* We need to enable or disable drawing of the tile numbers */
mPuzzle15.toggleTileNumbers();
}
return true;
}
@Override
public void onCameraViewStarted(int width, int height) {
mGameWidth = width;
mGameHeight = height;
mPuzzle15.prepareGameSize(width, height);
}
@Override
public void onCameraViewStopped() {
}
@Override
public Mat onCameraFrame(Mat inputFrame) {
return mPuzzle15.puzzleFrame(inputFrame);
}
@Override
public boolean onTouch(View view, MotionEvent event) {
int xpos, ypos;
xpos = (view.getWidth() - mGameWidth) / 2;
xpos = (int)event.getX() - xpos;
ypos = (view.getHeight() - mGameHeight) / 2;
ypos = (int)event.getY() - ypos;
if (xpos >=0 && xpos <= mGameWidth && ypos >=0 && ypos <= mGameHeight) {
/* click is inside the picture. Deliver this event to processor */
mPuzzle15.deliverTouchEvent(xpos, ypos);
}
// TODO Auto-generated method stub
return false;
}
}

@ -0,0 +1,203 @@
package org.opencv.samples.puzzle15;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.core.Point;
import android.util.Log;
/**
* This class is a controller for puzzle game.
* It converts the image from Camera into the shuffled image
*/
public class Puzzle15Processor {
private static final int GRID_SIZE = 4;
private static final int GRID_AREA = GRID_SIZE * GRID_SIZE;
private static final int GRID_EMPTY_INDEX = GRID_AREA - 1;
private static final String TAG = "Puzzle15Processor";
private int[] mIndexes;
private int[] mTextWidths;
private int[] mTextHeights;
private Mat mRgba;
private Mat mRgba15;
private Mat[] mCells;
private Mat[] mCells15;
private boolean mShowTileNumbers = true;
public Puzzle15Processor() {
mTextWidths = new int[GRID_AREA];
mTextHeights = new int[GRID_AREA];
mIndexes = new int [GRID_AREA];
for (int i = 0; i < GRID_AREA; 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;
mIndexes[i] = i;
}
}
/* this method is intended to make processor prepared for a new game */
public synchronized void prepareNewGame() {
do {
shuffle(mIndexes);
} while (!isPuzzleSolvable());
}
/* This method is to make the processor know the size of the frames that
* will be delivered via puzzleFrame.
* If the frames will be different size - then the result is unpredictable
*/
public synchronized void prepareGameSize(int width, int height) {
mRgba15 = new Mat(height, width, CvType.CV_8UC4);
mCells = new Mat[GRID_AREA];
mCells15 = new Mat[GRID_AREA];
for (int i = 0; i < GRID_SIZE; i++) {
for (int j = 0; j < GRID_SIZE; j++) {
int k = i * GRID_SIZE + j;
// mCells[k] = mRgba.submat(i * height / GRID_SIZE, (i + 1) * height / GRID_SIZE, j * width / GRID_SIZE, (j + 1) * width / GRID_SIZE);
mCells15[k] = mRgba15.submat(i * height / GRID_SIZE, (i + 1) * height / GRID_SIZE, j * width / GRID_SIZE, (j + 1) * width / GRID_SIZE);
}
}
}
/* this method to be called from the outside. it processes the frame and shuffles
* the tiles as specified by mIndexes array
*/
public synchronized Mat puzzleFrame(Mat inputPicture) {
int rows = inputPicture.rows();
int cols = inputPicture.cols();
int type = inputPicture.type();
rows = rows - rows%4;
cols = cols - cols%4;
for (int i = 0; i < GRID_SIZE; i++) {
for (int j = 0; j < GRID_SIZE; j++) {
int k = i * GRID_SIZE + j;
mCells[k] = inputPicture.submat(i * inputPicture.rows() / GRID_SIZE, (i + 1) * inputPicture.rows() / GRID_SIZE, j * inputPicture.cols()/ GRID_SIZE, (j + 1) * inputPicture.cols() / GRID_SIZE);
}
}
rows = rows - rows%4;
cols = cols - cols%4;
// copy shuffled tiles
for (int i = 0; i < GRID_AREA; i++) {
int idx = mIndexes[i];
if (idx == GRID_EMPTY_INDEX)
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 / GRID_SIZE - mTextWidths[idx]) / 2,
(rows / GRID_SIZE + mTextHeights[idx]) / 2), 3/* CV_FONT_HERSHEY_COMPLEX */, 1, new Scalar(255, 0, 0, 255), 2);
}
}
}
drawGrid(cols, rows, mRgba15);
return mRgba15;
}
public void toggleTileNumbers() {
mShowTileNumbers = !mShowTileNumbers;
}
public void deliverTouchEvent(int x, int y) {
int rows = mRgba15.rows();
int cols = mRgba15.cols();
int row = (int) Math.floor(y * GRID_SIZE / rows);
int col = (int) Math.floor(x * GRID_SIZE / cols);
if (row < 0 || row >= GRID_SIZE || col < 0 || col >= GRID_SIZE) {
Log.e(TAG, "It is not expected to get touch event outside of picture");
return ;
}
int idx = row * GRID_SIZE + col;
int idxtoswap = -1;
// left
if (idxtoswap < 0 && col > 0)
if (mIndexes[idx - 1] == GRID_EMPTY_INDEX)
idxtoswap = idx - 1;
// right
if (idxtoswap < 0 && col < GRID_SIZE - 1)
if (mIndexes[idx + 1] == GRID_EMPTY_INDEX)
idxtoswap = idx + 1;
// top
if (idxtoswap < 0 && row > 0)
if (mIndexes[idx - GRID_SIZE] == GRID_EMPTY_INDEX)
idxtoswap = idx - GRID_SIZE;
// bottom
if (idxtoswap < 0 && row < GRID_SIZE - 1)
if (mIndexes[idx + GRID_SIZE] == GRID_EMPTY_INDEX)
idxtoswap = idx + GRID_SIZE;
// swap
if (idxtoswap >= 0) {
synchronized (this) {
int touched = mIndexes[idx];
mIndexes[idx] = mIndexes[idxtoswap];
mIndexes[idxtoswap] = touched;
}
}
}
private void drawGrid(int cols, int rows, Mat drawMat) {
for (int i = 1; i < GRID_SIZE; i++) {
Core.line(drawMat, new Point(0, i * rows / GRID_SIZE), new Point(cols, i * rows / GRID_SIZE), new Scalar(0, 255, 0, 255), 3);
Core.line(drawMat, new Point(i * cols / GRID_SIZE, 0), new Point(i * cols / GRID_SIZE, rows), new Scalar(0, 255, 0, 255), 3);
}
}
private 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;
}
}
private boolean isPuzzleSolvable() {
int sum = 0;
for (int i = 0; i < GRID_AREA; i++) {
if (mIndexes[i] == GRID_EMPTY_INDEX)
sum += (i / GRID_SIZE) + 1;
else {
int smaller = 0;
for (int j = i + 1; j < GRID_AREA; j++) {
if (mIndexes[j] < mIndexes[i])
smaller++;
}
sum += smaller;
}
}
return sum % 2 == 0;
}
}
Loading…
Cancel
Save