Compose IO
针对File Bitmap Uri String ByteArray 进行常用方法扩展
- 创建缓存模板图片文件
- 文件转Uri
- Uri转Bitmap
- Bitmap转File
- Bitmap转ByteArray
- Bitmap旋转角度
- Bitmap ByteArray 旋转
- ByteArray转Bitmap
- 旋转图片
- 图片文件纠正的角度
- 图片文件压缩格式
- Bitmap压缩为ByteArrayOutputStream
- 压缩图片到多少kb
- File转对应宽高的Bitmap
- 图片File压缩到一定极限值
- 文件后缀
- File 获取 Bitmap options
- ByteArray 获取 Bitmap options
- InputStream 获取 Bitmap options
- InputStream 转 Bitmap
- 图片File缩略图
- File是否是Bitmap
- File是否可压缩图片
- 多行Base64处理为单行
- 文件转Base64字符串
- Base64转File
- Bitmap转Base64String
- Base64转Bitmap
- 创建媒体图片文件Uri
- 创建媒体视频文件Uri
- 创建媒体音频文件Uri
- 通过后缀名称获取文件类型
- 删除媒体文件
- 级联删除文件夹
- 级联清空文件夹
- 计算文件或文件夹的总大小
- InputStream转File
- File转ByteArray
package com.compose.demo.utilsimport android.annotation.SuppressLint
import android.content.ContentValues
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Bitmap.CompressFormat
import android.graphics.BitmapFactory
import android.graphics.ImageDecoder
import android.graphics.Matrix
import android.graphics.Rect
import android.media.ExifInterface
import android.media.ThumbnailUtils
import android.net.Uri
import android.provider.MediaStore
import android.util.Base64
import androidx.core.content.FileProvider
import java.io.BufferedOutputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.io.UnsupportedEncodingException
import java.net.URLDecoder
import java.net.URLEncoder
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import kotlin.math.min/*** 创建缓存模板图片文件* @param context 上下文对象* @return IMG_XXX.png*/
fun createExternalCacheImageFile(context: Context): File {return File.createTempFile("IMG_", ".jpg", context.externalCacheDir)
}/*** 文件转Uri* @param context 上下文对象* @return Uri*/
fun File.toUri(context: Context): Uri {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {return FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", this)}return Uri.fromFile(this)
}//***************************************[Bitmap]***************************************/*** Uri转Bitmap* @param context 上下文对象* @return Bitmap*/
@SuppressLint("NewApi")
fun Uri.toBitmap(context: Context): Bitmap {val source = ImageDecoder.createSource(context.contentResolver, this)return ImageDecoder.decodeBitmap(source)
}/*** Bitmap转File*/
fun Bitmap.toFile(context: Context): File {val file = File.createTempFile("IMG_", ".jpg", context.externalCacheDir)file.outputStream().use { stream ->compress(CompressFormat.PNG, 100, stream)}return file
}/*** Bitmap转File* @param path 路径*/
fun Bitmap.toFile(path: String): File {val file = File(path)file.outputStream().use { stream ->compress(CompressFormat.PNG, 100, stream)}return file
}/*** Bitmap转File** @param context 上下文* @param format 图片路径* @param quality 图片质量[1-100]* @return*/
fun Bitmap.toFile(context: Context, format: CompressFormat, quality: Int): File {val file = File.createTempFile("IMG_", ".jpg", context.externalCacheDir)file.outputStream().use { stream ->compress(format, quality, stream)}return file
}/*** Bitmap旋转角度* @param degrees 旋转角度* @return 操作后的Bitmap*/
fun Bitmap.rotate(degrees: Float): Bitmap {val matrix = Matrix()matrix.postRotate(degrees)return Bitmap.createBitmap(this, 0, 0, width, height, matrix, true)
}/*** Bitmap ByteArray 旋转* @param degrees 旋转角度*/
fun ByteArray.rotate(degrees: Float): Bitmap {val bitmap = BitmapFactory.decodeByteArray(this, 0, size)val matrix = Matrix()matrix.postRotate(degrees)return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
}/*** 旋转图片* @param degrees 旋转角度* @param width 目标宽度* @param height 目标高度* @return*/
fun File.rotate(degrees: Float, width: Int, height: Int): Bitmap {val source = if (width > 0 && height > 0) {BitmapFactory.decodeFile(absolutePath, toBitmapOptions(width, height))} else {BitmapFactory.decodeFile(absolutePath)}return source.rotate(degrees)
}/*** 旋转图片** @param outPadding 内间距* @param degrees 角度* @param width 目标宽度* @param height 目标高度* @return*/
fun InputStream.rotate(outPadding: Rect, degrees: Float, width: Int, height: Int): Bitmap? {val source = if (width > 0 && height > 0) {BitmapFactory.decodeStream(this, outPadding, toBitmapOptions(outPadding, width, height))} else {BitmapFactory.decodeStream(this, outPadding, null)}return source?.rotate(degrees)
}/*** 图片文件纠正的角度** @return*/
fun File.angle(): Int {var angle = 0try {val exifInterface = ExifInterface(path)val orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_NORMAL)if (orientation == ExifInterface.ORIENTATION_ROTATE_90) {angle = 90} else if (orientation == ExifInterface.ORIENTATION_ROTATE_180) {angle = 180} else if (orientation == ExifInterface.ORIENTATION_ROTATE_270) {angle = 270}} catch (e: IOException) {e.printStackTrace()}return angle
}/*** 图片文件压缩格式*/
fun File.imageCompressFormat(): CompressFormat {val upperName = absolutePath.uppercase(Locale.getDefault())var format = CompressFormat.JPEGif (upperName.endsWith("PNG")) {format = CompressFormat.PNG}if (upperName.endsWith("WEBP")) {format = CompressFormat.WEBP}if (upperName.endsWith("JPEG") or upperName.endsWith("JPG")) {format = CompressFormat.JPEG}return format
}/*** Bitmap压缩为ByteArrayOutputStream** @param format 格式 {@link File.imageCompressFormat()获取}* @param max 限制大小,压缩到<=max,单位KB* @param unit 每次压缩大小,1 = 10%* @return*/
fun Bitmap.toCompressStream(format: CompressFormat, max: Long, unit: Int): ByteArrayOutputStream {val bos = ByteArrayOutputStream()compress(format, 100, bos)var options = 100while (max > 0 && bos.toByteArray().size > max) {bos.reset()options -= unitcompress(format, options, bos)}return bos
}/*** Bitmap压缩为ByteArrayOutputStream** @param format 格式* @param max 限制大小,压缩到<=max,单位KB* @return*/
fun Bitmap.toCompressStream(format: CompressFormat, max: Long): ByteArrayOutputStream {val bos = ByteArrayOutputStream()compress(format, 100, bos)var options = 100while (max > 0 && bos.toByteArray().size > max) {bos.reset()options -= 10compress(format, options, bos)}return bos
}/*** 压缩图片** @param max 限制大小,压缩到< = max,单位KB* @param format 格式* @return*/
fun Bitmap.compress(max: Long, format: CompressFormat): Bitmap {val bos: ByteArrayOutputStream = toCompressStream(format, max)val bytes = bos.toByteArray()return BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
}/*** 压缩图片** @param max 限制大小,压缩到< = max,单位KB* @param format 格式* @param unit 每次压缩大小,1 = 10%* @return*/
fun Bitmap.compress(max: Long, unit: Int, format: CompressFormat): Bitmap {val bos: ByteArrayOutputStream = toCompressStream(format, max, unit)val bytes = bos.toByteArray()return BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
}/*** Bitmap压缩为ByteArrayOutputStream** @param format 格式* @param max 限制大小,压缩到<=max,单位KB* @param outPath 输出文件路径* @return*/
fun Bitmap.toCompressFile(format: CompressFormat, max: Long, outPath: String): File {val bos: ByteArrayOutputStream = toCompressStream(format, max)val file = File(outPath)if (file.exists()) {file.delete()}BufferedOutputStream(FileOutputStream(file)).use { out ->out.write(bos.toByteArray())out.flush()}return file
}/*** 压缩图片** @param width 目标宽度* @param height 目标高度* @param format 图片格式* @param max 限制大小,压缩到<=max,单位KB* @return*/
fun Bitmap.compress(width: Int, height: Int, format: CompressFormat, max: Long): Bitmap {val data: ByteArray = toByteArray()val bitmap = data.toBitmap(0, data.size, width, height)return bitmap.compress(max, format)
}/*** 图片文件压缩到一定极限值* @param max 限制大小,压缩到<=max,单位KB*/
fun File.compress(max: Long): Bitmap {return compress(CompressFormat.JPEG, max)
}/*** File转对应宽高的Bitmap** @param width 目标宽度* @param height 目标高度* @return*/
fun File.toBitmap(width: Int, height: Int): Bitmap {val options: BitmapFactory.Options = toBitmapOptions(width, height)return BitmapFactory.decodeFile(path, options)
}/*** Bitmap转File* @param format 图片路径* @param quality 图片质量[1-100]* @param path 输出路径* @return*/
fun Bitmap.toFile(format: CompressFormat, quality: Int, path: String): File {val outFile = File(path)BufferedOutputStream(FileOutputStream(outFile)).use { bos ->compress(format, quality, bos)bos.flush()}return outFile
}/*** 图片File压缩到一定极限值* @param format 目标格式* @param max 限制大小,压缩到<=max,单位KB*/
fun File.compress(format: CompressFormat, max: Long): Bitmap {var bitmap = BitmapFactory.decodeFile(absolutePath)var inSampleSize = 0val bos = ByteArrayOutputStream()bitmap.compress(format, 100, bos)var bytes: ByteArray = bitmap.toByteArray()while (bytes.size > max) {inSampleSize++bitmap = toBitmap(bitmap.width / inSampleSize, bitmap.height / inSampleSize)bos.reset()bitmap.compress(format, 100, bos)bytes = bos.toByteArray()}return bitmap
}/*** 文件后缀* @return*/
fun File.suffix(): String {if (!exists() || isDirectory) {return ""}if (name == "" || name.endsWith(".")) {return ""}val index = name.lastIndexOf(".")return if (index != -1) {name.substring(index + 1).lowercase()} else {""}
}/*** 压缩文件** @param context 上下文* @param max 文件最大值,单位byte* @return*/
fun File.compress(context: Context, max: Long): File {val file = createExternalCacheImageFile(context)if (isCompressible()) {val format: CompressFormat = imageCompressFormat()val bitmap: Bitmap = compress(format, max)return bitmap.toCompressFile(format, max, file.absolutePath)} else {println("compress image is not ordinary bitmap.")}return file
}/*** Bitmap转ByteArray* @return*/
fun Bitmap.toByteArray(): ByteArray {val bos = ByteArrayOutputStream()compress(CompressFormat.JPEG, 100, bos)return bos.toByteArray()
}/*** ByteArray转Bitmap*/
fun ByteArray.toBitmap(offset: Int, length: Int, width: Int, height: Int): Bitmap {val options: BitmapFactory.Options = toBitmapOptions(offset, length, width, height)return BitmapFactory.decodeByteArray(this, offset, length, options)
}/*** File 获取 Bitmap options* @param width 目标宽度* @param height 目标高度*/
fun File.toBitmapOptions(width: Int, height: Int): BitmapFactory.Options {val options = BitmapFactory.Options()options.inJustDecodeBounds = true//在不解码整个图像的情况下获取图像的尺寸信息,从而避免不必要的内存分配BitmapFactory.decodeFile(path, options)val w = options.outWidthval h = options.outHeightval inSampleSize = min((w / width).toDouble(), (h / height).toDouble()).toInt()options.inJustDecodeBounds = falseoptions.inPreferredConfig = Bitmap.Config.RGB_565options.inSampleSize = inSampleSizeoptions.inPurgeable = truereturn options
}/*** ByteArray获取 Bitmap options* @param offset 数据位移* @param length 数据长度* @param width 目标宽度* @param height 目标高度*/
fun ByteArray.toBitmapOptions(offset: Int,length: Int,width: Int,height: Int
): BitmapFactory.Options {val options = BitmapFactory.Options()options.inJustDecodeBounds = trueBitmapFactory.decodeByteArray(this, offset, length, options)val w = options.outWidthval h = options.outHeightval inSampleSize = min((w / width).toDouble(), (h / height).toDouble()).toInt()options.inJustDecodeBounds = falseoptions.inSampleSize = inSampleSizeoptions.inPurgeable = truereturn options
}/*** InputStream 获取 Bitmap options** @param outPadding 内间距* @param width 目标宽度* @param height 目标高度* @return*/
fun InputStream.toBitmapOptions(outPadding: Rect, width: Int, height: Int): BitmapFactory.Options {val options = BitmapFactory.Options()options.inJustDecodeBounds = trueBitmapFactory.decodeStream(this, outPadding, options)val w = options.outWidthval h = options.outHeightval inSampleSize = min((w / width).toDouble(), (h / height).toDouble()).toInt()options.inJustDecodeBounds = falseoptions.inSampleSize = inSampleSizeoptions.inPurgeable = truereturn options
}/*** InputStream 转 Bitmap* @param outPadding 内边距* @param width 目标宽度* @param height 目标高度* @return*/
fun InputStream.toBitmap(outPadding: Rect, width: Int, height: Int): Bitmap? {val options: BitmapFactory.Options = toBitmapOptions(outPadding, width, height)return BitmapFactory.decodeStream(this, outPadding, options)
}/*** 图片File缩略图** @param width 宽度* @param height 高度* @return*/
fun File.thumbnail(width: Int, height: Int): Bitmap {var bitmap = BitmapFactory.decodeFile(absolutePath)bitmap =ThumbnailUtils.extractThumbnail(bitmap, width, height, ThumbnailUtils.OPTIONS_RECYCLE_INPUT)return bitmap
}/*** File是否是Bitmap*/
fun File.isBitmap(): Boolean {return BitmapFactory.decodeFile(absolutePath) != null
}/*** File是否可压缩图片*/
fun File.isCompressible(): Boolean {val upper = absolutePath.uppercase(Locale.getDefault())return upper.endsWith(".JPG") || upper.endsWith(".JPEG") || upper.endsWith(".PNG") || upper.endsWith(".WEBP")
}//***************************************[Base64]***************************************/*** 多行Base64处理为单行** @return*/
fun String.toSingleLine(): String {return replace("[\\s*\t\n\r]".toRegex(), "")
}/*** 文件转Base64字符串** @return*/
fun File.encodeBase64(): String {return encodeBase64(false)
}/*** 文件转base64字符串** @param encode 是否URLEncoder* @return*/
fun File.encodeBase64(encode: Boolean): String {val bytes: ByteArray = toByteArray()var base64 = Base64.encodeToString(bytes, Base64.DEFAULT)if (encode) {try {base64 = URLEncoder.encode(base64, "UTF-8")} catch (e: UnsupportedEncodingException) {e.printStackTrace()}}return base64
}/*** Base64转File** @param path 路径*/
fun String.decodeBase64(path: String): File {return decodeBase64(path, false)
}/*** Base64转File** @param path 路径* @param decode 是否URL解密*/
fun String.decodeBase64(path: String, decode: Boolean): File {val file = File(path)file.parentFile?.mkdirs()if (!file.exists()) {try {file.createNewFile()} catch (e: IOException) {e.printStackTrace()}}val buffer = Base64.decode(if (decode) URLDecoder.decode(this, "UTF-8") else this, Base64.CRLF)FileOutputStream(file).use { out ->out.write(buffer)}return file
}/*** 文件转Base64String* @return*/
fun Bitmap.encodeBase64(): String {return encodeBase64(false)
}/*** Bitmap转Base64String* @param encode 是否Url加密* @return*/
fun Bitmap.encodeBase64(encode: Boolean): String {val byteArrayOutputStream = ByteArrayOutputStream()compress(CompressFormat.JPEG, 100, byteArrayOutputStream)var stringBitmap = Base64.encodeToString(byteArrayOutputStream.toByteArray(), Base64.DEFAULT)if (encode) {try {stringBitmap = URLEncoder.encode(stringBitmap, "UTF-8")} catch (e: UnsupportedEncodingException) {e.printStackTrace()}}try {byteArrayOutputStream.close()} catch (e: IOException) {e.printStackTrace()}return stringBitmap
}/*** Base64转Bitmap,默认不URLDecoder** @return*/
fun String.decodeBase64(): Bitmap? {return decodeBase64(false)
}/*** Base64转Bitmap** @param decode 是否Url解密* @return*/
fun String.decodeBase64(decode: Boolean): Bitmap? {// 将字符串转换成Bitmap类型var bitmap: Bitmap? = nulltry {val bitmapArray =Base64.decode(if (decode) URLDecoder.decode(this, "UTF-8") else this,Base64.DEFAULT)bitmap = BitmapFactory.decodeByteArray(bitmapArray, 0, bitmapArray.size)} catch (e: Exception) {e.printStackTrace()}return bitmap
}//***************************************[MediaStore]***************************************/*** 创建媒体图片文件Uri* @param relativePath 存储相对路径*/
fun Context.createMediaStoreImageUri(relativePath: String = "Pictures/App"): Uri? {val date = SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).format(Date())val displayName = "IMG_$date.jpg"val contentValues = ContentValues().apply {put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")put(MediaStore.MediaColumns.RELATIVE_PATH, relativePath)}return contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
}/*** 创建媒体视频文件Uri*/
fun Context.createMediaStoreVideoUri(relativePath: String = "Movies/App"): Uri? {val date = SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).format(Date())val displayName = "VIDEO_$date.mp4"val contentValues = ContentValues().apply {put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4")put(MediaStore.MediaColumns.RELATIVE_PATH, relativePath)}return contentResolver.insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, contentValues)
}/*** 创建媒体音频文件Uri* @param suffix 文件后缀,例如: .mp3 .wav .acc .amr .wma .flac .aiff*/
fun Context.createMediaStoreAudioUri(suffix: String,relativePath: String = "Music/Recordings/App"
): Uri? {val date = SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).format(Date())val displayName = "audio_$date$suffix"val contentValues = ContentValues().apply {put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)put(MediaStore.MediaColumns.MIME_TYPE, audioMimeTypeBySuffix(suffix))put(MediaStore.MediaColumns.RELATIVE_PATH, relativePath)}return contentResolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, contentValues)
}/*** 通过后缀名称获取文件类型*/
fun audioMimeTypeBySuffix(suffix: String): String {val lowSuffix = suffix.lowercase()if (lowSuffix.endsWith(".mp3")) {return "audio/mpeg"}if (lowSuffix.endsWith(".wav")) {return "audio/x-wav"}if (lowSuffix.endsWith(".wma")) {return "audio/x-ms-wma"}return "audio${suffix.replace(".", "/")}"
}/*** 删除媒体文件* @param*/
fun Context.deleteMediaStoreByUri(uri: Uri) {contentResolver.delete(uri, null, null)
}//***************************************[File]***************************************/*** 级联删除文件夹* @return 删除是否成功*/
fun File.deleteRecursively(): Boolean {//从最底层文件夹(叶子节点)开始遍历文件树。这样可以确保在删除父文件夹之前,先删除所有子文件和子文件夹walkBottomUp().forEach { file ->if (!file.delete()) {//删除文件或文件夹。如果删除失败,则会抛出 IOException 异常throw IOException("Failed to delete file: ${file.absolutePath}")}}//检查文件夹是否已被成功删除,如果文件夹不存在return !this.exists()
}/*** 级联清空文件夹* @return 删除是否成功*/
fun File.clearRecursively(): Boolean {return if (isDirectory) {listFiles().orEmpty().all { it.deleteRecursively() }} else {return false}
}/*** 计算文件或文件夹的总大小* @return*/
fun File.calculateSize(): Long {return if (isDirectory) {walkTopDown().sumOf { it.length() }} else {length()}
}/*** 计算文件或文件夹的总大小,并将其格式化为友好的单位(B、KB、MB、GB、TB)* @return 格式化后的大小字符串(例如:1.23 GB)*/
fun File.calculateFormatSize(): String {val size = calculateSize()val unitArray = arrayOf("B", "KB", "MB", "GB", "TB")var unitIndex = 0var sizeInUnit = size.toDouble()while (sizeInUnit >= 1024.0 && unitIndex < unitArray.size - 1) {sizeInUnit /= 1024.0unitIndex++}return "%.2f %s".format(sizeInUnit, unitArray[unitIndex])
}/*** InputStream转File* @param path 目标文件路径*/
fun InputStream.toFile(path: String) {toFile(File(path))
}/*** InputStream转File* @param file 目标文件*/
fun InputStream.toFile(file: File) {file.parentFile?.mkdirs()file.outputStream().use { output ->val buffer = ByteArray(4 * 1024)var read: Intwhile (read(buffer).also { read = it } != -1) {output.write(buffer, 0, read)}}
}/*** File转ByteArray* @return*/
fun File.toByteArray(): ByteArray {return this.inputStream().use { input ->input.readBytes()}
}/*** 通过文件名获取资源id 例子:getResId("icon", R.drawable.class);** @param variableName* @param cls* @return*/
fun findResId(variableName: String, cls: Class<*>): Int {try {val idField = cls.getDeclaredField(variableName)return idField.getInt(idField)} catch (e: Exception) {e.printStackTrace()return -1}
}