Skip to content
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 2.5.0
### Android
* Added automatic document edge detection for Huawei Mobile Services (HMS) devices using HMS ML Kit Document Skew Correction.
* Fixed a crash/restart loop issue on older GMS+HMS dual-service devices (such as Honor 8X and Huawei P30 Lite) by bypassing GMS and launching the fallback scanner directly on HMS-enabled devices.

## 2.4.0
### General
* Added cross-platform support for native PDF export. Call `CunningDocumentScanner.getPictures(asPdf: true)` to return a list containing a single path pointing to the generated PDF.
Expand Down
11 changes: 11 additions & 0 deletions android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ val agpMajor = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.substringBefore
if (agpMajor < 9) {
apply(plugin = "org.jetbrains.kotlin.android")
}

rootProject.allprojects {
repositories {
maven {
url = uri("https://developer.huawei.com/repo/")
}
}
}

android {
namespace = "biz.cunning.cunning_document_scanner"
compileSdk = 34
Expand Down Expand Up @@ -40,4 +49,6 @@ plugins.withId("org.jetbrains.kotlin.android") {

dependencies {
implementation("com.google.android.gms:play-services-mlkit-document-scanner:16.0.0")
implementation("com.huawei.hms:ml-computer-vision-documentskew:3.11.0.301")
implementation("com.huawei.hms:ml-computer-vision-documentskew-model:3.7.0.301")
}
4 changes: 4 additions & 0 deletions android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="biz.cunning.cunning_document_scanner">
<application android:theme="@style/Theme.AppCompat.NoActionBar">
<meta-data
android:name="com.huawei.hms.ml.DEPENDENCY"
android:value="dsc" />

<activity
android:name=".fallback.DocumentScannerActivity"
android:configChanges="orientation|screenSize"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,31 @@ class CunningDocumentScannerPlugin : FlutterPlugin, MethodCallHandler, ActivityA
}
}

private fun isHmsAvailable(context: android.content.Context): Boolean {
return try {
context.packageManager.getPackageInfo("com.huawei.hwid", 0)
true
} catch (e: android.content.pm.PackageManager.NameNotFoundException) {
false
}
}

