Skip to content
This repository was archived by the owner on Dec 27, 2024. It is now read-only.

Commit 8d6b4c9

Browse files
committed
Implement BitmapCompression
0 parents  commit 8d6b4c9

16 files changed

Lines changed: 554 additions & 0 deletions

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
*.iml
2+
.gradle
3+
/local.properties
4+
.idea
5+
.DS_Store
6+
/build
7+
/captures
8+
.externalNativeBuild
9+
.cxx
10+
local.properties

BitmapCompression/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build

BitmapCompression/build.gradle.kts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
plugins {
2+
alias(libs.plugins.androidLibrary)
3+
alias(libs.plugins.jetbrainsKotlinAndroid)
4+
id("maven-publish")
5+
}
6+
7+
android {
8+
namespace = "com.mutkuensert.bitmapcompression"
9+
compileSdk = 34
10+
11+
defaultConfig {
12+
minSdk = 24
13+
14+
testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner"
15+
consumerProguardFiles("consumer-rules.pro")
16+
}
17+
18+
buildTypes {
19+
release {
20+
isMinifyEnabled = false
21+
proguardFiles(
22+
getDefaultProguardFile("proguard-android-optimize.txt"),
23+
"proguard-rules.pro"
24+
)
25+
}
26+
}
27+
compileOptions {
28+
sourceCompatibility = JavaVersion.VERSION_1_8
29+
targetCompatibility = JavaVersion.VERSION_1_8
30+
}
31+
kotlinOptions {
32+
jvmTarget = "1.8"
33+
}
34+
}
35+
36+
dependencies {
37+
implementation(libs.appcompat.v7)
38+
implementation(libs.androidx.annotation.jvm)
39+
implementation(libs.androidx.core.ktx)
40+
}
41+
42+
afterEvaluate {
43+
publishing {
44+
publications {
45+
register("release", MavenPublication::class) {
46+
from(components["release"])
47+
groupId = "com.github.mutkuensert"
48+
artifactId = "bitmapcompression"
49+
version = "1.0"
50+
}
51+
}
52+
}
53+
}

BitmapCompression/consumer-rules.pro

Whitespace-only changes.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Add project specific ProGuard rules here.
2+
# You can control the set of applied configuration files using the
3+
# proguardFiles setting in build.gradle.
4+
#
5+
# For more details, see
6+
# http://developer.android.com/guide/developing/tools/proguard.html
7+
8+
# If your project uses WebView with JS, uncomment the following
9+
# and specify the fully qualified class name to the JavaScript interface
10+
# class:
11+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12+
# public *;
13+
#}
14+
15+
# Uncomment this to preserve the line number information for
16+
# debugging stack traces.
17+
#-keepattributes SourceFile,LineNumberTable
18+
19+
# If you keep the line number information, uncomment this to
20+
# hide the original source file name.
21+
#-renamesourcefileattribute SourceFile
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest />
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
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+
}

build.gradle.kts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Top-level build file where you can add configuration options common to all sub-projects/modules.
2+
plugins {
3+
alias(libs.plugins.androidApplication) apply false
4+
alias(libs.plugins.jetbrainsKotlinAndroid) apply false
5+
alias(libs.plugins.androidLibrary) apply false
6+
}

gradle.properties

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Project-wide Gradle settings.
2+
# IDE (e.g. Android Studio) users:
3+
# Gradle settings configured through the IDE *will override*
4+
# any settings specified in this file.
5+
# For more details on how to configure your build environment visit
6+
# http://www.gradle.org/docs/current/userguide/build_environment.html
7+
# Specifies the JVM arguments used for the daemon process.
8+
# The setting is particularly useful for tweaking memory settings.
9+
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10+
# When configured, Gradle will run in incubating parallel mode.
11+
# This option should only be used with decoupled projects. For more details, visit
12+
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
13+
# org.gradle.parallel=true
14+
# AndroidX package structure to make it clearer which packages are bundled with the
15+
# Android operating system, and which are packaged with your app's APK
16+
# https://developer.android.com/topic/libraries/support-library/androidx-rn
17+
android.useAndroidX=true
18+
# Kotlin code style for this project: "official" or "obsolete":
19+
kotlin.code.style=official
20+
# Enables namespacing of each library's R class so that its R class includes only the
21+
# resources declared in the library itself and none from the library's dependencies,
22+
# thereby reducing the size of the R class for that library
23+
android.nonTransitiveRClass=true

gradle/libs.versions.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[versions]
2+
agp = "8.3.0"
3+
kotlin = "1.9.22"
4+
coreKtx = "1.12.0"
5+
appcompatV7 = "28.0.0"
6+
annotationJvm = "1.7.1"
7+
8+
[libraries]
9+
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
10+
appcompat-v7 = { group = "com.android.support", name = "appcompat-v7", version.ref = "appcompatV7" }
11+
androidx-annotation-jvm = { group = "androidx.annotation", name = "annotation-jvm", version.ref = "annotationJvm" }
12+
13+
[plugins]
14+
androidApplication = { id = "com.android.application", version.ref = "agp" }
15+
jetbrainsKotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
16+
androidLibrary = { id = "com.android.library", version.ref = "agp" }
17+

0 commit comments

Comments
 (0)