前段时间又接触到二维码扫描项目,上一次是一年前了,无奈以前的代码丢失了,又没有留下只言片语,只能重新开始咯。还是老问题:竖屏扫描、预览图变形!
二维码么,自然得用Zxing project,google维护的一个开源工程,而且还提供了Android module。编译运行,虽然是横屏的扫描界面,但也很nice啊,只需要让其支持竖屏就行咯!
怎么办呢?好在 Api 提供了相应的方法:Camera.setDisplayOrientation(int degress)
顺时针旋转预览图,可接受的degress为0,90,180,270;那我们这里传入90就能得到竖屏的预览图了。
开始编程,我编,我编,我编编……运行下,咦,怎么预览图变形了!
变形的原因呢是因为设置Camera parameters preview size 与 surfaceview 的大小比例不同,所以让它们的比例保持相同就好了。
这些都弄完了之后,美滋滋的在那扫啊扫,不经意间扫到个条形码,怎么扫不出来呢?明明记得Zxing的客户端是可以扫的啊!
难道是因为竖屏的原因?这时,注意到 setDisplayOrientation的注释:
This affects the preview frames and the picture displayed after snapshot.This does not affect the order of byte array passed in onPreviewFrame(byte[], Camera), JPEG pictures, or recorded videos.
你看起来图像是竖过来了,但是内存里依然是横屏的!
那又要问了,二维码为什么可以解析,而条形码不可以解析呢?这得从他们的结构上说起了,二维码是有3个定位点,这提供了解析的起点、终点、方向;而条形码结构单一,一旦有角度问题就难以识别,更何况现在是逆时针旋转90度的图像(这原因是猜测的,有了解的同学敬请告知)。所以解决的办法是,解析之前将图像顺时针旋转90度。
图像存放在byte[]里,如何旋转?这得了解它是如何写到byte[]里。onPreviewFrame (byte[] data, Camera camera)注释中有写到:
If setPreviewFormat(int) is never called, the default will be the YCbCr_420_SP (NV21) format.
YCbCr_420_SP 是什么呢?wiki上的解释,另推荐一篇博文: 入门视频采集与处理(学会分析YUV数据)。有了这些知识,问题就好解决了(如果没有相关知识,建议点开浏览下,否则后边的会比较难懂)。
于是有了下边的图解:
接下来就是算法实现了:
//clockwise public byte[] rotated90(byte[] data, int width, int height) { byte[] rotatedData = new byte[data.length]; int area = width * height; if (data.length >= area) { for (int y = 0; y < height; y++) { // rotate Y for (int x = 0; x < width; x++) rotatedData[x * height + height - y - 1] = data[x + y * width]; } } if (data.length == area * 1.5f) { for (int y = 0; y < height / 2; y++) { // rotate CbCr for (int x = 0; x < width / 2; x++) { rotatedData[area + x * height + height - 2 * y - 2] = data[area + 2 * x + y * width]; rotatedData[area + x * height + height - 2 * y - 1] = data[area + 2 * x + y * width + 1]; } } } return rotatedData; }
如此,得到的图像就是竖直方向的了。条形码也可以解析了。那么如何保存竖直方向的照片?
可以在Camera.PictureCallback.onPictureTaken(byte[] data, Camera camera)里使用上述方法旋转图片;
也可以用其他的博文中提供的方发,用Matrix旋转图片;保存图片。
全文完。