首页 > 其他分享 >Android 图片处理部分知识点说明

Android 图片处理部分知识点说明

时间:2023-03-07 18:01:41浏览次数:52  
标签:知识点 ORIENTATION val 旋转 ExifInterface Android 翻转 图片

本文讲解在 Android 的日常开发中,针对图片的几个小 tips。

图片的 mimeType

在 Android 系统中,图片的 mimeType 系统默认是根据后缀名判断。比如 pic.jpg 的 mimeType 就是 "image/jpeg"。

这种逻辑在图片的后缀名正常时还 OK,但是如果图片的后缀名和图片的实际类型匹配不上,那大概率会导致业务异常。比如 zip 压缩文件的后缀名改为 "data.jpg",那么我们在筛选出手机上的图片时,这张 "data.jpg" 也会被识别出来,然后就会出现 ImageView 展示白屏、图片压缩失败、上传图片失败(压缩包过大,超出图片限制)等问题。

此时通过如 androidx 的 LoaderManager 那一套读取出来的系统的 mimeType 信息就不可靠了,我们需要自己确定文件实际的 mimeType,一般我们可以使用系统提供的 BitmapFactory,也可以读取文件头信息,拿到数据的 mimeType。前者适用范围更广,后者可定制性更强。实现时可按需选择。代码如下:

// 方法1:使用 BitmapFactory 确定 mimeType
val String.imageMimeType: String
    get() {
        return try {
            val decodeOptions = BitmapFactory.Options()
            decodeOptions.inJustDecodeBounds = true
            BitmapFactory.decodeFile(data, decodeOptions)

            decodeOptions.outMimeType
        } catch (ignored: Throwable) {
            Log.e("String.imageMimeType", ignored)
            
            "image/*"
        }
    }
// 方法2:读取文件头确定 mimeType
val File.imageMimeType: String
    get() {
        return try {
            FileInputStream(this).use { input ->
                when (input.read()) {
                    0xFF -> "image/jpeg"
                    0x89 -> "image/png"
                    0x47 -> "image/gif"
                    else -> readOtherType()
                }
            }
        } catch (ignored: Throwable) {
            Log.e("File.imageMimeType", ignored)
            "image/*"
        }
    }
// 读取 heic 图片(HEIF 图片)的文件头
private fun File.readOtherType(): String {
    return reader().use {
        // heic 格式,头4个字节不用管
        it.skip(4)
        // heic 格式:5 - 12 共 8 个字节是 ftypheic 8 个字符
        val array = CharArray(8)
        if (it.read(array) != -1 && String(array) == "ftypheic") {
            "image/heic"
        } else {
            "image/*"
        }
    }
}

图片的尺寸信息

通常,我们可以使用 BitmapFactory 图片的尺寸:

val decodeOptions = BitmapFactory.Options()
decodeOptions.inJustDecodeBounds = true
BitmapFactory.decodeFile(path, decodeOptions)
val width = decodeOptions.outWidth
val height = decodeOptions.outHeight

但是上面的代码存在一个问题,即 width 和 height 可能取反了。即 width 是 height 的值,height 是 width 的值。出现问题的原因可能是图片带有 EXIF 信息,这个信息是关于图片拍摄时的信息,相机会把诸如图片的旋转角度、拍摄地点、曝光等参数写入到其中。而 Android 系统兼容了这个信息的读取。

当图片具有 EXIF 信息,并且旋转角度是 90 度或者 270 度时,系统会把图片的宽高信息取反,即宽变成高,高变成宽。如果我们忽略了 EXIF 的信息,那么可能导致业务出异常,比如图片的缩放比例出现问题。此时我们需要手动兼容存在 EXIF 信息的场景,即当存在 EXIF 信息并且旋转了时,得把宽高值再取反一次。负负得正,保证图片的宽高信息是正常的。

Android 系统读取 EXIF 信息主要是用到了 ExifInterface 这个类。这个类支持图片 EXIF 信息的读取和修改。

// 先定义 bean 类
class ImageInfo {
    val width: Int
        get() {
            // 图片存在 Exif 信息,并且旋转了 90 度或者 270 度,则交换宽高值
            return if (careOrientation && isRotated) {
                options.outHeight
            } else {
                options.outWidth
            }
        }
    val height: Int
        get() {
            // 图片存在 Exif 信息,并且旋转了 90 度或者 270 度,则交换宽高值
            return if (careOrientation && isRotated) {
                options.outWidth
            } else {
                options.outHeight
            }
        }
    // 宽高不用从这里获取,可用于获取其他信息,比如 mimeType 等
    var options: BitmapFactory.Options = BitmapFactory.Options()
    // 是否关注图片旋转信息的标志位
    var careOrientation: Boolean = true
    // 图片的旋转角度
    var orientation = ExifInterface.ORIENTATION_UNDEFINED
    // 图片是否旋转了的标志
    val isRotated: Boolean
        get() {
            return orientation in arrayOf(
                ExifInterface.ORIENTATION_ROTATE_90,
                ExifInterface.ORIENTATION_ROTATE_270,
            )
        }
}

// 获取图片的旋转信息
private fun getRotateInfo(info: ImageInfo, path: String) {
    try {
        val exif = ExifInterface(path)
                
        info.orientation = exif.getAttributeInt(
            ExifInterface.TAG_ORIENTATION,
            ExifInterface.ORIENTATION_UNDEFINED
        )
    } catch (e: Exception) {
        Log.e(TAG, e)
    }
}

// 修改 Exif 的信息并保存
private fun setExifInfo(info: ImageInfo, path: String) {
    try {
        val exif = ExifInterface(path)
        // 调用多个 set 方法
        exif.setAttribute(
            ExifInterface.TAG_ORIENTATION,
            ExifInterface.ORIENTATION_NORMAL.toString()
        )
        exif.resetOrientation()
        // 最后调用 save 方法保存
        exif.saveAttributes()
    } catch (e: Exception) {
        Log.e(TAG, e)
    }
}

ExifInterface 还有大量其他的方法方便我们使用,具体的可见类的 API 文件。

图片的选项

在了解了图片的选项角度后,我们自然会想到如何根据旋转的图片获取正确的数据,其实我们只需要根据旋转角度再旋转下图片,就可以获取正确的图像数据了。

图片的旋转需要用到矩阵 Matrix,矩阵 Matrix 的详细讲解可以看这篇文章:https://blog.csdn.net/cquwentao/article/details/51445269

关于图片的翻转可以看这片文章:https://www.loongwind.com/archives/72.html

对于图像的处理,涉及到三种逻辑:

  • 旋转:用 rotate 表示,旋转后的图片有旋转角度(orientation),旋转操作有顺时针(clockwise:CW)和逆时针(counterclockwise:CCW)的区别
  • 翻转:用 flip 表示,有沿 x 轴和 y 轴翻转两种形式,沿 x 轴翻转是垂直翻转,沿 y 轴翻转是水平翻转。即沿 x 轴也沿 y 轴翻转相当于对图片进行了 180 度旋转。
  • 转置:用 transpose 和 transverse 表示,transpose 相当于沿左上 - 右下对角线翻转。也可以表示为先水平翻转再顺时针旋转270度(等于逆时针旋转 90 度);transverse 表示图像沿右上 - 左下对角线翻转,也可以表示为先水平翻转再顺时针旋转90度(等于逆时针旋转 270 度)。

旋转方法如下:

/**
 * 当角度为 [ExifInterface.ORIENTATION_UNDEFINED] 或者 
 * [ExifInterface.ORIENTATION_NORMAL] 时,表示不用旋转
 */
