.:: CODE SNIPPET ::.

"Your time is limited, so don't waste it living someone else's life"

Vẽ hình chữ nhật để khoanh vùng ảnh cần chụp trên camera


Như bài viết trước của mình về việc sử dụng lớp android.hardware.Camera để khai thác sử dụng camera của thiết bị android, thì bạn có thể dễ dàng tạo ra một ứng dụng với camera để lấy hình ảnh. Trong bài viết này, mình hướng dẫn các bạn tạo ra một khung hình chữ nhật để khoanh vùng hình ảnh mà bạn muốn chụp. Điều này cũng khác với việc chụp ảnh rồi và sau đó mới cắt ảnh theo một khung hình chữ nhật như đã mô tả ở bài viết trước.
Để thực hiện điều này, sau khi đã mở được camera lên, việc tiếp theo mà chúng ta phải làm là tạo ra một lớp view để vẽ lên mặt trên cùng của camera.

Wait.java

Đầu tiên, các bạn tạo ra lớp này. Lớp này chỉ làm nhiệm vụ đơn giản là tạo ra một thread đơn giản để đợi trong một khoảng thời gian nào đó thôi. Việc này hữu ích cho việc focus tự động của camera:

public class Wait {
public static void oneSec() {
	try {
		Thread.currentThread().sleep(1000);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
}
public static void manySec(long s) {
try {
	Thread.currentThread().sleep(s * 1000);
	} catch (InterruptedException e) {
e.printStackTrace();
	}
}
}

Preview

Trong lớp Preview này, sẽ sử dụng lại lớp Preview như đã mô tả trong bài viết trước. Tuy nhiên sẽ dùng thêm các hàm đặc biệt sau.
Hàm getPic(int,int,int,int): hàm này dùng để cắt ra một vùng của một tấm ảnh, hay nói cách khác là lấy ra một tập hợp các tọa độ nào đấy trên ảnh và chuyển nó thành một ảnh bitmap khác

public Bitmap getPic(int x, int y, int width, int height) {
	System.gc();
	Bitmap b = null;
	Size s = mParameters.getPreviewSize();
	YuvImage yuvimage = new YuvImage(mBuffer, ImageFormat.NV21, s.width,
			s.height, null);
	ByteArrayOutputStream outStream = new ByteArrayOutputStream();
	yuvimage.compressToJpeg(new Rect(x, y, width, height), 100, outStream);
	b = BitmapFactory.decodeByteArray(outStream.toByteArray(), 0,
			outStream.size()); // decode JPG
	if (b != null) {
	} else {
	}
	yuvimage = null;
	outStream = null;
	System.gc();
	return b;
}

Khi ảnh được capture thì cần có một bộ đệm để lưu trữ dữ liệu của ảnh chụp, vì vậy chúng ta cần đến một bộ đệm (buffer). Bộ đệm này phải đảm bào là đủ không gian để chứa dữ liệu ảnh, vì vậy, nó phải được cập nhật lại liên tục, vì thế chúng ta cần có hàm cập nhật bộ đệm như sau:

private void updateBufferSize() {
	mBuffer = null;
	System.gc();
	int h = mCamera.getParameters().getPreviewSize().height;
	int w = mCamera.getParameters().getPreviewSize().width;
	int bitsPerPixel = ImageFormat.getBitsPerPixel(mCamera.getParameters().getPreviewFormat());
	mBuffer = new byte[w * h * bitsPerPixel / 8];
}

Hàm rất quan trọng trong lớp này là cần override lại hàm surfaceCreated(SurfaceHolder). Trong hàm này, chúng ta cần làm nhiều việc mà mình đã comment trong code, các bạn theo dõi nhé:

public void surfaceCreated(SurfaceHolder holder) {
	try {
		mCamera = Camera.open(); 
	}
	catch (RuntimeException exception) {
		Toast.makeText(getContext(), "Camera broken, quitting :(",Toast.LENGTH_LONG).show();
		// TODO: exit program
	}
	try {
		/*
		 thiết lập camera được preview trực tiếp (nghĩa là ảnh đang đi qua camera thì ngư�?i dùng có thể
		xem trước được)
		holder là một bộ quản lý surface mà trong đó preview của camera được đặt lên
		*/
		mCamera.setPreviewDisplay(holder);
		updateBufferSize();
		//thiết lập nơi để chứa dữ liệu của hình ảnh
		mCamera.addCallbackBuffer(mBuffer);
		/*
		 cài một thực thể callback để được g�?i vào mỗi khung hình (preview frame)
		 sử dụng những buffer được cấp bởi hàm addCallbackBuffer để làm công việc như ở trên (là nơi lưu ảnh)
		 đối tượng callback truy�?n vào sẽ được liên tục g�?i ngay khi mà preview được kích hoạt.
		 
		 �?i�?u này sẽ làm tăng hiệu quả preview và frame rate bằng cách cho phép sử dụng lại bộ nhớ của khung hình
		 Chúng ta nên g�?i hàm addCallbackBuffer trước và sau khi g�?i hàm này, chắc cũng hiểu vì sao rùi chứ gì.
	 
		 một vấn đ�? nữa muốn đ�? cập ở đây là từ khóa synchronized được đưa vào bởi vì hàm onPreviewFrame được sử dụng
		 lại nhi�?u lần (hơn nữa, PreviewCallback được g�?i trong một event thread)và cần một sự đồng bộ: sau khi hàm này 
		 được g�?i và hoạt động hoàn tất rùi thì hàm này mới được g�?i thực hiện lại,....(ủa mà có biết hàm này sảy ra lúc 
		 nào ko vậy, sảy ra lúc mà khung hình được hiển thị lên 
		 */
		mCamera.setPreviewCallbackWithBuffer(new PreviewCallback() {
			public synchronized void onPreviewFrame(byte[] data, Camera c) {
				if (mCamera != null) { // there was a race condition when onStop() was called..
					mCamera.addCallbackBuffer(mBuffer); // it was consumed by the call, add it back
				}
			}
		});
	} catch (Exception exception) {
		//Log.e(TAG, "Exception trying to set preview");
		if (mCamera != null){
			mCamera.release();
			mCamera = null;
		}
		// TODO: add more exception handling logic here
	}
}

Khi surface được tạo ra, hàm này được triệu gọi chạy. Ở đây, chúng ta phải mở hardware.Camera và cho hiển thị Preview (bằng cách này, thì camera mới hiển thị được), sau đó chúng ta sử dụng buffer và nhận dữ liệu hình ảnh. Sử dụng hardware.Camera đôi khi khá phiền phức vì bạn phải nhớ giải phóng camera nếu không thì những chương trình khác muốn sử dụng camera sẽ không có tài nguyên sử dụng, hoặc chính khi bạn chạy lại chương trình của bạn thì nó cũng sẽ không hoạt động do camera đã bị chiếm quyền. Mình đã phải khởi động lại máy liên tục do không chu ý đến vấn đề này.
Trong hàm surfaceChanged bạn phải đặt lại các parameters cho camera.

public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
	//Log.i(TAG, "Preview: surfaceChanged() - size now " + w + "x" + h);
	// Now that the size is known, set up the camera parameters and begin
	// the preview.
	try {
		mParameters = mCamera.getParameters();
		mParameters.set("orientation","landscape");
		for (Integer i : mParameters.getSupportedPreviewFormats()) {
			//Log.i(TAG, "supported preview format: " + i);
		} 
		List<Size> sizes = mParameters.getSupportedPreviewSizes();
		for (Size size : sizes) {
			//Log.i(TAG, "supported preview size: " + size.width + "x" + size.height);
		}
		mCamera.setParameters(mParameters);
	} catch (Exception e) {
	}
	updateBufferSize();
	Size p = mCamera.getParameters().getPreviewSize();
	mCamera.startPreview();
}

