Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
198 views
in Technique[技术] by (71.8m points)

android - How to compress a photo picked from gallery in kotlin

I found so many questions on the topic but none had a full working example.

My case is simple:

  1. get picked image
  2. compress image
  3. upload compressed image to Firebase Storage

My current code (without compression):

Fragment:

    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) }
        }
    }

ViewModel:

    fun updatePhotoUrl(photoUrl: Uri?): Task<Void> =
        Storage.uploadFile("/$PS_USERS/$uid", photoUrl)
                    .continueWithTask { ... }

Storage: (object to wrap Firebase interactions)

    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
        }
    }

This works perfectly.

Now my question is: what is the right way to add compression in this process?

  • I found many answers pointing to the Compressor library (like here & here) and tried it, but it doesn't work with gallery result uris. I found ways to get the actual uri from that (like here), but they feel like a lot of boilerplate code, so it doesn't feel like the best practice.
  • I also found many answers using bitmap.compress (like here & here) and tried it too, but it asks for a bitmap. Getting the bitmap is easy with MediaStore.Images.Media.getBitmap, but it is deprecated, and this solution made me doubt if it's the right direction. Also, holding the bitmap in my LiveData object (to show on screen until the actual save, in the edit screen), instead of a uri, feels weird (I use Glide for presenting the images).

On top of that, both solutions demand a context. I think compression is a process that should belong in the backend (or Repository class. The Storage object in my case), So they feel a bit off.

Can someone please share a full working example of this trivial use case?

question from:https://stackoverflow.com/questions/65831790/how-to-compress-a-photo-picked-from-gallery-in-kotlin

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Use this it might help: Call compressAndSetImage from onActivityResult by passing compressAndSetImage(data.data)

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 
                }
            }
        }
    }

To work around this issue i had to do first convert this path to a real path and i was able to solve this issue that way.. First of all this is the dependency to be added in the build.gradle (app) : //image compression dependency

implementation 'id.zelory:compressor:3.0.0'

//converting uri path to real path

@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
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...