1+ package com.mutkuensert.bitmapcompression
2+
3+ import android.graphics.Bitmap
4+ import android.graphics.BitmapFactory
5+ import androidx.annotation.FloatRange
6+ import androidx.annotation.IntRange
7+ import androidx.core.graphics.scale
8+ import java.io.ByteArrayOutputStream
9+ import java.io.File
10+
11+ /* *
12+ * @property sizeLimitBytes Max size the file can be after compression.
13+ * @property compressPriority Start reducing file size by scaling down or compressing.
14+ * @property lowerWidthLimit Stop scaling down before dropping down below this value.
15+ * @property lowerHeightLimit Stop scaling down before dropping down below this value.
16+ * @property compressionQualityDownTo Lower value means lower quality and smaller size.
17+ * @property scaleDownFactor Scale factor to divide width and height of image in every loop.
18+ */
19+ class BitmapCompression (
20+ val sizeLimitBytes : Int ,
21+ val compressPriority : CompressPriority = CompressPriority .STARTBYCOMPRESS ,
22+ val lowerWidthLimit : Int? = null ,
23+ val lowerHeightLimit : Int? = null ,
24+ @IntRange(from = 1 , to = 90 )
25+ val compressionQualityDownTo : Int = 10 ,
26+ @FloatRange(from = 0.1 , to = 0.9 )
27+ val scaleDownFactor : Float = 0.5f
28+ ) {
29+ fun compress (file : File ) {
30+ if (file.length() < sizeLimitBytes) return
31+
32+ val byteArrayOutputStream = ByteArrayOutputStream ()
33+
34+ var bitmap = BitmapFactory .decodeFile(file.absolutePath)
35+
36+ bitmap = compressByPriority(bitmap, byteArrayOutputStream)
37+
38+ bitmap.recycle()
39+
40+ file.delete()
41+ file.createNewFile()
42+ file.outputStream().use {
43+ byteArrayOutputStream.writeTo(it)
44+ }
45+
46+ byteArrayOutputStream.close()
47+ }
48+
49+ private fun compressBitmap (
50+ bitmap : Bitmap ,
51+ byteArrayOutputStream : ByteArrayOutputStream ,
52+ ) {
53+ var factor = 90
54+
55+ do {
56+ byteArrayOutputStream.reset()
57+ bitmap.compress(Bitmap .CompressFormat .JPEG , factor, byteArrayOutputStream)
58+ factor - = 10
59+ if (factor <= compressionQualityDownTo) break
60+ } while (byteArrayOutputStream.size() >= sizeLimitBytes)
61+ }
62+
63+ private fun getScaledDownBitmap (
64+ bitmap : Bitmap ,
65+ byteArrayOutputStream : ByteArrayOutputStream ,
66+ ): Bitmap {
67+ var scaledDownBitmap = bitmap
68+
69+ do {
70+ val scaledDownWidth = scaledDownBitmap.width * scaleDownFactor
71+ val scaledDownHeight = scaledDownBitmap.height * scaleDownFactor
72+
73+ if ((lowerWidthLimit != null && scaledDownWidth < lowerWidthLimit)
74+ || (lowerHeightLimit != null && scaledDownHeight < lowerHeightLimit)
75+ ) {
76+ if (compressPriority == CompressPriority .STARTBYSCALEDOWN ) {
77+ break
78+ } else {
79+ throw RuntimeException (
80+ " File is too big for specified upper limits. " +
81+ " Try with lower limits."
82+ )
83+ }
84+ }
85+
86+ scaledDownBitmap = scaledDownBitmap.scale(
87+ width = scaledDownWidth.toInt(),
88+ height = scaledDownHeight.toInt()
89+ )
90+ byteArrayOutputStream.reset()
91+ scaledDownBitmap.compress(Bitmap .CompressFormat .JPEG , 90 , byteArrayOutputStream)
92+ } while ((byteArrayOutputStream.size() >= sizeLimitBytes))
93+
94+ return scaledDownBitmap
95+ }
96+
97+ enum class CompressPriority {
98+ STARTBYSCALEDOWN , STARTBYCOMPRESS
99+ }
100+
101+ private fun compressByPriority (
102+ bitmap : Bitmap ,
103+ byteArrayOutputStream : ByteArrayOutputStream ,
104+ ): Bitmap {
105+ var newBitmap = bitmap
106+
107+ if (compressPriority == CompressPriority .STARTBYSCALEDOWN ) {
108+ newBitmap = getScaledDownBitmap(newBitmap, byteArrayOutputStream)
109+ compressBitmap(newBitmap, byteArrayOutputStream)
110+ } else {
111+ compressBitmap(newBitmap, byteArrayOutputStream)
112+ newBitmap = getScaledDownBitmap(newBitmap, byteArrayOutputStream)
113+ }
114+ return newBitmap
115+ }
116+ }
0 commit comments