前言

公司项目中有一个祖传的自定义相机,之前发现它在横拍的时候没有对图片进行旋转,对其进行一番修改之后在测试机上面测试成功。上线后又发现只有部分手机正确旋转了,经过一番努力之后,终于解决。并且在文末给出一个与网上千篇一律的照片旋转不同的比较少见的快速旋转方案

写下此文章记录下问题的解决经过,一步步分析里面的问题。

只有部分手机正确旋转

这种做法只有部分手机获得了正确旋转的照片, 直接来看一下代码里面相机拍照回调方法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() {@Overridepublic void saveComplete() {finish();}});}
复制代码

其实这里我做的就只是把作者关于旋转的代码反注释了。

分析一下里面做了些什么:

  1. takePhotoOrientation这个变量是由一个重力监听器提供的,用它判断当前手机是否处于横拍状态,横拍需要旋转;
  2. 然后就可以看到原代码作者非常贴心地在这里注释了旋转非常耗时,告诫我们不要去旋转它,然后注释掉了旋转代码,我又把他的注释解开了;
  3. ImageUtils.rotatePic()旋转图片,里面走的核心代码就是网上一搜就是的旋转方案:
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();}
复制代码
  1. 保存byte[]数据。这里的照片保存方法内部实现也有问题,但是这里不是我们这篇文章的重点,所以忽略它先。

看到这里,一些明眼的同学可能已经看出为什么作者说旋转用了40倍时间了,是的呢~

ImageUtils.rotatePic()里面出了问题: 它先把byte[]解析成bitmap,然后旋转又create了一个bitmap,再把bitmap转换成byte[]来保存。这个过程有没有40倍我没有实践过,不过可以想象这耗时很长。留待优化。

获取EXIF

优化的事情先放下,先解决业务上的问题-旋转。

那怎么才能知道照片的方向呢?小case,难不倒玩摄影的我,相机在拍照的时候都会有保存提个叫EXIF的照片信息,它里面存放着这张照片的诸多信息,例如光圈、焦距、快门时间等等,最重要的还有我们需要的旋转方向-orientation。有了它我们不就可以轻轻松松判断方向了!?

嘿嘿~我读:

    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信息,经过一番资料查验,的确如此。

什么是Exif,为什么会丢失Exif信息?

从Exif2.2规范里面可以找到关于压缩图像文件数据的描述,其中有这么一幅图:

它描述了这么一个 事实:压缩图像文件由标记码和压缩图像数据组成,其中标记码数据中包括Exif。很显然,bitmap作为一个图像类只包含了解压出的图像数据。

总结

照片转换成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°。

惊了!居然和预想的不一致。。出现这样的结果证明这又是手机系统内部出现的差异,安卓碎片化!

工作陷入了停顿。。只能搬出大牛鱼哥~

一番询问之后,鱼哥show his code解决了我的问题~再次感谢鱼哥

配置相机参数

Camera#Parameters这个类相信对于熟悉相机的同学不会陌生,它能够用来获取和配置相机参数:获取预览尺寸,设置闪光灯,对焦模式等等,都在这个通过这个类进行调节配置,我们要的修正方法竟然藏在这里面!

public class IOrientationEventListener extends OrientationEventListener {public IOrientationEventListener(Context context) {super(context);}@Overridepublic 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);
复制代码

其他框架的话请自行查找啦~

结尾

终于写完了,要是觉得有用或者让你拓展了知识就点个赞以示支持呗~

谢谢你的观看和学习!下回再见~

转载于:https://juejin.im/post/5cf76785f265da1b827a8a98

我与照片之乾坤大挪移-瞬间旋转你的照片相关推荐

  1. 相机获取的照片向左自动旋转90度解决方法

    用相机拍摄出来的照片含有EXIF信息,UIImage的imageOrientation属性指的就是EXIF中的orientation信息. 如果我们忽略orientation信息,而直接对照片进行像素 ...

  2. 【照片动态特效系列】旋转吧,照片!

    [照片动态特效系列]旋转吧,照片! 实现"旋转" 核心-二维仿射变换 基础旋转 分块旋转 多方向+彩图 核彩图处理心部分 主函数 其他 by 今天不飞了 突然就想做,然后就做了-- ...

  3. 三星手机拍照,上传照片或者下载导致图片旋转90°的解决办法。

    项目需求. 因为项目中需要对拍照之后的图片进行上传. (这也是一个简单的问题) 但是就是三星手机(三星note3),出现拍照之后照片旋转了九十度. 然后我们上传上去,然后通过其他手机请求url再次显示 ...

  4. exif.js解决ios手机上传照片后显示为旋转90度问题(兼容ios13.4之前的版本 )

    exif.js解决ios手机上传照片后显示为旋转90度问题(兼容ios13.4 ) 问题描述: 在做手机移动端app时,发现iOS12.5.1版本(iphone6)上传照片出现顺时针旋转90问题,ip ...

  5. 如何使用 Pixelmator Pro调整照片,打印出最完美的照片效果?

    想把心爱的数码照片打印出来?在这之前,不妨先花几分钟,让画面变得尽可能清晰生动.这并不麻烦,Pixelmator Pro就能帮你轻松完成.精心裁剪 标准实体照片的尺す一般是 3 x5 或 4 x6(英 ...

  6. 查找多照片中包含指定人脸的所有照片

    查找多照片中包含指定人脸的所有照片 目标场景 目标任务 人脸检测 文件结构 代码详情 人脸相似度计算 目标场景 日常生活中,我们会拍下很多的瞬间,其中可能有一个人或多个人,当我们在看到某一张照片中的某 ...

  7. facebook人脸照片_如何处理Facebook上的不良照片

    facebook人脸照片 Not everyone is model pretty and capable of posing perfectly on a split second's notice ...

  8. 如何把照片正面变成反面_各国签证照片要求大全 (含模板)

    对于不是很熟悉签证的小伙伴来说, 面对全球那么多国家的签证 而且每张签证照片的规格不同 为此我们为您整理了各国签证照片要求大全 东南亚国家的签证照要求基本相同,就以泰国为例,告诉大家签证照的注意事项. ...

  9. 拍照尺寸 ios_iOS 14照片和相机:QuickTake快捷键,照片标题,镜像自拍照等

    iOS 14的最大变化集中在主屏幕,应用程序库,重新设计的用于电话和Siri的紧凑型界面,画中画,翻译应用程序以及更新的隐私保护,但苹果还改进了许多现有应用程序以添加新功能特性和功能." 照 ...

最新文章

  1. Oracle查看SQL执行计划的方式
  2. 浅谈JavaScript中按键事件的e.keyCode || e.which || e.charCode
  3. 有关session的登录注销的一个小例子
  4. vmware虚拟机不识别usb设备
  5. “References to generic type List should be parameterized”
  6. 使用Spring Boot和注释支持配置Spring JMS应用程序
  7. 记一次Project插件开发
  8. Docker cpu memory quota使用说明
  9. 如何利用开源风控系统 TH-Nubula(星云)防止撞库?
  10. 紫猫中控-脚本界面的基本设计和代码结构
  11. 【转】SSL协议、SET协议、HTTPS简介
  12. 混合开发中,H5页面如何监听Android手机返回键
  13. JasperReport| TTF和TTC字体介绍
  14. js破解 零度代理ip
  15. 扩散(diffusion)和弥散(dispersion)有什么区别
  16. 【特异性双端队列 | 最小调整顺序次数】
  17. Linux自动判断是否插入网线的几种方法
  18. 英特尔小心!AMD发ARM架构Opteron处理器
  19. 开源项目treasure_house
  20. debian 文件夹中文件大小_linux查看目录(文件夹)内容大小

热门文章

  1. 英伟达 TX2 蓝牙自动连接蓝牙 设备
  2. 为何腾讯云在云服务市场败给了阿里云?
  3. 论文笔记:CVPR2022 Regional Semantic Contrast and Aggregation for Weakly Supervised Semantic Segmentation
  4. 钛资本研究院:RegTech监管科技有望成为投融资新风口
  5. 苹果MT4手机软件怎么下载?下载后怎么使用?
  6. 图文识别Readiris Pro 17
  7. R语言实现数据按照行排序
  8. 如何让网站变成黑白色或者灰色?
  9. yolo v5 NVIDIA Jetson Xavier NX 部署刷机+安环境(2)
  10. Android效率组件篇 设置长按响应时间(时长)