玩轉Android Camera開發(四):預覽介面四周暗中間亮,僅僅拍攝矩形地區圖片(附完整原始碼)

來源:互聯網
上載者:User

標籤:答案   recycle   sch   XML   card   tom   car   預覽   raw   

雜家前文曾寫過一篇關於僅僅拍攝特定地區圖片的demo。僅僅是比較簡陋。在座標的換算上不是非常嚴謹,並且沒有完畢預覽介面四周暗中間亮的效果,深以為憾。今天把這個補齊了。

在上代碼之前首先交代下,這裡面存在著換算的兩種模式。第一種,是以螢幕上的矩形地區為基準進行換算。舉個範例。螢幕中間一個 矩形框為100dip*100dip.這裡一定要使用dip為單位,否則在不同的手機上螢幕呈現的矩形框大小不一樣。

先將這個dip換算成px。然後依據螢幕的寬和高的像素計算出矩形地區,傳給Surfaceview上鋪的一層View,這裡叫MaskView(蒙板),讓MaskView進行繪製。然後拍照時。通過螢幕矩形框的大小和螢幕的大小與終於拍攝圖片的PictureSize進行換算。得到圖片裡的矩形地區圖片,然後截取儲存。另外一種模式是,預Crowdsourced Security Testing道想要的圖片的長寬,如我就是想截400*400(單位為px)大小的圖片。

那就以此為基準,換算出螢幕上呈現的Rect的長寬,然後讓MaskView繪製。

到底用哪一種模式,按需選擇。本文以第一種模式示範範例。以下上代碼:

在雜家的前文基礎上進行封裝。首先封裝一個MaskView,用來繪製四周暗中間亮的效果,或者你能夠加一個滾動欄。這都不是事。

一、MaskView.java

package org.yanzi.ui;import org.yanzi.util.DisplayUtil;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Paint.Style;import android.graphics.Point;import android.graphics.Rect;import android.util.AttributeSet;import android.util.Log;import android.widget.ImageView;public class MaskView extends ImageView {private static final String TAG = "YanZi";private Paint mLinePaint;private Paint mAreaPaint;private Rect mCenterRect = null;private Context mContext;public MaskView(Context context, AttributeSet attrs) {super(context, attrs);// TODO Auto-generated constructor stubinitPaint();mContext = context;Point p= DisplayUtil.getScreenMetrics(mContext);widthScreen = p.x;heightScreen = p.y;}private void initPaint(){//繪製中間透明地區矩形邊界的PaintmLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);mLinePaint.setColor(Color.BLUE);mLinePaint.setStyle(Style.STROKE);mLinePaint.setStrokeWidth(5f);mLinePaint.setAlpha(30);//繪製四周陰影地區mAreaPaint = new Paint(Paint.ANTI_ALIAS_FLAG);mAreaPaint.setColor(Color.GRAY);mAreaPaint.setStyle(Style.FILL);mAreaPaint.setAlpha(180);}public void setCenterRect(Rect r){Log.i(TAG, "setCenterRect...");this.mCenterRect = r;postInvalidate();}public void clearCenterRect(Rect r){this.mCenterRect = null;}int widthScreen, heightScreen;@Overrideprotected void onDraw(Canvas canvas) {// TODO Auto-generated method stubLog.i(TAG, "onDraw...");if(mCenterRect == null)return;//繪製四周陰影地區canvas.drawRect(0, 0, widthScreen, mCenterRect.top, mAreaPaint);canvas.drawRect(0, mCenterRect.bottom + 1, widthScreen, heightScreen, mAreaPaint);canvas.drawRect(0, mCenterRect.top, mCenterRect.left - 1, mCenterRect.bottom  + 1, mAreaPaint);canvas.drawRect(mCenterRect.right + 1, mCenterRect.top, widthScreen, mCenterRect.bottom + 1, mAreaPaint);//繪製目標透明地區canvas.drawRect(mCenterRect, mLinePaint);super.onDraw(canvas);}}
說明例如以下:

1、為了讓這個MaskView有更好的適配型,裡面設定變數mCenterRect,這個矩陣的座標就是已經換算好的。對螢幕的尺寸進行適配過的,以全屏下的螢幕寬高為座標系,不須要再換算了。

2、當然這個MaskView是全屏的,這裡改動下PlayCamera_V1.0.0中的一個小問題,我將它的布局換成例如以下:

<RelativeLayout 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"    tools:context=".CameraActivity" >    <FrameLayout        android:layout_width="match_parent"        android:layout_height="match_parent" >        <org.yanzi.camera.preview.CameraSurfaceView            android:id="@+id/camera_surfaceview"            android:layout_width="0dip"            android:layout_height="0dip" />        <org.yanzi.ui.MaskView            android:id="@+id/view_mask"            android:layout_width="match_parent"            android:layout_height="match_parent" />    </FrameLayout>    <ImageButton        android:id="@+id/btn_shutter"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_alignParentBottom="true"        android:layout_centerHorizontal="true"        android:layout_marginBottom="10dip"        android:background="@drawable/btn_shutter_background" /></RelativeLayout>
更改的地方是讓FrameLayout直接全屏。不要設定成wrap_content,假設設它為wrap。代碼裡調整Surfaceview的大小,而MaskView設為wrap的話,它會覺得MaskView的長寬也是0.另外,讓Framelayout全屏。在日後16:9和4:3切換時,能夠通過設定Surfaceview的margin來調整預覽布局的大小,所以預覽的母布局FrameLayout必須全屏。

3.關於繪製陰影地區的代碼裡的+1 -1這幾個小地方盡量不要錯,按本文寫就不會錯。順序是先繪製最上面、最以下、左側、右側四個地區的陰影。

//繪製四周陰影地區canvas.drawRect(0, 0, widthScreen, mCenterRect.top, mAreaPaint);canvas.drawRect(0, mCenterRect.bottom + 1, widthScreen, heightScreen, mAreaPaint);canvas.drawRect(0, mCenterRect.top, mCenterRect.left - 1, mCenterRect.bottom  + 1, mAreaPaint);canvas.drawRect(mCenterRect.right + 1, mCenterRect.top, widthScreen, mCenterRect.bottom + 1, mAreaPaint);

二、在CameraActivity.java裡封裝兩個函數:

/**產生拍照後圖片的中間矩形的寬度和高度 * @param w 螢幕上的矩形寬度,單位px * @param h 螢幕上的矩形高度。單位px * @return */private Point createCenterPictureRect(int w, int h){int wScreen = DisplayUtil.getScreenMetrics(this).x;int hScreen = DisplayUtil.getScreenMetrics(this).y;int wSavePicture = CameraInterface.getInstance().doGetPrictureSize().y; //由於圖片旋轉了,所以此處寬高換位int hSavePicture = CameraInterface.getInstance().doGetPrictureSize().x; //由於圖片旋轉了。所以此處寬高換位float wRate = (float)(wSavePicture) / (float)(wScreen);float hRate = (float)(hSavePicture) / (float)(hScreen);float rate = (wRate <= hRate) ? wRate : hRate;//也能夠依照最小比率計算int wRectPicture = (int)( w * wRate);int hRectPicture = (int)( h * hRate);return new Point(wRectPicture, hRectPicture);}/** * 產生螢幕中間的矩形 * @param w 目標矩形的寬度,單位px * @param h目標矩形的高度,單位px * @return */private Rect createCenterScreenRect(int w, int h){int x1 = DisplayUtil.getScreenMetrics(this).x / 2 - w / 2;int y1 = DisplayUtil.getScreenMetrics(this).y / 2 - h / 2;int x2 = x1 + w;int y2 = y1 + h;return new Rect(x1, y1, x2, y2);}
各自是產生圖片的中間矩形的寬和高組成的一個Point,產生螢幕中間的矩形地區。兩個函數的輸入參數都是px為單位的螢幕中間矩形的寬和高。

這裡有個條件:矩形以螢幕中心為中心,否則的話計算公式要適當變換下

三、在開啟預覽後,就能夠讓MaskView繪製了

@Overridepublic void cameraHasOpened() {// TODO Auto-generated method stubSurfaceHolder holder = surfaceView.getSurfaceHolder();CameraInterface.getInstance().doStartPreview(holder, previewRate);if(maskView != null){Rect screenCenterRect = createCenterScreenRect(DisplayUtil.dip2px(this, DST_CENTER_RECT_WIDTH),DisplayUtil.dip2px(this, DST_CENTER_RECT_HEIGHT));maskView.setCenterRect(screenCenterRect);}}
這裡有個注意事項:由於camera.open的時候是放在一個單獨線程裡的。open之後進行回調到cameraHasOpened()這裡,那這個函數的運行時在主線程和子線程? 答案也是在子線程,即子線程的回調還是在子線程裡運行。

正因此。在封裝MaskView時set矩陣後用的是postInvalidate()進行重新整理的。

public void setCenterRect(Rect r){Log.i(TAG, "setCenterRect...");this.mCenterRect = r;postInvalidate();}

四、最後就是告訴拍照的回調了

private class BtnListeners implements OnClickListener{@Overridepublic void onClick(View v) {// TODO Auto-generated method stubswitch(v.getId()){case R.id.btn_shutter:if(rectPictureSize == null){rectPictureSize = createCenterPictureRect(DisplayUtil.dip2px(CameraActivity.this, DST_CENTER_RECT_WIDTH),DisplayUtil.dip2px(CameraActivity.this, DST_CENTER_RECT_HEIGHT));}CameraInterface.getInstance().doTakePicture(rectPictureSize.x, rectPictureSize.y);break;default:break;}}}
上面是拍照的監聽,在CameraInterface裡重寫一個doTakePicture函數:

int DST_RECT_WIDTH, DST_RECT_HEIGHT;public void doTakePicture(int w, int h){if(isPreviewing && (mCamera != null)){Log.i(TAG, "矩形拍照尺寸:width = " + w + " h = " + h);DST_RECT_WIDTH = w;DST_RECT_HEIGHT = h;mCamera.takePicture(mShutterCallback, null, mRectJpegPictureCallback);}}
這裡出來個mRectJpegPictureCallback,它相應的類:

/** * 拍攝指定地區的Rect */PictureCallback mRectJpegPictureCallback = new PictureCallback() //對jpeg映像資料的回調,最重要的一個回調{public void onPictureTaken(byte[] data, Camera camera) {// TODO Auto-generated method stubLog.i(TAG, "myJpegCallback:onPictureTaken...");Bitmap b = null;if(null != data){b = BitmapFactory.decodeByteArray(data, 0, data.length);//data是位元組資料,將其解析成位元影像mCamera.stopPreview();isPreviewing = false;}//儲存圖片到sdcardif(null != b){//設定FOCUS_MODE_CONTINUOUS_VIDEO)之後,myParam.set("rotation", 90)失效。

//圖片居然不能旋轉了,故這裡要旋轉下Bitmap rotaBitmap = ImageUtil.getRotateBitmap(b, 90.0f);int x = rotaBitmap.getWidth()/2 - DST_RECT_WIDTH/2;int y = rotaBitmap.getHeight()/2 - DST_RECT_HEIGHT/2;Log.i(TAG, "rotaBitmap.getWidth() = " + rotaBitmap.getWidth()+ " rotaBitmap.getHeight() = " + rotaBitmap.getHeight());Bitmap rectBitmap = Bitmap.createBitmap(rotaBitmap, x, y, DST_RECT_WIDTH, DST_RECT_HEIGHT);FileUtil.saveBitmap(rectBitmap);if(rotaBitmap.isRecycled()){rotaBitmap.recycle();rotaBitmap = null;}if(rectBitmap.isRecycled()){rectBitmap.recycle();rectBitmap = null;}}//再次進入預覽mCamera.startPreview();isPreviewing = true;if(!b.isRecycled()){b.recycle();b = null;}}};


注意事項:

1、為了讓截出的地區和螢幕上顯示的全然一致,這裡首先要滿足PreviewSize長寬比、PictureSize長寬比、螢幕預覽Surfaceview的長寬比為同一比例,這是個先決條件。然後再將螢幕矩形地區長寬換算成圖片矩形地區時:

/**產生拍照後圖片的中間矩形的寬度和高度
* @param w 螢幕上的矩形寬度,單位px
* @param h 螢幕上的矩形高度,單位px
* @return
*/
private Point createCenterPictureRect(int w, int h){

int wScreen = DisplayUtil.getScreenMetrics(this).x;
int hScreen = DisplayUtil.getScreenMetrics(this).y;
int wSavePicture = CameraInterface.getInstance().doGetPrictureSize().y; //由於圖片旋轉了,所以此處寬高換位
int hSavePicture = CameraInterface.getInstance().doGetPrictureSize().x; //由於圖片旋轉了。所以此處寬高換位
float wRate = (float)(wSavePicture) / (float)(wScreen);
float hRate = (float)(hSavePicture) / (float)(hScreen);
float rate = (wRate <= hRate) ?

wRate : hRate;//也能夠依照最小比率計算

int wRectPicture = (int)( w * wRate);
int hRectPicture = (int)( h * hRate);
return new Point(wRectPicture, hRectPicture);

}

原則上wRate 是應該等於hRate 的。。!!

。!!!

!!

2、我對CamParaUtil裡的getPropPreviewSize和getPropPictureSize進行了更新。曾經是以width進行推斷的,這裡改成了以height進行推斷。

由於在讀取參數時得到的是800*480(寬*高)這樣的類型,一般高是略微小的,所以以height進行推斷。而這個高在終於顯示和儲存時經過旋轉又成了寬。

public Size getPropPictureSize(List<Camera.Size> list, float th, int minHeight){Collections.sort(list, sizeComparator);int i = 0;for(Size s:list){if((s.height >= minHeight) && equalRate(s, th)){Log.i(TAG, "PictureSize : w = " + s.width + "h = " + s.height);break;}i++;}if(i == list.size()){i = 0;//假設沒找到,就選最小的size}return list.get(i);}
最後來看下效果吧。我設定螢幕上顯示的矩形尺寸為200dip*200dip, Camera預覽的參數是以螢幕的比例進行自己主動尋找,預覽尺寸的height不小於400,PictureSize的height不小於1300.

//設定PreviewSize和PictureSizeSize pictureSize = CamParaUtil.getInstance().getPropPictureSize(mParams.getSupportedPictureSizes(),previewRate, 1300);mParams.setPictureSize(pictureSize.width, pictureSize.height);Size previewSize = CamParaUtil.getInstance().getPropPreviewSize(mParams.getSupportedPreviewSizes(), previewRate, 400);mParams.setPreviewSize(previewSize.width, previewSize.height);

能夠看到單純的截取是不改變映像解析度的。注意真正的解析度的概念並不等於xxx * xxx,圖片放的越大越不清楚。稍後推出矩形地區能夠移動、且可展開的。拍攝任何位置的特定地區圖片demo。-------------------------------本文系原創,轉載請註明作者:yanzi1225627代碼下載連結:csdn:http://download.csdn.net/detail/yanzi1225627/7557539





玩轉Android Camera開發(四):預覽介面四周暗中間亮,僅僅拍攝矩形地區圖片(附完整原始碼)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.