# Cámara, galería de imágenes y reconocer texto ## Dependencias Usaremos el coil y el mlkit para text-recognition por lo que en build.gradle.kts(app) debemos añadir: ``` implementation("io.coil-kt:coil-compose:2.3.0") implementation("com.google.android.gms:play-services-mlkit-text-recognition:19.0.0") ``` Y usaremos mlkit para reconocer los caracteres en las imágenes. Investiga sobre el módulo: https://developers.google.com/ml-kit?hl=es-419 Puedes incluso hacer un codela, eso sí, en java: https://codelabs.developers.google.com/codelabs/mlkit-android#0 También necesitamos ir al manifest y añadir el permiso para usar la cámara: ``` <uses-permission android:name="android.permission.CAMERA"/> ``` Dará un error...arréglalo! ``` plugins { id("com.android.application") id("org.jetbrains.kotlin.android") } android { namespace = "com.example.recognitionapp" compileSdk = 33 defaultConfig { applicationId = "com.example.recognitionapp" minSdk = 24 targetSdk = 33 versionCode = 1 versionName = "1.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { useSupportLibrary = true } } buildTypes { release { isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) } } compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = "1.8" } buildFeatures { compose = true } composeOptions { kotlinCompilerExtensionVersion = "1.4.3" } packaging { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" } } } dependencies { implementation("androidx.core:core-ktx:1.9.0") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2") implementation("androidx.activity:activity-compose:1.7.2") implementation(platform("androidx.compose:compose-bom:2023.03.00")) implementation("androidx.compose.ui:ui") implementation("androidx.compose.ui:ui-graphics") implementation("androidx.compose.ui:ui-tooling-preview") implementation("androidx.compose.material3:material3") testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00")) androidTestImplementation("androidx.compose.ui:ui-test-junit4") debugImplementation("androidx.compose.ui:ui-tooling") debugImplementation("androidx.compose.ui:ui-test-manifest") implementation("io.coil-kt:coil-compose:2.3.0") implementation("com.google.android.gms:play-services-mlkit-text-recognition:19.0.0") } ``` ## Provider Necesitamos en el manifest además incluir la figura del proveedor o provider que es lo que genera una conexión de nuestra aplicación con los elementos del dispositivo, en este caso, la cámara. Dentro de application debemos incluir lo que sigue: ``` <provider android:authorities="${applicationId}.provider" android:name="androidx.core.content.FileProvider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/path_provider" /> </provider> ``` Nos dará error porque debemos crear el @xml/path_provider, sigue las instrucciones para la creación automática y llegaremos a esta pantalla: ![image](https://hackmd.io/_uploads/Sk9Hspvtp.png) Como ves es el generador de vistas XML pero vamos a escribir sólo el código: ``` <?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-cache-path name="my_images" path="/" /> </paths> ``` Este path nos va a permitir la caché de las imágenes que generemos con la cámara. ## TabsView Vamos a usar un nuevo tipo de navegación por tabs, así que creamos los típicos package: views y viewModel. En viewModel creamos la clase ScannerViewModel y la dejamos vacía por ahora. Empezamos con las Views, vamos a crearlas vacías sólo para ver cómo funciona el tab. Creamos los siguientes files: GalleryView, CameraView, CollectionGalleryView y TabsView, en este último crearemos los tabs pero en los anteriores para simplemente ver cómo funciona pondremos el código siguiente, sustituyendo nombres por los correspondientes: ``` import androidx.compose.material3.Text import androidx.compose.runtime.Composable import com.anluisa.recognitionapp1.viewModel.ScannerViewModel @Composable fun GalleryView(viewModel:ScannerViewModel){ Text(text = "GalleryView") } ``` En el TabsView programamos la navegación: ``` @Composable fun TabsView(viewModel: ScannerViewModel){ var selectedTab by remember { mutableStateOf(0) } val tabs = listOf("Galeria","Camara","Coleccion") Column { TabRow(selectedTabIndex = selectedTab, contentColor = Color.Black, indicator = { tabPositions -> TabRowDefaults.Indicator( Modifier.tabIndicatorOffset(tabPositions[selectedTab]) ) } ) { tabs.forEachIndexed { index, title -> Tab(selected = selectedTab == index, onClick = { selectedTab = index }, text = { Text(text = title) } ) } } when(selectedTab){ 0 -> GalleryView(viewModel) 1 -> CameraView(viewModel) 2 -> CollectionGalleryView() } } } ``` Modificamos el MainActivity para que podamos ver este nuevo componente. Intenta hacerlo por ti mismo. ## Galería Vamos a trabajar sobre la galería, para ello lo primero es añadir un recurso drawable, recuerda como llegar a esta pantalla: ![image](https://hackmd.io/_uploads/SyZWHJdKa.png) Este sería el código: ``` @Composable fun GalleryView(viewModel: ScannerViewModel) { val context = LocalContext.current val clipboard = LocalClipboardManager.current var image: Any? by remember { mutableStateOf(R.drawable.gallery) } val photoPicker = rememberLauncherForActivityResult( contract = ActivityResultContracts.PickVisualMedia() ){ if (it != null){ image = it }else{ //Mostrará algo si hay errores } } Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally ) { Image( modifier = Modifier .clickable { photoPicker.launch( PickVisualMediaRequest( ActivityResultContracts.PickVisualMedia.ImageOnly ) ) } .padding(16.dp, 8.dp), painter = rememberAsyncImagePainter(image), contentDescription = null ) } } ``` Ahora mismo deberíamos poder ejecutar la aplicación y abrir la Galería. ## TextRecognizer viewModel El código es bastante sencillo, investiga cómo funciona: ``` class ScannerViewModel:ViewModel() { var recognizedText by mutableStateOf("") private set fun cleanText(){ recognizedText = "" } fun onRecognizedText(text: Any?, context: Context){ var image: InputImage? = null try { image = InputImage.fromFilePath(context, text as Uri) } catch (e: IOException){ e.printStackTrace() } image?.let { TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS).process(it) .addOnSuccessListener { text -> recognizedText = text.text }.addOnFailureListener{ showToast(context, "Error al leer la imagen") } } } fun showToast(context: Context, message : String){ Toast.makeText(context, message, Toast.LENGTH_LONG).show() } } ``` ## Tomar texto de la imagen Para poder coger el texto de la imagen debemos modificar el GalleryView como sigue: ``` @Composable fun GalleryView(viewModel: ScannerViewModel) { val context = LocalContext.current val clipboard = LocalClipboardManager.current var image: Any? by remember { mutableStateOf(R.drawable.gallery) } val photoPicker = rememberLauncherForActivityResult( contract = ActivityResultContracts.PickVisualMedia() ){ if (it != null){ image = it viewModel.onRecognizedText(image, context) }else{ viewModel.showToast(context,"No se ha seleccionado ninguna imagen") } } Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally ) { Image( modifier = Modifier .clickable { photoPicker.launch( PickVisualMediaRequest( ActivityResultContracts.PickVisualMedia.ImageOnly ) ) } .padding(16.dp, 8.dp), painter = rememberAsyncImagePainter(image), contentDescription = null ) Spacer(modifier = Modifier.height(25.dp)) val scrollState = rememberScrollState() Text(text = viewModel.recognizedText, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth() .verticalScroll(scrollState) .clickable { clipboard.setText(AnnotatedString(viewModel.recognizedText)) viewModel.showToast(context,"Copiado") } ) } } ```