Merge branch 'google-ai-edge:main' into main

This commit is contained in:
Fattire 2025-05-26 17:11:31 -07:00 committed by GitHub
commit e897b15f26
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 117 additions and 64 deletions

View file

@ -1,5 +1,5 @@
--- ---
name: Bug report name: 🐛 Bug report
about: Create a report to help us improve about: Create a report to help us improve
title: '' title: ''
labels: '' labels: ''
@ -7,16 +7,16 @@ assignees: ''
--- ---
**Describe the bug** **Describe the bug:**
A clear and concise description of what the bug is. A clear and concise description of what the bug is.
**To Reproduce** **To Reproduce:**
Steps to reproduce the behavior: Steps to reproduce the behavior:
**Expected behavior** **Expected behavior:**
A clear and concise description of what you expected to happen. A clear and concise description of what you expected to happen.
**Screenshots** **Screenshots:**
If applicable, add screenshots to help explain your problem. If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):** **Desktop (please complete the following information):**
@ -30,5 +30,5 @@ If applicable, add screenshots to help explain your problem.
- Browser [e.g. stock browser, safari] - Browser [e.g. stock browser, safari]
- Version [e.g. 22] - Version [e.g. 22]
**Additional context** **Additional context:**
Add any other context about the problem here. Add any other context about the problem here.

View file

@ -1,5 +1,5 @@
--- ---
name: Feature request name: Feature request
about: Suggest an idea for this project about: Suggest an idea for this project
title: '' title: ''
labels: '' labels: ''

View file

@ -0,0 +1,46 @@
---
name: 🆘 Support Request
about: Ask a question or get help with usage.
title: "[Support]: "
labels: ["support", "question"]
assignees: []
---
<!--
Thanks for reaching out for help! To assist you efficiently, please provide as much detail as possible.
-->
**What do you need help with?**
Is this a question about how to do something, a configuration problem, or a general issue you can't solve?
**Describe the issue/question:**
Clearly describe what you are trying to achieve, what problem you are facing, or what question you have.
**What have you tried so far? (Optional):**
List any steps you've already taken to troubleshoot, find information, or attempt a solution.
**Expected outcome (Optional):**
If applicable, what did you hope would happen, or what solution are you looking for?
**Screenshots/Videos (Optional):**
If applicable, add screenshots or a short video that might help explain your situation.
**Environment & Details:**
Please provide details about your operating environment, relevant URLs, or any messages you see.
- **Operating System:**
- **Browser & Version (if applicable):**
- **Any relevant messages (e.g., from UI, console):**
```
PASTE_ANY_MESSAGES_HERE
```
**Any additional context?:**
Is there anything else that might be useful for us to know?

View file

