【问题标题】:How to compress a photo picked from gallery in kotlin如何压缩从科特林画廊挑选的照片
【发布时间】:2021-04-26 03:08:57
【问题描述】:

我发现了很多关于这个主题的问题,但没有一个有完整的工作示例。

我的情况很简单:

  1. 获取选取的图像
  2. 压缩图片
  3. 将压缩图像上传到 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 库的答案(如herehere)并尝试了它,但它不适用于图库结果uris。我找到了从中获取实际 uri 的方法(例如 here),但它们感觉像很多样板代码,所以感觉不是最佳实践。
  • 我还使用bitmap.compress(如herehere)找到了许多答案并尝试过,但它要求提供位图。使用MediaStore.Images.Media.getBitmap 获取位图很容易,但它已被弃用,this 解决方案让我怀疑它是否是正确的方向。此外,将位图保存在我的 LiveData 对象中(在屏幕上显示直到实际保存,在编辑屏幕中)而不是 uri,感觉很奇怪(我使用 Glide 来呈现图像)。

最重要的是,这两种解决方案都需要上下文。我认为压缩是一个应该属于后端(或 Repository 类。在我的例子中是 Storage 对象)的过程,所以他们感觉有点不对劲。

有人可以分享这个简单用例的完整工作示例吗?

【问题讨论】:

    标签: android kotlin image-gallery image-compression imagepicker


    【解决方案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】:

    流程应该是:

    1. 用户选择图像并显示在ImageView 中(scaleTyle 应为 centerCrop)。

    2. 用户点击保存按钮,我们开始上传图片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()
        }
    

    (当然:updateUserPictureuploadFileUri参数改为ByteArray,对putFile的调用改为putBytes

    这个link 对我很有帮助。

    我不太喜欢在整个应用程序中使用位图而不是 uris,但这是迄今为止对我最有效的方法。如果有人有更好的解决方案,请分享:)

    编辑:

    此解决方案会丢失元数据,如here 所述。当然是可以解决的(here),但是我认为因为元数据字段可以在android版本之间改变,所以Hascher7的解决方案above更加健壮。

    【讨论】:

      猜你喜欢
      • 2021-10-06
      • 1970-01-01
      • 1970-01-01
      • 2015-02-12
      • 1970-01-01
      • 2021-01-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多