mirror of
https://github.com/google-ai-edge/gallery.git
synced 2025-07-17 19:56:41 -04:00
Merge branch 'google-ai-edge:main' into main
This commit is contained in:
commit
e897b15f26
5 changed files with 117 additions and 64 deletions
12
.github/ISSUE_TEMPLATE/bug_report.md
vendored
12
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -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.
|
||||||
|
|
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
@ -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: ''
|
||||||
|
|
46
.github/ISSUE_TEMPLATE/support_request.md
vendored
Normal file
46
.github/ISSUE_TEMPLATE/support_request.md
vendored
Normal 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?
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue