mirror of
https://github.com/adaptech-cz/Tesseract4Android.git
synced 2026-01-09 06:12:45 +08:00
Dirty Kotlin Migration
This commit is contained in:
parent
c2c8de65ee
commit
8c61e0f7a1
@ -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]
|
||||
[plugins]
|
||||
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
@ -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)
|
||||
|
||||
@ -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,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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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,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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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,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<String>()
|
||||
|
||||
private val result = MutableLiveData<String>()
|
||||
|
||||
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<Boolean> {
|
||||
return processing
|
||||
}
|
||||
|
||||
fun getProgress(): LiveData<String> {
|
||||
return progress
|
||||
}
|
||||
|
||||
fun getResult(): LiveData<String> {
|
||||
return result
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "MainViewModel"
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user