@ -31,7 +31,7 @@ android {
minSdk = 26 minSdk = 26
targetSdk = 36 targetSdk = 36
versionCode = 1 versionCode = 1
versionName = "1.0.2" versionName = "1.0.3"
// Needed for HuggingFace auth workflows. // Needed for HuggingFace auth workflows.
manifestPlaceholders["appAuthRedirectScheme"] = "com.google.ai.edge.gallery.oauth" manifestPlaceholders["appAuthRedirectScheme"] = "com.google.ai.edge.gallery.oauth"

View file

@ -23,6 +23,8 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.graphics.Matrix import android.graphics.Matrix
import android.net.Uri import android.net.Uri
import android.util.Log
import android.util.Size
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
@ -31,6 +33,9 @@ import androidx.camera.core.CameraControl
import androidx.camera.core.CameraSelector import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageCapture import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageProxy import androidx.camera.core.ImageProxy
import androidx.camera.core.resolutionselector.AspectRatioStrategy
import androidx.camera.core.resolutionselector.ResolutionSelector
import androidx.camera.core.resolutionselector.ResolutionStrategy
import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.lifecycle.awaitInstance import androidx.camera.lifecycle.awaitInstance
import androidx.camera.view.PreviewView import androidx.camera.view.PreviewView
@ -75,9 +80,11 @@ import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
@ -104,6 +111,8 @@ import com.google.ai.edge.gallery.ui.theme.GalleryTheme
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.concurrent.Executors import java.util.concurrent.Executors
private const val TAG = "AGMessageInputText"
/** /**
* Composable function to display a text input field for composing chat messages. * Composable function to display a text input field for composing chat messages.
* *
@ -135,7 +144,7 @@ fun MessageInputText(
var showAddContentMenu by remember { mutableStateOf(false) } var showAddContentMenu by remember { mutableStateOf(false) }
var showTextInputHistorySheet by remember { mutableStateOf(false) } var showTextInputHistorySheet by remember { mutableStateOf(false) }
var showCameraCaptureBottomSheet by remember { mutableStateOf(false) } var showCameraCaptureBottomSheet by remember { mutableStateOf(false) }
var cameraCaptureSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) val cameraCaptureSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
var tempPhotoUri by remember { mutableStateOf(value = Uri.EMPTY) } var tempPhotoUri by remember { mutableStateOf(value = Uri.EMPTY) }
var pickedImages by remember { mutableStateOf<List<Bitmap>>(listOf()) } var pickedImages by remember { mutableStateOf<List<Bitmap>>(listOf()) }
val updatePickedImages: (Bitmap) -> Unit = { bitmap -> val updatePickedImages: (Bitmap) -> Unit = { bitmap ->
@ -150,21 +159,6 @@ fun MessageInputText(
checkFrontCamera(context = context, callback = { hasFrontCamera = it }) checkFrontCamera(context = context, callback = { hasFrontCamera = it })
} }
// launches camera
val cameraLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.TakePicture()) { isImageSaved ->
if (isImageSaved) {
handleImageSelected(
context = context,
uri = tempPhotoUri,
onImageSelected = { bitmap ->
updatePickedImages(bitmap)
},
rotateForPortrait = true,
)
}
}
// Permission request when taking picture. // Permission request when taking picture.
val takePicturePermissionLauncher = rememberLauncherForActivityResult( val takePicturePermissionLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.RequestPermission() ActivityResultContracts.RequestPermission()
@ -173,7 +167,6 @@ fun MessageInputText(
showAddContentMenu = false showAddContentMenu = false
tempPhotoUri = context.createTempPictureUri() tempPhotoUri = context.createTempPictureUri()
showCameraCaptureBottomSheet = true showCameraCaptureBottomSheet = true
// cameraLauncher.launch(tempPhotoUri)
} }
} }
@ -270,7 +263,6 @@ fun MessageInputText(
showAddContentMenu = false showAddContentMenu = false
tempPhotoUri = context.createTempPictureUri() tempPhotoUri = context.createTempPictureUri()
showCameraCaptureBottomSheet = true showCameraCaptureBottomSheet = true
// cameraLauncher.launch(tempPhotoUri)
} }
// Otherwise, ask for permission // Otherwise, ask for permission
@ -416,20 +408,32 @@ fun MessageInputText(
val lifecycleOwner = LocalLifecycleOwner.current val lifecycleOwner = LocalLifecycleOwner.current
val previewUseCase = remember { androidx.camera.core.Preview.Builder().build() } val previewUseCase = remember { androidx.camera.core.Preview.Builder().build() }
val imageCaptureUseCase = remember { ImageCapture.Builder().build() } val imageCaptureUseCase = remember {
// Try to limit the image size.
val preferredSize = Size(512, 512)
val resolutionStrategy = ResolutionStrategy(
preferredSize,
ResolutionStrategy.FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER
)
val resolutionSelector = ResolutionSelector.Builder()
.setResolutionStrategy(resolutionStrategy)
.setAspectRatioStrategy(AspectRatioStrategy.RATIO_4_3_FALLBACK_AUTO_STRATEGY)
.build()
ImageCapture.Builder().setResolutionSelector(resolutionSelector).build()
}
var cameraProvider by remember { mutableStateOf<ProcessCameraProvider?>(null) } var cameraProvider by remember { mutableStateOf<ProcessCameraProvider?>(null) }
var cameraControl by remember { mutableStateOf<CameraControl?>(null) } var cameraControl by remember { mutableStateOf<CameraControl?>(null) }
val localContext = LocalContext.current val localContext = LocalContext.current
var cameraSide by remember { mutableStateOf(CameraSelector.LENS_FACING_BACK) } var cameraSide by remember { mutableIntStateOf(CameraSelector.LENS_FACING_BACK) }
val executor = remember { Executors.newSingleThreadExecutor() } val executor = remember { Executors.newSingleThreadExecutor() }
val capturedImageUri = remember { mutableStateOf<Uri?>(null) }
fun rebindCameraProvider() { fun rebindCameraProvider() {
cameraProvider?.let { cameraProvider -> cameraProvider?.let { cameraProvider ->
val cameraSelector = CameraSelector.Builder() val cameraSelector = CameraSelector.Builder()
.requireLensFacing(cameraSide) .requireLensFacing(cameraSide)
.build() .build()
try {
cameraProvider.unbindAll() cameraProvider.unbindAll()
val camera = cameraProvider.bindToLifecycle( val camera = cameraProvider.bindToLifecycle(
lifecycleOwner = lifecycleOwner, lifecycleOwner = lifecycleOwner,
@ -438,6 +442,9 @@ fun MessageInputText(
imageCaptureUseCase imageCaptureUseCase
) )
cameraControl = camera.cameraControl cameraControl = camera.cameraControl
} catch (e: Exception) {
Log.d(TAG, "Failed to bind camera", e)
}
} }
} }
@ -450,31 +457,25 @@ fun MessageInputText(
rebindCameraProvider() rebindCameraProvider()
} }
// val cameraController = remember { DisposableEffect(Unit) { // Or key on lifecycleOwner if it makes more sense
// LifecycleCameraController(context).apply { onDispose {
// bindToLifecycle(lifecycleOwner) cameraProvider?.unbindAll() // Unbind all use cases from the camera provider
// } if (!executor.isShutdown) {
// } executor.shutdown() // Shut down the executor service
}
}
}
Box(modifier = Modifier.fillMaxSize()) { Box(modifier = Modifier.fillMaxSize()) {
// PreviewView for the camera feed. // PreviewView for the camera feed.
AndroidView( AndroidView(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
factory = { ctx -> factory = { ctx ->
PreviewView(context).also { PreviewView(ctx).also {
previewUseCase.surfaceProvider = it.surfaceProvider previewUseCase.surfaceProvider = it.surfaceProvider
rebindCameraProvider() rebindCameraProvider()
} }
// PreviewView(ctx).apply {
// scaleType = PreviewView.ScaleType.FILL_START
// implementationMode = PreviewView.ImplementationMode.COMPATIBLE
// controller = cameraController // Attach the lifecycle-aware camera controller.
// }
}, },
// onRelease = {
// // Called when the PreviewView is removed from the composable hierarchy
// cameraController.unbind() // Unbinds the camera to free up resources
// }
) )
// Close button. // Close button.
@ -508,25 +509,31 @@ fun MessageInputText(
.size(64.dp) .size(64.dp)
.border(2.dp, MaterialTheme.colorScheme.onPrimary, CircleShape), .border(2.dp, MaterialTheme.colorScheme.onPrimary, CircleShape),
onClick = { onClick = {
scope.launch {
val callback = object : ImageCapture.OnImageCapturedCallback() { val callback = object : ImageCapture.OnImageCapturedCallback() {
override fun onCaptureSuccess(image: ImageProxy) { override fun onCaptureSuccess(image: ImageProxy) {
try {
var bitmap = image.toBitmap() var bitmap = image.toBitmap()
val rotation = image.imageInfo.rotationDegrees val rotation = image.imageInfo.rotationDegrees
bitmap = if (rotation != 0) { bitmap = if (rotation != 0) {
val matrix = Matrix().apply { val matrix = Matrix().apply {
postRotate(rotation.toFloat()) postRotate(rotation.toFloat())
} }
Log.d(TAG, "image size: ${bitmap.width}, ${bitmap.height}")
Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true) Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
} else bitmap } else bitmap
updatePickedImages(bitmap) updatePickedImages(bitmap)
} catch (e: Exception) {
Log.e(TAG, "Failed to process image", e)
} finally {
image.close() image.close()
} scope.launch {
}
imageCaptureUseCase.takePicture(executor, callback)
cameraCaptureSheetState.hide() cameraCaptureSheetState.hide()
showCameraCaptureBottomSheet = false showCameraCaptureBottomSheet = false
} }
}
}
}
imageCaptureUseCase.takePicture(executor, callback)
}, },
) { ) {
Icon( Icon(
@ -601,7 +608,7 @@ private fun rotateImageIfNecessary(bitmap: Bitmap, rotateForPortrait: Boolean =
private fun checkFrontCamera(context: Context, callback: (Boolean) -> Unit) { private fun checkFrontCamera(context: Context, callback: (Boolean) -> Unit) {
val cameraProviderFuture = ProcessCameraProvider.getInstance(context) val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
cameraProviderFuture.addListener(Runnable { cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get() val cameraProvider = cameraProviderFuture.get()
try { try {
// Attempt to select the default front camera // Attempt to select the default front camera