Skip to content

Commit d63fc55

Browse files
committed
Update Tesseract, support additional scripts for MlKit
1 parent 91b9f30 commit d63fc55

File tree

13 files changed

+117
-34
lines changed

13 files changed

+117
-34
lines changed

app/build.gradle

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ android {
1818
applicationId "org.totschnig.ocr"
1919
minSdkVersion 21
2020
targetSdkVersion 35
21-
versionCode 9
22-
versionName "2.4.1"
21+
versionCode 10
22+
versionName "2.5.0"
2323

2424
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
2525
}
@@ -72,13 +72,20 @@ dependencies {
7272
implementation 'androidx.preference:preference-ktx:1.2.1'
7373
implementation 'androidx.appcompat:appcompat:1.7.0'
7474
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.7'
75-
implementation 'androidx.exifinterface:exifinterface:1.3.7'
76-
testImplementation 'junit:junit:4.13.2'
77-
mlkitImplementation 'com.google.mlkit:text-recognition:16.0.1'
78-
tesseractImplementation 'cz.adaptech.tesseract4android:tesseract4android-openmp:4.7.0'
75+
implementation 'androidx.exifinterface:exifinterface:1.4.0'
7976
implementation 'com.jakewharton.timber:timber:5.0.1'
8077
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7"
81-
implementation "androidx.activity:activity-ktx:1.9.3"
78+
implementation "androidx.activity:activity-ktx:1.10.1"
79+
80+
mlkitImplementation 'com.google.mlkit:text-recognition:16.0.1'
81+
mlkitImplementation 'com.google.mlkit:text-recognition-chinese:16.0.1'
82+
mlkitImplementation 'com.google.mlkit:text-recognition-devanagari:16.0.1'
83+
mlkitImplementation 'com.google.mlkit:text-recognition-japanese:16.0.1'
84+
mlkitImplementation 'com.google.mlkit:text-recognition-korean:16.0.1'
85+
86+
tesseractImplementation 'cz.adaptech.tesseract4android:tesseract4android-openmp:4.8.0'
87+
88+
testImplementation 'junit:junit:4.13.2'
8289
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
8390
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
8491

app/src/main/java/org/totschnig/ocr/BaseSettingsFragment.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,21 @@ abstract class BaseSettingsFragment : PreferenceFragmentCompat() {
1818
override fun onCreate(savedInstanceState: Bundle?) {
1919
viewModel = ViewModelProvider(requireActivity()).get(OcrViewModel::class.java)
2020
viewModel.getResult().observe(this) { result ->
21-
result.onSuccess {
21+
result?.onSuccess {
2222
val text = it.textBlocks.joinToString(separator = "\n") { textBlock ->
2323
textBlock.lines.joinToString(separator = "\n", transform = Line::text)
2424
}
2525
AlertDialog.Builder(requireContext())
2626
.setMessage(text)
2727
.setPositiveButton(R.string.copy_to_clipboard) { _: DialogInterface, _: Int ->
2828
getSystemService(requireContext(), ClipboardManager::class.java)?.setPrimaryClip(ClipData.newPlainText(null, text))
29+
viewModel.clearResult()
30+
}
31+
.setOnDismissListener {
32+
2933
}
3034
.create().show()
31-
}.onFailure {
35+
}?.onFailure {
3236
Toast.makeText(requireContext(), it.message, Toast.LENGTH_LONG).show()
3337
}
3438
}
@@ -52,6 +56,7 @@ abstract class BaseSettingsFragment : PreferenceFragmentCompat() {
5256
if (rootKey == "credits") {
5357
addPreferencesFromResource(R.xml.flavor_credits)
5458
} else {
59+
addPreferencesFromResource(R.xml.engine_preferences)
5560
findPreference<Preference>("test")?.setOnPreferenceClickListener {
5661
val gallIntent = Intent(Intent.ACTION_GET_CONTENT)
5762
gallIntent.type = "image/*"

app/src/main/java/org/totschnig/ocr/BaseViewModel.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@ import androidx.lifecycle.LiveData
88
import androidx.lifecycle.MutableLiveData
99

1010
open class BaseViewModel(application: Application) : AndroidViewModel(application) {
11-
protected val result = MutableLiveData<Result<Text>>()
12-
fun getResult(): LiveData<Result<Text>> = result
11+
12+
protected val result = MutableLiveData<Result<Text>?>()
13+
14+
fun getResult(): LiveData<Result<Text>?> = result
15+
1316
fun getOrientation(uri: Uri) =
1417
when (getApplication<Application>().contentResolver.openInputStream(uri)
1518
?.use { inputStream ->
@@ -24,4 +27,8 @@ open class BaseViewModel(application: Application) : AndroidViewModel(applicatio
2427
ExifInterface.ORIENTATION_ROTATE_270 -> 270
2528
else -> 0
2629
}
30+
31+
fun clearResult() {
32+
result.postValue(null)
33+
}
2734
}

app/src/main/java/org/totschnig/ocr/OCR.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ class OCR : ComponentActivity() {
1313
super.onCreate(savedInstanceState)
1414
viewModel = ViewModelProvider(this)[OcrViewModel::class.java]
1515
viewModel.getResult().observe(this) { result ->
16-
result.onSuccess {
16+
result?.onSuccess {
1717
setResult(RESULT_OK, Intent().apply {
1818
putExtra("result", it)
1919
})
2020
finish()
21-
}.onFailure {
21+
}?.onFailure {
2222
abort(it.message ?: "Failure")
2323
}
2424
}

app/src/mlkit/java/org/totschnig/ocr/OcrViewModel.kt

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
package org.totschnig.ocr
22

33
import android.app.Application
4+
import android.content.SharedPreferences
45
import android.graphics.BitmapFactory
56
import android.net.Uri
67
import androidx.lifecycle.viewModelScope
8+
import androidx.preference.PreferenceManager
79
import com.google.mlkit.vision.common.InputImage
810
import com.google.mlkit.vision.text.TextRecognition
11+
import com.google.mlkit.vision.text.TextRecognizerOptionsInterface
12+
import com.google.mlkit.vision.text.chinese.ChineseTextRecognizerOptions
13+
import com.google.mlkit.vision.text.devanagari.DevanagariTextRecognizerOptions
14+
import com.google.mlkit.vision.text.japanese.JapaneseTextRecognizerOptions
15+
import com.google.mlkit.vision.text.korean.KoreanTextRecognizerOptions
916
import com.google.mlkit.vision.text.latin.TextRecognizerOptions
1017
import kotlinx.coroutines.Dispatchers
1118
import kotlinx.coroutines.launch
@@ -14,6 +21,23 @@ import timber.log.Timber
1421

1522
class OcrViewModel(application: Application) : BaseViewModel(application) {
1623

24+
val prefKey = application.getString(R.string.pref_mlkit_script_key)
25+
26+
val preferences: SharedPreferences
27+
get() = PreferenceManager.getDefaultSharedPreferences(getApplication())
28+
29+
val script: String?
30+
get() = preferences.getString(prefKey, null)
31+
32+
val options: TextRecognizerOptionsInterface
33+
get() = when(script) {
34+
"Han" -> ChineseTextRecognizerOptions.Builder().build()
35+
"Deva" -> DevanagariTextRecognizerOptions.Builder().build()
36+
"Jpan" -> JapaneseTextRecognizerOptions.Builder().build()
37+
"Kore" -> KoreanTextRecognizerOptions.Builder().build()
38+
else -> TextRecognizerOptions.DEFAULT_OPTIONS
39+
}
40+
1741
fun runTextRecognition(uri: Uri) {
1842
viewModelScope.launch {
1943
withContext(Dispatchers.Default) {
@@ -23,7 +47,7 @@ class OcrViewModel(application: Application) : BaseViewModel(application) {
2347
}?.let {
2448
InputImage.fromBitmap(it, getOrientation(uri))
2549
}?.let {
26-
TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS).process(it)
50+
TextRecognition.getClient(options).process(it)
2751
.addOnSuccessListener { texts ->
2852
result.postValue(Result.success(texts.wrap()))
2953
}
@@ -32,11 +56,13 @@ class OcrViewModel(application: Application) : BaseViewModel(application) {
3256
result.postValue(Result.failure(e))
3357
}
3458
} ?: run {
35-
result.postValue(Result.failure(Exception("Unable to open " + uri)))
59+
result.postValue(Result.failure(Exception("Unable to open $uri")))
3660
}
3761
}
3862
}
3963
}
64+
65+
4066
}
4167

4268
fun com.google.mlkit.vision.text.Text.wrap() = Text(textBlocks.map { textBlock ->
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,30 @@
11
package org.totschnig.ocr
22

3+
import android.content.Context
4+
import android.os.Bundle
5+
import androidx.preference.ListPreference
6+
import java.util.Locale
7+
38
class SettingsFragment: BaseSettingsFragment() {
9+
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
10+
super.onCreatePreferences(savedInstanceState, rootKey)
11+
if (rootKey == null) {
12+
findPreference<ListPreference>(viewModel.prefKey)?.let { preference ->
13+
preference.entries = getScriptArray()
14+
}
15+
}
16+
}
17+
fun getScriptArray() =
18+
resources.getStringArray(R.array.pref_mlkit_script_values)
19+
.map { getDisplayNameForScript(requireContext(), it) }
20+
.toTypedArray()
21+
22+
fun getDisplayNameForScript(context: Context, script: String) =
23+
getDisplayNameForScript(resources.configuration.locale, script)
24+
25+
fun getDisplayNameForScript(locale: Locale, script: String): String =
26+
when (script) {
27+
"Han" -> Locale.CHINESE.getDisplayLanguage(locale)
28+
else -> Locale.Builder().setScript(script).build().getDisplayScript(locale)
29+
}
430
}

app/src/mlkit/res/values/keys.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<resources>
3+
<string name="pref_mlkit_script_key">mlkit_script</string>
4+
<string-array name="pref_mlkit_script_values">
5+
<item>Latn</item>
6+
<item>Han</item>
7+
<item>Deva</item>
8+
<item>Jpan</item>
9+
<item>Kore</item>
10+
</string-array>
11+
</resources>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
<resources>
22
<string name="app_name">OCR (Ml Kit)</string>
3+
<string name="pref_mlkit_script_title">Script</string>
34
</resources>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"
2+
xmlns:android="http://schemas.android.com/apk/res/android">
3+
<ListPreference
4+
android:entryValues="@array/pref_mlkit_script_values"
5+
app:useSimpleSummaryProvider="true"
6+
android:key="@string/pref_mlkit_script_key"
7+
android:title="@string/pref_mlkit_script_title" />
8+
</PreferenceScreen>

app/src/tesseract/java/org/totschnig/ocr/OcrViewModel.kt

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import kotlinx.coroutines.withContext
2222
import timber.log.Timber
2323
import java.io.File
2424
import java.util.*
25+
import androidx.core.net.toUri
2526

2627
const val TESSERACT_DOWNLOAD_FOLDER = "tesseract4/fast/"
2728

@@ -31,10 +32,6 @@ class OcrViewModel(application: Application) : BaseViewModel(application) {
3132
val preferences: SharedPreferences
3233
get() = PreferenceManager.getDefaultSharedPreferences(getApplication())
3334

34-
private fun initialize() {
35-
36-
}
37-
3835
fun runTextRecognition(uri: Uri) {
3936
viewModelScope.launch {
4037
withContext(Dispatchers.Default) {
@@ -43,7 +40,7 @@ class OcrViewModel(application: Application) : BaseViewModel(application) {
4340
if (!tessDataExists(application)) {
4441
throw IllegalStateException(application.getString(R.string.configuration_pending))
4542
}
46-
initialize()
43+
4744
application.contentResolver.openInputStream(uri)
4845
?.use { inputStream ->
4946
BitmapFactory.decodeStream(
@@ -136,15 +133,15 @@ class OcrViewModel(application: Application) : BaseViewModel(application) {
136133

137134
fun tessDataExists(context: Context): Boolean = language()?.let {
138135
File(context.getExternalFilesDir(null), filePath(it)).exists()
139-
} ?: false
136+
} == true
140137

141138
private fun language(): String? {
142139
return preferences.getString(prefKey, null)
143140
}
144141

145142
fun downloadTessData(context: Context) = language()?.let {
146143
val uri =
147-
Uri.parse("https://github.com/tesseract-ocr/tessdata_fast/raw/4.0.0/${fileName(it)}")
144+
"https://github.com/tesseract-ocr/tessdata_fast/raw/4.0.0/${fileName(it)}".toUri()
148145
ContextCompat.getSystemService(context, DownloadManager::class.java)?.enqueue(
149146
DownloadManager.Request(uri)
150147
.setTitle(
@@ -178,13 +175,9 @@ class OcrViewModel(application: Application) : BaseViewModel(application) {
178175
"tra" -> "Hant"
179176
else -> localeParts[1]
180177
}
181-
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
182-
Locale.Builder().setLanguage(lang).setScript(script).build().getDisplayName(
183-
localeFromContext
184-
)
185-
} else {
186-
"${Locale(lang).getDisplayName(localeFromContext)} ($script)"
187-
}
178+
Locale.Builder().setLanguage(lang).setScript(script).build().getDisplayName(
179+
localeFromContext
180+
)
188181
} else
189182
Locale(lang).getDisplayName(localeFromContext)
190183
}

0 commit comments

Comments
 (0)