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)
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()
保存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)
@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);
复制代码
其他框架的话请自行查找啦~
终于写完了,要是觉得有用或者让你拓展了知识就点个赞以示支持呗~
谢谢你的观看和学习!下回再见~
17.6w
嵌入式视觉
掘金·日新计划
ChatGPT
22.1w
来碗盐焗星球
JavaScript
掘金·金石计划