diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 92bc4735..0eca8dcd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,6 +14,8 @@ material = "1.11.0" constraintlayout = "2.1.4" test = "1.6.1" tesseract4android = "4.7.0" +coreKtx = "1.13.1" +kotlin = "2.0.0" [libraries] androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" } @@ -35,5 +37,7 @@ tesseract4android-jitpack = { group = "cz.adaptech.tesseract4android", name = "t 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" } -[plugins] \ No newline at end of file +[plugins] +jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } \ No newline at end of file diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts index 28d59cab..fe00f18a 100644 --- a/sample/build.gradle.kts +++ b/sample/build.gradle.kts @@ -1,5 +1,6 @@ plugins { id("com.android.application") + alias(libs.plugins.jetbrains.kotlin.android) } android { @@ -32,6 +33,9 @@ android { buildFeatures { viewBinding = true } + kotlinOptions { + jvmTarget = "17" + } } // In case you are using dependency on local library (the project(":tesseract4android") below), @@ -65,6 +69,7 @@ dependencies { implementation(libs.androidx.constraintlayout) implementation(libs.androidx.lifecycle.livedata) implementation(libs.androidx.lifecycle.viewmodel) + implementation(libs.androidx.core.ktx) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) diff --git a/sample/src/main/java/cz/adaptech/tesseract4android/sample/Assets.java b/sample/src/main/java/cz/adaptech/tesseract4android/sample/Assets.java deleted file mode 100644 index 25f23854..00000000 --- a/sample/src/main/java/cz/adaptech/tesseract4android/sample/Assets.java +++ /dev/null @@ -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(); - } - } -} diff --git a/sample/src/main/java/cz/adaptech/tesseract4android/sample/Assets.kt b/sample/src/main/java/cz/adaptech/tesseract4android/sample/Assets.kt new file mode 100644 index 00000000..d5d03066 --- /dev/null +++ b/sample/src/main/java/cz/adaptech/tesseract4android/sample/Assets.kt @@ -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() + } + } +} diff --git a/sample/src/main/java/cz/adaptech/tesseract4android/sample/Config.java b/sample/src/main/java/cz/adaptech/tesseract4android/sample/Config.java deleted file mode 100644 index d0f81a2d..00000000 --- a/sample/src/main/java/cz/adaptech/tesseract4android/sample/Config.java +++ /dev/null @@ -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"; -} diff --git a/sample/src/main/java/cz/adaptech/tesseract4android/sample/Config.kt b/sample/src/main/java/cz/adaptech/tesseract4android/sample/Config.kt new file mode 100644 index 00000000..0113bfa5 --- /dev/null +++ b/sample/src/main/java/cz/adaptech/tesseract4android/sample/Config.kt @@ -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" +} diff --git a/sample/src/main/java/cz/adaptech/tesseract4android/sample/MainActivity.java b/sample/src/main/java/cz/adaptech/tesseract4android/sample/MainActivity.java deleted file mode 100644 index f3df1ea9..00000000 --- a/sample/src/main/java/cz/adaptech/tesseract4android/sample/MainActivity.java +++ /dev/null @@ -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(); - } - } -} \ 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 new file mode 100644 index 00000000..12f8fc6f --- /dev/null +++ b/sample/src/main/java/cz/adaptech/tesseract4android/sample/MainActivity.kt @@ -0,0 +1,17 @@ +package cz.adaptech.tesseract4android.sample + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import cz.adaptech.tesseract4android.sample.ui.main.MainFragment + +class MainActivity : AppCompatActivity() { + 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() + } + } +} \ No newline at end of file diff --git a/sample/src/main/java/cz/adaptech/tesseract4android/sample/ui/main/MainFragment.java b/sample/src/main/java/cz/adaptech/tesseract4android/sample/ui/main/MainFragment.java deleted file mode 100644 index 5c9cb30d..00000000 --- a/sample/src/main/java/cz/adaptech/tesseract4android/sample/ui/main/MainFragment.java +++ /dev/null @@ -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); - }); - } -} \ 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 new file mode 100644 index 00000000..f5d17e07 --- /dev/null +++ b/sample/src/main/java/cz/adaptech/tesseract4android/sample/ui/main/MainFragment.kt @@ -0,0 +1,74 @@ +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/MainViewModel.java b/sample/src/main/java/cz/adaptech/tesseract4android/sample/ui/main/MainViewModel.java deleted file mode 100644 index 26d98262..00000000 --- a/sample/src/main/java/cz/adaptech/tesseract4android/sample/ui/main/MainViewModel.java +++ /dev/null @@ -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 processing = new MutableLiveData<>(false); - - private final MutableLiveData progress = new MutableLiveData<>(); - - private final MutableLiveData 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 getProcessing() { - return processing; - } - - @NonNull - public LiveData getProgress() { - return progress; - } - - @NonNull - public LiveData getResult() { - return result; - } -} \ 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 new file mode 100644 index 00000000..64edf88f --- /dev/null +++ b/sample/src/main/java/cz/adaptech/tesseract4android/sample/ui/main/MainViewModel.kt @@ -0,0 +1,163 @@ +package cz.adaptech.tesseract4android.sample.ui.main + +import android.app.Application +import android.os.SystemClock +import android.util.Log +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 +import kotlin.concurrent.Volatile + +class MainViewModel(application: Application) : AndroidViewModel(application) { + private val tessApi: TessBaseAPI + + private val processing = MutableLiveData(false) + + private val progress = MutableLiveData() + + private val result = MutableLiveData() + + var isInitialized: Boolean = false + private set + + @Volatile + private var stopped = false + + @Volatile + private var tessProcessing = false + + @Volatile + private var recycleAfterProcessing = false + + private val recycleLock = Any() + + init { + tessApi = TessBaseAPI { progressValues: TessBaseAPI.ProgressValues -> + progress.postValue("Progress: " + progressValues.percent + " %") + } + + // Show Tesseract version and library flavor at startup + progress.value = String.format( + Locale.ENGLISH, "Tesseract %s (%s)", + 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() + } + } + } + + 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) + } + } + + fun recognizeImage(imagePath: File) { + if (!this.isInitialized) { + Log.e(TAG, "recognizeImage: Tesseract is not initialized") + return + } + if (tessProcessing) { + Log.e(TAG, "recognizeImage: Processing is in progress") + return + } + tessProcessing = true + + result.value = "" + processing.value = true + progress.value = "Processing..." + stopped = false + + // Start process in another thread + Thread { + tessApi.setImage(imagePath) + // Or set it as Bitmap, Pix,... + // tessApi.setImage(imageBitmap); + 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.postValue(text) + processing.postValue(false) + if (stopped) { + progress.postValue("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() + } + } + }.start() + } + + fun stop() { + if (!tessProcessing) { + return + } + progress.value = "Stopping..." + stopped = true + tessApi.stop() + } + + fun getProcessing(): LiveData { + return processing + } + + fun getProgress(): LiveData { + return progress + } + + fun getResult(): LiveData { + return result + } + + companion object { + private const val TAG = "MainViewModel" + } +} \ No newline at end of file