项目中最近需要用到摄像机拍照,起初的时候肯定怎么省事怎么来,直接就是调用系统的摄像机了,那么问题来了,调用系统摄像机的时候,发现不同的手机拍摄出的照片的旋转角度不一样。举个例子来说,小米手机拍摄出的照片旋转角度为0度(照片是正的),三星手机拍摄出的照片,照片用同样的方法加载出来,而照片旋转了90度,(照片是反的),shit,这是为什么?
刚开始的时候肯定将照片旋转一定的角度来解决,但是发现这样有一点low啊,并且设计上要求在拍照界面中要自己绘制一个框框,用户看到这个框框后拍照,拍照后自动截取图片,并压缩到640*640,shit,这样以来调用系统的照相机肯定就是不行了啊。。。。。
没有办法了,只能到网上去搜索相关的解决方案了,但是搜索了好久基本上就是简单的预览,哎,最后到google官方的例子里面发现了一个比较好的SurfaceView,这个SurfaceView起码根据你的手机计算出了最合适的拍照比例,好吧。这还是不够的,还是太简单了。最后通过多方途径(参考以前项目的实现,问一个大哥),大概明白了摄像机的基本原理。
下面简单的大致描述一下摄像机:
1:我们要想拍照正确,自定义的拍照界面的屏幕现实方式要是横屏拍摄,最好设置为全屏并且没有标题,这样拍摄出的照片才是正确,否则你就哭去吧,这也是血泪摸索出来的,下面简单粘贴一个配置文件:
<activity android:name="com.example.AndroidCaptureCropTags.camera.ActivityCapture" android:configChanges="keyboardHidden|orientation|screenSize" android:screenOrientation="landscape" android:theme="@android:style/Theme.NoTitleBar.Fullscreen" android:windowSoftInputMode="adjustResize|stateHidden"> </activity>
2:其实我们拍照预览的时候,google把摄像头封装了一系列的API,主要是再预览之前计算最佳的预览比例,防止预览的时候变形。并计算最佳的拍摄出的图片的现实比例,这样拍摄出的图片也不会变形。
3:接下来就是摄像机界面白色框框的绘制,就是自定义了一个View而已。
4:最后拍摄出来照片后根据屏幕中显示的白色的框框来截取图片(这个地方也挺蛋疼的。。。);
pictureCallBack = new PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera camera) { _isCapturing = false; Bitmap bitmap = null; try { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeByteArray(data, 0, data.length, options); // Debug.debug("width--:" + options.outWidth + " height--:" + options.outHeight); options.inJustDecodeBounds = false; options.inPreferredConfig = Bitmap.Config.ARGB_8888; //此处就把图片压缩了 options.inSampleSize = Math.max(options.outWidth / kPhotoMaxSaveSideLen, options.outHeight / kPhotoMaxSaveSideLen); bitmap = BitmapUtil.decodeByteArrayUnthrow(data, options); if (null == bitmap) { options.inSampleSize = Math.max(2, options.inSampleSize * 2); bitmap = BitmapUtil.decodeByteArrayUnthrow(data, options); } } catch (Throwable e) { } if (null == bitmap) { Toast.makeText(ActivityCapture.this, "内存不足,保存照片失败!", Toast.LENGTH_SHORT).show(); return; } //long start = System.currentTimeMillis(); Bitmap addBitmap = BitmapUtil.rotateAndScale(bitmap, _rotation, kPhotoMaxSaveSideLen, true); Bitmap finalBitmap = cropPhotoImage(addBitmap); File photoFile = PathManager.getCropPhotoPath(); boolean successful = BitmapUtil.saveBitmap2file(finalBitmap, photoFile, Bitmap.CompressFormat.JPEG, 100); while (!successful) { successful = BitmapUtil.saveBitmap2file(finalBitmap, photoFile, Bitmap.CompressFormat.JPEG, 100); } if (finalBitmap != null && !finalBitmap.isRecycled()) { addBitmap.recycle(); } Intent intent = new Intent(); intent.putExtra(kPhotoPath, photoFile.getAbsolutePath()); ActivityCapture.this.setResult(RESULT_OK, intent); ActivityCapture.this.finish(); } };
上面代码的逻辑是:拍摄后系统会将图片的数据以byte[]的形式传递给我们,options.inJustDecodeBounds = true;先用这种形式避免直接将图片加载进内存中,得到了图片的宽度和高度后,然后计算图片的inSampleSize,这里自己写了一个最大的边长1600,通过实验发现定义成这个边长后,拍摄出的照片无论是手机上看还是电脑上看,体验效果都是不错的,这也是通过询问以前的大哥才了解的,(我想了半天也不知道为什么非要定义成1600.。。。)。下面就是将data加载成一个Bitmap,注意此时的Bitmap可能是旋转的,还要调用这个方法Bitmap
addBitmap = BitmapUtil.rotateAndScale(bitmap, _rotation, kPhotoMaxSaveSideLen, true);来将图片摆正,其中的_rotation这个角度是我们在拍摄的过程中不断的计算的(这个有些是从网上找的,正在消化中。。。。,自己不是太明白的就不说了,免的说错了误人子弟)。将图片旋转正确后下面就是所见即所得了,截图。。。。。。。
//根据拍照的图片来剪裁 private Bitmap cropPhotoImage(Bitmap bmp) { int dw = bmp.getWidth(); int dh = bmp.getHeight(); int height; int width; if (dh > dw) {//图片竖直方向 //切图片时按照竖屏来计算 height = getWindowManager().getDefaultDisplay().getWidth(); width = getWindowManager().getDefaultDisplay().getHeight(); } else {//图片是水平方向 //切图片时按照横屏来计算 width = getWindowManager().getDefaultDisplay().getWidth(); height = getWindowManager().getDefaultDisplay().getHeight(); } Rect rect = new Rect(); int left = (width - cropBorderView.getRect().width()) / 2; int top = (height - cropBorderView.getRect().height()) / 2; int right = left + cropBorderView.getRect().width(); int bottom = top + cropBorderView.getRect().height(); rect.set(left, top, right, bottom); float scale = 1.0f; // 如果图片的宽或者高大于屏幕,则缩放至屏幕的宽或者高 if (dw > width && dh <= height) { scale = width * 1.0f / dw; } if (dh > height && dw <= width) { scale = height * 1.0f / dh; } // 如果宽和高都大于屏幕,则让其按按比例适应屏幕大小 if (dw > width && dh > height) { scale = Math.max(width * 1.0f / dw, height * 1.0f / dh); } //如果图片的宽度和高度都小于屏幕的宽度和高度,则放大至屏幕大小 if (dw < width && dh < height) { scale = width * 1.0f / dw; } Matrix matrix = new Matrix(); matrix.postScale(scale, scale); try { Bitmap b2 = Bitmap.createBitmap(bmp, 0, 0, dw, dh, matrix, true); if (null != b2 && bmp != b2) { bmp.recycle(); bmp = b2; } } catch (OutOfMemoryError e) { e.printStackTrace(); } try { Bitmap b3 = Bitmap.createBitmap(bmp, rect.left, rect.top, rect.width(), rect.height()); if (null != b3 && bmp != b3) { bmp.recycle(); bmp = b3; } } catch (OutOfMemoryError e) { e.printStackTrace(); } //将图片压缩至640*640 try { Bitmap b4 = Bitmap.createScaledBitmap(bmp, 640, 640, false); if (null != b4 && bmp != b4) { bmp.recycle(); bmp = b4; } } catch (OutOfMemoryError e) { e.printStackTrace(); } return bmp; }
经过测试发现,拍摄出的照片并不是你在屏幕中看见是竖直方向就是竖直方向,可能拍摄出的照片是竖直方向也可能是水平方向(即照片是竖的还是横的)。这是两种情况要分别来进行处理的。。。下面简单用文字描述一下这两种情况。
1:照片是竖的,而我们的Activity是横屏的。。。所以计算截取框的时候我们要按照屏幕是竖的来计算,才可以确定截取框的正确的位置。
2:照片是横的,我们的Activity也是横屏的。。。所以正常计算就ok。
3:不要想着我们屏幕那么大,拍摄出的照片就是屏幕那么大,而是比屏幕大得多。。。。,这就需要我们缩放到屏幕的大小,这样才可以做到所见即所得嘛,你说是不是?具体的缩放无非就是图片等比例缩放。图片是等比例缩放了,但是可能担心这种情况,要是缩放后比屏幕小一点或者大一点那不就不准确了嘛?这点我刚开始的时候也是担心的,但是我们在代码中计算了拍照的最合适的预览比例,最合适的照片比例,经过很多的手机测试发现都是没有问题的,他们的比例是保持一致的。
4:接下来就是烦人的截图了,最后将图片压缩到640*640,就ok了。
5:明白一点不要重复的造车子,你的心里面就舒服多了。
截图完了就保存到本地的文件里面就ok了。下面就是给图片添加一个Tag什么的,Tag添加点动画,都是一点点调出来的,没什么技术含量,包括正方形的控件的自定义也很简单,直接看看代码就ok了。。。。。
草,我发现我写博客写不了那么的详细。。。shit