Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Crop always return uri (Gradle 7.0, Java 11, Android 12) #185

Merged
merged 6 commits into from
Sep 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .idea/kotlinc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 10 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- `Fixed` for any bug fixes.
- `Security` in case of vulnerabilities.

## [unreleased x.x.x] -
## [3.3.0] -
### Changed
- Update to Android 12
- Update library to gradle 7.0.1 and Java 11 [#191](https://github.com/CanHub/Android-Image-Cropper/issues/191)
- Any crop action should return uri content [#180](https://github.com/CanHub/Android-Image-Cropper/issues/180)

### Fixed
- Implement onBackPressed() in sample code for handle backButton pressed [#174](https://github.com/CanHub/Android-Image-Cropper/issues/174)

## [3.2.2] - 31/07/21
### Fixed
- After cropping a camera image, cancelling library picker shows again the last cropped image [#162](https://github.com/CanHub/Android-Image-Cropper/issues/162)
- implement onBackPressed() in sample code for handle backButton pressed [#174](https://github.com/CanHub/Android-Image-Cropper/issues/174)

## [3.2.1] - 14/07/21
### Fixed
Expand Down
290 changes: 290 additions & 0 deletions build.gradle

Large diffs are not rendered by default.

9 changes: 4 additions & 5 deletions cropper/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'com.github.dcendents.android-maven'
apply plugin: 'kotlin-parcelize'

group='com.github.Canato'
Expand All @@ -15,8 +14,8 @@ android {
versionName rootProject.libVersion
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
lintOptions {
abortOnError false
Expand All @@ -25,7 +24,7 @@ android {
viewBinding true
}
kotlinOptions {
jvmTarget = "1.8"
jvmTarget = JavaVersion.VERSION_11.toString()
}
testOptions {
unitTests {
Expand All @@ -35,7 +34,7 @@ android {
}

dependencies {
api "androidx.appcompat:appcompat:$androidXAppCompatVersionCropper"
implementation "androidx.appcompat:appcompat:$androidXAppCompatVersion"

implementation "androidx.activity:activity-ktx:$androidXActivity"

Expand Down
30 changes: 29 additions & 1 deletion cropper/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,34 @@
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/library_file_paths" />
</provider>
<activity android:name=".CropImageActivity" />
<activity
android:name=".CropImageActivity"
android:exported="true" />

<!-- This is here because the library did not update yet to Android 12 so we force the exported value when merging manifests -->
<activity
android:name="androidx.test.core.app.InstrumentationActivityInvoker$BootstrapActivity"
android:exported="true"
android:theme="@android:style/Theme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
<activity
android:name="androidx.test.core.app.InstrumentationActivityInvoker$EmptyActivity"
android:exported="true"
android:theme="@android:style/Theme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
<activity
android:name="androidx.test.core.app.InstrumentationActivityInvoker$EmptyFloatingActivity"
android:exported="true"
android:theme="@android:style/Theme.Dialog">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
</application>
</manifest>
37 changes: 16 additions & 21 deletions cropper/src/main/java/com/canhub/cropper/BitmapCroppingWorkerJob.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ class BitmapCroppingWorkerJob(
private val flipHorizontally: Boolean,
private val flipVertically: Boolean,
private val options: CropImageView.RequestSizeOptions,
private val saveUri: Uri?,
private val saveCompressFormat: Bitmap.CompressFormat? = Bitmap.CompressFormat.JPEG,
private val saveCompressFormat: Bitmap.CompressFormat,
private val saveCompressQuality: Int
) : CoroutineScope {

Expand Down Expand Up @@ -81,28 +80,24 @@ class BitmapCroppingWorkerJob(
val resizedBitmap =
BitmapUtils.resizeBitmap(bitmapSampled.bitmap, reqWidth, reqHeight, options)

if (saveUri == null)
onPostExecute(Result(resizedBitmap, bitmapSampled.sampleSize))
else
launch(Dispatchers.IO) {
BitmapUtils.writeBitmapToUri(
context,
resizedBitmap,
saveUri,
saveCompressFormat ?: Bitmap.CompressFormat.JPEG,
saveCompressQuality
)
resizedBitmap.recycle()
onPostExecute(
Result(
saveUri,
bitmapSampled.sampleSize
)
launch(Dispatchers.IO) {
val newUri = BitmapUtils.writeBitmapToUri(
context,
resizedBitmap,
saveCompressFormat,
saveCompressQuality
)
resizedBitmap.recycle()
onPostExecute(
Result(
newUri, // saveUri,
bitmapSampled.sampleSize
)
}
)
}
}
} catch (e: Exception) {
onPostExecute(Result(e, saveUri != null))
onPostExecute(Result(e, false))
}
}
}
Expand Down
90 changes: 47 additions & 43 deletions cropper/src/main/java/com/canhub/cropper/BitmapUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import android.graphics.Matrix
import android.graphics.Rect
import android.graphics.RectF
import android.net.Uri
import android.os.Environment
import android.util.Log
import android.util.Pair
import androidx.core.content.FileProvider
import androidx.exifinterface.media.ExifInterface
import com.canhub.cropper.CropImageView.RequestSizeOptions
import com.canhub.cropper.common.CommonValues
import com.canhub.cropper.common.CommonVersionCheck.isAtLeastQ29
import com.canhub.cropper.utils.getUriForFile
import java.io.Closeable
import java.io.File
import java.io.FileNotFoundException
Expand All @@ -41,8 +41,8 @@ internal object BitmapUtils {

val EMPTY_RECT = Rect()
val EMPTY_RECT_F = RectF()

private const val IMAGE_MAX_BITMAP_DIMENSION = 2048
private const val WRITE_AND_TRUNCATE = "wt"

/**
* Reusable rectangle for general internal usage
Expand Down Expand Up @@ -391,34 +391,12 @@ internal object BitmapUtils {
* Write given bitmap to a temp file. If file already exists no-op as we already saved the file in
* this session. Uses JPEG 95% compression.
*
* @param uri the uri to write the bitmap to, if null
* @return the uri where the image was saved in, either the given uri or new pointing to temp
* file.
*/
fun writeTempStateStoreBitmap(context: Context, bitmap: Bitmap?, uri: Uri?): Uri? {
var tempUri = uri
return try {
var needSave = true
if (tempUri == null) {
// We have this because of a HUAWEI path bug when we use getUriForFile
tempUri = if (isAtLeastQ29()) {
FileProvider.getUriForFile(
context,
context.packageName + CommonValues.authority,
File.createTempFile("aic_state_store_temp", ".jpg", context.cacheDir)
)
} else {
Uri.fromFile(
File.createTempFile("aic_state_store_temp", ".jpg", context.cacheDir)
)
}
} else if (tempUri.path?.let { File(it).exists() } == true) {
needSave = false
}
if (needSave) {
writeBitmapToUri(context, bitmap!!, tempUri, CompressFormat.JPEG, 95)
}
tempUri
fun writeTempStateStoreBitmap(context: Context, bitmap: Bitmap?): Uri? =
try {
writeBitmapToUri(context, bitmap!!, CompressFormat.JPEG, 95)
} catch (e: Exception) {
Log.w(
"AIC",
Expand All @@ -427,7 +405,6 @@ internal object BitmapUtils {
)
null
}
}

/**
* Write the given bitmap to the given uri using the given compression.
Expand All @@ -436,20 +413,50 @@ internal object BitmapUtils {
fun writeBitmapToUri(
context: Context,
bitmap: Bitmap,
uri: Uri?,
compressFormat: CompressFormat?,
compressFormat: CompressFormat,
compressQuality: Int
) {
): Uri? {
val newUri = buildUri(context, compressFormat)
var outputStream: OutputStream? = null
try {
outputStream = context.contentResolver.openOutputStream(uri!!)
outputStream = context.contentResolver.openOutputStream(newUri!!, WRITE_AND_TRUNCATE)

bitmap.compress(compressFormat ?: CompressFormat.JPEG, compressQuality, outputStream)
bitmap.compress(compressFormat, compressQuality, outputStream)
} finally {
closeSafe(outputStream)
}
return newUri
}

private fun buildUri(
context: Context,
compressFormat: CompressFormat
): Uri? =
try {
val ext = when (compressFormat) {
CompressFormat.JPEG -> ".jpg"
CompressFormat.PNG -> ".png"
else -> ".webp"
}
// We have this because of a HUAWEI path bug when we use getUriForFile
if (isAtLeastQ29()) {
try {
val file = File.createTempFile(
"cropped",
ext,
context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
)
getUriForFile(context, file)
} catch (e: Exception) {
Log.e("AIC", "${e.message}")
val file = File.createTempFile("cropped", ext, context.cacheDir)
getUriForFile(context, file)
}
} else Uri.fromFile(File.createTempFile("cropped", ext, context.cacheDir))
} catch (e: IOException) {
throw RuntimeException("Failed to create temp file for output image", e)
}

/**
* Resize the given bitmap to the given width/height by the given option.<br></br>
*/
Expand Down Expand Up @@ -703,8 +710,6 @@ internal object BitmapUtils {
reqHeight: Int,
sampleMulti: Int
): BitmapSampled {
var stream: InputStream? = null
var decoder: BitmapRegionDecoder? = null
try {
val options = BitmapFactory.Options()
options.inSampleSize = (
Expand All @@ -713,23 +718,23 @@ internal object BitmapUtils {
rect.width(), rect.height(), reqWidth, reqHeight
)
)
stream = context.contentResolver.openInputStream(uri)
decoder = BitmapRegionDecoder.newInstance(stream, false)
val stream = context.contentResolver.openInputStream(uri)
val decoder = BitmapRegionDecoder.newInstance(stream!!, false)
do {
try {
return BitmapSampled(decoder.decodeRegion(rect, options), options.inSampleSize)
return BitmapSampled(decoder!!.decodeRegion(rect, options), options.inSampleSize)
} catch (e: OutOfMemoryError) {
options.inSampleSize *= 2
}
} while (options.inSampleSize <= 512)

closeSafe(stream)
decoder?.recycle()
} catch (e: Exception) {
throw RuntimeException(
"Failed to load sampled bitmap: $uri\r\n${e.message}",
e
)
} finally {
closeSafe(stream)
decoder?.recycle()
}
return BitmapSampled(null, 1)
}
Expand Down Expand Up @@ -878,7 +883,6 @@ internal object BitmapUtils {
private val maxTextureSize: Int
get() {
// Safe minimum default size

return try {
// Get EGL Display
val egl = EGLContext.getEGL() as EGL10
Expand Down
Loading