diff --git a/build.gradle.kts b/build.gradle.kts
index dd1b9a32..9193624a 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -8,6 +8,7 @@ buildscript {
}
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
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 0eca8dcd..8ae6f34d 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -16,6 +16,12 @@ 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" }
@@ -25,8 +31,10 @@ androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", vers
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" }
@@ -38,6 +46,18 @@ tesseract4android-jitpack-openmp = { group = "cz.adaptech.tesseract4android", na
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", version.ref = "kotlin" }
\ No newline at end of file
+jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android" }
\ No newline at end of file
diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts
index fe00f18a..73f83658 100644
--- a/sample/build.gradle.kts
+++ b/sample/build.gradle.kts
@@ -15,11 +15,13 @@ android {
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ vectorDrawables {
+ useSupportLibrary = true
+ }
}
buildTypes {
release {
- isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
@@ -32,10 +34,19 @@ android {
}
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),
@@ -64,13 +75,26 @@ dependencies {
// Which flavor to use is determined by missingDimensionStrategy parameter above.
//implementation(project(":tesseract4android"))
- implementation(libs.androidx.appcompat)
implementation(libs.material)
- implementation(libs.androidx.constraintlayout)
- implementation(libs.androidx.lifecycle.livedata)
- implementation(libs.androidx.lifecycle.viewmodel)
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)
}
\ No newline at end of file
diff --git a/sample/src/androidTest/java/cz/adaptech/tesseract4android/sample/ExampleInstrumentedTest.java b/sample/src/androidTest/java/cz/adaptech/tesseract4android/sample/ExampleInstrumentedTest.java
deleted file mode 100644
index 8c29efb0..00000000
--- a/sample/src/androidTest/java/cz/adaptech/tesseract4android/sample/ExampleInstrumentedTest.java
+++ /dev/null
@@ -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 Testing documentation
- */
-@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());
- }
-}
\ No newline at end of file
diff --git a/sample/src/main/java/cz/adaptech/tesseract4android/sample/MainActivity.kt b/sample/src/main/java/cz/adaptech/tesseract4android/sample/MainActivity.kt
index 12f8fc6f..ae003bca 100644
--- a/sample/src/main/java/cz/adaptech/tesseract4android/sample/MainActivity.kt
+++ b/sample/src/main/java/cz/adaptech/tesseract4android/sample/MainActivity.kt
@@ -1,17 +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 cz.adaptech.tesseract4android.sample.ui.main.MainFragment
+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 : AppCompatActivity() {
+class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
- if (savedInstanceState == null) {
- supportFragmentManager.beginTransaction()
- .replace(R.id.container, MainFragment.newInstance())
- .commitNow()
+ enableEdgeToEdge()
+ setContent {
+ MainView()
}
}
}
\ No newline at end of file
diff --git a/sample/src/main/java/cz/adaptech/tesseract4android/sample/OCRState.kt b/sample/src/main/java/cz/adaptech/tesseract4android/sample/OCRState.kt
new file mode 100644
index 00000000..864f3db7
--- /dev/null
+++ b/sample/src/main/java/cz/adaptech/tesseract4android/sample/OCRState.kt
@@ -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
+}
\ No newline at end of file
diff --git a/sample/src/main/java/cz/adaptech/tesseract4android/sample/ui/main/MainFragment.kt b/sample/src/main/java/cz/adaptech/tesseract4android/sample/ui/main/MainFragment.kt
deleted file mode 100644
index f5d17e07..00000000
--- a/sample/src/main/java/cz/adaptech/tesseract4android/sample/ui/main/MainFragment.kt
+++ /dev/null
@@ -1,74 +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.fragment.app.Fragment
-import androidx.lifecycle.ViewModelProvider
-import cz.adaptech.tesseract4android.sample.Assets.extractAssets
-import cz.adaptech.tesseract4android.sample.Assets.getImageBitmap
-import cz.adaptech.tesseract4android.sample.Assets.getImageFile
-import cz.adaptech.tesseract4android.sample.Assets.getTessDataPath
-import cz.adaptech.tesseract4android.sample.Config
-import cz.adaptech.tesseract4android.sample.databinding.FragmentMainBinding
-import cz.adaptech.tesseract4android.sample.ui.main.MainViewModel
-
-class MainFragment : Fragment() {
- private var binding: FragmentMainBinding? = null
-
- private var viewModel: MainViewModel? = null
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
-
- // Copy sample image and language data to storage
- extractAssets(requireContext())
-
- if (!viewModel!!.isInitialized) {
- val dataPath = getTessDataPath(requireContext())
- viewModel!!.initTesseract(dataPath, Config.TESS_LANG, Config.TESS_ENGINE)
- }
- }
-
- override fun onCreateView(
- inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- binding = FragmentMainBinding.inflate(inflater, container, false)
- return binding!!.root
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- binding!!.image.setImageBitmap(getImageBitmap(requireContext()))
- binding!!.start.setOnClickListener { v: View? ->
- val imageFile = getImageFile(requireContext())
- viewModel!!.recognizeImage(imageFile)
- }
- binding!!.stop.setOnClickListener { v: View? ->
- viewModel!!.stop()
- }
- binding!!.text.movementMethod = ScrollingMovementMethod()
-
- viewModel!!.getProcessing().observe(viewLifecycleOwner) { processing: Boolean? ->
- binding!!.start.isEnabled = !processing!!
- binding!!.stop.isEnabled = processing
- }
- viewModel!!.getProgress().observe(viewLifecycleOwner) { progress: String? ->
- binding!!.status.text = progress
- }
- viewModel!!.getResult().observe(viewLifecycleOwner) { result: String? ->
- binding!!.text.text = result
- }
- }
-
- companion object {
- fun newInstance(): MainFragment {
- return MainFragment()
- }
- }
-}
\ No newline at end of file
diff --git a/sample/src/main/java/cz/adaptech/tesseract4android/sample/ui/main/MainView.kt b/sample/src/main/java/cz/adaptech/tesseract4android/sample/ui/main/MainView.kt
new file mode 100644
index 00000000..ddf9c5a7
--- /dev/null
+++ b/sample/src/main/java/cz/adaptech/tesseract4android/sample/ui/main/MainView.kt
@@ -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()
+ 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..."
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/sample/src/main/java/cz/adaptech/tesseract4android/sample/ui/main/MainViewModel.kt b/sample/src/main/java/cz/adaptech/tesseract4android/sample/ui/main/MainViewModel.kt
index 64edf88f..1129abca 100644
--- a/sample/src/main/java/cz/adaptech/tesseract4android/sample/ui/main/MainViewModel.kt
+++ b/sample/src/main/java/cz/adaptech/tesseract4android/sample/ui/main/MainViewModel.kt
@@ -1,66 +1,124 @@
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.LiveData
-import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.viewModelScope
import com.googlecode.tesseract.android.TessBaseAPI
-import java.io.File
-import java.util.Locale
-import kotlin.concurrent.Volatile
-
+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
- private val processing = MutableLiveData(false)
-
- private val progress = MutableLiveData()
-
- private val result = MutableLiveData()
+ /**
+ * Is the OCR in progress?
+ */
+ private val processing = MutableStateFlow(false)
+
+ /**
+ * The current state of the OCR
+ */
+ private val _progress = MutableStateFlow(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(null)
+
+ /**
+ * Immutable version for view access.
+ */
+ val status: StateFlow = _progress
+
+ /**
+ * Immutable version for view access.
+ */
+ val result: StateFlow = _result
+
+ /**
+ * Is the start button enabled or not.
+ */
+ val isStartEnabled: StateFlow = processing.map { !it }
+ .stateIn(viewModelScope, SharingStarted.Lazily, false)
+
+ /**
+ * Is the stop button enabled or not.
+ */
+ val isStopEnabled: StateFlow = processing
+
+ /**
+ * Converts the sample image into an ImageBitmap for UI
+ */
+ val image: StateFlow = _image.map {
+ it?.asImageBitmap()
+ }.stateIn(viewModelScope, SharingStarted.Eagerly, null)
- var isInitialized: Boolean = false
- private set
-
- @Volatile
- private var stopped = false
-
- @Volatile
- private var tessProcessing = false
+ init {
+ // Instantiate the API
+ tessApi = TessBaseAPI { progressValues: TessBaseAPI.ProgressValues ->
+ _progress.tryEmit(OCRState.Progress(progressValues.percent))
+ }
- @Volatile
- private var recycleAfterProcessing = false
+ // IO Tasks
+ viewModelScope.launch(Dispatchers.IO) {
+ // Copy sample image and language data to storage
+ extractAssets(application)
- private val recycleLock = Any()
+ // Load the image
+ _image.emit(Assets.getImageBitmap(application))
- init {
- tessApi = TessBaseAPI { progressValues: TessBaseAPI.ProgressValues ->
- progress.postValue("Progress: " + progressValues.percent + " %")
+ // Initialize tesseract
+ initTesseract(getTessDataPath(application), Config.TESS_LANG, Config.TESS_ENGINE)
}
// Show Tesseract version and library flavor at startup
- progress.value = String.format(
- Locale.ENGLISH, "Tesseract %s (%s)",
- tessApi.version, tessApi.libraryFlavor
- )
+ _progress.value = OCRState.StartUp(tessApi.version, tessApi.libraryFlavor)
}
override fun 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()
- }
- }
+ tessApi.stop()
+ tessApi.recycle()
}
- fun initTesseract(dataPath: String, language: String, engineMode: Int) {
+ private fun initTesseract(dataPath: String, language: String, engineMode: Int) {
Log.i(
TAG, "Initializing Tesseract with: dataPath = [" + dataPath + "], " +
"language = [" + language + "], engineMode = [" + engineMode + "]"
@@ -73,27 +131,25 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
}
}
- fun recognizeImage(imagePath: File) {
+ private fun recognizeImage() {
if (!this.isInitialized) {
Log.e(TAG, "recognizeImage: Tesseract is not initialized")
return
}
- if (tessProcessing) {
+ if (processing.value) {
Log.e(TAG, "recognizeImage: Processing is in progress")
return
}
- tessProcessing = true
-
- result.value = ""
+ _result.value = ""
processing.value = true
- progress.value = "Processing..."
+ _progress.value = OCRState.Processing
stopped = false
// Start process in another thread
- Thread {
- tessApi.setImage(imagePath)
- // Or set it as Bitmap, Pix,...
- // tessApi.setImage(imageBitmap);
+ 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
@@ -113,48 +169,34 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
tessApi.clear()
// Publish the results
- result.postValue(text)
- processing.postValue(false)
+ _result.emit(text)
+ processing.emit(false)
if (stopped) {
- progress.postValue("Stopped.")
+ _progress.emit(OCRState.Stopped)
} else {
val 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()
- }
+ _progress.emit(OCRState.Finished(duration / 1000f))
}
- }.start()
+ }
}
+ /**
+ * Stops the OCR.
+ */
fun stop() {
- if (!tessProcessing) {
+ if (!processing.value) {
return
}
- progress.value = "Stopping..."
+ _progress.value = OCRState.Stopping
stopped = true
tessApi.stop()
}
- fun getProcessing(): LiveData {
- return processing
- }
-
- fun getProgress(): LiveData {
- return progress
- }
-
- fun getResult(): LiveData {
- return result
+ /**
+ * Start the OCR
+ */
+ fun start() {
+ recognizeImage()
}
companion object {
diff --git a/sample/src/main/java/cz/adaptech/tesseract4android/sample/ui/theme/Color.kt b/sample/src/main/java/cz/adaptech/tesseract4android/sample/ui/theme/Color.kt
new file mode 100644
index 00000000..0a9b48ae
--- /dev/null
+++ b/sample/src/main/java/cz/adaptech/tesseract4android/sample/ui/theme/Color.kt
@@ -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)
\ No newline at end of file
diff --git a/sample/src/main/java/cz/adaptech/tesseract4android/sample/ui/theme/Theme.kt b/sample/src/main/java/cz/adaptech/tesseract4android/sample/ui/theme/Theme.kt
new file mode 100644
index 00000000..57a326eb
--- /dev/null
+++ b/sample/src/main/java/cz/adaptech/tesseract4android/sample/ui/theme/Theme.kt
@@ -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
+ )
+}
\ No newline at end of file
diff --git a/sample/src/main/java/cz/adaptech/tesseract4android/sample/ui/theme/Type.kt b/sample/src/main/java/cz/adaptech/tesseract4android/sample/ui/theme/Type.kt
new file mode 100644
index 00000000..813ec6e4
--- /dev/null
+++ b/sample/src/main/java/cz/adaptech/tesseract4android/sample/ui/theme/Type.kt
@@ -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
+ )
+ */
+)
\ No newline at end of file
diff --git a/sample/src/main/res/layout-land/fragment_main.xml b/sample/src/main/res/layout-land/fragment_main.xml
deleted file mode 100644
index 9f2755b8..00000000
--- a/sample/src/main/res/layout-land/fragment_main.xml
+++ /dev/null
@@ -1,64 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml
deleted file mode 100644
index 1dfb73b5..00000000
--- a/sample/src/main/res/layout/activity_main.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
\ No newline at end of file
diff --git a/sample/src/main/res/layout/fragment_main.xml b/sample/src/main/res/layout/fragment_main.xml
deleted file mode 100644
index 8337eb0f..00000000
--- a/sample/src/main/res/layout/fragment_main.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/sample/src/main/res/values-night/themes.xml b/sample/src/main/res/values-night/themes.xml
deleted file mode 100644
index 327888ea..00000000
--- a/sample/src/main/res/values-night/themes.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/sample/src/main/res/values/colors.xml b/sample/src/main/res/values/colors.xml
deleted file mode 100644
index f8c6127d..00000000
--- a/sample/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
- #FFBB86FC
- #FF6200EE
- #FF3700B3
- #FF03DAC5
- #FF018786
- #FF000000
- #FFFFFFFF
-
\ No newline at end of file
diff --git a/sample/src/main/res/values/themes.xml b/sample/src/main/res/values/themes.xml
index a2207935..d24ed3e7 100644
--- a/sample/src/main/res/values/themes.xml
+++ b/sample/src/main/res/values/themes.xml
@@ -1,25 +1,6 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/sample/src/test/java/cz/adaptech/tesseract4android/sample/ExampleUnitTest.java b/sample/src/test/java/cz/adaptech/tesseract4android/sample/ExampleUnitTest.java
deleted file mode 100644
index f78bbfa5..00000000
--- a/sample/src/test/java/cz/adaptech/tesseract4android/sample/ExampleUnitTest.java
+++ /dev/null
@@ -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 Testing documentation
- */
-public class ExampleUnitTest {
- @Test
- public void addition_isCorrect() {
- assertEquals(4, 2 + 2);
- }
-}
\ No newline at end of file