@@ -3,122 +3,59 @@ package friendly.android
33import android.content.Context
44import android.graphics.Bitmap
55import android.graphics.BitmapFactory
6- import android.graphics.Matrix
76import android.net.Uri
8- import android.util.Log
97import androidx.core.graphics.scale
10- import androidx.exifinterface.media.ExifInterface
11- import kotlinx.io.IOException
128import java.io.ByteArrayInputStream
139import java.io.ByteArrayOutputStream
1410import java.io.InputStream
1511
16- class ImageCompressor (
12+ private const val newAvatarDimension = 500
13+ private const val maxAvatarSize = 16_384L
14+ private const val startQuality = 80
15+ private const val qualityStep = 10
16+ private const val minQuality = 40
17+
18+ class AvatarAdjuster (
1719 private val uri : Uri ,
1820 private val inputStream : InputStream ,
19- private val maxImageSize : Long ,
20- private val compressionQuality : Int ,
21- private val maxFileSizeBytes : Long ,
22- private val context : Context ,
21+ context : Context ,
2322) {
2423 private val contentResolver = context.contentResolver
2524
26- fun compress (): Pair <InputStream , Long > = compressImage(uri, inputStream)
25+ /* *
26+ * Resizes, crops with aspect ration 1x1 and compresses avatar
27+ */
28+ fun adjust (): Pair <InputStream , Long > = adjustAvatar(uri, inputStream)
2729
28- private fun compressImage (
30+ private fun adjustAvatar (
2931 uri : Uri ,
3032 inputStream : InputStream ,
3133 ): Pair <InputStream , Long > {
3234 val bitmap = BitmapFactory .decodeStream(inputStream)
3335 ? : error(" Failed to decode image" )
34-
35- val rotatedBitmap = handleImageRotation(uri, bitmap)
36-
37- val widthIsLarger = rotatedBitmap.width > maxImageSize
38- val heightIsLarger = rotatedBitmap.height > maxImageSize
39- val isScaled = widthIsLarger || heightIsLarger
40-
41- val scaledBitmap = if (isScaled) {
42- scaleBitmap(rotatedBitmap)
43- } else {
44- rotatedBitmap
45- }
36+ val croppedDimension = minOf(bitmap.width, bitmap.height)
37+ val croppedBitmap = Bitmap
38+ .createBitmap(bitmap, 0 , 0 , croppedDimension, croppedDimension)
39+ val scaledBitmap = croppedBitmap
40+ .scale(newAvatarDimension, newAvatarDimension)
4641
4742 val outputStream = ByteArrayOutputStream ()
48- var quality = compressionQuality
49-
43+ var quality = startQuality
5044 do {
5145 outputStream.reset()
5246 scaledBitmap.compress(
5347 Bitmap .CompressFormat .JPEG ,
5448 quality,
5549 outputStream,
5650 )
57- quality - = 5
58- } while (outputStream.size() > maxFileSizeBytes && quality > 50 )
51+ quality - = qualityStep
52+ } while (outputStream.size() > maxAvatarSize && quality > minQuality )
5953
6054 val byteArray = outputStream.toByteArray()
6155
62- if (scaledBitmap != rotatedBitmap) scaledBitmap.recycle()
63- if (rotatedBitmap != bitmap) rotatedBitmap.recycle()
64- bitmap.recycle()
65-
66- Log .d(
67- " avatar" ,
68- " Compressed image: ${byteArray.size} bytes (quality: $quality )" ,
69- )
70-
71- return Pair (ByteArrayInputStream (byteArray), byteArray.size.toLong())
72- }
73-
74- private fun handleImageRotation (uri : Uri , bitmap : Bitmap ): Bitmap {
75- val inputStream = contentResolver.openInputStream(uri) ? : return bitmap
76-
77- return try {
78- val exif = ExifInterface (inputStream)
79- val orientation = exif.getAttributeInt(
80- ExifInterface .TAG_ORIENTATION ,
81- ExifInterface .ORIENTATION_NORMAL ,
82- )
83-
84- val rotation = when (orientation) {
85- ExifInterface .ORIENTATION_ROTATE_90 -> 90f
86- ExifInterface .ORIENTATION_ROTATE_180 -> 180f
87- ExifInterface .ORIENTATION_ROTATE_270 -> 270f
88- else -> 0f
89- }
90-
91- if (rotation != 0f ) {
92- val matrix = Matrix ().apply { postRotate(rotation) }
93- Bitmap .createBitmap(
94- bitmap,
95- 0 ,
96- 0 ,
97- bitmap.width,
98- bitmap.height,
99- matrix,
100- true ,
101- )
102- } else {
103- bitmap
104- }
105- } catch (exception: IOException ) {
106- Log .w(" avatar" , " Failed to read EXIF data" , exception)
107- bitmap
108- } finally {
109- inputStream.close()
110- }
111- }
112-
113- private fun scaleBitmap (bitmap : Bitmap ): Bitmap {
114- val ratio = minOf(
115- maxImageSize.toFloat() / bitmap.width,
116- maxImageSize.toFloat() / bitmap.height,
56+ return Pair (
57+ first = ByteArrayInputStream (outputStream.toByteArray()),
58+ second = byteArray.size.toLong(),
11759 )
118-
119- val newWidth = (bitmap.width * ratio).toInt()
120- val newHeight = (bitmap.height * ratio).toInt()
121-
122- return bitmap.scale(newWidth, newHeight)
12360 }
12461}
0 commit comments