Implement Touch-to-Focus (28_FOCUS_POINT.md): cache MediaPipe category mask, add focus ring view, class-based alpha selection

This commit is contained in:
2026-07-06 20:40:19 +07:00
parent 6636cdb418
commit e8fde43890
4 changed files with 344 additions and 5 deletions
@@ -69,6 +69,13 @@ class MainActivity : AppCompatActivity() {
private var imageSegmenter: ImageSegmenter? = null
private var isAiSupported = true
// Touch-to-Focus: ID lớp đối tượng được chạm chọn lấy nét (-1 = mặc định lấy nét người ID 1)
private var userSelectedClassFocus = -1
// Cache mặt nạ phân lớp mới nhất từ MediaPipe để phục vụ Touch-to-Focus
private var cachedCategoryMask: ByteArray? = null
private var cachedMaskWidth = 0
private var cachedMaskHeight = 0
private fun getImageSegmenter(): ImageSegmenter? {
synchronized(aiInferenceLock) {
if (!isAiSupported) return null
@@ -705,6 +712,42 @@ class MainActivity : AppCompatActivity() {
showSavePresetDialog(preset)
}
}
// Touch-to-Focus: Chạm vào kính ngắm để chọn điểm lấy nét
binding.viewFinder.setOnTouchListener { v, event ->
if (event.action == android.view.MotionEvent.ACTION_UP) {
val percentX = (event.x / v.width).coerceIn(0f, 1f)
val percentY = (event.y / v.height).coerceIn(0f, 1f)
// Cập nhật Class ID lấy nét từ điểm chạm
val mask = cachedCategoryMask
val mW = cachedMaskWidth
val mH = cachedMaskHeight
if (mask != null && mW > 0 && mH > 0) {
val maskX = (percentX * mW).toInt().coerceIn(0, mW - 1)
val maskY = (percentY * mH).toInt().coerceIn(0, mH - 1)
userSelectedClassFocus = mask[maskY * mW + maskX].toInt()
}
// Hiện vòng tròn lấy nét màu cam tại vị trí chạm
val focusRing = binding.viewFocusRing
focusRing.x = event.x - focusRing.width / 2f
focusRing.y = event.y - focusRing.height / 2f
focusRing.visibility = android.view.View.VISIBLE
focusRing.alpha = 1f
focusRing.scaleX = 1.4f
focusRing.scaleY = 1.4f
focusRing.animate()
.scaleX(1f).scaleY(1f)
.alpha(0f)
.setDuration(900)
.withEndAction { focusRing.visibility = android.view.View.GONE }
.start()
v.performClick()
}
true
}
}
private fun translateButtonToDock(button: android.view.View) {
@@ -1689,12 +1732,26 @@ class MainActivity : AppCompatActivity() {
val w = categoryMask.width
val h = categoryMask.height
val bufferCapacity = categoryMaskBuffer.capacity()
// Cache mask bytes for Touch-to-Focus
val rawBytes = ByteArray(bufferCapacity)
categoryMaskBuffer.rewind()
categoryMaskBuffer.get(rawBytes)
cachedCategoryMask = rawBytes
cachedMaskWidth = w
cachedMaskHeight = h
categoryMaskBuffer.rewind()
// Create and blur alpha mask
// Create and blur alpha mask with Touch-to-Focus class selection
val focusClassId = if (userSelectedClassFocus >= 0) userSelectedClassFocus else -1
val alphaMask = FloatArray(w * h)
for (i in 0 until (w * h)) {
val classId = if (i < bufferCapacity) categoryMaskBuffer.get(i).toInt() else 0
alphaMask[i] = if (classId in 1..5) 1.0f else 0.0f
val classId = if (i < bufferCapacity) rawBytes[i].toInt() else 0
alphaMask[i] = if (focusClassId >= 0) {
if (classId == focusClassId) 1.0f else 0.0f
} else {
if (classId in 1..5) 1.0f else 0.0f
}
}
blurAlphaMaskBox(alphaMask, w, h, radius = 3)
@@ -1804,11 +1861,16 @@ class MainActivity : AppCompatActivity() {
val h = categoryMask.height
val bufferCapacity = categoryMaskBuffer.capacity()
// Create and blur alpha mask
// Create and blur alpha mask with Touch-to-Focus class selection
val focusClassId = if (userSelectedClassFocus >= 0) userSelectedClassFocus else -1
val alphaMask = FloatArray(w * h)
for (i in 0 until (w * h)) {
val classId = if (i < bufferCapacity) categoryMaskBuffer.get(i).toInt() else 0
alphaMask[i] = if (classId in 1..5) 1.0f else 0.0f
alphaMask[i] = if (focusClassId >= 0) {
if (classId == focusClassId) 1.0f else 0.0f
} else {
if (classId in 1..5) 1.0f else 0.0f
}
}
blurAlphaMaskBox(alphaMask, w, h, radius = 5)
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<stroke
android:width="2dp"
android:color="#FFCC00" />
<solid android:color="#00000000" />
</shape>
@@ -143,6 +143,14 @@
android:visibility="gone"
android:text="3" />
<!-- Vòng tròn định vị lấy nét (Focus Ring) khi chạm vào Viewfinder -->
<View
android:id="@+id/viewFocusRing"
android:layout_width="60dp"
android:layout_height="60dp"
android:background="@drawable/bg_focus_ring"
android:visibility="gone" />
</FrameLayout>
<!-- ================= KHỐI 1: TIMELINE FRAMES & PRESETS ================= -->