公司项目中有一个祖传的自定义相机,以前发现它在横拍的时候没有对图片进行旋转,对其进行一番修改以后在测试机上面测试成功。上线后又发现只有部分手机正确旋转了,通过一番努力以后,终于解决。而且在文末给出一个与网上千篇一概的照片旋转不一样的比较少见的快速旋转方案。java
写下此文章记录下问题的解决通过,一步步分析里面的问题。android
这种作法只有部分手机得到了正确旋转的照片, 直接来看一下代码里面相机拍照回调方法onPictureTaken()的处理:缓存
public void onPictureTaken(byte[] data, Camera camera) { camera.stopPreview(); //是否须要旋转 boolean isOrientation = false; //用户是否横屏拍摄,true为横屏 0度左旋 90不旋 180右旋 270度旋 if (CameraActivity.this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE && (takePhotoOrientation <= 90+45)&&(takePhotoOrientation>=90-45)) {//不旋转 isOrientation = false; } else { isOrientation = true; } //旋转太过消耗时间,是储存的40倍时间左右,考虑后去掉旋转。 //---反注释start--- if (isOrientation) { //横屏拍摄,须要旋转90度 byte[] bytes = ImageUtils.rotatePic(data,takePhotoOrientation-90); if (bytes != null) { data = bytes; } } //---反注释end--- mPicPath = String.format("%s%s%s", AppConfig.getCarTradeFileDir("Camera"), System.currentTimeMillis() + ".jpg", tempFileSuffex); FileSaveUtils.saveFile(data, mPicPath, new FileSaveUtils.SaveListener() { @Override public void saveComplete() { finish(); } }); } 复制代码
其实这里我作的就只是把做者关于旋转的代码反注释了。markdown
分析一下里面作了些什么:app
public static byte[] rotatePic(byte[] data, int degree) { byte[] bytes = null; try { BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.RGB_565; options.inJustDecodeBounds = false; options.inPurgeable = true; options.inInputShareable = true; Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options);// data是字节数据,将其解析成位图 bitmap = rotateBitmap(bitmap, degree); bytes = bitmap2Bytes(bitmap); if (bitmap != null && !bitmap.isRecycled()) { bitmap.recycle(); } } catch (Error e) { e.printStackTrace(); } return bytes; } /** * 网上一搜就有的旋转代码 */ public static Bitmap rotateBitmap(Bitmap bitmap, int degree) { Matrix matrix = new Matrix(); matrix.postRotate(degree); Bitmap bm = null; try { bm = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); } catch (Exception e) { bm = bitmap; } return bm; } public static byte[] bitmap2Bytes(Bitmap bm) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); bm.compress(Bitmap.CompressFormat.JPEG, 100, baos); return baos.toByteArray(); } 复制代码
看到这里,一些明眼的同窗可能已经看出为何做者说旋转用了40倍时间了,是的呢~框架
ImageUtils.rotatePic()里面出了问题: 它先把byte[]解析成bitmap,而后旋转又create了一个bitmap,再把bitmap转换成byte[]来保存。这个过程有没有40倍我没有实践过,不过能够想象这耗时很长。留待优化。ide
优化的事情先放下,先解决业务上的问题-旋转。oop
那怎么才能知道照片的方向呢?小case,难不倒玩摄影的我,相机在拍照的时候都会有保存提个叫EXIF的照片信息,它里面存放着这张照片的诸多信息,例如光圈、焦距、快门时间等等,最重要的还有咱们须要的旋转方向-orientation。有了它咱们不就能够轻轻松松判断方向了!? post
ExifInterface exifInterface = new ExifInterface(filepath); int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); ... 复制代码
emmmm... 这,这里咱们不能立刻用这个代码来读,若是你用了这个项目的代码,去读exif,你可能就要进入死胡同里面出不来了!学习
先下载一个能看exif的app,我用的是photo exif editor,它打开是这样的:
我波波可爱吗?
如图exif一目了然,然而用photo exif editor去查看项目相机拍出来的图,全部exif数据都是空的,因此若是你用代码来读,永远都只会读出你填写的那个默认值。
再去探究一下exif丢失的缘由吧,开始我觉得是Carema Api的问题,但在onPictureTaken()里面一回调就直接保存图片,exif是存在的,明显问题出在旋转代码里面,能够猜测是转换成bitmap的时候丢失了exif信息,通过一番资料查验,的确如此。
从Exif2.2规范里面能够找到关于压缩图像文件数据的描述,其中有这么一幅图:
照片转换成bitmap会丢失exif,因此编辑图片的时候须要把exif保存起来再修改后从新保存到照片中去。
想要深刻了解Exif的话这里有一个2.2版本的规范文档 Exif2.2传送门。
既然了解好了,读Exif吧!
ExifInterface exifInterface = new ExifInterface(filepath); int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); ... 复制代码
我读~~~~↓
小米:照片方向与手机方向一致,没有正确旋转,Exif 90°;
VIVO:照片方向与手机方向不一致,正确旋转,Exif 90°。
工做陷入了停顿。。只能搬出大牛鱼哥~
Camera#Parameters这个类相信对于熟悉相机的同窗不会陌生,它可以用来获取和配置相机参数:获取预览尺寸,设置闪光灯,对焦模式等等,都在这个经过这个类进行调节配置,咱们要的修正方法居然藏在这里面!
public class IOrientationEventListener extends OrientationEventListener { public IOrientationEventListener(Context context) { super(context); } @Override public void onOrientationChanged(int orientation) { if (ORIENTATION_UNKNOWN == orientation) { return; } Camera.CameraInfo info = new Camera.CameraInfo(); Camera.getCameraInfo(defaultCameraId, info); orientation = (orientation + 45) / 90 * 90; int rotation = 0; if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { rotation = (info.orientation - orientation + 360) % 360; } else { rotation = (info.orientation + orientation) % 360; } if (null != mCamera) { Camera.Parameters parameters = mCamera.getParameters(); parameters.setRotation(rotation);//关键代码 mCamera.setParameters(parameters); } } } 复制代码
获取实例以后分别在SurfaceHolder#Callback#surfaceCreated()和SurfaceHolder#Callback#surfaceDestroyed()调用一下实例的enable()和disable()便可统一拍照旋转问题。
因为onOrientationChanged()回调频繁,更加优化的作法能够在按下拍摄按钮以后才对相机进行设置。
经过修改rotation的值能够发现,只要rotation的值必定,全部手机的拍照方向都是统一的,意味着厂商对相机设置的方向默认值碎片化。
至此解决自定义相机拍照旋转问题。
经过上面的一大轮介绍,聪明的同窗可能已经猜测出这个快速旋转的方案了--就是利用Exif。
由于照片自己是有设备生成的Exif的,里面含有标记照片方向的orientation,图片加载框架会经过读取orientation来对照片进行显示,也就是说,经过修改Exif里orientation的值,便可以达到旋转照片的效果!并且只须要操做一个小参数就能完成,瞬间完成,速度杠杠的!不再怕旋转图片慢啦!
看一下Glide的处理: 对Exif解析确认图像方向
//默认的图片头解析器 public final class DefaultImageHeaderParser implements ImageHeaderParser { ... private int getOrientation(Reader reader, ArrayPool byteArrayPool) throws IOException { ... int exifSegmentLength = moveToExifSegmentAndGetLength(reader); if (exifSegmentLength == -1) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Failed to parse exif segment length, or exif segment not found"); } return UNKNOWN_ORIENTATION; } byte[] exifData = byteArrayPool.get(exifSegmentLength, byte[].class); try { return parseExifSegment(reader, exifData, exifSegmentLength); } finally { byteArrayPool.put(exifData); } ... } ... } 复制代码
逆时针方向旋转的代码:
ExifInterface exifInterface = new ExifInterface(currentPath); // 获取图片的旋转信息 int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); LogUtils.v("exif orientation:" + orientation); //根据当前图片方向设置想要的图片方向 switch (orientation) { case ExifInterface.ORIENTATION_ROTATE_90://正常角度 exifInterface.setAttribute(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_NORMAL+""); break; case ExifInterface.ORIENTATION_ROTATE_180: exifInterface.setAttribute(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_ROTATE_90+""); break; case ExifInterface.ORIENTATION_ROTATE_270: exifInterface.setAttribute(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_ROTATE_180+""); break; case ExifInterface.ORIENTATION_NORMAL: exifInterface.setAttribute(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_ROTATE_270+""); break; } exifInterface.saveAttributes(); 复制代码
使用注意: 这种方案只能用于具备Exif的照片,旋转以后的照片显示须要注意缓存问题,每次旋转以后要更新缓存以正确显示照片。
Glide在加载图片的时候直接经过signature()方法new ObjectKey传入文件的修改时间做缓存标记便可:
Glide.with(context) .load(filePath) .signature(new ObjectKey(file.lastModified()) .into(imageView); 复制代码
其余框架的话请自行查找啦~
谢谢你的观看和学习!下回再见~