├── app
├── .gitignore
├── libs
│ └── main.jar
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── 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
│ │ │ │ ├── attrs.xml
│ │ │ │ ├── button_style.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── strings.xml
│ │ │ │ └── themes.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── drawable
│ │ │ │ ├── primary_button.xml
│ │ │ │ └── ic_launcher_background.xml
│ │ │ ├── layout
│ │ │ │ └── activity_main.xml
│ │ │ ├── values-night
│ │ │ │ └── themes.xml
│ │ │ └── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── android
│ │ │ └── mrzcardreader
│ │ │ └── MainActivity.kt
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── android
│ │ │ └── mrzcardreader
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── android
│ │ └── mrzcardreader
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle
├── mrzcardscanner
├── .gitignore
├── consumer-rules.pro
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── res.xml
│ │ │ │ ├── attrs.xml
│ │ │ │ ├── button_style.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── strings.xml
│ │ │ │ └── themes.xml
│ │ │ ├── drawable
│ │ │ │ ├── primary_button.xml
│ │ │ │ └── white_btn_background.xml
│ │ │ ├── values-night
│ │ │ │ └── themes.xml
│ │ │ └── layout
│ │ │ │ ├── activity_mrz.xml
│ │ │ │ └── mrz_card_view_layout.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── android
│ │ │ │ └── mrzcardreader
│ │ │ │ ├── CardResult.kt
│ │ │ │ ├── camera
│ │ │ │ ├── MRZResponse.kt
│ │ │ │ ├── models
│ │ │ │ │ └── IdData.kt
│ │ │ │ ├── overlay
│ │ │ │ │ ├── TextGraphic.kt
│ │ │ │ │ └── TextOverlay.kt
│ │ │ │ ├── MrzCameraManager.kt
│ │ │ │ └── ImageAnalyzer.kt
│ │ │ │ ├── CardDetailResponse.kt
│ │ │ │ ├── cardconnectors
│ │ │ │ └── CardConnector.kt
│ │ │ │ ├── MrzBuilder.kt
│ │ │ │ └── MainMrzActivity.kt
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── android
│ │ │ └── mrzcardscanner
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── android
│ │ └── mrzcardscanner
│ │ └── ExampleInstrumentedTest.kt
├── libs
│ └── main.jar
├── proguard-rules.pro
└── build.gradle
├── .idea
├── .gitignore
├── compiler.xml
├── vcs.xml
├── gradle.xml
└── misc.xml
├── art
└── mrz.png
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── settings.gradle
├── gradle.properties
├── readme.md
├── gradlew.bat
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/mrzcardscanner/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/mrzcardscanner/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/art/mrz.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AmosKorir/MRZ-Scanner/HEAD/art/mrz.png
--------------------------------------------------------------------------------
/app/libs/main.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AmosKorir/MRZ-Scanner/HEAD/app/libs/main.jar
--------------------------------------------------------------------------------
/mrzcardscanner/src/main/res/values/res.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/mrzcardscanner/libs/main.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AmosKorir/MRZ-Scanner/HEAD/mrzcardscanner/libs/main.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AmosKorir/MRZ-Scanner/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AmosKorir/MRZ-Scanner/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AmosKorir/MRZ-Scanner/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AmosKorir/MRZ-Scanner/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AmosKorir/MRZ-Scanner/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AmosKorir/MRZ-Scanner/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AmosKorir/MRZ-Scanner/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AmosKorir/MRZ-Scanner/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AmosKorir/MRZ-Scanner/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AmosKorir/MRZ-Scanner/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AmosKorir/MRZ-Scanner/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/mrzcardscanner/src/main/java/com/android/mrzcardreader/CardResult.kt:
--------------------------------------------------------------------------------
1 | package com.android.mrzcardreader
2 |
3 | import com.android.mrzcardreader.camera.models.IdData
4 |
5 | interface CardResult {
6 | fun cardDetails(card:IdData)
7 | }
--------------------------------------------------------------------------------
/mrzcardscanner/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Apr 29 11:51:02 EAT 2022
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/mrzcardscanner/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/mrzcardscanner/src/main/java/com/android/mrzcardreader/camera/MRZResponse.kt:
--------------------------------------------------------------------------------
1 | package com.android.mrzcardreader.camera
2 |
3 | import com.android.mrzcardreader.camera.models.IdData
4 |
5 |
6 | interface MRZResponse{
7 |
8 | fun cardResponse(card: IdData)
9 |
10 | fun cardReadResponse()
11 |
12 | fun failedToRead()
13 | }
--------------------------------------------------------------------------------
/mrzcardscanner/src/main/java/com/android/mrzcardreader/CardDetailResponse.kt:
--------------------------------------------------------------------------------
1 | package com.android.mrzcardreader
2 |
3 | import com.android.mrzcardreader.camera.models.IdData
4 |
5 | interface CardDetailResponse {
6 | fun onCardRead(card: IdData)
7 |
8 | fun onCardReadingCancelled()
9 |
10 | fun onFailed(e: Exception)
11 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/primary_button.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
--------------------------------------------------------------------------------
/mrzcardscanner/src/main/res/drawable/primary_button.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
--------------------------------------------------------------------------------
/mrzcardscanner/src/main/res/drawable/white_btn_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/values/button_style.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/mrzcardscanner/src/main/res/values/button_style.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #0288D1
4 | #039BE5
5 | #01579B
6 | #FF03DAC5
7 | #FF018786
8 | #61000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 | rootProject.name = "MRZCardReader"
16 | include ':app'
17 | include ':mrzcardscanner'
18 |
--------------------------------------------------------------------------------
/mrzcardscanner/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #0288D1
4 | #039BE5
5 | #01579B
6 | #FF03DAC5
7 | #FF018786
8 | #61000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/mrzcardscanner/src/main/java/com/android/mrzcardreader/camera/models/IdData.kt:
--------------------------------------------------------------------------------
1 | package com.android.mrzcardreader.camera.models
2 |
3 | import java.io.Serializable
4 |
5 | data class IdData(
6 | var firstName: String,
7 | var middleName: String,
8 | var lastName: String,
9 | var gender:String,
10 | var documentNo:String,
11 | var dateOfBirth:String,
12 | var idNo: String,
13 | var nationality:String,
14 | ):Serializable
--------------------------------------------------------------------------------
/app/src/test/java/com/android/mrzcardreader/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.android.mrzcardreader
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 | }
--------------------------------------------------------------------------------
/mrzcardscanner/src/test/java/com/android/mrzcardscanner/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.android.mrzcardscanner
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 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | MRZCardReader
3 | Confirm
4 | Doc No:
5 | ID:
6 | DOB:
7 | Gender:
8 | Last Name:
9 | Middle Name:
10 | First Name:
11 | Rescan
12 |
--------------------------------------------------------------------------------
/mrzcardscanner/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | MRZCardReader
3 | Confirm
4 | Doc No:
5 | ID:
6 | DOB:
7 | Gender:
8 | Last Name:
9 | Middle Name:
10 | First Name:
11 | Rescan
12 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
17 |
18 |
--------------------------------------------------------------------------------
/app/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
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/android/mrzcardreader/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.android.mrzcardreader
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.mrzcardreader", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/mrzcardscanner/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
--------------------------------------------------------------------------------
/mrzcardscanner/src/androidTest/java/com/android/mrzcardscanner/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.android.mrzcardscanner
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.mrzcardscanner.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/mrzcardscanner/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/mrzcardscanner/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
16 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'org.jetbrains.kotlin.android'
4 | }
5 |
6 | android {
7 | compileSdk 31
8 |
9 | defaultConfig {
10 | applicationId "com.android.mrzcardreader"
11 | minSdk 21
12 | targetSdk 31
13 | versionCode 1
14 | versionName "1.0"
15 |
16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
17 | }
18 |
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 | compileOptions {
26 | sourceCompatibility JavaVersion.VERSION_1_8
27 | targetCompatibility JavaVersion.VERSION_1_8
28 | }
29 | buildFeatures {
30 | viewBinding true
31 | }
32 | kotlinOptions {
33 | jvmTarget = '1.8'
34 | }
35 | }
36 |
37 | dependencies {
38 |
39 | implementation 'androidx.core:core-ktx:1.7.0'
40 | implementation 'androidx.appcompat:appcompat:1.4.1'
41 | implementation 'com.google.android.material:material:1.5.0'
42 | implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
43 | implementation project(path: ':mrzcardscanner')
44 | testImplementation 'junit:junit:4.13.2'
45 | androidTestImplementation 'androidx.test.ext:junit:1.1.3'
46 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
47 |
48 | }
--------------------------------------------------------------------------------
/mrzcardscanner/src/main/java/com/android/mrzcardreader/cardconnectors/CardConnector.kt:
--------------------------------------------------------------------------------
1 | package com.android.mrzcardreader.cardconnectors
2 | import android.util.Log
3 | import com.android.mrzcardreader.camera.MRZResponse
4 | import com.android.mrzcardreader.camera.models.IdData
5 | import com.google.mlkit.vision.text.Text
6 | import ocr.MrzReader
7 |
8 |
9 | object CardConnector {
10 | val mrzReader = MrzReader()
11 |
12 | fun onDetailsCaptured(details: MutableList, mrzResponse: MRZResponse) {
13 | val mrzString = cleanMRZ(details.last().text)
14 |
15 | val cardType = mrzReader.getCardType(mrzString)
16 |
17 | val isValidCard = mrzReader.isValidCard(cardType, mrzString)
18 |
19 | if (!isValidCard) {
20 |
21 | return
22 | }else{
23 | val readCard = mrzReader.readDocument(cardType, mrzString)
24 |
25 | val card = IdData(
26 | readCard.firstName,
27 | readCard.secondName,
28 | readCard.lastName,
29 | readCard.gender,
30 | readCard.documentNumber,
31 | readCard.dateOfBirth,
32 | readCard.id,
33 | readCard.country,
34 | )
35 | mrzResponse.cardResponse(card)
36 | }
37 |
38 |
39 | }
40 |
41 | //TODO implement clearing
42 | fun clear(){
43 | // mrzReader
44 | }
45 |
46 | private fun cleanMRZ(details_: String): String {
47 | var details = details_.replace(" ", "")
48 | details = details.replace("«", "<")
49 | details = details.replace("e", "<")
50 | details = details.replace("€", "<")
51 | return details
52 | }
53 | }
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # MRZ Scannner
2 |
3 |
4 | # 🚧 ___🚧 ___🚧 __🚧 - Ongoing Construction
5 |
6 | Android Library for scanning document MRZ documents.
7 |
8 | **Current Supported documents**
9 |
10 | 1. Nation ID
11 | 2. Passport ->development
12 |
13 |
14 |
15 |
16 |
17 | |
18 |
19 |
20 |
21 |
22 | **Installation**
23 |
24 | Add the Following to your gradle file.
25 |
26 | ```java
27 | //add jitpack
28 | allprojects {
29 | repositories {
30 | ...
31 | maven { url 'https://jitpack.io' }
32 | }
33 | }
34 |
35 | //add dependency, check on the lastest release tag
36 | implementation 'com.github.AmosKorir:mrz_scanner:Tag'
37 |
38 | ```
39 |
40 | How to start reading
41 |
42 | ```kotlin
43 | MrzBuilder(this, this.activityResultRegistry)
44 | .setOnCardDetailsResponse(object : CardDetailResponse {
45 | override fun onCardRead(card: IdData) {
46 | Toast.makeText(this@MainActivity, card.toString(), Toast.LENGTH_SHORT).show()
47 |
48 | }
49 |
50 | override fun onCardReadingCancelled() {
51 | Toast.makeText(this@MainActivity, "Cancelled", Toast.LENGTH_SHORT).show()
52 | }
53 |
54 | override fun onFailed(e: Exception) {
55 | Toast.makeText(this@MainActivity, e.toString(), Toast.LENGTH_SHORT).show()
56 | }
57 |
58 | })
59 | .start()
60 | ```
61 |
62 |
63 |
64 | **⚙️ ⚙️ ⚙️ ⚙️** MRZ reading Algorithms
65 |
66 | Depend on a jar that is currently maintained in a separate private repository
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/mrzcardscanner/src/main/java/com/android/mrzcardreader/MrzBuilder.kt:
--------------------------------------------------------------------------------
1 | package com.android.mrzcardreader
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.content.Intent
6 | import androidx.activity.result.ActivityResultLauncher
7 | import androidx.activity.result.ActivityResultRegistry
8 | import androidx.activity.result.contract.ActivityResultContracts
9 | import com.android.mrzcardreader.camera.models.IdData
10 |
11 | class MrzBuilder(
12 | private val context: Context,
13 | registry: ActivityResultRegistry
14 | ) {
15 | private var cardResponse: CardDetailResponse? = null
16 |
17 | private var content: ActivityResultLauncher =
18 | registry.register("card", ActivityResultContracts.StartActivityForResult()) { result ->
19 | if (result.resultCode == 505) {
20 | val intent: Intent? = result.data
21 | intent?.let {
22 | try {
23 | val card = it.extras?.getSerializable("card") as IdData
24 | cardResponse?.onCardRead(card)
25 | } catch (e: Exception) {
26 | cardResponse?.onFailed(e)
27 | }
28 |
29 | }
30 | }else if (result.resultCode == Activity.RESULT_CANCELED){
31 | cardResponse?.onCardReadingCancelled()
32 | }
33 | }
34 |
35 | fun setOnCardDetailsResponse(cardDetailResponse: CardDetailResponse) = apply {
36 | this.cardResponse = cardDetailResponse
37 | }
38 |
39 | fun start() = apply {
40 | if(cardResponse == null){
41 | throw (java.lang.Exception("Please set card response"))
42 | }
43 | content.launch(
44 | Intent(
45 | context,
46 | MainMrzActivity::class.java
47 | )
48 | )
49 |
50 | }
51 |
52 |
53 | }
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/mrzcardscanner/src/main/java/com/android/mrzcardreader/camera/overlay/TextGraphic.kt:
--------------------------------------------------------------------------------
1 | package com.android.mrzcardreader.camera.overlay
2 |
3 | import android.content.Context
4 | import android.graphics.*
5 | import com.google.mlkit.vision.text.Text
6 | import kotlin.math.ceil
7 |
8 | class TextGraphic(
9 | private val context: Context,
10 | private val imageRect: Rect,
11 | private val textBlock: Text.TextBlock,
12 | private val textOverlay: TextOverlay
13 | ) {
14 |
15 | private val painter = Paint()
16 |
17 | fun draw(canvas: Canvas?) {
18 | canvas?.let { _canvas ->
19 | painter.color = Color.GREEN
20 | painter.strokeWidth = 4f
21 | painter.style = Paint.Style.STROKE
22 | val textRect = textBlock.boundingBox
23 |
24 | _canvas.drawRect(
25 | calculateTextRect(
26 | textRect!!
27 | ), painter
28 | )
29 | }
30 | }
31 |
32 | fun Float.toDp(): Float {
33 | val density = context.resources.displayMetrics.density
34 | return this / density
35 | }
36 |
37 | fun isValidTextBlock(): Boolean {
38 | textBlock.boundingBox?.let { rect ->
39 |
40 | val text = calculateTextRect(
41 | rect
42 | )
43 | if (text.top < textOverlay.frameTop) {
44 | return false
45 | }
46 | if (text.bottom > textOverlay.frameBottom) {
47 | return false
48 | }
49 | return true
50 |
51 | } ?: return false
52 |
53 | }
54 |
55 | private fun calculateTextRect(textRect: Rect): RectF {
56 | val scaleX = textOverlay.width.toFloat() / imageRect.width()
57 | val scaleY = textOverlay.height.toFloat() / imageRect.height()
58 | val scale = scaleX.coerceAtLeast(scaleY)
59 |
60 | val offsetX = (textOverlay.width.toFloat() - ceil(imageRect.width() * scale)) / 2.0f
61 | val offsetY = (textOverlay.height.toFloat() - ceil(imageRect.height() * scale)) / 2.0f
62 |
63 | return RectF().apply {
64 | left = textRect.left * scale + offsetX
65 | top = textRect.top * scale + offsetY
66 | right = textRect.right * scale + offsetX
67 | bottom = textRect.bottom * scale + offsetY
68 | }
69 | }
70 |
71 |
72 | }
--------------------------------------------------------------------------------
/mrzcardscanner/src/main/res/layout/activity_mrz.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
23 |
24 |
35 |
36 |
37 |
46 |
47 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/mrzcardscanner/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'org.jetbrains.kotlin.android'
4 | id 'maven-publish'
5 | }
6 |
7 | android {
8 | compileSdk 31
9 |
10 | defaultConfig {
11 | minSdk 21
12 | targetSdk 31
13 |
14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
15 | consumerProguardFiles "consumer-rules.pro"
16 | }
17 |
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 | compileOptions {
25 | sourceCompatibility JavaVersion.VERSION_1_8
26 | targetCompatibility JavaVersion.VERSION_1_8
27 | }
28 | kotlinOptions {
29 | jvmTarget = '1.8'
30 | }
31 | buildFeatures {
32 | viewBinding true
33 | }
34 | }
35 |
36 | dependencies {
37 |
38 | implementation 'androidx.core:core-ktx:1.7.0'
39 | implementation 'androidx.appcompat:appcompat:1.4.1'
40 | implementation 'com.google.android.material:material:1.5.0'
41 | implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
42 | implementation 'com.google.android.gms:play-services-mlkit-text-recognition-common:17.0.0'
43 | implementation 'com.google.android.gms:play-services-mlkit-text-recognition:18.0.0'
44 | implementation files('libs/main.jar')
45 | testImplementation 'junit:junit:4.13.2'
46 | androidTestImplementation 'androidx.test.ext:junit:1.1.3'
47 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
48 | implementation 'com.google.android.gms:play-services-mlkit-face-detection:17.0.1'
49 |
50 | //camera
51 | def camerax_version = "1.1.0-beta03"
52 | implementation "androidx.camera:camera-core:1.0.2"
53 | implementation "androidx.camera:camera-camera2:1.0.2"
54 | implementation "androidx.camera:camera-lifecycle:1.0.2"
55 | implementation "androidx.camera:camera-video:${camerax_version}"
56 |
57 | implementation "androidx.camera:camera-view:${camerax_version}"
58 | implementation "androidx.camera:camera-extensions:${camerax_version}"
59 | }
60 |
61 | afterEvaluate {
62 | publishing {
63 | publications {
64 | release(MavenPublication) {
65 | from components.release
66 |
67 | groupId = 'com.github.amoskorir'
68 | artifactId = 'mrz_scanner'
69 | version = '0.0.1'
70 | }
71 | }
72 | }
73 | }
--------------------------------------------------------------------------------
/mrzcardscanner/src/main/java/com/android/mrzcardreader/MainMrzActivity.kt:
--------------------------------------------------------------------------------
1 | package com.android.mrzcardreader
2 |
3 | import android.content.Intent
4 | import android.content.pm.PackageManager
5 | import android.os.Bundle
6 | import android.widget.Toast
7 | import androidx.appcompat.app.AppCompatActivity
8 | import androidx.core.content.ContextCompat
9 | import com.android.mrzcardreader.camera.MrzCameraManager
10 | import com.android.mrzcardreader.camera.models.IdData
11 | import com.android.mrzcardscanner.databinding.ActivityMrzBinding
12 |
13 |
14 | class MainMrzActivity : AppCompatActivity(), CardResult {
15 | private lateinit var viewBinding: ActivityMrzBinding
16 | private lateinit var cameraManager: MrzCameraManager
17 |
18 |
19 | override fun onCreate(savedInstanceState: Bundle?) {
20 | super.onCreate(savedInstanceState)
21 | viewBinding = ActivityMrzBinding.inflate(layoutInflater)
22 | setContentView(viewBinding.root)
23 | initializeCamera()
24 | startCamera()
25 |
26 | }
27 |
28 | private fun initializeCamera() {
29 | cameraManager =
30 | MrzCameraManager(
31 | this,
32 | this,
33 | viewBinding,
34 | this
35 | )
36 | }
37 |
38 | override fun onRequestPermissionsResult(
39 | requestCode: Int, permissions: Array, grantResults:
40 | IntArray
41 | ) {
42 | super.onRequestPermissionsResult(requestCode, permissions, grantResults)
43 | if (requestCode == REQUEST_CODE_PERMISSIONS) {
44 | if (allPermissionsGranted()) {
45 | startCamera()
46 | } else {
47 | Toast.makeText(
48 | this,
49 | "Permissions not granted by the user.",
50 | Toast.LENGTH_SHORT
51 | ).show()
52 | finish()
53 | }
54 | }
55 | }
56 |
57 | private fun startCamera() {
58 | cameraManager.startCamera()
59 | }
60 |
61 | private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
62 | ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
63 | }
64 |
65 | companion object {
66 | private const val REQUEST_CODE_PERMISSIONS = 10
67 | private val REQUIRED_PERMISSIONS = arrayOf(android.Manifest.permission.CAMERA)
68 |
69 | }
70 |
71 | override fun cardDetails(card: IdData) {
72 | val intent = Intent("MRZ_ACTION")
73 | intent.putExtra("card", card)
74 | setResult(505, intent)
75 | finish()
76 | }
77 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/android/mrzcardreader/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.android.mrzcardreader
2 |
3 | import android.content.pm.PackageManager
4 | import android.os.Bundle
5 | import android.widget.Toast
6 | import androidx.appcompat.app.AppCompatActivity
7 | import androidx.core.content.ContextCompat
8 | import com.android.mrzcardreader.camera.MrzCameraManager
9 | import com.android.mrzcardreader.camera.models.IdData
10 | import com.android.mrzcardreader.databinding.ActivityMainBinding
11 |
12 |
13 | class MainActivity : AppCompatActivity() {
14 | private lateinit var viewBinding: ActivityMainBinding
15 | private lateinit var cameraManager: MrzCameraManager
16 |
17 |
18 | override fun onCreate(savedInstanceState: Bundle?) {
19 | super.onCreate(savedInstanceState)
20 | viewBinding = ActivityMainBinding.inflate(layoutInflater)
21 | setContentView(viewBinding.root)
22 |
23 | viewBinding.scanButton.setOnClickListener {
24 | openScanner()
25 | }
26 |
27 | }
28 |
29 | private fun openScanner() {
30 | MrzBuilder(this, this.activityResultRegistry)
31 | .setOnCardDetailsResponse(object : CardDetailResponse {
32 | override fun onCardRead(card: IdData) {
33 | Toast.makeText(this@MainActivity, card.toString(), Toast.LENGTH_SHORT).show()
34 |
35 | }
36 |
37 | override fun onCardReadingCancelled() {
38 | Toast.makeText(this@MainActivity, "Cancelled", Toast.LENGTH_SHORT).show()
39 | }
40 |
41 | override fun onFailed(e: Exception) {
42 | Toast.makeText(this@MainActivity, e.toString(), Toast.LENGTH_SHORT).show()
43 | }
44 |
45 | })
46 | .start()
47 | }
48 |
49 |
50 | override fun onRequestPermissionsResult(
51 | requestCode: Int, permissions: Array, grantResults:
52 | IntArray
53 | ) {
54 | super.onRequestPermissionsResult(requestCode, permissions, grantResults)
55 | if (requestCode == REQUEST_CODE_PERMISSIONS) {
56 | if (allPermissionsGranted()) {
57 | startCamera()
58 | } else {
59 | Toast.makeText(
60 | this, "Permissions not granted by the user.",
61 | Toast.LENGTH_SHORT
62 | ).show()
63 | finish()
64 | }
65 | }
66 | }
67 |
68 |
69 | private fun startCamera() {
70 | cameraManager.startCamera()
71 | }
72 |
73 | private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
74 | ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
75 | }
76 |
77 | companion object {
78 | private const val REQUEST_CODE_PERMISSIONS = 10
79 | private val REQUIRED_PERMISSIONS = arrayOf(android.Manifest.permission.CAMERA)
80 |
81 | }
82 | }
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/mrzcardscanner/src/main/java/com/android/mrzcardreader/camera/overlay/TextOverlay.kt:
--------------------------------------------------------------------------------
1 | package com.android.mrzcardreader.camera.overlay
2 |
3 | import android.content.Context
4 | import android.graphics.*
5 | import android.util.AttributeSet
6 | import android.util.Log
7 | import android.view.View
8 | import com.android.mrzcardscanner.R
9 |
10 |
11 | class TextOverlay(context: Context, attrs: AttributeSet?) : View(context, attrs) {
12 |
13 | var frameTop: Float = 0f
14 | var frameBottom: Float = 0f
15 | private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
16 | private val outPath = Path()
17 | private val outLinePath = Path()
18 | private val frameRect = RectF()
19 | private val graphicLock = Any()
20 | private val textGraphics: MutableList = ArrayList()
21 |
22 |
23 | private val outerRegionPaint: Paint = Paint().apply {
24 | color = Color.parseColor("#bdbdbd")
25 | style = Paint.Style.FILL
26 | }
27 |
28 | private val cardOutline: Paint = Paint().apply {
29 | color = Color.WHITE
30 | strokeWidth = 4f.toDp()
31 | style = Paint.Style.STROKE
32 | }
33 |
34 | init {
35 | attrs?.let { attributeSet ->
36 | val attributes = context.obtainStyledAttributes(attributeSet, R.styleable.TextOverlay)
37 | try {
38 | val backGroundColor =
39 | attributes.getColor(R.styleable.TextOverlay_backgroundColor, Color.BLACK)
40 | outerRegionPaint.apply {
41 | color = backGroundColor
42 | }
43 | } finally {
44 | attributes.recycle()
45 | }
46 | }
47 | }
48 |
49 | fun Float.toDp(): Float {
50 | val density = context.resources.displayMetrics.density
51 | return this / density
52 | }
53 |
54 | override fun onDraw(canvas: Canvas?) {
55 | super.onDraw(canvas)
56 |
57 | outPath.reset()
58 | outLinePath.reset()
59 |
60 | val height = height.toFloat()
61 | val width = width.toFloat()
62 |
63 | outPath.apply {
64 | moveTo(0f, 0f)
65 | lineTo(0f, height)
66 | lineTo(width, height)
67 | lineTo(width, 0f)
68 | fillType = Path.FillType.EVEN_ODD
69 | }
70 |
71 | frameRect.apply {
72 | left = 30f.toDp()
73 | top = (height / 2) - 300f
74 | right = width - 30f.toDp()
75 | bottom = (height / 2) + 300f
76 | }
77 |
78 | frameTop = frameRect.top
79 | frameBottom = frameRect.bottom
80 |
81 | outLinePath.addRoundRect(
82 | frameRect,
83 | 16f.toDp(),
84 | 16f.toDp(),
85 | Path.Direction.CW
86 | )
87 |
88 | outPath.addPath(outLinePath)
89 |
90 | canvas?.drawPath(outPath, outerRegionPaint)
91 | canvas?.drawPath(outLinePath, cardOutline)
92 |
93 | canvas?.let { drawTextOverlays(it) }
94 | }
95 |
96 | private fun drawTextOverlays(canvas: Canvas) {
97 | Log.d("ak", "drawTextOverlays: " + textGraphics.size)
98 | synchronized(graphicLock) {
99 | textGraphics.forEach() { textGraphic ->
100 | textGraphic.draw(canvas)
101 | }
102 | }
103 |
104 | }
105 |
106 | fun clear() {
107 | synchronized(graphicLock) {
108 | textGraphics.clear()
109 | postInvalidate()
110 | }
111 | }
112 |
113 | fun add(textGraphic: List) {
114 | clear()
115 | synchronized(graphicLock) {
116 | textGraphics.addAll(textGraphic)
117 | postInvalidate()
118 | }
119 | }
120 |
121 | }
122 |
123 |
124 |
125 |
--------------------------------------------------------------------------------
/mrzcardscanner/src/main/java/com/android/mrzcardreader/camera/MrzCameraManager.kt:
--------------------------------------------------------------------------------
1 | package com.android.mrzcardreader.camera
2 |
3 | import android.content.Context
4 | import android.util.Log
5 | import android.view.Surface
6 | import android.view.View
7 | import androidx.camera.core.CameraSelector
8 | import androidx.camera.core.ImageAnalysis
9 | import androidx.camera.core.ImageCapture
10 | import androidx.camera.core.Preview
11 | import androidx.camera.lifecycle.ProcessCameraProvider
12 | import androidx.camera.view.PreviewView
13 | import androidx.core.content.ContextCompat
14 | import androidx.lifecycle.LifecycleOwner
15 | import com.android.mrzcardreader.CardResult
16 | import com.android.mrzcardscanner.databinding.ActivityMrzBinding
17 | import com.google.android.material.bottomsheet.BottomSheetBehavior
18 | import java.util.concurrent.ExecutorService
19 | import java.util.concurrent.Executors
20 |
21 | class MrzCameraManager(
22 | private val context: Context,
23 | private val lifecycleOwner: LifecycleOwner,
24 | private val viewBinding: ActivityMrzBinding,
25 | private val cardResult: CardResult
26 | ) {
27 | private lateinit var cameraProvider: ProcessCameraProvider
28 | private lateinit var cameraExecutor: ExecutorService
29 | private lateinit var preview: Preview
30 | private var findView: PreviewView = viewBinding.viewFinder
31 | private var cardLayout = viewBinding.cardLayout
32 | private var textOverLay = viewBinding.textOverLay
33 | private lateinit var bottomSheetBehavior: BottomSheetBehavior<*>
34 |
35 | init {
36 | createNewExecutor()
37 | val bottomSheet = viewBinding.cardLayout
38 | bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet)
39 |
40 |
41 | bottomSheetBehavior.addBottomSheetCallback(object :
42 | BottomSheetBehavior.BottomSheetCallback() {
43 | override fun onStateChanged(bottomSheet: View, newState: Int) {
44 |
45 | }
46 |
47 | override fun onSlide(bottomSheet: View, slideOffset: Float) {
48 |
49 | }
50 |
51 | })
52 | bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
53 | }
54 |
55 | private fun createNewExecutor() {
56 | cameraExecutor = Executors.newSingleThreadExecutor()
57 | }
58 |
59 | fun startCamera() {
60 | val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
61 | val viewport = findView.viewPort
62 | val imageAnalyzer = ImageAnalysis.Builder()
63 | .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
64 | .build()
65 | .also {
66 | it.setAnalyzer(
67 | cameraExecutor, ImageAnalyzer(
68 | textOverLay,
69 | viewBinding,
70 | bottomSheetBehavior,
71 | cardResult
72 | )
73 | )
74 | }
75 |
76 | cameraProviderFuture.addListener({
77 | cameraProvider = cameraProviderFuture.get()
78 |
79 | val imageCapture = ImageCapture.Builder()
80 | .setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
81 | .setTargetRotation(Surface.ROTATION_0)
82 | .build()
83 |
84 | val cameraSelector = CameraSelector.Builder()
85 | .requireLensFacing(CameraSelector.LENS_FACING_BACK)
86 | .build()
87 |
88 |
89 | try {
90 |
91 | cameraProvider.unbindAll()
92 |
93 | preview = Preview.Builder().build()
94 |
95 |
96 | cameraProvider.bindToLifecycle(
97 | lifecycleOwner,
98 | cameraSelector,
99 | imageCapture,
100 | preview,
101 | imageAnalyzer,
102 | )
103 |
104 | preview.setSurfaceProvider(findView.surfaceProvider)
105 |
106 | } catch (exc: Exception) {
107 | Log.e("k", "Use case binding failed", exc)
108 | }
109 |
110 | }, ContextCompat.getMainExecutor(context))
111 | }
112 |
113 | }
--------------------------------------------------------------------------------
/mrzcardscanner/src/main/java/com/android/mrzcardreader/camera/ImageAnalyzer.kt:
--------------------------------------------------------------------------------
1 | package com.android.mrzcardreader.camera
2 |
3 | import android.annotation.SuppressLint
4 | import android.graphics.Rect
5 | import android.util.Log
6 | import android.widget.Button
7 | import android.widget.TextView
8 | import androidx.camera.core.ImageAnalysis
9 | import androidx.camera.core.ImageProxy
10 | import com.android.mrzcardreader.CardResult
11 | import com.android.mrzcardreader.camera.models.IdData
12 | import com.android.mrzcardreader.camera.overlay.TextGraphic
13 | import com.android.mrzcardreader.camera.overlay.TextOverlay
14 | import com.android.mrzcardreader.cardconnectors.CardConnector
15 | import com.android.mrzcardscanner.R
16 | import com.android.mrzcardscanner.databinding.ActivityMrzBinding
17 | import com.google.android.gms.tasks.Task
18 | import com.google.android.material.bottomsheet.BottomSheetBehavior
19 | import com.google.mlkit.vision.common.InputImage
20 | import com.google.mlkit.vision.text.Text
21 | import com.google.mlkit.vision.text.TextRecognition
22 | import com.google.mlkit.vision.text.latin.TextRecognizerOptions
23 |
24 | class ImageAnalyzer(
25 | private val textOverlay: TextOverlay,
26 | private val activityMainBinding: ActivityMrzBinding,
27 | private val bottomSheetBehavior: BottomSheetBehavior<*>,
28 | private val cardResult: CardResult
29 | ) : ImageAnalysis.Analyzer, MRZResponse {
30 |
31 | private val recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
32 | private val textBlocks: MutableList = ArrayList()
33 | private var scannerRunning = true
34 |
35 | @SuppressLint("UnsafeOptInUsageError")
36 | override fun analyze(imageProxy: ImageProxy) {
37 | val mediaImage = imageProxy.image
38 |
39 | mediaImage?.let {
40 |
41 | Log.d("imageProxy", "analyze: " + imageProxy.imageInfo.rotationDegrees)
42 | detectImageContent(InputImage.fromMediaImage(it, imageProxy.imageInfo.rotationDegrees))
43 | .addOnSuccessListener { results ->
44 | onSuccess(
45 | results,
46 | getImageCropRect(imageProxy)
47 | )
48 | imageProxy.close()
49 | }
50 | .addOnFailureListener {
51 | onFailure(it)
52 | imageProxy.close()
53 | }
54 | }
55 | }
56 |
57 | private fun onFailure(it: Exception) {
58 | it.printStackTrace()
59 | }
60 |
61 | private fun onSuccess(results: Text?, cropRect: Rect?) {
62 | results?.let { result ->
63 |
64 | val blocks = result.textBlocks
65 |
66 | if (blocks.isNotEmpty()) {
67 | cropRect?.let { imageRect ->
68 |
69 | val textGraphics = blocks.map { block ->
70 | TextGraphic(
71 | textOverlay.context,
72 | imageRect,
73 | textBlock = blocks.last(),
74 | textOverlay
75 | )
76 | }
77 | val h = textGraphics.filter { textGraphic -> textGraphic.isValidTextBlock() }
78 | textBlocks.clear()
79 | textOverlay.add(textGraphics)
80 | textBlocks.addAll(blocks)
81 |
82 | val rows = textBlocks.last().text.split("\n")
83 | try {
84 | if (scannerRunning) {
85 | CardConnector.onDetailsCaptured(textBlocks, this)
86 |
87 | } else {
88 | CardConnector.clear()
89 | }
90 |
91 | } catch (e: java.lang.Exception) {
92 |
93 | }
94 | }
95 | } else {
96 | textOverlay.clear()
97 | }
98 | }
99 | }
100 |
101 | private fun minimizeCard() {
102 | bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
103 | }
104 |
105 | private fun maximizedCard() {
106 | bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
107 |
108 | }
109 |
110 | private fun getImageCropRect(imageProxy: ImageProxy): Rect {
111 | val rotation = imageProxy.imageInfo.rotationDegrees
112 | if (rotation == 90 || rotation == 270) {
113 | return Rect(0, 0, imageProxy.height, imageProxy.width)
114 | }
115 | return Rect(0, 0, imageProxy.width, imageProxy.height)
116 | }
117 |
118 | private fun detectImageContent(image: InputImage): Task {
119 | return recognizer.process(image)
120 | }
121 |
122 | override fun cardResponse(card: IdData) {
123 | updateCardDetails(card)
124 | maximizedCard()
125 | }
126 |
127 | private fun updateCardDetails(idData: IdData) {
128 | scannerRunning = false
129 | val rootView = activityMainBinding.cardLayout.rootView
130 | rootView.findViewById(R.id.fNameTv).text = idData.firstName
131 | rootView.findViewById(R.id.lNameTv).text = idData.lastName
132 | rootView.findViewById(R.id.nameTv).text = idData.middleName
133 | rootView.findViewById(R.id.genderTv).text = idData.gender
134 | rootView.findViewById(R.id.idTv).text = idData.idNo
135 | rootView.findViewById(R.id.docNoTv).text = idData.documentNo
136 | rootView.findViewById(R.id.docTypeTv).text = "ID"
137 | rootView.findViewById(R.id.dobTv).text = idData.dateOfBirth
138 |
139 | rootView.findViewById