From 0c4cdf89b7176c7af6c803ff737b2ee7f0977952 Mon Sep 17 00:00:00 2001 From: Pezcraft Date: Fri, 3 Feb 2023 09:00:34 +0100 Subject: [PATCH 1/3] option to use gallery --- .../documentscanner/DocumentScanner.kt | 8 +- .../DocumentScannerActivity.kt | 125 ++++++++++++++++-- .../constants/DefaultSetting.kt | 1 + .../constants/DocumentScannerExtra.kt | 1 + .../constants/ImageProvider.kt | 9 ++ .../documentscanner/utils/GalleryUtil.kt | 88 ++++++++++++ 6 files changed, 222 insertions(+), 10 deletions(-) create mode 100644 documentscanner/src/main/java/com/websitebeaver/documentscanner/constants/ImageProvider.kt create mode 100644 documentscanner/src/main/java/com/websitebeaver/documentscanner/utils/GalleryUtil.kt diff --git a/documentscanner/src/main/java/com/websitebeaver/documentscanner/DocumentScanner.kt b/documentscanner/src/main/java/com/websitebeaver/documentscanner/DocumentScanner.kt index 4fe5ff9..3896e36 100644 --- a/documentscanner/src/main/java/com/websitebeaver/documentscanner/DocumentScanner.kt +++ b/documentscanner/src/main/java/com/websitebeaver/documentscanner/DocumentScanner.kt @@ -24,6 +24,7 @@ import java.io.File * @param letUserAdjustCrop whether or not the user can change the auto detected document corners * @param maxNumDocuments the maximum number of documents a user can scan at once * @param croppedImageQuality the 0 - 100 quality of the cropped image + * @param imageProvider whether to use the camera or gallery to choose documents * @constructor creates document scanner */ class DocumentScanner( @@ -34,7 +35,8 @@ class DocumentScanner( private var responseType: String? = null, private var letUserAdjustCrop: Boolean? = null, private var maxNumDocuments: Int? = null, - private var croppedImageQuality: Int? = null + private var croppedImageQuality: Int? = null, + private var imageProvider: String? = null ) { init { responseType = responseType ?: DefaultSetting.RESPONSE_TYPE @@ -58,6 +60,10 @@ class DocumentScanner( DocumentScannerExtra.EXTRA_MAX_NUM_DOCUMENTS, maxNumDocuments ) + documentScanIntent.putExtra( + DocumentScannerExtra.EXTRA_IMAGE_PROVIDER, + imageProvider + ) return documentScanIntent } diff --git a/documentscanner/src/main/java/com/websitebeaver/documentscanner/DocumentScannerActivity.kt b/documentscanner/src/main/java/com/websitebeaver/documentscanner/DocumentScannerActivity.kt index c8a661b..ab06245 100644 --- a/documentscanner/src/main/java/com/websitebeaver/documentscanner/DocumentScannerActivity.kt +++ b/documentscanner/src/main/java/com/websitebeaver/documentscanner/DocumentScannerActivity.kt @@ -10,6 +10,7 @@ import android.widget.ImageButton import androidx.appcompat.app.AppCompatActivity import com.websitebeaver.documentscanner.constants.DefaultSetting import com.websitebeaver.documentscanner.constants.DocumentScannerExtra +import com.websitebeaver.documentscanner.constants.ImageProvider import com.websitebeaver.documentscanner.extensions.move import com.websitebeaver.documentscanner.extensions.onClick import com.websitebeaver.documentscanner.extensions.saveToFile @@ -20,6 +21,7 @@ import com.websitebeaver.documentscanner.models.Quad import com.websitebeaver.documentscanner.ui.ImageCropView import com.websitebeaver.documentscanner.utils.CameraUtil import com.websitebeaver.documentscanner.utils.FileUtil +import com.websitebeaver.documentscanner.utils.GalleryUtil import com.websitebeaver.documentscanner.utils.ImageUtil import java.io.File import org.opencv.android.OpenCVLoader @@ -49,6 +51,11 @@ class DocumentScannerActivity : AppCompatActivity() { */ private var croppedImageQuality = DefaultSetting.CROPPED_IMAGE_QUALITY + /** + * @property imageProvider whether to use the camera or gallery to choose documents + */ + private var imageProvider = DefaultSetting.IMAGE_PROVIDER + /** * @property cropperOffsetWhenCornersNotFound if we can't find document corners, we set * corners to image size with a slight margin @@ -144,6 +151,75 @@ class DocumentScannerActivity : AppCompatActivity() { } ) + /** + * @property galleryUtil gets called with photo file path once user chooses image, or + * exits gallery + */ + private val galleryUtil = GalleryUtil( + this, + onGallerySuccess = { + // user chooses photo + originalPhotoPath = it + isLatestPhotoCaptureSuccessful = true + + // if maxNumDocuments is 3 and this is the 3rd photo, hide the new photo button since + // we reach the allowed limit + if (documents.size == maxNumDocuments - 1) { + val newPhotoButton: ImageButton = findViewById(R.id.new_photo_button) + newPhotoButton.isClickable = false + newPhotoButton.visibility = View.INVISIBLE + } + + // get bitmap from photo file path + val photo: Bitmap = ImageUtil().getImageFromFilePath(originalPhotoPath) + + // get document corners by detecting them, or falling back to photo corners with + // slight margin if we can't find the corners + val corners = try { + val (topLeft, topRight, bottomLeft, bottomRight) = getDocumentCorners(photo) + Quad(topLeft, topRight, bottomRight, bottomLeft) + } catch (exception: Exception) { + finishIntentWithError( + "unable to get document corners: ${exception.message}" + ) + return@GalleryUtil + } + + if (letUserAdjustCrop) { + // user is allowed to move corners to make corrections + try { + // set preview image height based off of photo dimensions + imageView.setImagePreviewBounds(photo, screenWidth, screenHeight) + + // display original photo, so user can adjust detected corners + imageView.setImage(photo) + + // display cropper, and allow user to move corners + imageView.setCropper(corners) + } catch (exception: Exception) { + finishIntentWithError( + "unable get image preview ready: ${exception.message}" + ) + return@GalleryUtil + } + } else { + // user isn't allowed to move corners, so accept automatically detected corners + documents.add(Document(originalPhotoPath, corners)) + + // create cropped document image, and return file path to complete document scan + cropDocumentAndFinishIntent() + } + }, + onCancelGallery = { + // user exits gallery + // complete document scan if this is the first document since we can't go to crop view + // until user takes at least 1 photo + if (documents.isEmpty()) { + onClickCancel() + } + } + ) + /** * @property imageView container with original photo and cropper */ @@ -208,6 +284,14 @@ class DocumentScannerActivity : AppCompatActivity() { } croppedImageQuality = it } + + // validate imageProvider option, and update default if user sets it + intent.extras?.get(DocumentScannerExtra.EXTRA_IMAGE_PROVIDER)?.let { + if (!arrayOf(ImageProvider.CAMERA, ImageProvider.GALLERY).contains(it.toString())) { + throw Exception("${DocumentScannerExtra.EXTRA_LET_USER_ADJUST_CROP} must be either camera or gallery") + } + imageProvider = it as String + } } catch (exception: Exception) { finishIntentWithError( "invalid extra: ${exception.message}" @@ -227,13 +311,19 @@ class DocumentScannerActivity : AppCompatActivity() { completeDocumentScanButton.onClick { onClickDone() } retakePhotoButton.onClick { onClickRetake() } - // open camera, so user can snap document photo - try { - openCamera() - } catch (exception: Exception) { - finishIntentWithError( - "error opening camera: ${exception.message}" - ) + // open camera or gallery, so user can snap or choose document photo + if (imageProvider == ImageProvider.CAMERA) { + try { + openCamera() + } catch (exception: Exception) { + finishIntentWithError("error opening camera: ${exception.message}") + } + } else if (imageProvider == ImageProvider.GALLERY) { + try { + openGallery() + } catch (exception: Exception) { + finishIntentWithError("error opening gallery: ${exception.message}") + } } } @@ -294,6 +384,15 @@ class DocumentScannerActivity : AppCompatActivity() { cameraUtil.openCamera(documents.size) } + /** + * Set isLatestPhotoCaptureSuccessful to false, and open the gallery. If the user chooses + * a photo successfully isLatestPhotoCaptureSuccessful gets set to true. + */ + private fun openGallery() { + isLatestPhotoCaptureSuccessful = false + galleryUtil.openGallery(documents.size) + } + /** * Once user accepts by pressing check button, or by pressing add new document button, add * original photo path and 4 document corners to documents list. If user isn't allowed to @@ -312,7 +411,11 @@ class DocumentScannerActivity : AppCompatActivity() { */ private fun onClickNew() { addSelectedCornersAndOriginalPhotoPathToDocuments() - openCamera() + if (imageProvider == ImageProvider.CAMERA) { + openCamera() + } else if (imageProvider == ImageProvider.GALLERY) { + openGallery() + } } /** @@ -329,7 +432,11 @@ class DocumentScannerActivity : AppCompatActivity() { * case the original document photo isn't good, and they need to take it again. */ private fun onClickRetake() { - openCamera() + if (imageProvider == ImageProvider.CAMERA) { + openCamera() + } else if (imageProvider == ImageProvider.GALLERY) { + openGallery() + } } /** diff --git a/documentscanner/src/main/java/com/websitebeaver/documentscanner/constants/DefaultSetting.kt b/documentscanner/src/main/java/com/websitebeaver/documentscanner/constants/DefaultSetting.kt index e22938f..b1680ec 100644 --- a/documentscanner/src/main/java/com/websitebeaver/documentscanner/constants/DefaultSetting.kt +++ b/documentscanner/src/main/java/com/websitebeaver/documentscanner/constants/DefaultSetting.kt @@ -9,5 +9,6 @@ class DefaultSetting { const val LET_USER_ADJUST_CROP = true const val MAX_NUM_DOCUMENTS = 24 const val RESPONSE_TYPE = ResponseType.IMAGE_FILE_PATH + const val IMAGE_PROVIDER = ImageProvider.CAMERA } } \ No newline at end of file diff --git a/documentscanner/src/main/java/com/websitebeaver/documentscanner/constants/DocumentScannerExtra.kt b/documentscanner/src/main/java/com/websitebeaver/documentscanner/constants/DocumentScannerExtra.kt index 442d455..152a6fb 100644 --- a/documentscanner/src/main/java/com/websitebeaver/documentscanner/constants/DocumentScannerExtra.kt +++ b/documentscanner/src/main/java/com/websitebeaver/documentscanner/constants/DocumentScannerExtra.kt @@ -8,5 +8,6 @@ class DocumentScannerExtra { const val EXTRA_CROPPED_IMAGE_QUALITY = "croppedImageQuality" const val EXTRA_LET_USER_ADJUST_CROP = "letUserAdjustCrop" const val EXTRA_MAX_NUM_DOCUMENTS = "maxNumDocuments" + const val EXTRA_IMAGE_PROVIDER = "imageProvider" } } \ No newline at end of file diff --git a/documentscanner/src/main/java/com/websitebeaver/documentscanner/constants/ImageProvider.kt b/documentscanner/src/main/java/com/websitebeaver/documentscanner/constants/ImageProvider.kt new file mode 100644 index 0000000..cb2cd5c --- /dev/null +++ b/documentscanner/src/main/java/com/websitebeaver/documentscanner/constants/ImageProvider.kt @@ -0,0 +1,9 @@ +package com.websitebeaver.documentscanner.constants + +/** constants that represent all image provider */ +class ImageProvider { + companion object { + const val CAMERA = "camera" + const val GALLERY = "gallery" + } +} \ No newline at end of file diff --git a/documentscanner/src/main/java/com/websitebeaver/documentscanner/utils/GalleryUtil.kt b/documentscanner/src/main/java/com/websitebeaver/documentscanner/utils/GalleryUtil.kt new file mode 100644 index 0000000..1f71780 --- /dev/null +++ b/documentscanner/src/main/java/com/websitebeaver/documentscanner/utils/GalleryUtil.kt @@ -0,0 +1,88 @@ +package com.websitebeaver.documentscanner.utils + +import android.app.Activity +import android.content.Intent +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.net.Uri +import androidx.activity.ComponentActivity +import androidx.activity.result.ActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import com.websitebeaver.documentscanner.extensions.saveToFile +import java.io.IOException +import java.io.File + +/** + * This class contains a helper function for opening the gallery. + * + * @param activity current activity + * @param onGallerySuccess gets called with photo file path when photo is ready + * @param onCancelGallery gets called when user cancels out of gallery + * @constructor creates gallery util + */ +class GalleryUtil( + private val activity: ComponentActivity, + private val onGallerySuccess: (photoFilePath: String) -> Unit, + private val onCancelGallery: () -> Unit +) { + /** @property photoFilePath the photo file path */ + private lateinit var photoFilePath: String + + /** @property photoFile the photo file */ + private lateinit var photoFile: File + + /** @property startForResult used to launch gallery */ + private val startForResult = + activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + result: ActivityResult -> + when (result.resultCode) { + Activity.RESULT_OK -> { + val data = result.data + val uri = data?.data!! + + // create bitmap from image selection and save it to file + ImageUtil().readBitmapFromFileUriString( + uri.toString(), + activity.contentResolver + ).saveToFile(photoFile, 100) + + // send back photo file path on success + onGallerySuccess(photoFilePath) + } + Activity.RESULT_CANCELED -> { + onCancelGallery() + } + } + } + + /** + * open the gallery by launching an open document intent + * + * @param pageNumber the current document page number + */ + @Throws(IOException::class) + fun openGallery(pageNumber: Int) { + // create intent to open gallery + val openGalleryIntent = getGalleryIntent() + + // create new file for photo + photoFile = FileUtil().createImageFile(activity, 1) + + // store the photo file path, and send it back once the photo is saved + photoFilePath = photoFile.absolutePath + + // open gallery + startForResult.launch(openGalleryIntent) + } + + private fun getGalleryIntent(): Intent { + val openGalleryIntent = Intent(Intent.ACTION_OPEN_DOCUMENT) + openGalleryIntent.type = "image/*" + openGalleryIntent.addCategory(Intent.CATEGORY_OPENABLE) + openGalleryIntent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) + openGalleryIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + openGalleryIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + return openGalleryIntent + } + +} \ No newline at end of file From d7dec381c02b5432fa1226b13b4b3f77d4396a38 Mon Sep 17 00:00:00 2001 From: Pezcraft Date: Mon, 6 Feb 2023 01:27:03 +0100 Subject: [PATCH 2/3] fixed pagenumber mistake --- .../documentscanner/utils/GalleryUtil.kt | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/documentscanner/src/main/java/com/websitebeaver/documentscanner/utils/GalleryUtil.kt b/documentscanner/src/main/java/com/websitebeaver/documentscanner/utils/GalleryUtil.kt index 1f71780..1d90210 100644 --- a/documentscanner/src/main/java/com/websitebeaver/documentscanner/utils/GalleryUtil.kt +++ b/documentscanner/src/main/java/com/websitebeaver/documentscanner/utils/GalleryUtil.kt @@ -66,7 +66,7 @@ class GalleryUtil( val openGalleryIntent = getGalleryIntent() // create new file for photo - photoFile = FileUtil().createImageFile(activity, 1) + photoFile = FileUtil().createImageFile(activity, pageNumber) // store the photo file path, and send it back once the photo is saved photoFilePath = photoFile.absolutePath @@ -75,14 +75,12 @@ class GalleryUtil( startForResult.launch(openGalleryIntent) } - private fun getGalleryIntent(): Intent { - val openGalleryIntent = Intent(Intent.ACTION_OPEN_DOCUMENT) - openGalleryIntent.type = "image/*" - openGalleryIntent.addCategory(Intent.CATEGORY_OPENABLE) - openGalleryIntent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) - openGalleryIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - openGalleryIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - return openGalleryIntent + private fun getGalleryIntent(): Intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + type = "image/*" + addCategory(Intent.CATEGORY_OPENABLE) + addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) } } \ No newline at end of file From 903d079d1555729643c82feac1236cbb1b216901 Mon Sep 17 00:00:00 2001 From: Pezcraft Date: Thu, 16 Feb 2023 23:54:49 +0100 Subject: [PATCH 3/3] deleting photo when canceled --- .../java/com/websitebeaver/documentscanner/utils/GalleryUtil.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/documentscanner/src/main/java/com/websitebeaver/documentscanner/utils/GalleryUtil.kt b/documentscanner/src/main/java/com/websitebeaver/documentscanner/utils/GalleryUtil.kt index 1d90210..35a97c5 100644 --- a/documentscanner/src/main/java/com/websitebeaver/documentscanner/utils/GalleryUtil.kt +++ b/documentscanner/src/main/java/com/websitebeaver/documentscanner/utils/GalleryUtil.kt @@ -50,6 +50,8 @@ class GalleryUtil( onGallerySuccess(photoFilePath) } Activity.RESULT_CANCELED -> { + // delete the photo since the user didn't finish choosing the photo + File(photoFilePath).delete() onCancelGallery() } }