前言
在SurfaceView初步以及Camera初步中我介绍了如何使用SurfaceView以及如何使用SurfaceView来预览Camera画面,接下来我就来介绍下如何给Camera添加水印效果。由于SurfaceView不支持回显,也就是将摄像头捕获到的数据处理以后重新设置到界面上,所以我会使用两个SurfaceView来介绍。
思路
我们想给预览界面添加水印第一步肯定是需要获取到每一帧的数据然后处理完以后再绘制到界面上,Android为Camera提供了两个接口用于获取每一帧的数据。分别如下
//每一帧准备好了就直接回调 void android.hardware.Camera.setPreviewCallback(PreviewCallback cb) //带有缓存的回调,调用 //void android.hardware.Camera.addCallbackBuffer(byte[] callbackBuffer) //方法才会回调,每调用一次回调一次 void android.hardware.Camera.setPreviewCallbackWithBuffer(PreviewCallback cb)
第一种方式:setPreviewCallback方法,设置回调接口:PreviewCallback
在回调方法:onPreviewFrame(byte[] data, Camera camera) 中处理每一帧数据
第二种方式:setPreviewCallbackWithBuffer方法,同样设置回调接口:PreviewCallback,不过还需要一个方法配合使用:addCallbackBuffer,这个方法接受一个byte数组。
第二种方式和第一种方式唯一的区别:
第一种方式是onPreviewFrame回调方法会在每一帧数据准备好了就调用,但是第二种方式是在需要在前一帧的onPreviewFrame方法中调用addCallbackBuffer方法,下一帧的onPreviewFrame才会调用,同时addCallbackBuffer方法的参数的byte数据就是每一帧的原数据。所以这么一看就好理解了,就是第一种方法的onPreviewFrame调用是不可控制的,就是每一帧数据准备好了就回调,但是第二种方法是可控的,我们通过addCallbackBuffer的调用来控制onPreviewFrame的回调机制。
注意:
因为第二种方式在调用的时候有点注意的地方:
1、在调用Camera.startPreview()接口前,我们需要setPreviewCallbackWithBuffer,而setPreviewCallbackWithBuffer之前我们需要重新addCallbackBuffer,因为setPreviewCallbackWithBuffer 使用时需要指定一个字节数组作为缓冲区,用于预览图像数据 即addCallbackBuffer,然后你在onPerviewFrame中的data才会有值。
2、从上面看来,我们设置addCallbackBuffer的地方有两个,一个是在startPreview之前,一个是在onPreviewFrame中,这两个都需要调用,如果在onPreviewFrame中不调用,那么,就无法继续回调到onPreviewFrame中了。
编码
当我们设置好回调接口以后就可以在里面获取到数据了,不过这里的数据默认为NV21格式,所以我们需要转换为图像才行。
@Override public void onPreviewFrame(byte[] data, Camera camera) { Size size = camera.getParameters().getPreviewSize(); if (mCustomSurfaceView == null) { return; } // 将NV21类型的数据转为Image YuvImage yuvImage = new YuvImage(data, ImageFormat.NV21, size.width, size.height, null); if (yuvImage != null) { initPaint(); ByteArrayOutputStream out = new ByteArrayOutputStream(); yuvImage.compressToJpeg(new Rect(0, 0, size.width, size.height), 80, out); // 得到原始图片 Bitmap bitmap = BitmapFactory.decodeByteArray(out.toByteArray(), 0, out.size()); SurfaceHolder customSurfaceHolder = mCustomSurfaceView.getHolder(); // 获取到画布 Canvas canvas = customSurfaceHolder.lockCanvas(); // 将加了水印的图片绘制到预览窗口 canvas.drawBitmap(BitmapUtil.rotateBitmapAndWaterMark(bitmap, 90, "www.27house.cn", paint), 0, 0, null); customSurfaceHolder.unlockCanvasAndPost(canvas); } }
上面的代码中,我首先将NV21类型的数据转为Image,然后获取预览SurfaceView的Canvas,然后将处理的结果绘制上去即可。关于图片的旋转以及添加水印,可以参考Android绘图基本操作。http://27house.cn/archives/405
图片处理操作如下,主要是将图片旋转了90°,关于旋转的原因请查看上篇博客Camera初步,然后绘制水印以及时间戳。
/** * 给图片添加水印和时间戳 * * @param originBitmap 原始图片 * @param degree 旋转角度 * @param watermark 水印文字 * @param paint 绘制水印的画笔对象 * @return 最终处理的结果 */ public static Bitmap rotateBitmapAndWaterMark(Bitmap originBitmap, int degree, String watermark, Paint paint) { int width = originBitmap.getWidth(); int height = originBitmap.getHeight(); Bitmap resultBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(resultBitmap); canvas.save(); canvas.rotate(degree, width / 2, height / 2); canvas.drawBitmap(originBitmap, 0, 0, null); canvas.restore(); int textWidht = (int) paint.measureText(watermark); FontMetrics fontMetrics = paint.getFontMetrics(); int textHeight = (int) (fontMetrics.ascent - fontMetrics.descent); int x = (width - textWidht) / 2; int y = (height - textHeight) / 2; y = (int) (y - fontMetrics.descent); canvas.drawText(watermark, x, y, paint); canvas.drawText(String.valueOf(System.currentTimeMillis()), x, y + textHeight, paint); // 立即回收无用内存 originBitmap.recycle(); originBitmap = null; return resultBitmap; }