Cuối cùng trong lớp này chúng ta sẽ có các hàm sau:

public void setCameraFocus(AutoFocusCallback autoFocus){
	if (mCamera.getParameters().getFocusMode().equals(mCamera.getParameters().FOCUS_MODE_AUTO) ||
			mCamera.getParameters().getFocusMode().equals(mCamera.getParameters().FOCUS_MODE_MACRO)){
			mCamera.autoFocus(autoFocus);
	}
}
public void setFlash(boolean flash){
	if (flash){
		mParameters.setFlashMode(Parameters.FLASH_MODE_TORCH);
		mCamera.setParameters(mParameters);
	}
	else{
		mParameters.setFlashMode(Parameters.FLASH_MODE_OFF);
		mCamera.setParameters(mParameters);
	}
}

TouchView

Lớp này rất quan trọng vì nó sẽ tạo ra một canvas để vẽ lên trên camera. Vì thế nó phải là một lớp thực thi của View.
Mình tạo ra những hình chữ nhật đóng vai trò là các góc của khung hình và vẽ các đường thằng nối các góc đó lại với nhau.
Trước tiên, bạn cần khởi tạo các biến số để chưa các chuyện như cọ vẽ, vị trí của các góc hình chữ nhật,…
Tiếp theo giới thiệu các bạn hàm onTouchEvent(MotionEvent ev):

