0

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

杂家前文曾写过一篇关于只拍摄特定区域图片的demo,只是比较简陋,在坐标的换算上不是很严谨,而且没有完成预览界面四周暗中间亮的效果,深以为憾,今天把这个补齐了。

在上代码之前首先交代下,这里面存在着换算的两种模式。第一种,是以屏幕上的矩形区域为基准进行换算。举个例子,屏幕中间一个 矩形框为100dip*100dip.这里一定要使用dip为单位,否则在不同的手机上屏幕呈现的矩形框大小不一样。先将这个dip换算成px,然后根据屏幕的宽和高的像素计算出矩形区域,传给Surfaceview上铺的一层View,这里叫MaskView(蒙板),让MaskView进行绘制。然后拍照时,通过屏幕矩形框的大小和屏幕的大小与最终拍摄图片的PictureSize进行换算,得到图片里的矩形区域图片,然后截取保存。第二种模式是,预先知道想要的图片的长宽,如我就是想截400*400(单位为px)大小的图片。那就以此为基准,换算出屏幕上呈现的Rect的长宽,然后让MaskView绘制。究竟用哪一种模式,按需选择。本文以第一种模式示例。下面上代码:

在杂家的前文基础上进行封装,首先封装一个MaskView,用来绘制四周暗中间亮的效果,或者你可以加一个滚动条,这都不是事。

一、MaskView.java

 

  1. <span style=“font-family:Comic Sans MS;font-size:18px;”>package org.yanzi.ui;  
  2.   
  3. import org.yanzi.util.DisplayUtil;  
  4.   
  5. import android.content.Context;  
  6. import android.graphics.Canvas;  
  7. import android.graphics.Color;  
  8. import android.graphics.Paint;  
  9. import android.graphics.Paint.Style;  
  10. import android.graphics.Point;  
  11. import android.graphics.Rect;  
  12. import android.util.AttributeSet;  
  13. import android.util.Log;  
  14. import android.widget.ImageView;  
  15.   
  16. public class MaskView extends ImageView {  
  17.     private static final String TAG = “YanZi”;  
  18.     private Paint mLinePaint;  
  19.     private Paint mAreaPaint;  
  20.     private Rect mCenterRect = null;  
  21.     private Context mContext;  
  22.   
  23.   
  24.     public MaskView(Context context, AttributeSet attrs) {  
  25.         super(context, attrs);  
  26.         // TODO Auto-generated constructor stub  
  27.         initPaint();  
  28.         mContext = context;  
  29.         Point p = DisplayUtil.getScreenMetrics(mContext);  
  30.         widthScreen = p.x;  
  31.         heightScreen = p.y;  
  32.     }  
  33.   
  34.     private void initPaint(){  
  35.         //绘制中间透明区域矩形边界的Paint  
  36.         mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
  37.         mLinePaint.setColor(Color.BLUE);  
  38.         mLinePaint.setStyle(Style.STROKE);  
  39.         mLinePaint.setStrokeWidth(5f);  
  40.         mLinePaint.setAlpha(30);  
  41.   
  42.         //绘制四周阴影区域  
  43.         mAreaPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
  44.         mAreaPaint.setColor(Color.GRAY);  
  45.         mAreaPaint.setStyle(Style.FILL);  
  46.         mAreaPaint.setAlpha(180);  
  47.           
  48.           
  49.           
  50.     }  
  51.     public void setCenterRect(Rect r){  
  52.         Log.i(TAG, “setCenterRect…”);  
  53.         this.mCenterRect = r;  
  54.         postInvalidate();  
  55.     }  
  56.     public void clearCenterRect(Rect r){  
  57.         this.mCenterRect = null;  
  58.     }  
  59.   
  60.     int widthScreen, heightScreen;  
  61.     @Override  
  62.     protected void onDraw(Canvas canvas) {  
  63.         // TODO Auto-generated method stub  
  64.         Log.i(TAG, “onDraw…”);  
  65.         if(mCenterRect == null)  
  66.             return;  
  67.         //绘制四周阴影区域  
  68.         canvas.drawRect(00, widthScreen, mCenterRect.top, mAreaPaint);  
  69.         canvas.drawRect(0, mCenterRect.bottom + 1, widthScreen, heightScreen, mAreaPaint);  
  70.         canvas.drawRect(0, mCenterRect.top, mCenterRect.left – 1, mCenterRect.bottom  + 1, mAreaPaint);  
  71.         canvas.drawRect(mCenterRect.right + 1, mCenterRect.top, widthScreen, mCenterRect.bottom + 1, mAreaPaint);  
  72.   
  73.         //绘制目标透明区域  
  74.         canvas.drawRect(mCenterRect, mLinePaint);  
  75.         super.onDraw(canvas);  
  76.     }  
  77.   
  78.   
  79.   
  80. }  
  81. </span>  

说明如下:

 

1、为了让这个MaskView有更好的适配型,里面设置变量mCenterRect,这个矩阵的坐标就是已经换算好的,对屏幕的尺寸进行适配过的,以全屏下的屏幕宽高为坐标系,不需要再换算了。

2、当然这个MaskView是全屏的,这里修改下PlayCamera_V1.0.0中的一个小问题,我将它的布局换成如下:

 

  1. <span style=“font-family:Comic Sans MS;font-size:18px;”><RelativeLayout xmlns:android=“http://schemas.android.com/apk/res/android”  
  2.     xmlns:tools=“http://schemas.android.com/tools”  
  3.     android:layout_width=“match_parent”  
  4.     android:layout_height=“match_parent”  
  5.     tools:context=“.CameraActivity” >  
  6.   
  7.     <FrameLayout  
  8.         android:layout_width=“match_parent”  
  9.         android:layout_height=“match_parent” >  
  10.         <org.yanzi.camera.preview.CameraSurfaceView  
  11.             android:id=“@+id/camera_surfaceview”  
  12.             android:layout_width=“0dip”  
  13.             android:layout_height=“0dip” />  
  14.         <org.yanzi.ui.MaskView  
  15.             android:id=“@+id/view_mask”  
  16.             android:layout_width=“match_parent”  
  17.             android:layout_height=“match_parent” />  
  18.     </FrameLayout>  
  19.   
  20.     <ImageButton  
  21.         android:id=“@+id/btn_shutter”  
  22.         android:layout_width=“wrap_content”  
  23.         android:layout_height=“wrap_content”  
  24.         android:layout_alignParentBottom=“true”  
  25.         android:layout_centerHorizontal=“true”  
  26.         android:layout_marginBottom=“10dip”  
  27.         android:background=“@drawable/btn_shutter_background” />  
  28.   
  29. </RelativeLayout></span>  

更改的地方是让FrameLayout直接全屏,不要设置成wrap_content,如果设它为wrap,代码里调整Surfaceview的大小,而MaskView设为wrap的话,它会认为MaskView的长宽也是0.另外,让Framelayout全屏,在日后16:9和4:3切换时,可以通过设置Surfaceview的margin来调整预览布局的大小,所以预览的母布局FrameLayout必须全屏。

 

3.关于绘制阴影区域的代码里的+1 -1这几个小地方尽量不要错,按本文写就不会错。顺序是先绘制最上面、最下面、左侧、右侧四个区域的阴影。

 

  1. <span style=“font-family:Comic Sans MS;font-size:18px;”>//绘制四周阴影区域  
  2.         canvas.drawRect(00, widthScreen, mCenterRect.top, mAreaPaint);  
  3.         canvas.drawRect(0, mCenterRect.bottom + 1, widthScreen, heightScreen, mAreaPaint);  
  4.         canvas.drawRect(0, mCenterRect.top, mCenterRect.left – 1, mCenterRect.bottom  + 1, mAreaPaint);  
  5.         canvas.drawRect(mCenterRect.right + 1, mCenterRect.top, widthScreen, mCenterRect.bottom + 1, mAreaPaint);</span>  

 

二、在CameraActivity.java里封装两个函数:

 

 

  1. <span style=“font-family:Comic Sans MS;font-size:18px;”>    /**生成拍照后图片的中间矩形的宽度和高度 
  2.      * @param w 屏幕上的矩形宽度,单位px 
  3.      * @param h 屏幕上的矩形高度,单位px 
  4.      * @return 
  5.      */  
  6.     private Point createCenterPictureRect(int w, int h){  
  7.           
  8.         int wScreen = DisplayUtil.getScreenMetrics(this).x;  
  9.         int hScreen = DisplayUtil.getScreenMetrics(this).y;  
  10.         int wSavePicture = CameraInterface.getInstance().doGetPrictureSize().y; //因为图片旋转了,所以此处宽高换位  
  11.         int hSavePicture = CameraInterface.getInstance().doGetPrictureSize().x; //因为图片旋转了,所以此处宽高换位  
  12.         float wRate = (float)(wSavePicture) / (float)(wScreen);  
  13.         float hRate = (float)(hSavePicture) / (float)(hScreen);  
  14.         float rate = (wRate <= hRate) ? wRate : hRate;//也可以按照最小比率计算  
  15.           
  16.         int wRectPicture = (int)( w * wRate);  
  17.         int hRectPicture = (int)( h * hRate);  
  18.         return new Point(wRectPicture, hRectPicture);  
  19.           
  20.     }  
  21.     /** 
  22.      * 生成屏幕中间的矩形 
  23.      * @param w 目标矩形的宽度,单位px 
  24.      * @param h 目标矩形的高度,单位px 
  25.      * @return 
  26.      */  
  27.     private Rect createCenterScreenRect(int w, int h){  
  28.         int x1 = DisplayUtil.getScreenMetrics(this).x / 2 – w / 2;  
  29.         int y1 = DisplayUtil.getScreenMetrics(this).y / 2 – h / 2;  
  30.         int x2 = x1 + w;  
  31.         int y2 = y1 + h;  
  32.         return new Rect(x1, y1, x2, y2);  
  33.     }</span>  

分别是生成图片的中间矩形的宽和高组成的一个Point,生成屏幕中间的矩形区域。两个函数的输入参数都是px为单位的屏幕中间矩形的宽和高。这里有个条件:矩形以屏幕中心为中心,否则的话计算公式要适当变换下

 

三、在开启预览后,就可以让MaskView绘制了

 

  1. <span style=“font-family:Comic Sans MS;font-size:18px;”>    @Override  
  2.     public void cameraHasOpened() {  
  3.         // TODO Auto-generated method stub  
  4.         SurfaceHolder holder = surfaceView.getSurfaceHolder();  
  5.         CameraInterface.getInstance().doStartPreview(holder, previewRate);  
  6.         if(maskView != null){  
  7.             Rect screenCenterRect = createCenterScreenRect(DisplayUtil.dip2px(this, DST_CENTER_RECT_WIDTH)  
  8.                     ,DisplayUtil.dip2px(this, DST_CENTER_RECT_HEIGHT));  
  9.             maskView.setCenterRect(screenCenterRect);  
  10.         }  
  11.     }</span>  

这里有个注意事项:因为camera.open的时候是放在一个单独线程里的,open之后进行回调到cameraHasOpened()这里,那这个函数的执行时在主线程和子线程?答案也是在子线程,即子线程的回调还是在子线程里执行。正因此,在封装MaskView时set矩阵后用的是postInvalidate()进行刷新的。

  1. <span style=“font-family:Comic Sans MS;font-size:18px;”>    public void setCenterRect(Rect r){  
  2.         Log.i(TAG, “setCenterRect…”);  
  3.         this.mCenterRect = r;  
  4.         postInvalidate();  
  5.     }</span>  

 

四、最后就是告诉拍照的回调了

 

 

  1. <span style=“font-family:Comic Sans MS;font-size:18px;”>private class BtnListeners implements OnClickListener{  
  2.   
  3.         @Override  
  4.         public void onClick(View v) {  
  5.             // TODO Auto-generated method stub  
  6.             switch(v.getId()){  
  7.             case R.id.btn_shutter:  
  8.                 if(rectPictureSize == null){  
  9.                     rectPictureSize = createCenterPictureRect(DisplayUtil.dip2px(CameraActivity.this, DST_CENTER_RECT_WIDTH)  
  10.                             ,DisplayUtil.dip2px(CameraActivity.this, DST_CENTER_RECT_HEIGHT));  
  11.                 }  
  12.                 CameraInterface.getInstance().doTakePicture(rectPictureSize.x, rectPictureSize.y);  
  13.                 break;  
  14.             default:break;  
  15.             }  
  16.         }  
  17.   
  18.     }</span>  

上面是拍照的监听,在CameraInterface里重写一个doTakePicture函数:

 

 

  1. <span style=“font-family:Comic Sans MS;font-size:18px;”>    int DST_RECT_WIDTH, DST_RECT_HEIGHT;  
  2.     public void doTakePicture(int w, int h){  
  3.         if(isPreviewing && (mCamera != null)){  
  4.             Log.i(TAG, “矩形拍照尺寸:width = “ + w + ” h = “ + h);  
  5.             DST_RECT_WIDTH = w;  
  6.             DST_RECT_HEIGHT = h;  
  7.             mCamera.takePicture(mShutterCallback, null, mRectJpegPictureCallback);  
  8.         }  
  9.     }</span>  

这里出来个mRectJpegPictureCallback,它对应的类:

 

 

  1. <span style=“font-family:Comic Sans MS;font-size:18px;”>/** 
  2.      * 拍摄指定区域的Rect 
  3.      */  
  4.     PictureCallback mRectJpegPictureCallback = new PictureCallback()   
  5.     //对jpeg图像数据的回调,最重要的一个回调  
  6.     {  
  7.         public void onPictureTaken(byte[] data, Camera camera) {  
  8.             // TODO Auto-generated method stub  
  9.             Log.i(TAG, “myJpegCallback:onPictureTaken…”);  
  10.             Bitmap b = null;  
  11.             if(null != data){  
  12.                 b = BitmapFactory.decodeByteArray(data, 0, data.length);//data是字节数据,将其解析成位图  
  13.                 mCamera.stopPreview();  
  14.                 isPreviewing = false;  
  15.             }  
  16.             //保存图片到sdcard  
  17.             if(null != b)  
  18.             {  
  19.                 //设置FOCUS_MODE_CONTINUOUS_VIDEO)之后,myParam.set(“rotation”, 90)失效。  
  20.                 //图片竟然不能旋转了,故这里要旋转下  
  21.                 Bitmap rotaBitmap = ImageUtil.getRotateBitmap(b, 90.0f);  
  22.                 int x = rotaBitmap.getWidth()/2 – DST_RECT_WIDTH/2;  
  23.                 int y = rotaBitmap.getHeight()/2 – DST_RECT_HEIGHT/2;  
  24.                 Log.i(TAG, “rotaBitmap.getWidth() = “ + rotaBitmap.getWidth()  
  25.                         + ” rotaBitmap.getHeight() = “ + rotaBitmap.getHeight());  
  26.                 Bitmap rectBitmap = Bitmap.createBitmap(rotaBitmap, x, y, DST_RECT_WIDTH, DST_RECT_HEIGHT);  
  27.                 FileUtil.saveBitmap(rectBitmap);  
  28.                 if(rotaBitmap.isRecycled()){  
  29.                     rotaBitmap.recycle();  
  30.                     rotaBitmap = null;  
  31.                 }  
  32.                 if(rectBitmap.isRecycled()){  
  33.                     rectBitmap.recycle();  
  34.                     rectBitmap = null;  
  35.                 }  
  36.             }  
  37.             //再次进入预览  
  38.             mCamera.startPreview();  
  39.             isPreviewing = true;  
  40.             if(!b.isRecycled()){  
  41.                 b.recycle();  
  42.                 b = null;  
  43.             }  
  44.   
  45.         }  
  46.     };</span>  

 

注意事项:

 

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进行判断。而这个高在最终显示和保存时经过旋转又成了宽。

 

  1. <span style=“font-family:Comic Sans MS;font-size:18px;”>    public Size getPropPictureSize(List<Camera.Size> list, float th, int minHeight){  
  2.         Collections.sort(list, sizeComparator);  
  3.   
  4.         int i = 0;  
  5.         for(Size s:list){  
  6.             if((s.height >= minHeight) && equalRate(s, th)){  
  7.                 Log.i(TAG, “PictureSize : w = “ + s.width + “h = “ + s.height);  
  8.                 break;  
  9.             }  
  10.             i++;  
  11.         }  
  12.         if(i == list.size()){  
  13.             i = 0;//如果没找到,就选最小的size  
  14.         }  
  15.         return list.get(i);  
  16.     }</span>  

最后来看下效果吧,我设定屏幕上显示的矩形尺寸为200dip*200dip, Camera预览的参数是以屏幕的比例进行自动寻找,预览尺寸的height不小于400,PictureSize的height不小于1300.

 

 

  1. <span style=“font-family:Comic Sans MS;font-size:18px;”>            //设置PreviewSize和PictureSize  
  2.             Size pictureSize = CamParaUtil.getInstance().getPropPictureSize(  
  3.                     mParams.getSupportedPictureSizes(),previewRate, 1300);  
  4.             mParams.setPictureSize(pictureSize.width, pictureSize.height);  
  5.             Size previewSize = CamParaUtil.getInstance().getPropPreviewSize(  
  6.                     mParams.getSupportedPreviewSizes(), previewRate, 400);  
  7.             mParams.setPreviewSize(previewSize.width, previewSize.height);</span>  

 

 

 

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

天边的星星