Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 98856e71 authored by Moez Bhatti's avatar Moez Bhatti
Browse files

Improve image compression

Previously, we'd jump from a 100% quality jpeg compressed bitmap to a 60% quality compressed bitmap at 1/4th the resolution. This meant that an image that was slightly over the auto-compression threshold would lose an unnecessarily large amount of resolution/quality.

This new method is more precise with how it compresses the images, and will bring the compressed size much closer to the target size
parent 71897b4e
Loading
Loading
Loading
Loading
+20 −11
Original line number Diff line number Diff line
@@ -247,7 +247,7 @@ class MessageRepositoryImpl @Inject constructor(
                    .also { totalImageBytes = it.sumBy { it.allocationByteCount } }
                    .map { bitmap ->
                        val byteRatio = bitmap.allocationByteCount / totalImageBytes.toFloat()
                        shrink(bitmap, (prefs.mmsSize.get() * 1024 * byteRatio).toInt())
                        compress(bitmap, (prefs.mmsSize.get() * 1024 * byteRatio).toInt())
                    }
                    .map { bitmap -> MMSPart("image", ContentType.IMAGE_JPEG, bitmap) }

@@ -283,27 +283,36 @@ class MessageRepositoryImpl @Inject constructor(
        smsManager.sendMultipartTextMessage(message.address, null, parts, ArrayList(sentIntents), ArrayList(deliveredIntents))
    }

    private fun shrink(src: Bitmap, maxBytes: Int): ByteArray {
        var step = 0.0
        val factor = 0.5
        val quality = 60
    private fun compress(src: Bitmap, maxBytes: Int): ByteArray {
        val quality = 90

        val height = src.height
        val width = src.width

        val stream = ByteArrayOutputStream()
        src.compress(Bitmap.CompressFormat.JPEG, 100, stream)
        src.compress(Bitmap.CompressFormat.JPEG, quality, stream)

        while (maxBytes > 0 && stream.size() > maxBytes) {
            step++
            val scale = Math.pow(factor, step)
        // Based on the byte size of the bitmap, we'll try to reduce the image's dimensions such
        // that it will fit within the max byte size set. If we don't get it right the first time,
        // use a slightly heavier compression until we fit within the max size
        var attempts = 0
        var bytes = stream.size().toDouble()
        while (maxBytes > 0 && bytes > maxBytes) {
            val scale = Math.sqrt(maxBytes / bytes) * (1 - attempts * 0.1)

            stream.reset()
            Bitmap.createScaledBitmap(src, (width * scale).toInt(), (height * scale).toInt(), false)
            Bitmap.createScaledBitmap(src, (width * scale).toInt(), (height * scale).toInt(), true)
                    .compress(Bitmap.CompressFormat.JPEG, quality, stream)

            attempts++
            bytes = stream.size().toDouble()
        }

        if (maxBytes > 0 && stream.size() > maxBytes) {
            Timber.w(Exception("Compressed image size exceeds max byte size (${stream.size()}/$maxBytes) after $attempts attempts"))
        }

        return stream.toByteArray()
        return stream.toByteArray().also { src.recycle() }
    }

    override fun cancelDelayedSms(id: Long) {