fun Bitmap.rotateBitmap(orientation: Int): Bitmap {
    val matrix = Matrix()
    when (orientation) {
        // 图片已水平翻转
        ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> {
            matrix.setScale(-1f, 1f)
        }
        // 图片已旋转 180 度
        ExifInterface.ORIENTATION_ROTATE_180 -> {
            // 顺时针旋转 180 度
            matrix.setRotate(180f)
        }
        // 图片已垂直翻转
        ExifInterface.ORIENTATION_FLIP_VERTICAL -> {
            // 下面两个语句等价于 matrix.postScale(1f, -1f)
            matrix.setRotate(180f)
            matrix.postScale(-1f, 1f)
        }
        // 沿左上 - 右下对角线翻转
        ExifInterface.ORIENTATION_TRANSPOSE -> {
            // 先顺时针旋转 90 度,再水平翻转
            matrix.setRotate(90f)
            matrix.postScale(-1f, 1f)
        }
        // 图片已逆时针旋转 90 度
        ExifInterface.ORIENTATION_ROTATE_90 -> {
            // 顺时针旋转 90 度
            matrix.setRotate(90f)
        }
        // 沿右上 - 左下对角线翻转
        ExifInterface.ORIENTATION_TRANSVERSE -> {
            // 先逆时针旋转 90 度,再水平翻转
            matrix.setRotate(-90f)
            matrix.postScale(-1f, 1f)
        }
        // 图片已逆时针旋转 270 度
        ExifInterface.ORIENTATION_ROTATE_270 -> {
            // 逆时针旋转 90 度
            matrix.setRotate(-90f)
        }
        // 其他场景不做转换
        else -> return this
    }
    try {
        // 创建新的 bitmap
        val bmRotated = Bitmap.createBitmap(
            this,
            0,
            0,
            width,
            height,
            matrix,
            true
        )
        if (bmRotated != this)
            recycle()
        return bmRotated
    } catch (e: Exception) {
        e.printStackTrace()
        return this
    }
}

标签:知识点,ORIENTATION,val,旋转,ExifInterface,Android,翻转,图片
From: https://www.cnblogs.com/wellcherish/p/17188969.html

相关文章

  • oss/obs对象存储上传图片,在浏览器输入地址却是下载图片。不能直接在浏览器上查看。
    1.问题oss/obs对象存储上传图片获取链接地址后,在浏览器输入地址却是下载。不能直接在浏览器上面浏览图片信息。2.解决上传文件的时候需要设置:content-type类型,需要指示浏览......
  • c# 小知识点
    1.VS编译报错原因:项目属性-->生成-->目标平台:改成AnyCPU 2.VS发布文件为空原因:发布--配置--允许更新编译站点,不合并。为每个页面创建单独程序集。参考:https:/......
  • Android 简单学习开源换肤框架(ThemeSkinning)
    Android简单学习开源换肤框架(ThemeSkinning)GitHub地址ThemeSkinning找到初始化View的入口并替换自定义的入口通常我们都是通过setContentView(intID)把View加载到我......
  • 限制图片上传时的文件大小和尺寸
    constbeforeUpload=(file:any)=>{    constisLt2M=file.size/1024/1024<2;  if(!isLt2M){    ElMessage.error('上传头像图片大......
  • 小知识点
    1、0.1+0.2!=0.3,理由?计算机表示十进制是采用二进制表示的。0.1在二进制表示为:0.1=2^-4*1.10011(0011)原生解决方式:letnum=parseFloat((0.1+0.2).toFixed(10));//......
  • 富文本编辑器粘贴word图片且图片文件自动上传功能
    ​ 1.编辑器修改(可选)1.1在 ueditor/config.json 中添加代码块    /* 上传word配置 */    "wordActionName":"wordupload",/* 执行上传视频的action......
  • Android分区
    1.查看分区数catproc/partitions这里的blocks和高通平台的分区表partition.xml是对应的 ... <partitionlabel="modem"size_in_kb="90112"type="EBD0A0A2-B9E5......
  • 图片视频组件封装
          <template><div><el-uploadref="upload":class="disabled?'disabled':''"list-type="picture-card":actio......
  • 通过HHDESK,将IPad上的图片导入到个人电脑
    IOS与PC的跨系统“沟壑”一直是一件令人头疼的事情。虽然可以使用微信等工具,实现小文件的传输,但大容量传输,以及在不能联互联网的情况下,跨系统传输依旧不方便。比如,Ios的图......
  • 【android】android-检测耳机的插入和拔出动作
    在android系统中,检测耳机的插入和拔出,也是通过广播实现的,可以建立一个BroadcastReceiver监听"android.intent.action.HEADSET_PLUG"这个Intent。在这个Intent中包含以......