Skip to content

Instantly share code, notes, and snippets.

@mrsasha
Last active February 24, 2020 15:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mrsasha/5fa9eae3f7a016554c4b297df4c8e2c4 to your computer and use it in GitHub Desktop.
Save mrsasha/5fa9eae3f7a016554c4b297df4c8e2c4 to your computer and use it in GitHub Desktop.
Image passing
<manifest>
<application>
<activity
android:name="com.yalantis.ucrop.UCropActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
</application>
</manifest>
dependencies {
implementation "com.github.yalantis:ucrop:2.2.4"
implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
implementation "io.reactivex.rxjava2:rxjava:2.2.17"
implementation "androidx.exifinterface:exifinterface:1.1.0"
}
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.net.Uri
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.FileProvider
import com.yalantis.ucrop.UCrop
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposables
import java.io.File
import java.io.IOException
const val GALLERY_IMAGE_URI = "com.mi.great.GALLERY_URI"
private const val PICK_IMAGE_REQUEST = 689
private const val DEFAULT_QUALITY_GALLERY = 100
private const val DEFAULT_QUALITY_CROP = 80
private const val IMAGE_RATIO_X = 4f
private const val IMAGE_RATIO_Y = 3f
private const val IMAGE_SIZE_MAX = 500
class GalleryPicker : AppCompatActivity() {
companion object {
fun getIntent(context: Context): Intent = Intent(context, GalleryPicker::class.java)
}
private val imageSaver: ImageSaver = InternalImageSaver(this)
private var bitmapCreationSubscription = Disposables.disposed()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val intent = Intent()
intent.type = "image/*"
intent.action = Intent.ACTION_GET_CONTENT
if (savedInstanceState == null) {
startActivityForResult(Intent.createChooser(intent, getString(R.string.image_chooser)), PICK_IMAGE_REQUEST)
}
}
override fun onStop() {
super.onStop()
bitmapCreationSubscription.dispose()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
super.onActivityResult(requestCode, resultCode, intent)
if (requestCode == PICK_IMAGE_REQUEST && resultCode == RESULT_OK && intent != null) {
passGalleryImageForCropping(intent)
} else if (requestCode == UCrop.REQUEST_CROP && intent != null) {
returnCroppingResult(resultCode, intent)
} else {
showError(Throwable("Expected result from gallery or cropping!"))
finish()
}
}
private fun passGalleryImageForCropping(intent: Intent) {
Timber.d("Received image from gallery")
if (!bitmapCreationSubscription.isDisposed) {
bitmapCreationSubscription.dispose()
}
bitmapCreationSubscription = Single.create<Bitmap> { singleEmitter ->
try {
singleEmitter.onSuccess(contentResolver.getBitmapFromUri(intent.data!!))
} catch (exception: IOException) {
singleEmitter.onError(exception)
}
}.flatMap { bitmap -> imageSaver.saveImage(GALLERY_IMAGE_NAME, bitmap, DEFAULT_QUALITY_GALLERY) }
.subscribeOn(AndroidSchedulers.mainThread())
.map { stringUri -> Uri.fromFile(File(stringUri)) }
.subscribe(
{ fileUri -> openCropping(fileUri) },
{ throwable ->
showError(throwable)
finish()
})
}
private fun openCropping(galleryImageUri: Uri) {
Timber.d("Cropping image: $galleryImageUri")
val croppedImageUri = Uri.fromFile(imageSaver.createImageFile(CROPPED_IMAGE_NAME))
val options = UCrop.Options()
options.setCompressionQuality(DEFAULT_QUALITY_CROP)
options.setCompressionFormat(Bitmap.CompressFormat.JPEG)
UCrop.of(galleryImageUri, croppedImageUri)
.withAspectRatio(IMAGE_RATIO_X, IMAGE_RATIO_Y)
.withMaxResultSize(IMAGE_SIZE_MAX, IMAGE_SIZE_MAX)
.withOptions(options)
.start(this)
}
private fun returnCroppingResult(resultCode: Int, intent: Intent) {
if (resultCode == RESULT_OK) {
val resultUri = UCrop.getOutput(intent)
Timber.d("Got result from cropping: $resultUri")
val finalUri = FileProvider.getUriForFile(applicationContext, "$packageName.provider", File(resultUri?.path))
val resultIntent = Intent(GALLERY_IMAGE_URI, finalUri)
resultIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
setResult(Activity.RESULT_OK, resultIntent)
} else if (resultCode == UCrop.RESULT_ERROR) {
showError(UCrop.getError(intent))
}
finish()
}
private fun showError(error: Throwable?) {
Timber.e(error, "Failed to get image from gallery/cropping")
Toast.makeText(this, getString(R.string.error_loading_image), Toast.LENGTH_LONG).show()
}
}
import android.content.Context
import android.graphics.Bitmap
import io.reactivex.Single
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
const val GALLERY_IMAGE_NAME = "galleryImage"
const val CROPPED_IMAGE_NAME = "croppedImage"
private const val JPG = ".jpg"
interface ImageSaver {
fun saveImage(name: String, bitmap: Bitmap, quality: Int): Single<String>
fun createImageFile(name: String): File
}
class InternalImageSaver(val context: Context) : ImageSaver {
private val internalImagesFiles: File = File(context.filesDir, "images")
init {
internalImagesFiles.mkdirs()
}
override fun saveImage(name: String, bitmap: Bitmap, quality: Int): Single<String> {
return Single.create { singleEmitter ->
try {
val imageFile = createImageFile(name)
val fileOutputStream = FileOutputStream(imageFile)
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, fileOutputStream)
fileOutputStream.flush()
fileOutputStream.close()
singleEmitter.onSuccess(imageFile.path)
} catch (e: IOException) {
singleEmitter.onError(e)
}
}
}
@Throws(IOException::class)
override fun createImageFile(name: String): File {
val imageFile = File(internalImagesFiles, name + JPG)
imageFile.createNewFile()
return imageFile
}
}
<!-- put this in /res/xml -->
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="external_files"
path="." />
<files-path
name="files"
path="." />
</paths>
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
import android.graphics.drawable.Drawable
import android.net.Uri
import androidx.exifinterface.media.ExifInterface
@Throws(IOException::class)
fun ContentResolver.getBitmapFromUri(uri: Uri): Bitmap {
val angle = getRotationFromExif(uri, this)
val parcelFileDescriptor = this.openFileDescriptor(uri, "r")
checkNotNull(parcelFileDescriptor) { "Unable to get file descriptor for uri $uri" }
parcelFileDescriptor?.let {
val fileDescriptor = parcelFileDescriptor.fileDescriptor
val bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor, null, BitmapFactory.Options())
parcelFileDescriptor.close()
val matrix = Matrix()
matrix.postRotate(angle.toFloat())
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, false)
}
}
@Suppress("ComplexMethod", "MagicNumber")
private fun getRotationFromExif(uri: Uri, contentResolver: ContentResolver): Int {
var inputStream: InputStream? = null
try {
inputStream = contentResolver.openInputStream(uri)
return if (inputStream == null) {
0
} else {
val exif = ExifInterface(inputStream)
when (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1)) {
1 -> 0
3 -> 180
6 -> 90
8 -> 270
else -> 0
}
}
} catch (e: IOException) {
return 0
} finally {
if (inputStream != null) {
try {
inputStream.close()
} catch (ignored: IOException) {
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment