├── .github
└── workflows
│ ├── android.yml
│ ├── gradle-publish.yml
│ └── gradle.yml
├── .gitignore
├── ImageCompressor
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── android
│ │ └── imagecompressor
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ └── java
│ │ └── com
│ │ └── android
│ │ └── imagecompressor
│ │ └── compressImageUtils.kt
│ └── test
│ └── java
│ └── com
│ └── android
│ └── imagecompressor
│ └── ExampleUnitTest.kt
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── kl
│ │ └── compress_image
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── kl
│ │ │ └── compress_image
│ │ │ └── MainActivity.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ └── activity_main.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── values-night
│ │ └── themes.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ └── test
│ └── java
│ └── com
│ └── kl
│ └── compress_image
│ └── ExampleUnitTest.kt
├── build.gradle
├── google6401b219a8c25f30.html
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── image (2)_google-pixel4xl-clearlywhite-portrait.jpg
└── settings.gradle
/.github/workflows/android.yml:
--------------------------------------------------------------------------------
1 | name: Android CI
2 |
3 | on:
4 | push:
5 | branches: [ "master" ]
6 | pull_request:
7 | branches: [ "master" ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v3
16 | - name: set up JDK 11
17 | uses: actions/setup-java@v3
18 | with:
19 | java-version: '11'
20 | distribution: 'temurin'
21 | cache: gradle
22 |
23 | - name: Grant execute permission for gradlew
24 | run: chmod +x gradlew
25 | - name: Build with Gradle
26 | run: ./gradlew build
27 |
--------------------------------------------------------------------------------
/.github/workflows/gradle-publish.yml:
--------------------------------------------------------------------------------
1 | # This workflow uses actions that are not certified by GitHub.
2 | # They are provided by a third-party and are governed by
3 | # separate terms of service, privacy policy, and support
4 | # documentation.
5 | # This workflow will build a package using Gradle and then publish it to GitHub packages when a release is created
6 | # For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#Publishing-using-gradle
7 |
8 | name: Gradle Package
9 |
10 | on:
11 | release:
12 | types: [created]
13 |
14 | jobs:
15 | build:
16 |
17 | runs-on: ubuntu-latest
18 | permissions:
19 | contents: read
20 | packages: write
21 |
22 | steps:
23 | - uses: actions/checkout@v3
24 | - name: Set up JDK 11
25 | uses: actions/setup-java@v3
26 | with:
27 | java-version: '11'
28 | distribution: 'temurin'
29 | server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
30 | settings-path: ${{ github.workspace }} # location for the settings.xml file
31 |
32 | - name: Build with Gradle
33 | uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
34 | with:
35 | arguments: build
36 |
37 | # The USERNAME and TOKEN need to correspond to the credentials environment variables used in
38 | # the publishing section of your build.gradle
39 | - name: Publish to GitHub Packages
40 | uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
41 | with:
42 | arguments: publish
43 | env:
44 | USERNAME: ${{ github.actor }}
45 | TOKEN: ${{ secrets.GITHUB_TOKEN }}
46 |
--------------------------------------------------------------------------------
/.github/workflows/gradle.yml:
--------------------------------------------------------------------------------
1 | # This workflow uses actions that are not certified by GitHub.
2 | # They are provided by a third-party and are governed by
3 | # separate terms of service, privacy policy, and support
4 | # documentation.
5 | # This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
6 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
7 |
8 | name: Java CI with Gradle
9 |
10 | on:
11 | push:
12 | branches: [ "master" ]
13 | pull_request:
14 | branches: [ "master" ]
15 |
16 | permissions:
17 | contents: read
18 |
19 | jobs:
20 | build:
21 |
22 | runs-on: ubuntu-latest
23 |
24 | steps:
25 | - uses: actions/checkout@v3
26 | - name: Set up JDK 11
27 | uses: actions/setup-java@v3
28 | with:
29 | java-version: '11'
30 | distribution: 'temurin'
31 | - name: Build with Gradle
32 | uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
33 | with:
34 | arguments: build
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/ImageCompressor/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/ImageCompressor/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'org.jetbrains.kotlin.android'
4 | }
5 |
6 | android {
7 | compileSdk 33
8 |
9 | defaultConfig {
10 | minSdk 21
11 | targetSdk 33
12 |
13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
14 | consumerProguardFiles "consumer-rules.pro"
15 | }
16 |
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 | compileOptions {
24 | sourceCompatibility JavaVersion.VERSION_1_8
25 | targetCompatibility JavaVersion.VERSION_1_8
26 | }
27 | kotlinOptions {
28 | jvmTarget = '1.8'
29 | }
30 | }
31 |
32 | dependencies {
33 |
34 | implementation 'androidx.core:core-ktx:1.10.0'
35 | implementation 'androidx.appcompat:appcompat:1.6.1'
36 | implementation 'com.google.android.material:material:1.8.0'
37 | testImplementation 'junit:junit:4.13.2'
38 | androidTestImplementation 'androidx.test.ext:junit:1.1.5'
39 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
40 | }
--------------------------------------------------------------------------------
/ImageCompressor/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vinodbaste/Image-compressor/b7cfaa6bb17ed3bbaab3728cb1b0517b4edc615d/ImageCompressor/consumer-rules.pro
--------------------------------------------------------------------------------
/ImageCompressor/proguard-rules.pro:
--------------------------------------------------------------------------------
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
--------------------------------------------------------------------------------
/ImageCompressor/src/androidTest/java/com/android/imagecompressor/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.android.imagecompressor
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.android.imagecompressor.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/ImageCompressor/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/ImageCompressor/src/main/java/com/android/imagecompressor/compressImageUtils.kt:
--------------------------------------------------------------------------------
1 | /*Copyright [2022] [Vinod Baste]
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.*/
14 |
15 | @file:Suppress("DEPRECATION")
16 |
17 | package com.android.imagecompressor
18 |
19 | import android.content.Context
20 | import android.graphics.*
21 | import android.media.ExifInterface
22 | import android.os.Build
23 | import android.os.Environment
24 | import android.util.Log
25 | import java.io.File
26 | import java.io.FileNotFoundException
27 | import java.io.FileOutputStream
28 | import java.io.IOException
29 | import java.nio.file.Files
30 | import kotlin.math.roundToInt
31 |
32 | object ImageCompressUtils {
33 |
34 | //compress the image
35 | @JvmOverloads
36 | fun compressImage(
37 | context: Context,
38 | imagePath: String?,
39 | imageName: String?,
40 | imageQuality: Int = 50
41 | ): String {
42 |
43 | var filePath = ""
44 | try {
45 | var scaledBitmap: Bitmap? = null
46 | val options = BitmapFactory.Options()
47 | // by setting this field as true, the actual bitmap pixels are not loaded in the memory.
48 | // Just the bounds are loaded. If you try the use the bitmap here, you will get null.
49 | options.inJustDecodeBounds = true
50 | var actualHeight = options.outHeight
51 | var actualWidth = options.outWidth
52 |
53 | val imageFile = File(imagePath!!)
54 | val fileContent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
55 | Files.readAllBytes(imageFile.toPath())
56 | } else {
57 | File(imagePath).readBytes()
58 | }
59 | var bmp = BitmapFactory.decodeByteArray(fileContent, 0, fileContent.size, options)
60 |
61 | //max Height and width values of the compressed image is taken as 1024x912
62 | val maxHeight = 1024.0f
63 | val maxWidth = 912.0f
64 | var imgRatio = actualWidth / actualHeight.toFloat()
65 | val maxRatio = maxWidth / maxHeight
66 |
67 | //width and height values are set maintaining the aspect ratio of the image
68 | if (actualHeight > maxHeight || actualWidth > maxWidth) {
69 | when {
70 | imgRatio < maxRatio -> {
71 | imgRatio = maxHeight / actualHeight
72 | actualWidth = (imgRatio * actualWidth).toInt()
73 | actualHeight = maxHeight.toInt()
74 | }
75 | imgRatio > maxRatio -> {
76 | imgRatio = maxWidth / actualWidth
77 | actualHeight = (imgRatio * actualHeight).toInt()
78 | actualWidth = maxWidth.toInt()
79 | }
80 | else -> {
81 | actualHeight = maxHeight.toInt()
82 | actualWidth = maxWidth.toInt()
83 | }
84 | }
85 | }
86 |
87 | //setting inSampleSize value allows to load a scaled down version of the original image
88 | options.inSampleSize = calculateInSampleSize(options, actualWidth, actualHeight)
89 | //inJustDecodeBounds set to false to load the actual bitmap
90 | options.inJustDecodeBounds = false
91 | //this removes the redundant quality of the image
92 | options.inPurgeable = true
93 | //this options allow android to claim the bitmap memory if it runs low on memory
94 | options.inInputShareable = true
95 |
96 | options.inTempStorage = ByteArray(16 * 1024)
97 | try {
98 | //load the bitmap from its path
99 | bmp = BitmapFactory.decodeFile(imagePath, options)
100 | } catch (exception: OutOfMemoryError) {
101 | exception.printStackTrace()
102 | }
103 | try {
104 | scaledBitmap =
105 | Bitmap.createBitmap(actualWidth, actualHeight, Bitmap.Config.ARGB_8888)
106 | } catch (exception: OutOfMemoryError) {
107 | exception.printStackTrace()
108 | }
109 | val ratioX = actualWidth / options.outWidth.toFloat()
110 | val ratioY = actualHeight / options.outHeight.toFloat()
111 | val middleX = actualWidth / 2.0f
112 | val middleY = actualHeight / 2.0f
113 | val scaleMatrix = Matrix()
114 | scaleMatrix.setScale(ratioX, ratioY, middleX, middleY)
115 | val canvas = Canvas(scaledBitmap!!)
116 | canvas.setMatrix(scaleMatrix)
117 | canvas.drawBitmap(
118 | bmp,
119 | middleX - bmp.width / 2,
120 | middleY - bmp.height / 2,
121 | Paint(Paint.FILTER_BITMAP_FLAG)
122 | )
123 |
124 | //check the rotation of the image and display it properly
125 | val exif: ExifInterface
126 | try {
127 | exif = ExifInterface(imagePath.toString())
128 | val orientation = exif.getAttributeInt(
129 | ExifInterface.TAG_ORIENTATION, 0
130 | )
131 | Log.d("EXIF", "Exif: $orientation")
132 | val matrix = Matrix()
133 | when (orientation) {
134 | 6 -> {
135 | matrix.postRotate(90f)
136 | Log.d("EXIF", "Exif: $orientation")
137 | }
138 | 3 -> {
139 | matrix.postRotate(180f)
140 | Log.d("EXIF", "Exif: $orientation")
141 | }
142 | 8 -> {
143 | matrix.postRotate(270f)
144 | Log.d("EXIF", "Exif: $orientation")
145 | }
146 | }
147 | scaledBitmap = Bitmap.createBitmap(
148 | scaledBitmap, 0, 0,
149 | scaledBitmap.width, scaledBitmap.height, matrix,
150 | true
151 | )
152 | } catch (e: IOException) {
153 | e.printStackTrace()
154 | } catch (e: java.lang.Exception) {
155 | e.printStackTrace()
156 | }
157 | val out: FileOutputStream?
158 | filePath = imageName?.let { getOutputMediaFile(it, context) }!!.absolutePath
159 | try {
160 | out = FileOutputStream(filePath)
161 |
162 | //write the compressed bitmap at the destination specified by filename.
163 | scaledBitmap!!.compress(Bitmap.CompressFormat.JPEG, imageQuality, out)
164 | } catch (e: FileNotFoundException) {
165 | e.printStackTrace()
166 | }
167 | } catch (e: java.lang.Exception) {
168 | e.printStackTrace()
169 | }
170 |
171 | return filePath
172 | }
173 |
174 | }
175 |
176 | private fun getOutputMediaFile(imageName: String, context: Context): File? {
177 | var imageFile1: File? = null
178 | try {
179 | imageFile1 = createImageFile(context, imageName)
180 | } catch (e: IOException) {
181 | e.printStackTrace()
182 | }
183 | if (imageFile1!!.exists()) imageFile1.delete()
184 | var imageNew: File? = null
185 | try {
186 | imageNew = createImageFile(context, imageName)
187 | } catch (e: IOException) {
188 | e.printStackTrace()
189 | }
190 | return imageNew
191 | }
192 |
193 | private fun calculateInSampleSize(
194 | options: BitmapFactory.Options,
195 | reqWidth: Int,
196 | reqHeight: Int
197 | ): Int {
198 | val height = options.outHeight
199 | val width = options.outWidth
200 | var inSampleSize = 1
201 | try {
202 | if (height > reqHeight || width > reqWidth) {
203 | val heightRatio =
204 | (height.toFloat() / reqHeight.toFloat()).roundToInt()
205 | val widthRatio =
206 | (width.toFloat() / reqWidth.toFloat()).roundToInt()
207 | inSampleSize = if (heightRatio < widthRatio) heightRatio else widthRatio
208 | }
209 | val totalPixels = width * height.toFloat()
210 | val totalReqPixelsCap = reqWidth * reqHeight * 2.toFloat()
211 | while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
212 | inSampleSize++
213 | }
214 | } catch (e: java.lang.Exception) {
215 | e.printStackTrace()
216 | }
217 | return inSampleSize
218 | }
219 |
220 | @Throws(IOException::class)
221 | private fun createImageFile(context: Context, FileName: String): File {
222 | return File(
223 | context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
224 | .toString() + File.separator + FileName + ".png"
225 | )
226 | }
--------------------------------------------------------------------------------
/ImageCompressor/src/test/java/com/android/imagecompressor/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.android.imagecompressor
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Vinod Baste
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Image Compressor
2 |
3 | An Android image compress library, **image compressor**, is small and effective. With very little or no image quality degradation, a compressor enables you to reduce the size of large photos into smaller size photos.
4 |
5 | [![Google DevLibrary - VinodBaste](https://img.shields.io/badge/Google_DevLibrary-VinodBaste-ea9f2d?logo=