private fun startScan(noOfPages: Int, isGalleryImportAllowed: Boolean, scannerMode: Int, asPdf: Boolean) {
if (isHmsAvailable(activity)) {
val intent = createDocumentScanIntent(noOfPages)
try {
ActivityCompat.startActivityForResult(
this.activity,
intent,
START_DOCUMENT_FB_ACTIVITY,
null
)
} catch (e: ActivityNotFoundException) {
pendingResult?.error("ERROR", "FAILED TO START ACTIVITY", null)
}
return
}

val optionsBuilder = GmsDocumentScannerOptions.Builder()
.setGalleryImportAllowed(isGalleryImportAllowed)
.setPageLimit(noOfPages)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ import biz.cunning.cunning_document_scanner.fallback.utils.CameraUtil
import biz.cunning.cunning_document_scanner.fallback.utils.FileUtil
import biz.cunning.cunning_document_scanner.fallback.utils.ImageUtil
import java.io.File
import com.huawei.hms.mlsdk.common.MLFrame
import com.huawei.hms.mlsdk.dsc.MLDocumentSkewCorrectionAnalyzerFactory
import com.huawei.hms.mlsdk.dsc.MLDocumentSkewCorrectionAnalyzerSetting
import android.content.pm.PackageManager
/**
* This class contains the main document scanner code. It opens the camera, lets the user
* take a photo of a document (homework paper, business card, etc.), detects document corners,
Expand Down Expand Up @@ -92,45 +96,34 @@ class DocumentScannerActivity : AppCompatActivity() {
return@CameraUtil
}

// 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@CameraUtil
}

document = Document(originalPhotoPath, photo.width, photo.height, corners)


// 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)

// document corner points are in original image coordinates, so we need to
// scale and move the points to account for blank space (caused by photo and
// photo container having different aspect ratios)
val cornersInImagePreviewCoordinates = corners
.mapOriginalToPreviewImageCoordinates(
imageView.imagePreviewBounds,
imageView.imagePreviewBounds.height() / photo.height
// get document corners asynchronously using HMS if available, or fallback to default corners
detectCorners(photo) { corners ->
document = Document(originalPhotoPath, photo.width, photo.height, corners)

// 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)

// document corner points are in original image coordinates, so we need to
// scale and move the points to account for blank space (caused by photo and
// photo container having different aspect ratios)
val cornersInImagePreviewCoordinates = corners
.mapOriginalToPreviewImageCoordinates(
imageView.imagePreviewBounds,
imageView.imagePreviewBounds.height() / photo.height
)

// display cropper, and allow user to move corners
imageView.setCropper(cornersInImagePreviewCoordinates)
} catch (exception: Exception) {
finishIntentWithError(
"unable get image preview ready: ${exception.message}"
)

// display cropper, and allow user to move corners
imageView.setCropper(cornersInImagePreviewCoordinates)
} catch (exception: Exception) {
finishIntentWithError(
"unable get image preview ready: ${exception.message}"
)
return@CameraUtil
}
}
},
onCancelPhoto = {
Expand Down Expand Up @@ -214,19 +207,74 @@ class DocumentScannerActivity : AppCompatActivity() {
}
}

/**
* Pass in a photo of a document, and get back 4 corner points (top left, top right, bottom
* right, bottom left). This tries to detect document corners, but falls back to photo corners
* with slight margin in case we can't detect document corners.
*
* @param photo the original photo with a rectangular document
* @return a List of 4 OpenCV points (document corners)
*/
private fun getDocumentCorners(photo: Bitmap): List<Point> {
val cornerPoints: List<Point>? = null
private fun isHmsAvailable(): Boolean {
return try {
packageManager.getPackageInfo("com.huawei.hwid", 0)
true
} catch (e: PackageManager.NameNotFoundException) {
false
}
}

private fun detectCorners(photo: Bitmap, onComplete: (Quad) -> Unit) {
if (isHmsAvailable()) {
try {
val setting = MLDocumentSkewCorrectionAnalyzerSetting.Factory().create()
val analyzer = MLDocumentSkewCorrectionAnalyzerFactory.getInstance()
.getDocumentSkewCorrectionAnalyzer(setting)
val frame = MLFrame.fromBitmap(photo)

analyzer.asyncDocumentSkewDetect(frame)
.addOnSuccessListener { result: com.huawei.hms.mlsdk.dsc.MLDocumentSkewDetectResult? ->
if (result != null) {
val lt = result.leftTopPosition
val rt = result.rightTopPosition
val lb = result.leftBottomPosition
val rb = result.rightBottomPosition

if (lt != null && rt != null && lb != null && rb != null) {
val tl = Point(lt.x.toDouble(), lt.y.toDouble())
val tr = Point(rt.x.toDouble(), rt.y.toDouble())
val bl = Point(lb.x.toDouble(), lb.y.toDouble())
val br = Point(rb.x.toDouble(), rb.y.toDouble())
val sortedQuad = sortPoints(listOf(tl, tr, bl, br))
analyzer.stop()
onComplete(sortedQuad)
return@addOnSuccessListener
}
}
analyzer.stop()
onComplete(getDefaultCorners(photo))
}
.addOnFailureListener {
try {
analyzer.stop()
} catch (e: Exception) {}
onComplete(getDefaultCorners(photo))
}
return
} catch (e: Exception) {
// Fallback to default corners if HMS ML Kit fails to initialize/analyze
}
}
onComplete(getDefaultCorners(photo))
}

private fun sortPoints(points: List<Point>): Quad {
val sortedByY = points.sortedBy { it.y }
val topPoints = sortedByY.take(2).sortedBy { it.x }
val bottomPoints = sortedByY.takeLast(2).sortedBy { it.x }

// if cornerPoints is null then default the corners to the photo bounds with a margin
return cornerPoints ?: listOf(
val topLeft = topPoints[0]
val topRight = topPoints[1]
val bottomLeft = bottomPoints[0]
val bottomRight = bottomPoints[1]

return Quad(topLeft, topRight, bottomRight, bottomLeft)
}

private fun getDefaultCorners(photo: Bitmap): Quad {
return Quad(
Point(0.0, 0.0).move(
cropperOffsetWhenCornersNotFound,
cropperOffsetWhenCornersNotFound
Expand All @@ -235,13 +283,13 @@ class DocumentScannerActivity : AppCompatActivity() {
-cropperOffsetWhenCornersNotFound,
cropperOffsetWhenCornersNotFound
),
Point(0.0, photo.height.toDouble()).move(
cropperOffsetWhenCornersNotFound,
-cropperOffsetWhenCornersNotFound
),
Point(photo.width.toDouble(), photo.height.toDouble()).move(
-cropperOffsetWhenCornersNotFound,
-cropperOffsetWhenCornersNotFound
),
Point(0.0, photo.height.toDouble()).move(
cropperOffsetWhenCornersNotFound,
-cropperOffsetWhenCornersNotFound
)
)
}
Expand Down
2 changes: 1 addition & 1 deletion example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ packages:
path: ".."
relative: true
source: path
version: "2.5.0"
version: "2.4.0"
cupertino_icons:
dependency: "direct main"
description:
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: cunning_document_scanner
description: A document scanner plugin for flutter. Scan and crop automatically on iOS and Android.
version: 2.4.0
version: 2.5.0
homepage: https://cunning.biz
repository: https://github.com/jachzen/cunning_document_scanner

Expand Down