public boolean onTouchEvent(MotionEvent ev) {
	final int action = ev.getAction();
	boolean intercept = true;
	switch (action) {
	case MotionEvent.ACTION_DOWN: {
		final float x = ev.getX();
		final float y = ev.getY();
		/*
		 * Nếu ngư�?i dùng chạm vào button chụp thì sự kiện này sẽ trả v�? giá trị false
		 * và kết thục tại đây để lớp TouchView b�? qua sự kiện touch
		 */
		if ((x >= buttonRec.left) && (x <=buttonRec.right) && (y>=buttonRec.top) && (y<=buttonRec.bottom)){
				intercept = false;
				break;
		}
		//kiểm tra khoản cách xem có đủ gần các góc chưa
		manhattanDistance(x,y);
		// Lưu lại vị trí bắt đầu chạm
		mLastTouchX = x;
		mLastTouchY = y;
		mActivePointerId = ev.getPointerId(0);
		break;
	}
	case MotionEvent.ACTION_MOVE: {
		final int pointerIndex = ev.findPointerIndex(mActivePointerId);
		final float x = ev.getX();
		final float y = ev.getY();
		if (!mScaleDetector.isInProgress()) {
			final float dx = x - mLastTouchX;
			final float dy = y - mLastTouchY;
			mPosX += dx;
			mPosY += dy;
			invalidate();
		}
		// Calculate the distance moved
		final float dx = x - mLastTouchX;
		final float dy = y - mLastTouchY;
		// Move the object
		if (mPosX >= 0 && mPosX <=800){
			mPosX += dx;
		}
		if (mPosY >=0 && mPosY <= 480){
			mPosY += dy;
		}
		//Khi đang được giữ và di chuyển thì đư�?ng trên không được vượt quá đư�?ng dưới, 
		//trái ko được quá phải...
		if (mLeftTopBool && ((y+mCenter*2) < mLeftBottomPosY) && ((x+mCenter*2) < mRightTopPosX)){
			if (dy != 0){
				mRightTopPosY = y;
			}
			if (dx != 0){
				mLeftBottomPosX = x;
			}
			mLeftTopPosX = x;//mPosX;
			mLeftTopPosY = y;//mPosY;
		}
		if (mRightTopBool && ((y+mCenter*2) < mRightBottomPosY) && (x > (mLeftTopPosX+mCenter*2))){
			if (dy != 0){
				mLeftTopPosY = y;
			}
			if (dx != 0){
				mRightBottomPosX = x;
			}
			mRightTopPosX = x;//mPosX;
			mRightTopPosY = y;//mPosY;
		}
		if (mLeftBottomBool && (y > (mLeftTopPosY+mCenter*2)) && ((x +mCenter*2) < mRightBottomPosX)){
			if (dx != 0){
				mLeftTopPosX = x;
			}
			if (dy != 0){
				mRightBottomPosY = y;
			}
			mLeftBottomPosX = x;
			mLeftBottomPosY = y;
		}
		if (mRightBottomBool && (y > (mLeftTopPosY+mCenter*2)) && (x > (mLeftBottomPosX+mCenter*2) )){
			if (dx != 0){
				mRightTopPosX = x;
			}
			if (dy != 0){
				mLeftBottomPosY = y;
			}
			mRightBottomPosX = x;
			mRightBottomPosY = y;
		}
		//Lưu lại vị trí cuối
		mLastTouchX = x;
		mLastTouchY = y;
		// Vẽ lại
		invalidate();
		break;
	}
	case MotionEvent.ACTION_UP: {
		mLeftTopBool = false;
		mRightTopBool = false;
		mLeftBottomBool = false;
		mRightBottomBool = false;
		//mActivePointerId = INVALID_POINTER_ID;
		break;
	}
	case MotionEvent.ACTION_CANCEL: {
		mActivePointerId = INVALID_POINTER_ID;
		break;
	}
	case MotionEvent.ACTION_POINTER_UP: {
		// Extract the index of the pointer that left the touch sensor
		final int pointerIndex = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) 
		>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
		final int pointerId = ev.getPointerId(pointerIndex);
		if (pointerId == mActivePointerId) {
			// This was our active pointer going up. Choose a new
			// active pointer and adjust accordingly.
			final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
			mLastTouchX = ev.getX(newPointerIndex);
			mLastTouchY = ev.getY(newPointerIndex);
			mActivePointerId = ev.getPointerId(newPointerIndex);
		}
		break;
	}
	}
	return intercept;
}

Code mình đã giải thích bên trong, các bạn đọc nhé 🙂
Khi màn hình được chạm vào thì hàm này tính toán khoảng cách đến 4 góc của hình chữ nhật để thực hiện hành vi chạm và kéo góc để thay đổi hình chữ nhật:

private void manhattanDistance(float x, float y) {
	double leftTopMan = Math.sqrt(Math.pow((Math.abs((double)x-(double)mLeftTopPosX)),2)
			+ Math.pow((Math.abs((double)y-(double)mLeftTopPosY)),2));
	double rightTopMan = Math.sqrt(Math.pow((Math.abs((double)x-(double)mRightTopPosX)),2)
			+ Math.pow((Math.abs((double)y-(double)mRightTopPosY)),2));
	double leftBottomMan = Math.sqrt(Math.pow((Math.abs((double)x-(double)mLeftBottomPosX)),2)
			+ Math.pow((Math.abs((double)y-(double)mLeftBottomPosY)),2));
	double rightBottomMan = Math.sqrt(Math.pow((Math.abs((double)x-(double)mRightBottomPosX)),2)
			+ Math.pow((Math.abs((double)y-(double)mRightBottomPosY)),2));
	if (leftTopMan < 50){
		mLeftTopBool = true;
		mRightTopBool = false;
		mLeftBottomBool = false;
		mRightBottomBool = false;
	}
	else if (rightTopMan < 50){
		mLeftTopBool = false;
		mRightTopBool = true;
		mLeftBottomBool = false;
		mRightBottomBool = false;
	}
	else if (leftBottomMan < 50){
		mLeftTopBool = false;
		mRightTopBool = false;
		mLeftBottomBool = true;
		mRightBottomBool = false;
	}
	else if (rightBottomMan < 50){
		mLeftTopBool = false;
		mRightTopBool = false;
		mLeftBottomBool = false;
		mRightBottomBool = true;
	}
}

MainActivity

Đây là activity chính của chương trình, nó sắp xếp mọi thứ vào hoạt động
Khởi tạo nhé:

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	// Log.i(TAG, "onCreate()");
	// Xác định kích thước của màn hình device
	DisplayMetrics displaymetrics = new DisplayMetrics();
	getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
	mScreenHeight = displaymetrics.heightPixels;
	mScreenWidth = displaymetrics.widthPixels;
	Toast.makeText(this, String.valueOf(mScreenHeight+"/"+mScreenWidth), Toast.LENGTH_LONG).show();
		requestWindowFeature(Window.FEATURE_NO_TITLE);
	getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
			WindowManager.LayoutParams.FLAG_FULLSCREEN);
		setContentView(R.layout.main);
		// bộ quản lý gia tốc để thực hiện focus cho camera
	mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
	mAccel = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
		
		Drawable mButtonDrawable = this.getResources().getDrawable(
			R.drawable.camera1);
	mTakePicture = (ImageView) findViewById(R.id.btnCapture);
		// setting where I will draw the ImageView for taking pictures
	LayoutParams lp = new LayoutParams(mTakePicture.getLayoutParams());
	
	/*lp.setMargins(
			(int) ((double) mScreenWidth * .85),
			(int) ((double) mScreenHeight * .70),
			(int) ((double) mScreenWidth * .85)
					+ mButtonDrawable.getMinimumWidth(),
			(int) ((double) mScreenHeight * .70)
					+ mButtonDrawable.getMinimumHeight());*/
	lp.setMargins(mScreenWidth-80// -mButtonDrawable.getIntrinsicWidth()
			,0,0,mScreenHeight-128);
			/*(int) ((double) mScreenWidth -mButtonDrawable.getIntrinsicWidth()),
			(int) ((double) mScreenHeight -mButtonDrawable.getIntrinsicHeight()),
			mScreenWidth,
			(int)(mScreenHeight));*/
	mTakePicture.setLayoutParams(lp);
	mTakePicture.setImageResource(R.drawable.button_blue_stop);
	/*
	 rec được dùng cho sự kiện onInterceptTouchEvent. Sự kiện này được truy�?n từ lớp cao nhất xuống
	 lớp thấp nhất để khi vùng chữ nhật này trên màn hình được chạm, nó sẽ b�? qua sự kiện TouchView
	 và truy�?n cho activity để button có thể được click.
	 Cụ thể hơn, thì đây chính là vùng hình chữ nhật chứa button để chụp ảnh.
	 */
	/*rec.set((int) ((double) mScreenWidth * .85),
			(int) ((double) mScreenHeight * .10),
			(int) ((double) mScreenWidth * .85)
					+ mButtonDrawable.getMinimumWidth(),
			(int) ((double) mScreenHeight * .70)
					+ mButtonDrawable.getMinimumHeight());*/
	/*rec.set((int) ((double) mScreenWidth -mButtonDrawable.getMinimumWidth()),
			(int) ((double) mScreenHeight -mButtonDrawable.getMinimumHeight()),
			mScreenWidth,
			mScreenHeight);*/
	rec.set(mScreenWidth-mButtonDrawable.getIntrinsicWidth()
			,0,mScreenWidth,mButtonDrawable.getIntrinsicHeight());
	Toast.makeText(this, String.valueOf(mButtonDrawable.getIntrinsicHeight()), Toast.LENGTH_LONG).show();
	mButtonDrawable = null;
	mTakePicture.setOnClickListener(previewListener);
	
	mPreview = (Preview) findViewById(R.id.preview);
	mView = (TouchView) findViewById(R.id.touch_view);
	mView.setRec(rec);
	
	txtOCRText=(TextView)findViewById(R.id.txtOCRText);
}

Tiếp theo, hàm này dùng để lấy tỉ lệ giữa kích thước của màn hình với kích thước của khung hình chữ nhật đã được khoanh vùng, từ đó giúp có thể lấy được hình ảnh của vùng hình chữ nhật mà thôi.

public Double[] getRatio() {
	Size s = mPreview.getCameraParameters().getPreviewSize();
	double heightRatio = (double) s.height / (double) mScreenHeight;
	double widthRatio = (double) s.width / (double) mScreenWidth;
	Double[] ratio = { heightRatio, widthRatio };
	return ratio;
}

Interface này dùng để lắng nghe sự kiện chạm vào hình camera, khi ngư�?i dùng chạm vào hình thì hình ảnh bên trong khung hình chữ nhật sẽ được chụp lại và lưu vào file

private OnClickListener previewListener = new OnClickListener() {
	@Override
	public void onClick(View v) {
		if (mAutoFocus) {
			mAutoFocus = false;
			// mPreview.setCameraFocus(myAutoFocusCallback);
			Wait.oneSec();
			Thread tGetPic = new Thread(new Runnable() {
				public void run() {
					Double[] ratio = getRatio();
					int left = (int) (ratio[1] * (double) mView
							.getmLeftTopPosX());
					// 0 is height
					int top = (int) (ratio[0] * (double) mView
							.getmLeftTopPosY());
					int right = (int) (ratio[1] * (double) mView
							.getmRightBottomPosX());
					int bottom = (int) (ratio[0] * (double) mView
							.getmRightBottomPosY());
					Bitmap bmp=null;
					bmp=mPreview.getPic(left, top, right, bottom);
					savePhoto(bmp);
					runOnUiThread(new Runnable() {
						public void run() {
							txtOCRText.setText("");
						}
					});
					//txtOCRText.setText("");
					if(bmp!=null){
						TessBaseAPI baseApi = new TessBaseAPI();
						baseApi.setDebug(true);
						baseApi.init(DATA_PATH, lang);
						baseApi.setImage(bmp);
							final String recognizedText = baseApi.getUTF8Text();
						baseApi.end();
					if(recognizedText.length()!=0){
						System.out.println(recognizedText);
							runOnUiThread(new Runnable() {
							
							@Override
							public void run() {
								// TODO Auto-generated method stub
								txtOCRText.setText(recognizedText);
								}
							});
						
						//Toast.makeText(getApplicationContext(), recognizedText, Toast.LENGTH_LONG).show();
						}else{
							System.out.println("Deo nhan duoc");
							//Toast.makeText(getApplicationContext(), "Deo nhan duoc!", Toast.LENGTH_LONG).show();
							}
					}
					mAutoFocus = true;
				}
			});
			tGetPic.start();
		}
		boolean pressed = false;
		if (!mTakePicture.isPressed()) {
			pressed = true;
		}
	}
};

Hàm sau đây xử lý giúp cho nút bấm có thể được bấm khi màn hình được chạm, vì khi người dùng chạm vào là chạm vào lớp trên cùng, sự kiện sẽ không được truyền vào activity đễ xử lý, vì thế hàm này giúp cho việc đó có thể được thực hiện:

public boolean onInterceptTouchEvent(MotionEvent ev) {
	final int action = ev.getAction();
	boolean intercept = false;
	switch (action) {
	case MotionEvent.ACTION_UP:
		break;
	case MotionEvent.ACTION_DOWN:
		float x = ev.getX();
			float y = ev.getY();
		/*
		 Nếu chạm vào vùng nút thì chặn sự kiện từ lớp trên và truy�?n xuống activity
		 để bắt sự kiện bấm
		 */
			if ((x &gt;= rec.left) &amp;&amp; (x = rec.top)
				&amp;&amp; (y &lt;= rec.bottom)) {
			intercept = true;
		}
		break;
		}
	return intercept;
}

Hàm sau được dùng để focus khi người dùng chụp ảnh. Nó cũng được dùng để vẽ lại sử dụng hàm invalidate() khi cần vẽ lại gì đó

public void onSensorChanged(SensorEvent event) {
	if (mInvalidate == true) {
		mView.invalidate();
			mInvalidate = false;
	}
	float x = event.values[0];
	float y = event.values[1];
	float z = event.values[2];
		if (!mInitialized) {
		mLastX = x;
		mLastY = y;
		mLastZ = z;
			mInitialized = true;
	}
	float deltaX = Math.abs(mLastX - x);
	float deltaY = Math.abs(mLastY - y);
	float deltaZ = Math.abs(mLastZ - z);
if (deltaX > .5 && mAutoFocus) {
			mAutoFocus = false;
		mPreview.setCameraFocus(myAutoFocusCallback);
	}
	if (deltaY > .5 && mAutoFocus) {
		mAutoFocus = false;
		mPreview.setCameraFocus(myAutoFocusCallback);
	}
		if (deltaZ > .5 && mAutoFocus) {
		mAutoFocus = false;
		mPreview.setCameraFocus(myAutoFocusCallback);
	}
		mLastX = x;
		mLastY = y;
	mLastZ = z;
}

Main.xml

Với tất cả những công việc trên, bạn phải có một giao diện phù hợp:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <com.camera.ocruit.Preview
        android:id="@+id/preview"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" ></com.camera.ocruit.Preview>

    <com.camera.ocruit.TouchView
        android:id="@+id/touch_view"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" ></com.camera.ocruit.TouchView>

    <ImageView
        android:id="@+id/btnCapture"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"/>
    <TextView 
        android:id="@+id/txtOCRText"
        android:layout_width="fill_parent"
        android:layout_height="300dp"
        android:text="No line"
        android:textSize="15dp"
        
      ></TextView>

</RelativeLayout>

Chúc thành công nhé

Advertisements

5 responses to “Vẽ hình chữ nhật để khoanh vùng ảnh cần chụp trên camera

  1. Pingback: Vẽ màn màn tối bao quanh vùng trên hình ảnh. « .:: Hoàng Minh ::.

  2. Chasidy November 22, 2012 at 1:06 PM

    This is a comment to the admin. Your website:https://tranhoangminh.wordpress.com/2012/11/15/ve-hinh-chu-nhat-de-khoanh-vung-anh-can-chup-tren-camera/ is missing out on at least 300 visitors per day. I came to this page via Google but it was hard to find as you were not on the front page of search results. I have found a website which offers to dramatically increase your traffic to your website: http://voxseo.com/traffic/. I managed to get over 10,000 visitors per month using their services, you could also get lot more targeted traffic than you have now. Hope this helps 🙂 Take care.

  3. Hoàng Minh November 23, 2012 at 6:46 PM

    I visited that website, but i do not know how to use it. Would you please direct me?

  4. TrieuDoLa August 24, 2015 at 8:22 AM

    Xin lỗi,nhưng anh có thể chia sẻ cho em project này được kg ạ!
    Thank anh!

  5. Pingback: Xin lỗi mọi người! Mọi người ai có project của… | Hội Lập Trình Android

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: