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

在上代码之前首先交代下,这里面存在着换算的两种模式。第一种,是以屏幕上的矩形区域为基准进行换算。举个样例。屏幕中间一个 矩形框为100dip*100dip.这里一定要使用dip为单位,否则在不同的手机上屏幕呈现的矩形框大小不一样。

先将这个dip换算成px。然后依据屏幕的宽和高的像素计算出矩形区域,传给Surfaceview上铺的一层View,这里叫MaskView(蒙板),让MaskView进行绘制。然后拍照时。通过屏幕矩形框的大小和屏幕的大小与终于拍摄图片的PictureSize进行换算。得到图片里的矩形区域图片,然后截取保存。另外一种模式是,预先知道想要的图片的长宽,如我就是想截400*400(单位为px)大小的图片。

那就以此为基准,换算出屏幕上呈现的Rect的长宽,然后让MaskView绘制。

到底用哪一种模式,按需选择。本文以第一种模式演示样例。以下上代码:

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

一、MaskView.java

  1. 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. public MaskView(Context context, AttributeSet attrs) {
  24. super(context, attrs);
  25. // TODO Auto-generated constructor stub
  26. initPaint();
  27. mContext = context;
  28. Point p = DisplayUtil.getScreenMetrics(mContext);
  29. widthScreen = p.x;
  30. heightScreen = p.y;
  31. }
  32.  
  33. private void initPaint(){
  34. //绘制中间透明区域矩形边界的Paint
  35. mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
  36. mLinePaint.setColor(Color.BLUE);
  37. mLinePaint.setStyle(Style.STROKE);
  38. mLinePaint.setStrokeWidth(5f);
  39. mLinePaint.setAlpha(30);
  40.  
  41. //绘制四周阴影区域
  42. mAreaPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
  43. mAreaPaint.setColor(Color.GRAY);
  44. mAreaPaint.setStyle(Style.FILL);
  45. mAreaPaint.setAlpha(180);
  46.  
  47. }
  48. public void setCenterRect(Rect r){
  49. Log.i(TAG, "setCenterRect...");
  50. this.mCenterRect = r;
  51. postInvalidate();
  52. }
  53. public void clearCenterRect(Rect r){
  54. this.mCenterRect = null;
  55. }
  56.  
  57. int widthScreen, heightScreen;
  58. @Override
  59. protected void onDraw(Canvas canvas) {
  60. // TODO Auto-generated method stub
  61. Log.i(TAG, "onDraw...");
  62. if(mCenterRect == null)
  63. return;
  64. //绘制四周阴影区域
  65. canvas.drawRect(0, 0, widthScreen, mCenterRect.top, mAreaPaint);
  66. canvas.drawRect(0, mCenterRect.bottom + 1, widthScreen, heightScreen, mAreaPaint);
  67. canvas.drawRect(0, mCenterRect.top, mCenterRect.left - 1, mCenterRect.bottom + 1, mAreaPaint);
  68. canvas.drawRect(mCenterRect.right + 1, mCenterRect.top, widthScreen, mCenterRect.bottom + 1, mAreaPaint);
  69.  
  70. //绘制目标透明区域
  71. canvas.drawRect(mCenterRect, mLinePaint);
  72. super.onDraw(canvas);
  73. }
  74.  
  75. }

说明例如以下:

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

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

  1. <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>

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

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

  1. //绘制四周阴影区域
  2. canvas.drawRect(0, 0, 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);

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

  1. /**生成拍照后图片的中间矩形的宽度和高度
  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. }

各自是生成图片的中间矩形的宽和高组成的一个Point,生成屏幕中间的矩形区域。两个函数的输入參数都是px为单位的屏幕中间矩形的宽和高。

这里有个条件:矩形以屏幕中心为中心,否则的话计算公式要适当变换下

三、在开启预览后,就能够让MaskView绘制了

  1. @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. }

这里有个注意事项:由于camera.open的时候是放在一个单独线程里的。open之后进行回调到cameraHasOpened()这里,那这个函数的运行时在主线程和子线程?答案也是在子线程,即子线程的回调还是在子线程里运行。

正因此。在封装MaskView时set矩阵后用的是postInvalidate()进行刷新的。

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

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

  1. 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. }

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

  1. 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. }

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

  1. /**
  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. //图片居然不能旋转了,故这里要旋转下
  22. Bitmap rotaBitmap = ImageUtil.getRotateBitmap(b, 90.0f);
  23. int x = rotaBitmap.getWidth()/2 - DST_RECT_WIDTH/2;
  24. int y = rotaBitmap.getHeight()/2 - DST_RECT_HEIGHT/2;
  25. Log.i(TAG, "rotaBitmap.getWidth() = " + rotaBitmap.getWidth()
  26. + " rotaBitmap.getHeight() = " + rotaBitmap.getHeight());
  27. Bitmap rectBitmap = Bitmap.createBitmap(rotaBitmap, x, y, DST_RECT_WIDTH, DST_RECT_HEIGHT);
  28. FileUtil.saveBitmap(rectBitmap);
  29. if(rotaBitmap.isRecycled()){
  30. rotaBitmap.recycle();
  31. rotaBitmap = null;
  32. }
  33. if(rectBitmap.isRecycled()){
  34. rectBitmap.recycle();
  35. rectBitmap = null;
  36. }
  37. }
  38. //再次进入预览
  39. mCamera.startPreview();
  40. isPreviewing = true;
  41. if(!b.isRecycled()){
  42. b.recycle();
  43. b = null;
  44. }
  45.  
  46. }
  47. };

注意事项:

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. 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. }

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

  1. //设置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);

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

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

  1. 【转】玩转Android Camera开发(三):国内首发---使用GLSurfaceView预览Camera 基础拍照demo

    http://blog.csdn.net/yanzi1225627/article/details/33339965 GLSurfaceView是OpenGL中的一个类,也是可以预览Camera的,而 ...

  2. 玩转Android Camera开发(二):使用TextureView和SurfaceTexture预览Camera 基础拍照demo

    Google自Android4.0出了TextureView,为什么推出呢?就是为了弥补Surfaceview的不足,另外一方面也是为了平衡GlSurfaceView,当然这是本人揣度的.关于Text ...

  3. 玩转Android Camera开发(一):Surfaceview预览Camera,基础拍照功能完整demo

    杂家前文是在2012年的除夕之夜仓促完成,后来很多人指出了一些问题,琐事缠身一直没有进行升级.后来随着我自己的使用,越来越发现不出个升级版的demo是不行了.有时候就连我自己用这个demo测一些性能. ...

  4. 玩转Android Camera开发(三):国内首发---使用GLSurfaceView预览Camera 基础拍照demo

    GLSurfaceView是OpenGL中的一个类,也是能够预览Camera的,并且在预览Camera上有其独到之处. 独到之处在哪?当使用Surfaceview无能为力.痛不欲生时就仅仅有使用GLS ...

  5. Android Camera开发:使用GLSurfaceView预览Camera 基础拍照

    GLSurfaceView是OpenGL中的一个类,也是可以预览Camera的,而且在预览Camera上有其独到之处.独到之处在哪?当使用Surfaceview无能为力.痛不欲生时就只有使用GLSur ...

  6. Android开发中遇到的问题(三)——eclipse创建android项目无法正常预览布局文件

    一.问题描述 今天使用SDK Manager将Android SDK的版本更新到了Android 5.1的版本,eclipse创建android项目时,预览activity_main.xml文件时提示 ...

  7. Android摄像头:只拍摄SurfaceView预览界面特定区域内容(矩形框)---完整(原理:底层SurfaceView+上层绘制ImageView)

    Android摄像头:只拍摄SurfaceView预览界面特定区域内容(矩形框)---完整实现(原理:底层SurfaceView+上层绘制ImageView) 分类: Android开发 Androi ...

  8. Android Camera开发系列(下)——自定义Camera实现拍照查看图片等功能

    Android Camera开发系列(下)--自定义Camera实现拍照查看图片等功能 Android Camera开发系列(上)--Camera的基本调用与实现拍照功能以及获取拍照图片加载大图片 上 ...

  9. Android Camera开发:周期性循环自动聚焦auto focus挂掉原因分析(preview is not enabled)

    参考:Android Camera开发:扫描二维码,周期性循环自动聚焦auto focus挂掉原因分析(preview is not enabled) 最近做Android人脸识别时,camera在自 ...

随机推荐

  1. 云计算大会有感—MapReduce和UDF

    (转载请注明出处:http://blog.csdn.net/buptgshengod) 1.參会有感       首先还是非常感谢CSDN能给我票,让我有机会參加这次中国云计算峰会.感觉不写点什么对不 ...

  2. SILICA Xynergy-M4 Board -- STM32F417 meets XILINX Spartan-6

    The SILICA Xynergy-M4 Board combines an ARM Cortex-M4 based STMicroelectronics STM32F417 controller ...

  3. 【Go入门教程5】流程(if、goto、for、switch)和函数(多个返回值、变参、传值与传指针、defer、函数作为值/类型、Panic和Recover、main函数和init函数、import)

    这小节我们要介绍Go里面的流程控制以及函数操作. 流程控制 流程控制在编程语言中是最伟大的发明了,因为有了它,你可以通过很简单的流程描述来表达很复杂的逻辑.Go中流程控制分三大类:条件判断,循环控制和 ...

  4. PHP自学之路---雇员管理系统(1)

    前面已经介绍了Zend studio工具的使用以及软件开发的基本阶段,下面就是我们第一个练习,雇员管理系统,从设计到实现来简单介绍下: 开发环境: 服务器:基于Linux 2.618环境下配置PHP服 ...

  5. Linux入门基础篇

    Linux入门基础篇 Linux诞生 Linux发行版本说明 Linux官方网站 Linux内核官方网站 比较有名的Linux发行版 虚拟机(Virtual Machine),一个虚拟的系统,安装在系 ...

  6. 扩展redisTemplate实现分布式锁

    原文:https://blog.csdn.net/qq1010267837/article/details/79697572 依赖jar包 compile group: 'redis.clients' ...

  7. C#中使用NLua z

    直接下载NLua编译好的版本在c#项目中使用,运行的时候会提示无法加载lua52.dll,但lua52.dll这个文件又是在运行目录下的. 其实NLua不是无法加载lua52.dll本身,而是找不到l ...

  8. Selenium2+python自动化57-捕获异常(NoSuchElementException)

    前言 在定位元素的时候,经常会遇到各种异常,为什么会发生这些异常,遇到异常又该如何处理呢? 本篇通过学习selenium的exceptions模块,了解异常发生的原因. selenium+python ...

  9. Nginx和Tomcat负载均衡实现session共享

    以前的项目使用Nginx作为反向代理实现了多个Tomcat的负载均衡,为了实现多个Tomcat之间的session共享,使用了开源的Memcached-Session-Manager框架. 此框架的优 ...

  10. ASP.NET—015:ASP.NET中无刷新页面实现

    原文作者:杨友山 原文地址:http://blog.csdn.net/yysyangyangyangshan/article/details/39679823 前面也说过在asp.net中前后前交互的 ...