Fix 29_FIX_FOCUS: semantic focus plane (person ID1+ID2 default), dedicated photoProcessExecutor prevents viewfinder freeze after shutter

This commit is contained in:
2026-07-06 20:53:25 +07:00
parent e8fde43890
commit cd61e09042
2 changed files with 222 additions and 12 deletions
@@ -31,6 +31,8 @@ import java.nio.ByteBuffer
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var cameraExecutor: ExecutorService
// Luồng xử lý ảnh độc lập - tránh chiếm dụng cameraExecutor gây freeze viewfinder
private lateinit var photoProcessExecutor: ExecutorService
// Camera settings state
private var lensFacing = CameraSelector.DEFAULT_FRONT_CAMERA // Default front camera
@@ -136,6 +138,7 @@ class MainActivity : AppCompatActivity() {
}
cameraExecutor = Executors.newSingleThreadExecutor()
photoProcessExecutor = Executors.newSingleThreadExecutor()
}
private fun initFrameData() {
@@ -1381,19 +1384,18 @@ class MainActivity : AppCompatActivity() {
val tempFile = java.io.File.createTempFile("raw_capture_", ".jpg", cacheDir)
val outputOptions = ImageCapture.OutputFileOptions.Builder(tempFile).build()
// Dùng ContextCompat.getMainExecutor để callback nhanh, rồi chuyển xử lý nặng sang photoProcessExecutor
imageCapture.takePicture(
outputOptions,
cameraExecutor,
ContextCompat.getMainExecutor(this),
object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
// Update gallery button thumbnail instantly
// Update gallery button thumbnail instantly on UI thread
try {
val rawBitmap = android.graphics.BitmapFactory.decodeFile(tempFile.absolutePath)
if (rawBitmap != null) {
val thumb = android.graphics.Bitmap.createScaledBitmap(rawBitmap, 128, 128, true)
runOnUiThread {
binding.btnGalleryReview.setImageBitmap(thumb)
}
binding.btnGalleryReview.setImageBitmap(thumb)
if (thumb != rawBitmap) {
rawBitmap.recycle()
}
@@ -1402,14 +1404,15 @@ class MainActivity : AppCompatActivity() {
// Ignore thumbnail decoding errors
}
// Process and save in the background
processAndSaveCapturedPhoto(tempFile)
// ĐẨY TOÀN BỘ XỬ LÝ NẶNG (AI + LƯU ẢNH) SANG LUỒNG ĐỘC LẬP
// cameraExecutor giờ rảnh hoàn toàn để tiếp tục chạy preview 60fps
photoProcessExecutor.execute {
processAndSaveCapturedPhoto(tempFile)
}
}
override fun onError(exception: ImageCaptureException) {
runOnUiThread {
Toast.makeText(this@MainActivity, "Lỗi chụp ảnh: ${exception.message}", Toast.LENGTH_SHORT).show()
}
Toast.makeText(this@MainActivity, "Lỗi chụp ảnh: ${exception.message}", Toast.LENGTH_SHORT).show()
if (tempFile.exists()) {
tempFile.delete()
}
@@ -1748,9 +1751,11 @@ class MainActivity : AppCompatActivity() {
for (i in 0 until (w * h)) {
val classId = if (i < bufferCapacity) rawBytes[i].toInt() else 0
alphaMask[i] = if (focusClassId >= 0) {
// Người dùng đã chạm chọn: chỉ lớp đúng class ID mới nét (Semantic Focus Plane)
if (classId == focusClassId) 1.0f else 0.0f
} else {
if (classId in 1..5) 1.0f else 0.0f
// Mặc định: Giữ nét toàn bộ Người (ID 1) và Phụ kiện cận cảnh (ID 2)
if (classId == 1 || classId == 2) 1.0f else 0.0f
}
}
blurAlphaMaskBox(alphaMask, w, h, radius = 3)
@@ -1867,9 +1872,11 @@ class MainActivity : AppCompatActivity() {
for (i in 0 until (w * h)) {
val classId = if (i < bufferCapacity) categoryMaskBuffer.get(i).toInt() else 0
alphaMask[i] = if (focusClassId >= 0) {
// Người dùng đã chạm chọn: chỉ lớp đúng class ID mới nét (Semantic Focus Plane)
if (classId == focusClassId) 1.0f else 0.0f
} else {
if (classId in 1..5) 1.0f else 0.0f
// Mặc định: Giữ nét toàn bộ Người (ID 1) và Phụ kiện cận cảnh (ID 2)
if (classId == 1 || classId == 2) 1.0f else 0.0f
}
}
blurAlphaMaskBox(alphaMask, w, h, radius = 5)
@@ -2154,6 +2161,7 @@ class MainActivity : AppCompatActivity() {
override fun onDestroy() {
super.onDestroy()
cameraExecutor.shutdown()
photoProcessExecutor.shutdown()
try {
imageSegmenter?.close()
} catch (e: Exception) {