玩转Android Camera开发:预览界面四周暗中间亮,仅仅拍摄矩形区域图片(附完整源代码)

登山则情满于山,观海则意溢于海。这篇文章主要讲述玩转Android Camera开发:预览界面四周暗中间亮,仅仅拍摄矩形区域图片(附完整源代码)相关的知识,希望能为你提供帮助。
杂家前文曾写过一篇关于仅仅拍摄特定区域图片的demo。仅仅是比較简陋。在坐标的换算上不是非常严谨,并且没有完毕预览界面四周暗中间亮的效果,深以为憾。今天把这个补齐了。
【玩转Android Camera开发:预览界面四周暗中间亮,仅仅拍摄矩形区域图片(附完整源代码)】 在上代码之前首先交代下,这里面存在着换算的两种模式。第一种,是以屏幕上的矩形区域为基准进行换算。举个样例。屏幕中间一个 矩形框为100dip*100dip.这里一定要使用dip为单位,否则在不同的手机上屏幕呈现的矩形框大小不一样。
先将这个dip换算成px。然后依据屏幕的宽和高的像素计算出矩形区域,传给Surfaceview上铺的一层View,这里叫MaskView(蒙板),让MaskView进行绘制。然后拍照时。通过屏幕矩形框的大小和屏幕的大小与终于拍摄图片的PictureSize进行换算。得到图片里的矩形区域图片,然后截取保存。另外一种模式是,预先知道想要的图片的长宽,如我就是想截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 stub initPaint(); mContext = context; Point p = DisplayUtil.getScreenMetrics(mContext); widthScreen = p.x; heightScreen = p.y; } private void initPaint(){ //绘制中间透明区域矩形边界的Paint mLinePaint = 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; @Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub Log.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绘制了
@Override public void cameraHasOpened() { // TODO Auto-generated method stub SurfaceHolder 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{@Override public void onClick(View v) { // TODO Auto-generated method stub switch(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 stub Log.i(TAG, "myJpegCallback:onPictureTaken..."); Bitmap b = null; if(null != data){ b = BitmapFactory.decodeByteArray(data, 0, data.length); //data是字节数据,将其解析成位图 mCamera.stopPreview(); isPreviewing = false; } //保存图片到sdcard if(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和PictureSize Size 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);

玩转Android Camera开发:预览界面四周暗中间亮,仅仅拍摄矩形区域图片(附完整源代码)

文章图片



玩转Android Camera开发:预览界面四周暗中间亮,仅仅拍摄矩形区域图片(附完整源代码)

文章图片
能够看到单纯的截取是不改变图像分辨率的。注意真正的分辨率的概念并不等于xxx * xxx,图片放的越大越不清楚。稍后推出矩形区域能够移动、且可拉伸的。拍摄任何位置的特定区域图片demo。-------------------------------本文系原创,转载请注明作者:yanzi1225627代码下载链接:csdn:http://download.csdn.net/detail/yanzi1225627/7557539






































    推荐阅读