mirror of
https://github.com/adaptech-cz/Tesseract4Android.git
synced 2026-01-09 06:12:45 +08:00
Merge 550a818d7187001101e464f05e8b0dc3828327ff into 71597673649efe75a56a052fb885f4dda77c5ff0
This commit is contained in:
commit
b47a1990b8
32
build.gradle
32
build.gradle
@ -1,32 +0,0 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
|
||||||
|
|
||||||
buildscript {
|
|
||||||
repositories {
|
|
||||||
google()
|
|
||||||
mavenCentral()
|
|
||||||
|
|
||||||
}
|
|
||||||
dependencies {
|
|
||||||
classpath 'com.android.tools.build:gradle:8.2.2'
|
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
|
||||||
// in the individual module build.gradle files
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
allprojects {
|
|
||||||
repositories {
|
|
||||||
mavenLocal()
|
|
||||||
google()
|
|
||||||
mavenCentral()
|
|
||||||
maven { url 'https://jitpack.io' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ext {
|
|
||||||
tesseract4AndroidVersion = '4.7.0'
|
|
||||||
}
|
|
||||||
|
|
||||||
task clean(type: Delete) {
|
|
||||||
delete rootProject.buildDir
|
|
||||||
}
|
|
||||||
29
build.gradle.kts
Normal file
29
build.gradle.kts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath(libs.gradle)
|
||||||
|
classpath(libs.kotlin.gradle.plugin)
|
||||||
|
|
||||||
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
|
// in the individual module build.gradle files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
maven("https://jitpack.io")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register<Delete>("clean") {
|
||||||
|
delete(rootProject.buildDir)
|
||||||
|
}
|
||||||
63
gradle/libs.versions.toml
Normal file
63
gradle/libs.versions.toml
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
[versions]
|
||||||
|
# Intentionally use old version of annotation library which doesn"t depend on kotlin-stdlib
|
||||||
|
# to not unnecessarily complicate client projects due to potential duplicate class build errors
|
||||||
|
# caused by https://kotlinlang.org/docs/whatsnew18.html#updated-jvm-compilation-target
|
||||||
|
#noinspection GradleDependency
|
||||||
|
annotation = "1.3.0"
|
||||||
|
appcompat = "1.6.1"
|
||||||
|
espressoCore = "3.5.1"
|
||||||
|
gradle = "8.5.0"
|
||||||
|
junit = "4.13.2"
|
||||||
|
androidJUnit = "1.1.5"
|
||||||
|
lifecycleLivedata = "2.7.0"
|
||||||
|
material = "1.11.0"
|
||||||
|
constraintlayout = "2.1.4"
|
||||||
|
test = "1.6.1"
|
||||||
|
tesseract4android = "4.7.0"
|
||||||
|
coreKtx = "1.13.1"
|
||||||
|
kotlin = "2.0.0"
|
||||||
|
kotlinGradlePlugin = "1.9.0"
|
||||||
|
lifecycleRuntimeKtx = "2.8.3"
|
||||||
|
activityCompose = "1.9.0"
|
||||||
|
composeBom = "2024.06.00"
|
||||||
|
window = "1.3.0"
|
||||||
|
adaptiveAndroid = "1.0.0-beta04"
|
||||||
|
|
||||||
|
[libraries]
|
||||||
|
androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" }
|
||||||
|
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||||
|
androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayout" }
|
||||||
|
androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoCore" }
|
||||||
|
androidx-junit = { module = "androidx.test.ext:junit", version.ref = "androidJUnit" }
|
||||||
|
androidx-lifecycle-livedata = { module = "androidx.lifecycle:lifecycle-livedata", version.ref = "lifecycleLivedata" }
|
||||||
|
androidx-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel", version.ref = "lifecycleLivedata" }
|
||||||
|
androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose" }
|
||||||
|
androidx-rules = { module = "androidx.test:rules", version.ref = "test" }
|
||||||
|
androidx-runner = { module = "androidx.test:runner", version.ref = "test" }
|
||||||
|
androidx-window = { module = "androidx.window:window", version.ref = "window" }
|
||||||
|
gradle = { module = "com.android.tools.build:gradle", version.ref = "gradle" }
|
||||||
|
junit = { module = "junit:junit", version.ref = "junit" }
|
||||||
|
material = { module = "com.google.android.material:material", version.ref = "material" }
|
||||||
|
|
||||||
|
# Note that since we have 2 artifacts, we must use cz.adaptech.tesseract4android groupId,
|
||||||
|
# instead of just cz.adaptech groupId we use when using local maven repository.
|
||||||
|
tesseract4android-jitpack = { group = "cz.adaptech.tesseract4android", name = "tesseract4android", version.ref = "tesseract4android" }
|
||||||
|
tesseract4android-jitpack-openmp = { group = "cz.adaptech.tesseract4android", name = "tesseract4android-openmp", version.ref = "tesseract4android" }
|
||||||
|
tesseract4android-local = { group = "cz.adaptech", name = "tesseract4android", version.ref = "tesseract4android" }
|
||||||
|
tesseract4android-local-openmp = { group = "cz.adaptech", name = "tesseract4android-openmp", version.ref = "tesseract4android" }
|
||||||
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
|
kotlin-gradle-plugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlinGradlePlugin" }
|
||||||
|
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
|
||||||
|
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
||||||
|
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
|
||||||
|
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
|
||||||
|
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
|
||||||
|
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
|
||||||
|
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
|
||||||
|
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
|
||||||
|
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
||||||
|
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
||||||
|
androidx-adaptive-android = { group = "androidx.compose.material3.adaptive", name = "adaptive-android", version.ref = "adaptiveAndroid" }
|
||||||
|
|
||||||
|
[plugins]
|
||||||
|
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android" }
|
||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-all.zip
|
||||||
|
|||||||
@ -1,70 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id 'com.android.application'
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
|
||||||
namespace 'cz.adaptech.tesseract4android.sample'
|
|
||||||
compileSdk 34
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
applicationId "cz.adaptech.tesseract4android.sample"
|
|
||||||
minSdk 21
|
|
||||||
targetSdk 34
|
|
||||||
versionCode 1
|
|
||||||
versionName "1.0"
|
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
|
||||||
release {
|
|
||||||
minifyEnabled false
|
|
||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility JavaVersion.VERSION_17
|
|
||||||
targetCompatibility JavaVersion.VERSION_17
|
|
||||||
}
|
|
||||||
buildFeatures {
|
|
||||||
viewBinding true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// In case you are using dependency on local library (the project(':tesseract4android') below),
|
|
||||||
// uncomment this to specify which flavor you want to build.
|
|
||||||
// Or you can specify *same* flavors also for the app - then they will be matched automatically.
|
|
||||||
// See more: https://developer.android.com/studio/build/build-variants#variant_aware
|
|
||||||
/*android {
|
|
||||||
defaultConfig {
|
|
||||||
// Choose 'standard' or 'openmp' flavor of the library
|
|
||||||
missingDimensionStrategy 'parallelization', 'standard'
|
|
||||||
}
|
|
||||||
flavorDimensions = ['parallelization']
|
|
||||||
}*/
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
// To use library from JitPack
|
|
||||||
// Note that since we have 2 artifacts, we must use cz.adaptech.tesseract4android groupId,
|
|
||||||
// instead of just cz.adaptech groupId we use when using local maven repository.
|
|
||||||
implementation "cz.adaptech.tesseract4android:tesseract4android:$tesseract4AndroidVersion" // standard flavor
|
|
||||||
// implementation "cz.adaptech.tesseract4android:tesseract4android-openmp:$tesseract4AndroidVersion" // openmp flavor
|
|
||||||
|
|
||||||
// To use library from local maven repository
|
|
||||||
// Don't forget to specify mavenLocal() in repositories block in project's build.gradle file
|
|
||||||
// implementation "cz.adaptech:tesseract4android:$tesseract4AndroidVersion" // standard flavor
|
|
||||||
// implementation "cz.adaptech:tesseract4android-openmp:$tesseract4AndroidVersion" // openmp flavor
|
|
||||||
|
|
||||||
// To use library compiled locally
|
|
||||||
// Which flavor to use is determined by missingDimensionStrategy parameter above.
|
|
||||||
// implementation project(':tesseract4android')
|
|
||||||
|
|
||||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
|
||||||
implementation 'com.google.android.material:material:1.11.0'
|
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
|
||||||
implementation 'androidx.lifecycle:lifecycle-livedata:2.7.0'
|
|
||||||
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.7.0'
|
|
||||||
testImplementation 'junit:junit:4.13.2'
|
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
|
||||||
}
|
|
||||||
100
sample/build.gradle.kts
Normal file
100
sample/build.gradle.kts
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
plugins {
|
||||||
|
id("com.android.application")
|
||||||
|
alias(libs.plugins.jetbrains.kotlin.android)
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "cz.adaptech.tesseract4android.sample"
|
||||||
|
compileSdk = 34
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId = "cz.adaptech.tesseract4android.sample"
|
||||||
|
minSdk = 21
|
||||||
|
targetSdk = 34
|
||||||
|
versionCode = 1
|
||||||
|
versionName = "1.0"
|
||||||
|
|
||||||
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
vectorDrawables {
|
||||||
|
useSupportLibrary = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
proguardFiles(
|
||||||
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
|
"proguard-rules.pro"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
buildFeatures {
|
||||||
|
viewBinding = true
|
||||||
|
compose = true
|
||||||
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "17"
|
||||||
|
}
|
||||||
|
composeOptions {
|
||||||
|
kotlinCompilerExtensionVersion = "1.5.1"
|
||||||
|
}
|
||||||
|
packaging {
|
||||||
|
resources {
|
||||||
|
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// In case you are using dependency on local library (the project(":tesseract4android") below),
|
||||||
|
// uncomment this to specify which flavor you want to build.
|
||||||
|
// Or you can specify *same* flavors also for the app - then they will be matched automatically.
|
||||||
|
// See more: https://developer.android.com/studio/build/build-variants#variant_aware
|
||||||
|
/*android {
|
||||||
|
defaultConfig {
|
||||||
|
// Choose "standard" or "openmp" flavor of the library
|
||||||
|
missingDimensionStrategy "parallelization", "standard"
|
||||||
|
}
|
||||||
|
flavorDimensions = ["parallelization"]
|
||||||
|
}*/
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
// To use library from JitPack
|
||||||
|
implementation(libs.tesseract4android.jitpack) // standard flavor
|
||||||
|
//implementation(libs.tesseract4android.jitpack.openmp) // openmp flavor
|
||||||
|
|
||||||
|
// To use library from local maven repository
|
||||||
|
// Don't forget to specify mavenLocal() in repositories block in project's build.gradle file
|
||||||
|
//implementation(libs.tesseract4android.local) // standard flavor
|
||||||
|
//implementation(libs.tesseract4android.local.openmp) // openmp flavor
|
||||||
|
|
||||||
|
// To use library compiled locally
|
||||||
|
// Which flavor to use is determined by missingDimensionStrategy parameter above.
|
||||||
|
//implementation(project(":tesseract4android"))
|
||||||
|
|
||||||
|
implementation(libs.material)
|
||||||
|
implementation(libs.androidx.core.ktx)
|
||||||
|
implementation(libs.androidx.activity.compose)
|
||||||
|
implementation(platform(libs.androidx.compose.bom))
|
||||||
|
implementation(libs.androidx.ui)
|
||||||
|
implementation(libs.androidx.ui.graphics)
|
||||||
|
implementation(libs.androidx.ui.tooling.preview)
|
||||||
|
implementation(libs.androidx.material3)
|
||||||
|
implementation(libs.androidx.window)
|
||||||
|
|
||||||
|
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||||
|
implementation(libs.androidx.lifecycle.viewmodel)
|
||||||
|
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
||||||
|
implementation(libs.androidx.adaptive.android)
|
||||||
|
|
||||||
|
testImplementation(libs.junit)
|
||||||
|
androidTestImplementation(libs.androidx.junit)
|
||||||
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
|
androidTestImplementation(platform(libs.androidx.compose.bom))
|
||||||
|
androidTestImplementation(libs.androidx.ui.test.junit4)
|
||||||
|
debugImplementation(libs.androidx.ui.tooling)
|
||||||
|
debugImplementation(libs.androidx.ui.test.manifest)
|
||||||
|
}
|
||||||
@ -1,26 +0,0 @@
|
|||||||
package cz.adaptech.tesseract4android.sample;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry;
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instrumented test, which will execute on an Android device.
|
|
||||||
*
|
|
||||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
|
||||||
*/
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
public class ExampleInstrumentedTest {
|
|
||||||
@Test
|
|
||||||
public void useAppContext() {
|
|
||||||
// Context of the app under test.
|
|
||||||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
|
||||||
assertEquals("cz.adaptech.tesseract4android.sample", appContext.getPackageName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,93 +0,0 @@
|
|||||||
package cz.adaptech.tesseract4android.sample;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.AssetManager;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.BitmapFactory;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
public class Assets {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns locally accessible directory where our assets are extracted.
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
public static File getLocalDir(@NonNull Context context) {
|
|
||||||
return context.getFilesDir();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns locally accessible directory path which contains the "tessdata" subdirectory
|
|
||||||
* with *.traineddata files.
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
public static String getTessDataPath(@NonNull Context context) {
|
|
||||||
return getLocalDir(context).getAbsolutePath();
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static File getImageFile(@NonNull Context context) {
|
|
||||||
return new File(getLocalDir(context), Config.IMAGE_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static Bitmap getImageBitmap(@NonNull Context context) {
|
|
||||||
return BitmapFactory.decodeFile(getImageFile(context).getAbsolutePath());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void extractAssets(@NonNull Context context) {
|
|
||||||
AssetManager am = context.getAssets();
|
|
||||||
|
|
||||||
File localDir = getLocalDir(context);
|
|
||||||
if (!localDir.exists() && !localDir.mkdir()) {
|
|
||||||
throw new RuntimeException("Can't create directory " + localDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
File tessDir = new File(getTessDataPath(context), "tessdata");
|
|
||||||
if (!tessDir.exists() && !tessDir.mkdir()) {
|
|
||||||
throw new RuntimeException("Can't create directory " + tessDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract all assets to our local directory.
|
|
||||||
// All *.traineddata into "tessdata" subdirectory, other files into root.
|
|
||||||
try {
|
|
||||||
for (String assetName : am.list("")) {
|
|
||||||
final File targetFile;
|
|
||||||
if (assetName.endsWith(".traineddata")) {
|
|
||||||
targetFile = new File(tessDir, assetName);
|
|
||||||
} else {
|
|
||||||
targetFile = new File(localDir, assetName);
|
|
||||||
}
|
|
||||||
if (!targetFile.exists()) {
|
|
||||||
copyFile(am, assetName, targetFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void copyFile(@NonNull AssetManager am, @NonNull String assetName,
|
|
||||||
@NonNull File outFile) {
|
|
||||||
try (
|
|
||||||
InputStream in = am.open(assetName);
|
|
||||||
OutputStream out = new FileOutputStream(outFile)
|
|
||||||
) {
|
|
||||||
byte[] buffer = new byte[1024];
|
|
||||||
int read;
|
|
||||||
while ((read = in.read(buffer)) != -1) {
|
|
||||||
out.write(buffer, 0, read);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,88 @@
|
|||||||
|
package cz.adaptech.tesseract4android.sample
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.res.AssetManager
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
object Assets {
|
||||||
|
/**
|
||||||
|
* Returns locally accessible directory where our assets are extracted.
|
||||||
|
*/
|
||||||
|
fun getLocalDir(context: Context): File {
|
||||||
|
return context.filesDir
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns locally accessible directory path which contains the "tessdata" subdirectory
|
||||||
|
* with *.traineddata files.
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun getTessDataPath(context: Context): String {
|
||||||
|
return getLocalDir(context).absolutePath
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getImageFile(context: Context): File {
|
||||||
|
return File(getLocalDir(context), Config.IMAGE_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getImageBitmap(context: Context): Bitmap? {
|
||||||
|
return BitmapFactory.decodeFile(getImageFile(context).absolutePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun extractAssets(context: Context) {
|
||||||
|
val am = context.assets
|
||||||
|
|
||||||
|
val localDir = getLocalDir(context)
|
||||||
|
if (!localDir.exists() && !localDir.mkdir()) {
|
||||||
|
throw RuntimeException("Can't create directory $localDir")
|
||||||
|
}
|
||||||
|
|
||||||
|
val tessDir = File(getTessDataPath(context), "tessdata")
|
||||||
|
if (!tessDir.exists() && !tessDir.mkdir()) {
|
||||||
|
throw RuntimeException("Can't create directory $tessDir")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract all assets to our local directory.
|
||||||
|
// All *.traineddata into "tessdata" subdirectory, other files into root.
|
||||||
|
try {
|
||||||
|
for (assetName in am.list("")!!) {
|
||||||
|
val targetFile = if (assetName.endsWith(".traineddata")) {
|
||||||
|
File(tessDir, assetName)
|
||||||
|
} else {
|
||||||
|
File(localDir, assetName)
|
||||||
|
}
|
||||||
|
if (!targetFile.exists()) {
|
||||||
|
copyFile(am, assetName, targetFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun copyFile(
|
||||||
|
am: AssetManager, assetName: String,
|
||||||
|
outFile: File
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
am.open(assetName).use { `in` ->
|
||||||
|
FileOutputStream(outFile).use { out ->
|
||||||
|
val buffer = ByteArray(1024)
|
||||||
|
var read: Int
|
||||||
|
while ((`in`.read(buffer).also { read = it }) != -1) {
|
||||||
|
out.write(buffer, 0, read)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,12 +0,0 @@
|
|||||||
package cz.adaptech.tesseract4android.sample;
|
|
||||||
|
|
||||||
import com.googlecode.tesseract.android.TessBaseAPI;
|
|
||||||
|
|
||||||
public class Config {
|
|
||||||
|
|
||||||
public static final int TESS_ENGINE = TessBaseAPI.OEM_LSTM_ONLY;
|
|
||||||
|
|
||||||
public static final String TESS_LANG = "eng";
|
|
||||||
|
|
||||||
public static final String IMAGE_NAME = "sample.jpg";
|
|
||||||
}
|
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
package cz.adaptech.tesseract4android.sample
|
||||||
|
|
||||||
|
import com.googlecode.tesseract.android.TessBaseAPI
|
||||||
|
|
||||||
|
object Config {
|
||||||
|
const val TESS_ENGINE: Int = TessBaseAPI.OEM_LSTM_ONLY
|
||||||
|
|
||||||
|
const val TESS_LANG: String = "eng"
|
||||||
|
|
||||||
|
const val IMAGE_NAME: String = "sample.jpg"
|
||||||
|
}
|
||||||
@ -1,21 +0,0 @@
|
|||||||
package cz.adaptech.tesseract4android.sample;
|
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
import cz.adaptech.tesseract4android.sample.ui.main.MainFragment;
|
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_main);
|
|
||||||
if (savedInstanceState == null) {
|
|
||||||
getSupportFragmentManager().beginTransaction()
|
|
||||||
.replace(R.id.container, MainFragment.newInstance())
|
|
||||||
.commitNow();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
package cz.adaptech.tesseract4android.sample
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import cz.adaptech.tesseract4android.sample.ui.main.MainView
|
||||||
|
import cz.adaptech.tesseract4android.sample.ui.theme.Tesseract4AndroidTheme
|
||||||
|
|
||||||
|
class MainActivity : ComponentActivity() {
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
enableEdgeToEdge()
|
||||||
|
setContent {
|
||||||
|
MainView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
package cz.adaptech.tesseract4android.sample
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the various states that the OCR can be in.
|
||||||
|
*
|
||||||
|
* @since 2024/07/22
|
||||||
|
* @author Clocks
|
||||||
|
*/
|
||||||
|
sealed interface OCRState {
|
||||||
|
/**
|
||||||
|
* OCR is loading up.
|
||||||
|
*/
|
||||||
|
data object Loading : OCRState
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OCR is prepared.
|
||||||
|
*
|
||||||
|
* @param version Version of tesseract
|
||||||
|
* @param flavour Build flavour of tesseract
|
||||||
|
*/
|
||||||
|
data class StartUp(val version: String, val flavour: String) : OCRState
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OCR has been stopped.
|
||||||
|
*/
|
||||||
|
data object Stopped : OCRState
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OCR is being stopped.
|
||||||
|
*/
|
||||||
|
data object Stopping : OCRState
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OCR is starting up.
|
||||||
|
*/
|
||||||
|
data object Processing : OCRState
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OCR is currently in process.
|
||||||
|
*
|
||||||
|
* @param progress 0-100 progress indication.
|
||||||
|
*/
|
||||||
|
data class Progress(val progress: Int) : OCRState
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OCR has completed its task.
|
||||||
|
*
|
||||||
|
* @param time How many seconds it took to process the image.
|
||||||
|
*/
|
||||||
|
data class Finished(val time: Float) : OCRState
|
||||||
|
}
|
||||||
@ -1,77 +0,0 @@
|
|||||||
package cz.adaptech.tesseract4android.sample.ui.main;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.text.method.ScrollingMovementMethod;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
import cz.adaptech.tesseract4android.sample.Assets;
|
|
||||||
import cz.adaptech.tesseract4android.sample.Config;
|
|
||||||
import cz.adaptech.tesseract4android.sample.databinding.FragmentMainBinding;
|
|
||||||
|
|
||||||
public class MainFragment extends Fragment {
|
|
||||||
|
|
||||||
private FragmentMainBinding binding;
|
|
||||||
|
|
||||||
private MainViewModel viewModel;
|
|
||||||
|
|
||||||
public static MainFragment newInstance() {
|
|
||||||
return new MainFragment();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
viewModel = new ViewModelProvider(this).get(MainViewModel.class);
|
|
||||||
|
|
||||||
// Copy sample image and language data to storage
|
|
||||||
Assets.extractAssets(requireContext());
|
|
||||||
|
|
||||||
if (!viewModel.isInitialized()) {
|
|
||||||
String dataPath = Assets.getTessDataPath(requireContext());
|
|
||||||
viewModel.initTesseract(dataPath, Config.TESS_LANG, Config.TESS_ENGINE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
|
||||||
@Nullable Bundle savedInstanceState) {
|
|
||||||
binding = FragmentMainBinding.inflate(inflater, container, false);
|
|
||||||
return binding.getRoot();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
|
||||||
super.onViewCreated(view, savedInstanceState);
|
|
||||||
|
|
||||||
binding.image.setImageBitmap(Assets.getImageBitmap(requireContext()));
|
|
||||||
binding.start.setOnClickListener(v -> {
|
|
||||||
File imageFile = Assets.getImageFile(requireContext());
|
|
||||||
viewModel.recognizeImage(imageFile);
|
|
||||||
});
|
|
||||||
binding.stop.setOnClickListener(v -> {
|
|
||||||
viewModel.stop();
|
|
||||||
});
|
|
||||||
binding.text.setMovementMethod(new ScrollingMovementMethod());
|
|
||||||
|
|
||||||
viewModel.getProcessing().observe(getViewLifecycleOwner(), processing -> {
|
|
||||||
binding.start.setEnabled(!processing);
|
|
||||||
binding.stop.setEnabled(processing);
|
|
||||||
});
|
|
||||||
viewModel.getProgress().observe(getViewLifecycleOwner(), progress -> {
|
|
||||||
binding.status.setText(progress);
|
|
||||||
});
|
|
||||||
viewModel.getResult().observe(getViewLifecycleOwner(), result -> {
|
|
||||||
binding.text.setText(result);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,169 @@
|
|||||||
|
package cz.adaptech.tesseract4android.sample.ui.main
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.ImageBitmap
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.window.core.layout.WindowHeightSizeClass
|
||||||
|
import androidx.window.core.layout.WindowWidthSizeClass
|
||||||
|
import cz.adaptech.tesseract4android.sample.OCRState
|
||||||
|
import cz.adaptech.tesseract4android.sample.ui.theme.Tesseract4AndroidTheme
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 2024/07/22
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun MainView() {
|
||||||
|
val viewModel = viewModel<MainViewModel>()
|
||||||
|
val image by viewModel.image.collectAsState()
|
||||||
|
val status by viewModel.status.collectAsState()
|
||||||
|
val result by viewModel.result.collectAsState()
|
||||||
|
|
||||||
|
val isStartEnabled by viewModel.isStartEnabled.collectAsState()
|
||||||
|
val isStopEnabled by viewModel.isStopEnabled.collectAsState()
|
||||||
|
|
||||||
|
val sizeClass = currentWindowAdaptiveInfo().windowSizeClass
|
||||||
|
val landscape = sizeClass.windowWidthSizeClass != WindowWidthSizeClass.COMPACT
|
||||||
|
|
||||||
|
Tesseract4AndroidTheme {
|
||||||
|
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
||||||
|
MainContent(
|
||||||
|
innerPadding,
|
||||||
|
image,
|
||||||
|
status,
|
||||||
|
result,
|
||||||
|
viewModel::start,
|
||||||
|
viewModel::stop,
|
||||||
|
isStartEnabled,
|
||||||
|
isStopEnabled,
|
||||||
|
landscape
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MainContent(
|
||||||
|
innerPadding: PaddingValues,
|
||||||
|
bitmap: ImageBitmap?,
|
||||||
|
status: OCRState,
|
||||||
|
result: String,
|
||||||
|
onStart: () -> Unit,
|
||||||
|
onStop: () -> Unit,
|
||||||
|
isStartEnabled: Boolean,
|
||||||
|
isStopEnabled: Boolean,
|
||||||
|
landscape: Boolean
|
||||||
|
) {
|
||||||
|
if (landscape) {
|
||||||
|
Row(
|
||||||
|
Modifier
|
||||||
|
.padding(innerPadding)
|
||||||
|
.fillMaxSize(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Image(bitmap)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.weight(1f), // let it fill up space
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Status(status)
|
||||||
|
|
||||||
|
Controls(onStart, onStop, isStartEnabled, isStopEnabled)
|
||||||
|
|
||||||
|
Result(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.padding(innerPadding)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.fillMaxSize(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
Image(bitmap)
|
||||||
|
|
||||||
|
Status(status)
|
||||||
|
|
||||||
|
Controls(onStart, onStop, isStartEnabled, isStopEnabled)
|
||||||
|
|
||||||
|
Result(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Result(result: String) {
|
||||||
|
Text(text = result, Modifier.padding(16.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Image(bitmap: ImageBitmap?) {
|
||||||
|
AnimatedVisibility(visible = bitmap != null) {
|
||||||
|
Image(bitmap = bitmap!!, contentDescription = "Sample")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Controls(
|
||||||
|
onStart: () -> Unit,
|
||||||
|
onStop: () -> Unit,
|
||||||
|
isStartEnabled: Boolean,
|
||||||
|
isStopEnabled: Boolean
|
||||||
|
) {
|
||||||
|
Row {
|
||||||
|
Button(onClick = onStart, enabled = isStartEnabled) {
|
||||||
|
Text(text = "START")
|
||||||
|
}
|
||||||
|
Button(onClick = onStop, enabled = isStopEnabled) {
|
||||||
|
Text(text = "STOP")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Status(status: OCRState) {
|
||||||
|
Row {
|
||||||
|
Text(text = "Status: ")
|
||||||
|
Text(
|
||||||
|
text = when (status) {
|
||||||
|
is OCRState.Finished ->
|
||||||
|
"Completed in %.3fs.".format(Locale.getDefault(), status.time)
|
||||||
|
|
||||||
|
OCRState.Processing -> "Processing..."
|
||||||
|
is OCRState.Progress -> "Processing ${status.progress}%"
|
||||||
|
is OCRState.StartUp ->
|
||||||
|
"Tesseract %s (%s)"
|
||||||
|
.format(Locale.getDefault(), status.version, status.flavour)
|
||||||
|
|
||||||
|
OCRState.Stopped -> "Stopped."
|
||||||
|
OCRState.Stopping -> "Stopping..."
|
||||||
|
OCRState.Loading -> "Loading..."
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,166 +0,0 @@
|
|||||||
package cz.adaptech.tesseract4android.sample.ui.main;
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
import android.os.SystemClock;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.lifecycle.AndroidViewModel;
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.MutableLiveData;
|
|
||||||
|
|
||||||
import com.googlecode.tesseract.android.TessBaseAPI;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
public class MainViewModel extends AndroidViewModel {
|
|
||||||
|
|
||||||
private static final String TAG = "MainViewModel";
|
|
||||||
|
|
||||||
private final TessBaseAPI tessApi;
|
|
||||||
|
|
||||||
private final MutableLiveData<Boolean> processing = new MutableLiveData<>(false);
|
|
||||||
|
|
||||||
private final MutableLiveData<String> progress = new MutableLiveData<>();
|
|
||||||
|
|
||||||
private final MutableLiveData<String> result = new MutableLiveData<>();
|
|
||||||
|
|
||||||
private boolean tessInit;
|
|
||||||
|
|
||||||
private volatile boolean stopped;
|
|
||||||
|
|
||||||
private volatile boolean tessProcessing;
|
|
||||||
|
|
||||||
private volatile boolean recycleAfterProcessing;
|
|
||||||
|
|
||||||
private final Object recycleLock = new Object();
|
|
||||||
|
|
||||||
public MainViewModel(@NonNull Application application) {
|
|
||||||
super(application);
|
|
||||||
|
|
||||||
tessApi = new TessBaseAPI(progressValues -> {
|
|
||||||
progress.postValue("Progress: " + progressValues.getPercent() + " %");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Show Tesseract version and library flavor at startup
|
|
||||||
progress.setValue(String.format(Locale.ENGLISH, "Tesseract %s (%s)",
|
|
||||||
tessApi.getVersion(), tessApi.getLibraryFlavor()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCleared() {
|
|
||||||
synchronized (recycleLock) {
|
|
||||||
if (tessProcessing) {
|
|
||||||
// Processing is active, set flag to recycle tessApi after processing is completed
|
|
||||||
recycleAfterProcessing = true;
|
|
||||||
// Stop the processing as we don't care about the result anymore
|
|
||||||
tessApi.stop();
|
|
||||||
} else {
|
|
||||||
// No ongoing processing, we must recycle it here
|
|
||||||
tessApi.recycle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void initTesseract(@NonNull String dataPath, @NonNull String language, int engineMode) {
|
|
||||||
Log.i(TAG, "Initializing Tesseract with: dataPath = [" + dataPath + "], " +
|
|
||||||
"language = [" + language + "], engineMode = [" + engineMode + "]");
|
|
||||||
try {
|
|
||||||
tessInit = tessApi.init(dataPath, language, engineMode);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
tessInit = false;
|
|
||||||
Log.e(TAG, "Cannot initialize Tesseract:", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void recognizeImage(@NonNull File imagePath) {
|
|
||||||
if (!tessInit) {
|
|
||||||
Log.e(TAG, "recognizeImage: Tesseract is not initialized");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (tessProcessing) {
|
|
||||||
Log.e(TAG, "recognizeImage: Processing is in progress");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
tessProcessing = true;
|
|
||||||
|
|
||||||
result.setValue("");
|
|
||||||
processing.setValue(true);
|
|
||||||
progress.setValue("Processing...");
|
|
||||||
stopped = false;
|
|
||||||
|
|
||||||
// Start process in another thread
|
|
||||||
new Thread(() -> {
|
|
||||||
tessApi.setImage(imagePath);
|
|
||||||
// Or set it as Bitmap, Pix,...
|
|
||||||
// tessApi.setImage(imageBitmap);
|
|
||||||
|
|
||||||
long startTime = SystemClock.uptimeMillis();
|
|
||||||
|
|
||||||
// Use getHOCRText(0) method to trigger recognition with progress notifications and
|
|
||||||
// ability to cancel ongoing processing.
|
|
||||||
tessApi.getHOCRText(0);
|
|
||||||
|
|
||||||
// At this point the recognition has completed (or was interrupted by calling stop())
|
|
||||||
// and we can get the results we want. In this case just normal UTF8 text.
|
|
||||||
//
|
|
||||||
// Note that calling only this method (without the getHOCRText() above) would also
|
|
||||||
// trigger the recognition and return the same result, but we would received no progress
|
|
||||||
// notifications and we wouldn't be able to stop() the ongoing recognition.
|
|
||||||
String text = tessApi.getUTF8Text();
|
|
||||||
|
|
||||||
// We can free up the recognition results and any stored image data in the tessApi
|
|
||||||
// if we don't need them anymore.
|
|
||||||
tessApi.clear();
|
|
||||||
|
|
||||||
// Publish the results
|
|
||||||
result.postValue(text);
|
|
||||||
processing.postValue(false);
|
|
||||||
if (stopped) {
|
|
||||||
progress.postValue("Stopped.");
|
|
||||||
} else {
|
|
||||||
long duration = SystemClock.uptimeMillis() - startTime;
|
|
||||||
progress.postValue(String.format(Locale.ENGLISH,
|
|
||||||
"Completed in %.3fs.", (duration / 1000f)));
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized (recycleLock) {
|
|
||||||
tessProcessing = false;
|
|
||||||
|
|
||||||
// Recycle the instance here if the view model is already destroyed
|
|
||||||
if (recycleAfterProcessing) {
|
|
||||||
tessApi.recycle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stop() {
|
|
||||||
if (!tessProcessing) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
progress.setValue("Stopping...");
|
|
||||||
stopped = true;
|
|
||||||
tessApi.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isInitialized() {
|
|
||||||
return tessInit;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public LiveData<Boolean> getProcessing() {
|
|
||||||
return processing;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public LiveData<String> getProgress() {
|
|
||||||
return progress;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public LiveData<String> getResult() {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,205 @@
|
|||||||
|
package cz.adaptech.tesseract4android.sample.ui.main
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.os.SystemClock
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.ui.graphics.ImageBitmap
|
||||||
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.googlecode.tesseract.android.TessBaseAPI
|
||||||
|
import cz.adaptech.tesseract4android.sample.Assets
|
||||||
|
import cz.adaptech.tesseract4android.sample.Assets.extractAssets
|
||||||
|
import cz.adaptech.tesseract4android.sample.Assets.getTessDataPath
|
||||||
|
import cz.adaptech.tesseract4android.sample.Config
|
||||||
|
import cz.adaptech.tesseract4android.sample.OCRState
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View Model for Main View.
|
||||||
|
*/
|
||||||
|
class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
|
/**
|
||||||
|
* Tesseract API
|
||||||
|
*/
|
||||||
|
private val tessApi: TessBaseAPI
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the OCR in progress?
|
||||||
|
*/
|
||||||
|
private val processing = MutableStateFlow(false)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current state of the OCR
|
||||||
|
*/
|
||||||
|
private val _progress = MutableStateFlow<OCRState>(OCRState.Loading)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resulting text from the OCR.
|
||||||
|
*/
|
||||||
|
private val _result = MutableStateFlow("")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Has the tesseract API been initialized?
|
||||||
|
*/
|
||||||
|
private var isInitialized = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the OCR has been stopped by the user or not.
|
||||||
|
*/
|
||||||
|
private var stopped: Boolean = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds the bitmap of the sample image.
|
||||||
|
*/
|
||||||
|
private val _image = MutableStateFlow<Bitmap?>(null)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immutable version for view access.
|
||||||
|
*/
|
||||||
|
val status: StateFlow<OCRState> = _progress
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immutable version for view access.
|
||||||
|
*/
|
||||||
|
val result: StateFlow<String> = _result
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the start button enabled or not.
|
||||||
|
*/
|
||||||
|
val isStartEnabled: StateFlow<Boolean> = processing.map { !it }
|
||||||
|
.stateIn(viewModelScope, SharingStarted.Lazily, false)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the stop button enabled or not.
|
||||||
|
*/
|
||||||
|
val isStopEnabled: StateFlow<Boolean> = processing
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the sample image into an ImageBitmap for UI
|
||||||
|
*/
|
||||||
|
val image: StateFlow<ImageBitmap?> = _image.map {
|
||||||
|
it?.asImageBitmap()
|
||||||
|
}.stateIn(viewModelScope, SharingStarted.Eagerly, null)
|
||||||
|
|
||||||
|
init {
|
||||||
|
// Instantiate the API
|
||||||
|
tessApi = TessBaseAPI { progressValues: TessBaseAPI.ProgressValues ->
|
||||||
|
_progress.tryEmit(OCRState.Progress(progressValues.percent))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IO Tasks
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
// Copy sample image and language data to storage
|
||||||
|
extractAssets(application)
|
||||||
|
|
||||||
|
// Load the image
|
||||||
|
_image.emit(Assets.getImageBitmap(application))
|
||||||
|
|
||||||
|
// Initialize tesseract
|
||||||
|
initTesseract(getTessDataPath(application), Config.TESS_LANG, Config.TESS_ENGINE)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show Tesseract version and library flavor at startup
|
||||||
|
_progress.value = OCRState.StartUp(tessApi.version, tessApi.libraryFlavor)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCleared() {
|
||||||
|
tessApi.stop()
|
||||||
|
tessApi.recycle()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initTesseract(dataPath: String, language: String, engineMode: Int) {
|
||||||
|
Log.i(
|
||||||
|
TAG, "Initializing Tesseract with: dataPath = [" + dataPath + "], " +
|
||||||
|
"language = [" + language + "], engineMode = [" + engineMode + "]"
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
this.isInitialized = tessApi.init(dataPath, language, engineMode)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
this.isInitialized = false
|
||||||
|
Log.e(TAG, "Cannot initialize Tesseract:", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun recognizeImage() {
|
||||||
|
if (!this.isInitialized) {
|
||||||
|
Log.e(TAG, "recognizeImage: Tesseract is not initialized")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (processing.value) {
|
||||||
|
Log.e(TAG, "recognizeImage: Processing is in progress")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_result.value = ""
|
||||||
|
processing.value = true
|
||||||
|
_progress.value = OCRState.Processing
|
||||||
|
stopped = false
|
||||||
|
|
||||||
|
// Start process in another thread
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
tessApi.setImage(_image.value!!)
|
||||||
|
// Or set it via a File.
|
||||||
|
// tessApi.setImage(imageFile);
|
||||||
|
val startTime = SystemClock.uptimeMillis()
|
||||||
|
|
||||||
|
// Use getHOCRText(0) method to trigger recognition with progress notifications and
|
||||||
|
// ability to cancel ongoing processing.
|
||||||
|
tessApi.getHOCRText(0)
|
||||||
|
|
||||||
|
// At this point the recognition has completed (or was interrupted by calling stop())
|
||||||
|
// and we can get the results we want. In this case just normal UTF8 text.
|
||||||
|
//
|
||||||
|
// Note that calling only this method (without the getHOCRText() above) would also
|
||||||
|
// trigger the recognition and return the same result, but we would received no progress
|
||||||
|
// notifications and we wouldn't be able to stop() the ongoing recognition.
|
||||||
|
val text = tessApi.utF8Text
|
||||||
|
|
||||||
|
// We can free up the recognition results and any stored image data in the tessApi
|
||||||
|
// if we don't need them anymore.
|
||||||
|
tessApi.clear()
|
||||||
|
|
||||||
|
// Publish the results
|
||||||
|
_result.emit(text)
|
||||||
|
processing.emit(false)
|
||||||
|
if (stopped) {
|
||||||
|
_progress.emit(OCRState.Stopped)
|
||||||
|
} else {
|
||||||
|
val duration = SystemClock.uptimeMillis() - startTime
|
||||||
|
_progress.emit(OCRState.Finished(duration / 1000f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the OCR.
|
||||||
|
*/
|
||||||
|
fun stop() {
|
||||||
|
if (!processing.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_progress.value = OCRState.Stopping
|
||||||
|
stopped = true
|
||||||
|
tessApi.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the OCR
|
||||||
|
*/
|
||||||
|
fun start() {
|
||||||
|
recognizeImage()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "MainViewModel"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
package cz.adaptech.tesseract4android.sample.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
val Purple80 = Color(0xFFD0BCFF)
|
||||||
|
val PurpleGrey80 = Color(0xFFCCC2DC)
|
||||||
|
val Pink80 = Color(0xFFEFB8C8)
|
||||||
|
|
||||||
|
val Purple40 = Color(0xFF6650a4)
|
||||||
|
val PurpleGrey40 = Color(0xFF625b71)
|
||||||
|
val Pink40 = Color(0xFF7D5260)
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
package cz.adaptech.tesseract4android.sample.ui.theme
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.darkColorScheme
|
||||||
|
import androidx.compose.material3.dynamicDarkColorScheme
|
||||||
|
import androidx.compose.material3.dynamicLightColorScheme
|
||||||
|
import androidx.compose.material3.lightColorScheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
|
||||||
|
private val DarkColorScheme = darkColorScheme(
|
||||||
|
primary = Purple80,
|
||||||
|
secondary = PurpleGrey80,
|
||||||
|
tertiary = Pink80
|
||||||
|
)
|
||||||
|
|
||||||
|
private val LightColorScheme = lightColorScheme(
|
||||||
|
primary = Purple40,
|
||||||
|
secondary = PurpleGrey40,
|
||||||
|
tertiary = Pink40
|
||||||
|
|
||||||
|
/* Other default colors to override
|
||||||
|
background = Color(0xFFFFFBFE),
|
||||||
|
surface = Color(0xFFFFFBFE),
|
||||||
|
onPrimary = Color.White,
|
||||||
|
onSecondary = Color.White,
|
||||||
|
onTertiary = Color.White,
|
||||||
|
onBackground = Color(0xFF1C1B1F),
|
||||||
|
onSurface = Color(0xFF1C1B1F),
|
||||||
|
*/
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Tesseract4AndroidTheme(
|
||||||
|
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||||
|
// Dynamic color is available on Android 12+
|
||||||
|
dynamicColor: Boolean = true,
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
val colorScheme = when {
|
||||||
|
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||||
|
val context = LocalContext.current
|
||||||
|
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
darkTheme -> DarkColorScheme
|
||||||
|
else -> LightColorScheme
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialTheme(
|
||||||
|
colorScheme = colorScheme,
|
||||||
|
typography = Typography,
|
||||||
|
content = content
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
package cz.adaptech.tesseract4android.sample.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.material3.Typography
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
|
// Set of Material typography styles to start with
|
||||||
|
val Typography = Typography(
|
||||||
|
bodyLarge = TextStyle(
|
||||||
|
fontFamily = FontFamily.Default,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
lineHeight = 24.sp,
|
||||||
|
letterSpacing = 0.5.sp
|
||||||
|
)
|
||||||
|
/* Other default text styles to override
|
||||||
|
titleLarge = TextStyle(
|
||||||
|
fontFamily = FontFamily.Default,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 22.sp,
|
||||||
|
lineHeight = 28.sp,
|
||||||
|
letterSpacing = 0.sp
|
||||||
|
),
|
||||||
|
labelSmall = TextStyle(
|
||||||
|
fontFamily = FontFamily.Default,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
fontSize = 11.sp,
|
||||||
|
lineHeight = 16.sp,
|
||||||
|
letterSpacing = 0.5.sp
|
||||||
|
)
|
||||||
|
*/
|
||||||
|
)
|
||||||
@ -1,64 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:id="@+id/main"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
tools:context=".ui.main.MainFragment">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/image"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
tools:src="@tools:sample/avatars" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/status"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:textStyle="bold"
|
|
||||||
tools:text="Status" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/start"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="4dp"
|
|
||||||
android:text="Start" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/stop"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="4dp"
|
|
||||||
android:text="Stop" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/text"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:padding="16dp"
|
|
||||||
android:scrollbars="vertical"
|
|
||||||
android:text="" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:id="@+id/container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
tools:context=".MainActivity" />
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:id="@+id/main"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical"
|
|
||||||
tools:context=".ui.main.MainFragment">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/image"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
tools:src="@tools:sample/avatars" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/status"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:textStyle="bold"
|
|
||||||
tools:text="Status" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/start"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="4dp"
|
|
||||||
android:text="Start" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/stop"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="4dp"
|
|
||||||
android:text="Stop" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/text"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:padding="16dp"
|
|
||||||
android:scrollbars="vertical"
|
|
||||||
android:text="" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
|
||||||
<!-- Base application theme. -->
|
|
||||||
<style name="Theme.Tesseract4Android" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
|
||||||
<!-- Primary brand color. -->
|
|
||||||
<item name="colorPrimary">@color/purple_200</item>
|
|
||||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
|
||||||
<item name="colorOnPrimary">@color/black</item>
|
|
||||||
<!-- Secondary brand color. -->
|
|
||||||
<item name="colorSecondary">@color/teal_200</item>
|
|
||||||
<item name="colorSecondaryVariant">@color/teal_200</item>
|
|
||||||
<item name="colorOnSecondary">@color/black</item>
|
|
||||||
<!-- Status bar color. -->
|
|
||||||
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
|
|
||||||
<!-- Customize your theme here. -->
|
|
||||||
</style>
|
|
||||||
</resources>
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<color name="purple_200">#FFBB86FC</color>
|
|
||||||
<color name="purple_500">#FF6200EE</color>
|
|
||||||
<color name="purple_700">#FF3700B3</color>
|
|
||||||
<color name="teal_200">#FF03DAC5</color>
|
|
||||||
<color name="teal_700">#FF018786</color>
|
|
||||||
<color name="black">#FF000000</color>
|
|
||||||
<color name="white">#FFFFFFFF</color>
|
|
||||||
</resources>
|
|
||||||
@ -1,25 +1,6 @@
|
|||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
<!-- Base application theme. -->
|
<!-- Base application theme. -->
|
||||||
<style name="Theme.Tesseract4Android" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
<style name="Theme.Tesseract4Android" parent="Theme.Material3.DayNight.NoActionBar">
|
||||||
<!-- Primary brand color. -->
|
<!-- Customize your theme here. -->
|
||||||
<item name="colorPrimary">@color/purple_500</item>
|
|
||||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
|
||||||
<item name="colorOnPrimary">@color/white</item>
|
|
||||||
<!-- Secondary brand color. -->
|
|
||||||
<item name="colorSecondary">@color/teal_200</item>
|
|
||||||
<item name="colorSecondaryVariant">@color/teal_700</item>
|
|
||||||
<item name="colorOnSecondary">@color/black</item>
|
|
||||||
<!-- Status bar color. -->
|
|
||||||
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
|
|
||||||
<!-- Customize your theme here. -->
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.Tesseract4Android.NoActionBar">
|
|
||||||
<item name="windowActionBar">false</item>
|
|
||||||
<item name="windowNoTitle">true</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="Theme.Tesseract4Android.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
|
|
||||||
|
|
||||||
<style name="Theme.Tesseract4Android.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
|
|
||||||
</resources>
|
</resources>
|
||||||
@ -1,17 +0,0 @@
|
|||||||
package cz.adaptech.tesseract4android.sample;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Example local unit test, which will execute on the development machine (host).
|
|
||||||
*
|
|
||||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
|
||||||
*/
|
|
||||||
public class ExampleUnitTest {
|
|
||||||
@Test
|
|
||||||
public void addition_isCorrect() {
|
|
||||||
assertEquals(4, 2 + 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
include ':tesseract4android'
|
|
||||||
if (!System.env.JITPACK) {
|
|
||||||
include ':sample'
|
|
||||||
}
|
|
||||||
4
settings.gradle.kts
Normal file
4
settings.gradle.kts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
include(":tesseract4android")
|
||||||
|
if (System.getenv("JITPACK") == null) {
|
||||||
|
include(":sample")
|
||||||
|
}
|
||||||
@ -1,123 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id 'com.android.library'
|
|
||||||
id 'maven-publish'
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
|
||||||
namespace 'cz.adaptech.tesseract4android'
|
|
||||||
compileSdk 33
|
|
||||||
ndkVersion "25.1.8937393"
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
minSdk 16
|
|
||||||
targetSdk 33
|
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
||||||
externalNativeBuild {
|
|
||||||
cmake {
|
|
||||||
// Specifies which native libraries or executables to build and package.
|
|
||||||
// TODO: Include eyes-two in some build flavor of the library?
|
|
||||||
//targets "jpeg", "pngx", "leptonica", "tesseract"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ndk {
|
|
||||||
// Specify the ABI configurations that Gradle should build and package.
|
|
||||||
// By default it compiles all available ABIs.
|
|
||||||
//abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
externalNativeBuild {
|
|
||||||
cmake {
|
|
||||||
path "src/main/cpp/CMakeLists.txt"
|
|
||||||
version '3.22.1'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buildTypes {
|
|
||||||
release {
|
|
||||||
minifyEnabled false
|
|
||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
|
||||||
}
|
|
||||||
debug {
|
|
||||||
externalNativeBuild {
|
|
||||||
cmake {
|
|
||||||
// Force building release version of native libraries even in debug variant.
|
|
||||||
// This is for projects that has direct dependency on this library,
|
|
||||||
// but doesn't really want its debug version, which is very slow.
|
|
||||||
// Note that this only affects native code.
|
|
||||||
arguments "-DCMAKE_BUILD_TYPE=Release"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
flavorDimensions = ["parallelization"]
|
|
||||||
productFlavors {
|
|
||||||
standard {
|
|
||||||
}
|
|
||||||
openmp {
|
|
||||||
externalNativeBuild {
|
|
||||||
cmake {
|
|
||||||
// NOTE: We must add -static-openmp argument to build it statically,
|
|
||||||
// because shared library is not being included in the resulting APK.
|
|
||||||
// See: https://github.com/android/ndk/issues/1028
|
|
||||||
// Use of that argument shows warnings during build:
|
|
||||||
// > C/C++: clang: warning: argument unused during compilation: '-static-openmp' [-Wunused-command-line-argument]
|
|
||||||
// But it has no effect on the result.
|
|
||||||
cFlags "-fopenmp -static-openmp -Wno-unused-command-line-argument"
|
|
||||||
cppFlags "-fopenmp -static-openmp -Wno-unused-command-line-argument"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
publishing {
|
|
||||||
singleVariant("standardRelease") {
|
|
||||||
withSourcesJar()
|
|
||||||
withJavadocJar()
|
|
||||||
}
|
|
||||||
singleVariant("openmpRelease") {
|
|
||||||
withSourcesJar()
|
|
||||||
withJavadocJar()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility JavaVersion.VERSION_17
|
|
||||||
targetCompatibility JavaVersion.VERSION_17
|
|
||||||
}
|
|
||||||
buildFeatures {
|
|
||||||
buildConfig true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
// Intentionally use old version of annotation library which doesn't depend on kotlin-stdlib
|
|
||||||
// to not unnecessarily complicate client projects due to potential duplicate class build errors
|
|
||||||
// caused by https://kotlinlang.org/docs/whatsnew18.html#updated-jvm-compilation-target
|
|
||||||
//noinspection GradleDependency
|
|
||||||
implementation 'androidx.annotation:annotation:1.3.0'
|
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.13.2'
|
|
||||||
androidTestImplementation 'androidx.test:runner:1.5.2'
|
|
||||||
androidTestImplementation 'androidx.test:rules:1.5.0'
|
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
|
||||||
}
|
|
||||||
|
|
||||||
afterEvaluate {
|
|
||||||
publishing {
|
|
||||||
publications {
|
|
||||||
standard(MavenPublication) {
|
|
||||||
from components.findByName('standardRelease')
|
|
||||||
|
|
||||||
groupId 'cz.adaptech'
|
|
||||||
artifactId 'tesseract4android'
|
|
||||||
version rootProject.ext.tesseract4AndroidVersion
|
|
||||||
}
|
|
||||||
openmp(MavenPublication) {
|
|
||||||
from components.findByName('openmpRelease')
|
|
||||||
|
|
||||||
groupId 'cz.adaptech'
|
|
||||||
artifactId 'tesseract4android-openmp'
|
|
||||||
version rootProject.ext.tesseract4AndroidVersion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
122
tesseract4android/build.gradle.kts
Normal file
122
tesseract4android/build.gradle.kts
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
plugins {
|
||||||
|
id("com.android.library")
|
||||||
|
id("maven-publish")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "cz.adaptech.tesseract4android"
|
||||||
|
compileSdk = 33
|
||||||
|
ndkVersion = "25.1.8937393"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = 16
|
||||||
|
lint.targetSdk = 33
|
||||||
|
|
||||||
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
// Specifies which native libraries or executables to build and package.
|
||||||
|
// TODO: Include eyes-two in some build flavor of the library?
|
||||||
|
//targets "jpeg", "pngx", "leptonica", "tesseract"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ndk {
|
||||||
|
// Specify the ABI configurations that Gradle should build and package.
|
||||||
|
// By default it compiles all available ABIs.
|
||||||
|
//abiFilters "x86", "x86_64", "armeabi-v7a", "arm64-v8a"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
path("src/main/cpp/CMakeLists.txt")
|
||||||
|
version = "3.22.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
isMinifyEnabled = false
|
||||||
|
proguardFiles(
|
||||||
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
|
"proguard-rules.pro"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
debug {
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
// Force building release version of native libraries even in debug variant.
|
||||||
|
// This is for projects that has direct dependency on this library,
|
||||||
|
// but doesn"t really want its debug version, which is very slow.
|
||||||
|
// Note that this only affects native code.
|
||||||
|
arguments("-DCMAKE_BUILD_TYPE=Release")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flavorDimensions += listOf("parallelization")
|
||||||
|
productFlavors {
|
||||||
|
create("standard") {
|
||||||
|
}
|
||||||
|
create("openmp") {
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
// NOTE: We must add -static-openmp argument to build it statically,
|
||||||
|
// because shared library is not being included in the resulting APK.
|
||||||
|
// See: https://github.com/android/ndk/issues/1028
|
||||||
|
// Use of that argument shows warnings during build:
|
||||||
|
// > C/C++: clang: warning: argument unused during compilation: "-static-openmp" [-Wunused-command-line-argument]
|
||||||
|
// But it has no effect on the result.
|
||||||
|
cFlags("-fopenmp -static-openmp -Wno-unused-command-line-argument")
|
||||||
|
cppFlags("-fopenmp -static-openmp -Wno-unused-command-line-argument")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
publishing {
|
||||||
|
singleVariant("standardRelease") {
|
||||||
|
withSourcesJar()
|
||||||
|
withJavadocJar()
|
||||||
|
}
|
||||||
|
singleVariant("openmpRelease") {
|
||||||
|
withSourcesJar()
|
||||||
|
withJavadocJar()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
buildFeatures {
|
||||||
|
buildConfig = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(libs.androidx.annotation)
|
||||||
|
|
||||||
|
testImplementation(libs.junit)
|
||||||
|
androidTestImplementation(libs.androidx.runner)
|
||||||
|
androidTestImplementation(libs.androidx.rules)
|
||||||
|
androidTestImplementation(libs.androidx.junit)
|
||||||
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEvaluate {
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
create<MavenPublication>("standard") {
|
||||||
|
from(components.findByName("standardRelease"))
|
||||||
|
|
||||||
|
groupId = "cz.adaptech"
|
||||||
|
artifactId = "tesseract4android"
|
||||||
|
version = libs.versions.tesseract4android.get()
|
||||||
|
}
|
||||||
|
create<MavenPublication>("openmp") {
|
||||||
|
from(components.findByName("openmpRelease"))
|
||||||
|
|
||||||
|
groupId = "cz.adaptech"
|
||||||
|
artifactId = "tesseract4android-openmp"
|
||||||
|
version = libs.versions.tesseract4android.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user