如何压缩从科特林画廊挑选的照片
Posted
技术标签:
【中文标题】如何压缩从科特林画廊挑选的照片【英文标题】:How to compress a photo picked from gallery in kotlin 【发布时间】:2021-04-26 03:08:57 【问题描述】:我发现了很多关于这个主题的问题,但没有一个有完整的工作示例。
我的情况很简单:
-
获取选取的图像
压缩图片
将压缩图像上传到 Firebase 存储
我当前的代码(未压缩):
片段:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?)
super.onActivityResult(requestCode, resultCode, data)
when (requestCode)
RC_PICK_IMAGE ->
if (resultCode == Activity.RESULT_OK)
data?.data?.let viewModel.updateUserPicture(it)
视图模型:
fun updatePhotoUrl(photoUrl: Uri?): Task<Void> =
Storage.uploadFile("/$PS_USERS/$uid", photoUrl)
.continueWithTask ...
存储:(包装 Firebase 交互的对象)
fun uploadFile(path: String, uri: Uri): Task<Uri>
val imageRef = storageRef.child(path)
val uploadTask = imageRef.putFile(uri)
// Return the task getting the download URL
return uploadTask.continueWithTask task ->
if (!task.isSuccessful)
task.exception?.let
throw it
imageRef.downloadUrl
这很好用。
现在我的问题是:在这个过程中添加压缩的正确方法是什么?
我找到了许多指向Compressor 库的答案(如here 和here)并尝试了它,但它不适用于图库结果uris。我找到了从中获取实际 uri 的方法(例如 here),但它们感觉像很多样板代码,所以感觉不是最佳实践。 我还使用bitmap.compress
(如here 和here)找到了许多答案并尝试过,但它要求提供位图。使用MediaStore.Images.Media.getBitmap
获取位图很容易,但它已被弃用,this 解决方案让我怀疑它是否是正确的方向。此外,将位图保存在我的 LiveData 对象中(在屏幕上显示直到实际保存,在编辑屏幕中)而不是 uri,感觉很奇怪(我使用 Glide 来呈现图像)。
最重要的是,这两种解决方案都需要上下文。我认为压缩是一个应该属于后端(或 Repository 类。在我的例子中是 Storage 对象)的过程,所以他们感觉有点不对劲。
有人可以分享这个简单用例的完整工作示例吗?
【问题讨论】:
【参考方案1】:使用它可能会有所帮助: 通过传递 compressAndSetImage(data.data) 从 onActivityResult 调用 compressAndSetImage
fun compressAndSetImage(result: Uri)
val job = Job()
val uiScope = CoroutineScope(Dispatchers.IO + job)
val fileUri = getFilePathFromUri(result, context!!)
uiScope.launch
val compressedImageFile = Compressor.compress(context!!, File(fileUri.path))
quality(50) // combine with compressor constraint
format(Bitmap.CompressFormat.JPEG)
resultUri = Uri.fromFile(compressedImageFile)
activity!!.runOnUiThread
resultUri?.let
//set image here
要解决此问题,我必须先将此路径转换为真实路径,然后我才能通过这种方式解决此问题.. 首先这是要在 build.gradle (app) 中添加的依赖项: //图片压缩依赖
implementation 'id.zelory:compressor:3.0.0'
//将uri路径转换为真实路径
@Throws(IOException::class)
fun getFilePathFromUri(uri: Uri?, context: Context?): Uri?
val fileName: String? = getFileName(uri, context)
val file = File(context?.externalCacheDir, fileName)
file.createNewFile()
FileOutputStream(file).use outputStream ->
context?.contentResolver?.openInputStream(uri).use inputStream ->
copyFile(inputStream, outputStream)
outputStream.flush()
return Uri.fromFile(file)
@Throws(IOException::class)
private fun copyFile(`in`: InputStream?, out: OutputStream)
val buffer = ByteArray(1024)
var read: Int? = null
while (`in`?.read(buffer).also( read = it!! ) != -1)
read?.let out.write(buffer, 0, it)
//copyFile ends
fun getFileName(uri: Uri?, context: Context?): String?
var fileName: String? = getFileNameFromCursor(uri, context)
if (fileName == null)
val fileExtension: String? = getFileExtension(uri, context)
fileName = "temp_file" + if (fileExtension != null) ".$fileExtension" else ""
else if (!fileName.contains("."))
val fileExtension: String? = getFileExtension(uri, context)
fileName = "$fileName.$fileExtension"
return fileName
fun getFileExtension(uri: Uri?, context: Context?): String?
val fileType: String? = context?.contentResolver?.getType(uri)
return MimeTypeMap.getSingleton().getExtensionFromMimeType(fileType)
fun getFileNameFromCursor(uri: Uri?, context: Context?): String?
val fileCursor: Cursor? = context?.contentResolver
?.query(uri, arrayOf<String>(OpenableColumns.DISPLAY_NAME), null, null, null)
var fileName: String? = null
if (fileCursor != null && fileCursor.moveToFirst())
val cIndex: Int = fileCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
if (cIndex != -1)
fileName = fileCursor.getString(cIndex)
return fileName
【讨论】:
您好,感谢您的回答!我试过了(已经和现在再次分享错误),但它不适用于图像选择器的 uri 结果,因为它不是一个真正的文件(我理解这是我找到的所有解决方案中的核心问题)。Compressor.compress(context!!, File(result.path))
抛出:kotlin.io.NoSuchFileException: /-1/1/content:/media/external/images/media/25/ORIGINAL/NONE/1161564586: The source file doesn't exist.
。如何获得适用于该函数的 uri??
我已经编辑了我的答案。请查看它,如果仍然出现任何问题,请告诉我。但希望您的问题可以通过这种方式解决。
好的,现在可以了。我对这个解决方案的问题是它就像 75 行样板代码,实际上打开了一个新文件并将所有数据从 uri 复制到它。这真的是从标准图库选择器压缩图像的最佳方式吗?请查看我的回答 below - 它在不到 10 行代码的情况下实现了相同的结果 - 不使用 Compressor 库。我想知道为什么我从来没有看到有人建议过它,而大多数人都指向 Compressor 库 - 你能告诉我你的解决方案的优势吗?
实际上,您之前遇到的关于 NoSuchFleException 的问题,要解决我们必须像我们一样解决的问题。我不会称其为最佳解决方案,但要解决该问题,我想这是这样做的方式,因为我自己也遇到过很多次同样的问题。除了举个例子,如果我们想从驱动器或在线某个地方而不是在画廊中选择一张照片,那么这就是让事情顺利进行的方式.. 是的,您的解决方案也符合要求,但您不能再次尝试从画廊中挑选图像并查看您的解决方案是否有效..
如果需要进一步的帮助,请告诉我,让我知道,以便我也获得更多信息 :)【参考方案2】:
流程应该是:
用户选择图像并显示在ImageView
中(scaleTyle 应为 centerCrop)。
用户点击保存按钮,我们开始上传图片bytes
,如下所示:
val uploadTask = profilePicturesReference.putBytes(getImageBytes())
private fun getImageBytes(): ByteArray
val bitmap = Bitmap.createBitmap(my_profile_imageView.width, my_profile_imageView.height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
my_profile_imageView.draw(canvas)
val outputStream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream) //here, 100 is the quality in %
return outputStream.toByteArray()
演示:https://www.youtube.com/watch?v=iTXCn3NVqDM
将原始大小的图片上传到 Firebase 存储:
val uploadTask = profilePicturesReference.putFile(fileUri)
【讨论】:
感谢您的回答!有趣的方法,但是如果我想要原始图片的压缩版本怎么办?在我的例子中,编辑屏幕在一个小的 ImageView 中显示图像,而在显示屏幕中它显示在一个更大的视图中。此外,它是圆形裁剪的,将来可能会显示为方形裁剪。如果我按照你的方法,我会得到一张特别匹配小编辑屏幕 ImageView 大小和裁剪(或更小的视图)的图片。【参考方案3】:我能想到的最佳解决方案是:
片段:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?)
super.onActivityResult(requestCode, resultCode, data)
when (requestCode)
RC_PICK_IMAGE ->
if (resultCode == Activity.RESULT_OK)
data?.data?.let uri ->
context?.let
viewModel.updateUserPicture(uriToCompressedBitmap(it, uri))
fun uriToCompressedBitmap(context: Context, uri: Uri): ByteArray
val pfd = context.contentResolver.openFileDescriptor(uri, "r")
val bitmap =
BitmapFactory.decodeFileDescriptor(pfd?.fileDescriptor, null, null)
val baos = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.JPEG, 75, baos)
return baos.toByteArray()
(当然:updateUserPicture
和uploadFile
的Uri
参数改为ByteArray
,对putFile
的调用改为putBytes
)
这个link 对我很有帮助。
我不太喜欢在整个应用程序中使用位图而不是 uris,但这是迄今为止对我最有效的方法。如果有人有更好的解决方案,请分享:)
编辑:
此解决方案会丢失元数据,如here 所述。当然是可以解决的(here),但是我认为因为元数据字段可以在android版本之间改变,所以Hascher7的解决方案above更加健壮。
【讨论】:
以上是关于如何压缩从科特林画廊挑选的照片的主要内容,如果未能解决你的问题,请参考以下文章
如果 MutableList 为空,如何更改 TextView 的可见性? (安卓/科特林)
如何在firestore中检索当前auth用户的所有字段? - 科特林
科特林 |杰克逊注解 |如何修复超出 START_ARRAY 令牌错误