Skip to content

Commit

Permalink
Added ability to Import/Export OCR models by #1196
Browse files Browse the repository at this point in the history
  • Loading branch information
T8RIN committed Jul 15, 2024
1 parent 71abbe8 commit 427deaf
Show file tree
Hide file tree
Showing 8 changed files with 378 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,65 @@ val Icons.Rounded.DownloadFile: ImageVector by lazy {
close()
}
}.build()
}

val Icons.Outlined.DownloadFile: ImageVector by lazy {
Builder(
name = "Download File Outline", defaultWidth = 24.0.dp,
defaultHeight = 24.0.dp, viewportWidth = 24.0f, viewportHeight = 24.0f
).apply {
path(
fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f,
strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f,
pathFillType = NonZero
) {
moveTo(14.0f, 2.0f)
lineTo(20.0f, 8.0f)
verticalLineTo(20.0f)
arcTo(
2.0f, 2.0f, 0.0f,
isMoreThanHalf = false,
isPositiveArc = true,
x1 = 18.0f,
y1 = 22.0f
)
horizontalLineTo(6.0f)
arcTo(
horizontalEllipseRadius = 2.0f,
verticalEllipseRadius = 2.0f,
theta = 0.0f,
isMoreThanHalf = false,
isPositiveArc = true,
x1 = 4.0f,
y1 = 20.0f
)
verticalLineTo(4.0f)
arcTo(
horizontalEllipseRadius = 2.0f,
verticalEllipseRadius = 2.0f,
theta = 0.0f,
isMoreThanHalf = false,
isPositiveArc = true,
x1 = 6.0f,
y1 = 2.0f
)
horizontalLineTo(14.0f)
moveTo(18.0f, 20.0f)
verticalLineTo(9.0f)
horizontalLineTo(13.0f)
verticalLineTo(4.0f)
horizontalLineTo(6.0f)
verticalLineTo(20.0f)
horizontalLineTo(18.0f)
moveTo(12.0f, 19.0f)
lineTo(8.0f, 15.0f)
horizontalLineTo(10.5f)
verticalLineTo(12.0f)
horizontalLineTo(13.5f)
verticalLineTo(15.0f)
horizontalLineTo(16.0f)
lineTo(12.0f, 19.0f)
close()
}
}.build()
}
4 changes: 4 additions & 0 deletions core/resources/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1262,4 +1262,8 @@
<string name="image_splitting_sub">Split single image by rows or columns</string>
<string name="fit_to_bounds">Fit To Bounds</string>
<string name="fit_to_bounds_sub">Combine crop resize mode with this parameter to achieve desired behavior (Crop/Fit to aspect ratio)</string>
<string name="languages_imported">Languages imported successfully</string>
<string name="backup_ocr_models">Backup OCR models</string>
<string name="import_word">Import</string>
<string name="export">Export</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ package ru.tech.imageresizershrinker.core.ui.widget.controls
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Text
Expand Down Expand Up @@ -48,6 +50,7 @@ fun ResizeImageField(
Column(
modifier = Modifier
.container(shape = RoundedCornerShape(24.dp))
.padding(8.dp)
.animateContentSize()
) {
Row {
Expand All @@ -68,9 +71,9 @@ fun ResizeImageField(
},
shape = RoundedCornerShape(
topStart = 12.dp,
topEnd = 4.dp,
topEnd = 6.dp,
bottomStart = 12.dp,
bottomEnd = 4.dp
bottomEnd = 6.dp
),
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number
Expand All @@ -83,15 +86,9 @@ fun ResizeImageField(
)
)
},
modifier = Modifier
.weight(1f)
.padding(
start = 8.dp,
top = 8.dp,
bottom = 8.dp,
end = 2.dp
)
modifier = Modifier.weight(1f)
)
Spacer(modifier = Modifier.width(4.dp))
RoundedTextField(
value = imageInfo.height.takeIf { it != 0 }
.let { it ?: "" }
Expand All @@ -112,9 +109,9 @@ fun ResizeImageField(
),
shape = RoundedCornerShape(
topEnd = 12.dp,
topStart = 4.dp,
topStart = 6.dp,
bottomEnd = 12.dp,
bottomStart = 4.dp
bottomStart = 6.dp
),
label = {
Text(
Expand All @@ -125,14 +122,7 @@ fun ResizeImageField(
)
)
},
modifier = Modifier
.weight(1f)
.padding(
start = 2.dp,
top = 8.dp,
bottom = 8.dp,
end = 8.dp
)
modifier = Modifier.weight(1f)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ package ru.tech.imageresizershrinker.feature.recognize.text.data

import android.content.Context
import android.graphics.Bitmap
import androidx.core.net.toUri
import androidx.exifinterface.media.ExifInterface
import com.googlecode.tesseract.android.TessBaseAPI
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
import ru.tech.imageresizershrinker.core.domain.dispatchers.DispatchersHolder
import ru.tech.imageresizershrinker.core.domain.image.ImageGetter
import ru.tech.imageresizershrinker.core.domain.image.ShareProvider
import ru.tech.imageresizershrinker.core.resources.R
import ru.tech.imageresizershrinker.feature.recognize.text.domain.Constants
import ru.tech.imageresizershrinker.feature.recognize.text.domain.DownloadData
Expand All @@ -37,19 +39,25 @@ import ru.tech.imageresizershrinker.feature.recognize.text.domain.RecognitionTyp
import ru.tech.imageresizershrinker.feature.recognize.text.domain.SegmentationMode
import ru.tech.imageresizershrinker.feature.recognize.text.domain.TextRecognitionResult
import java.io.BufferedInputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.InputStream
import java.io.OutputStream
import java.lang.String.format
import java.net.HttpURLConnection
import java.net.URL
import java.util.Locale
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
import javax.inject.Inject

internal class AndroidImageTextReader @Inject constructor(
private val imageGetter: ImageGetter<Bitmap, ExifInterface>,
@ApplicationContext private val context: Context,
private val shareProvider: ShareProvider<Bitmap>,
dispatchersHolder: DispatchersHolder
) : DispatchersHolder by dispatchersHolder, ImageTextReader<Bitmap> {

Expand Down Expand Up @@ -324,4 +332,53 @@ internal class AndroidImageTextReader @Inject constructor(
locale.getDisplayName(Locale.getDefault()).replaceFirstChar { it.uppercase(locale) }
} else locale.getDisplayName(locale).replaceFirstChar { it.uppercase(locale) }
}

override suspend fun exportLanguagesToZip(): String? = withContext(ioDispatcher) {
val out = ByteArrayOutputStream()

ZipOutputStream(out).use { zipOut ->
RecognitionType.entries.forEach { type ->
val dir = File(context.filesDir, "${type.displayName}/tessdata")
dir.listFiles()?.forEach { file ->
FileInputStream(file).use { fis ->
val zipEntry = ZipEntry("${type.displayName}/tessdata/${file.name}")
zipOut.putNextEntry(zipEntry)
fis.copyTo(zipOut)
zipOut.closeEntry()
}
}
}
}

shareProvider.cacheByteArray(
byteArray = out.toByteArray(),
filename = "exported_languages.zip"
)
}

override suspend fun importLanguagesFromUri(
zipUri: String
): Result<Any> = withContext(ioDispatcher) {
val zipInput = context.contentResolver.openInputStream(
zipUri.toUri()
) ?: return@withContext Result.failure(NullPointerException())

runCatching {
zipInput.use { inputStream ->
ZipInputStream(inputStream).use { zipIn ->
var entry: ZipEntry?
while (zipIn.nextEntry.also { entry = it } != null) {
entry?.let { zipEntry ->
val outFile = File(context.filesDir, zipEntry.name)
outFile.parentFile?.mkdirs()
FileOutputStream(outFile).use { fos ->
zipIn.copyTo(fos)
}
zipIn.closeEntry()
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,10 @@ interface ImageTextReader<Image> {
types: List<RecognitionType>
)

suspend fun exportLanguagesToZip(): String?

suspend fun importLanguagesFromUri(
zipUri: String
): Result<Any>

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
package ru.tech.imageresizershrinker.feature.recognize.text.presentation

import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.layout.Box
Expand All @@ -26,6 +28,7 @@ import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Language
import androidx.compose.material.icons.outlined.SignalCellularConnectedNoInternet0Bar
import androidx.compose.material.icons.rounded.ContentCopy
import androidx.compose.material.icons.rounded.CopyAll
Expand Down Expand Up @@ -63,6 +66,7 @@ import ru.tech.imageresizershrinker.core.ui.utils.helper.ImageUtils.toBitmap
import ru.tech.imageresizershrinker.core.ui.utils.helper.Picker
import ru.tech.imageresizershrinker.core.ui.utils.helper.isPortraitOrientationAsState
import ru.tech.imageresizershrinker.core.ui.utils.helper.localImagePickerMode
import ru.tech.imageresizershrinker.core.ui.utils.helper.parseFileSaveResult
import ru.tech.imageresizershrinker.core.ui.utils.helper.rememberImagePicker
import ru.tech.imageresizershrinker.core.ui.utils.provider.LocalImageLoader
import ru.tech.imageresizershrinker.core.ui.widget.AdaptiveLayoutScreen
Expand All @@ -75,6 +79,7 @@ import ru.tech.imageresizershrinker.core.ui.widget.image.AutoFilePicker
import ru.tech.imageresizershrinker.core.ui.widget.image.ImageNotPickedWidget
import ru.tech.imageresizershrinker.core.ui.widget.image.Picture
import ru.tech.imageresizershrinker.core.ui.widget.modifier.container
import ru.tech.imageresizershrinker.core.ui.widget.other.LoadingDialog
import ru.tech.imageresizershrinker.core.ui.widget.other.LocalToastHostState
import ru.tech.imageresizershrinker.core.ui.widget.other.ToastDuration
import ru.tech.imageresizershrinker.core.ui.widget.other.TopAppBarEmoji
Expand Down Expand Up @@ -201,6 +206,60 @@ fun RecognizeTextContent(

var showCropper by rememberSaveable { mutableStateOf(false) }

val exportLanguagesPicker = rememberLauncherForActivityResult(
contract = ActivityResultContracts.CreateDocument("application/zip"),
onResult = {
it?.let { uri ->
viewModel.exportLanguagesTo(uri) { result ->
context.parseFileSaveResult(
saveResult = result,
onSuccess = {
confettiHostState.showConfetti()
},
toastHostState = toastHostState,
scope = scope
)
}
}
}
)

val importLanguagesPicker = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocument(),
onResult = { uri ->
uri?.let {
viewModel.importLanguagesFrom(
uri = uri,
onSuccess = {
scope.launch {
confettiHostState.showConfetti()
}
scope.launch {
toastHostState.showToast(
message = context.getString(R.string.languages_imported),
icon = Icons.Outlined.Language
)
}
startRecognition()
},
onFailure = {
scope.launch {
toastHostState.showError(context, it)
}
}
)
}
}
)

val onExportLanguages: () -> Unit = {
exportLanguagesPicker.launch(viewModel.generateExportFilename())
}

val onImportLanguages: () -> Unit = {
importLanguagesPicker.launch(arrayOf("application/zip"))
}

AdaptiveLayoutScreen(
title = {
AnimatedContent(
Expand Down Expand Up @@ -296,10 +355,14 @@ fun RecognizeTextContent(
startRecognition()
},
onDeleteLanguage = { language, types ->
viewModel.deleteLanguage(language, types) {
startRecognition()
}
}
viewModel.deleteLanguage(
language = language,
types = types,
onSuccess = startRecognition
)
},
onImportLanguages = onImportLanguages,
onExportLanguages = onExportLanguages
)
Spacer(modifier = Modifier.height(8.dp))
OCRTextPreviewItem(
Expand Down Expand Up @@ -418,4 +481,8 @@ fun RecognizeTextContent(
selectedAspectRatio = viewModel.selectedAspectRatio,
loadImage = viewModel::loadImage
)

if (viewModel.isExporting) {
LoadingDialog()
}
}
Loading

0 comments on commit 427deaf

Please sign in to comment.