Files
photobooth-app/30_FOCUS_API.md

6.3 KiB

🟢 Giải pháp triệt để:

Khi đã dùng Focus vật lý, nhiệm vụ của AI lúc này không phải là đi làm mờ (Blur) nữa, mà chỉ đóng vai trò là "Mắt thần" hỗ trợ điều khiển động cơ thấu kính di chuyển đến đúng vị trí (CONTROL_AF_MODE). Đồng thời, ta phải tắt hoàn toàn các hàm boxBlur hay trộn Alpha Mask để trả lại chất lượng thấu kính tự nhiên nhất.


🛠️ KẾ HOẠCH MÀU SẮC & PHẦN CỨNG: CHUYỂN ĐỔI SANG FOCUS VẬT LÝ CAMERA2 API


📐 1. Tắt Hoàn Toàn Thuật Toán Xử Lý Đồ Họa Phần Mềm (MainActivity.kt)

Bước đầu tiên, chúng ta gỡ bỏ hoàn toàn hàm trộn pixel và làm mờ phông nền nhân tạo để giải phóng hoàn toàn CPU/GPU và triệt tiêu lỗi răng cưa ở tóc.

fun processHardwareFocusPipeline(capturedBitmap: Bitmap): Bitmap {
    // ❌ XÓA HOÀN TOÀN: boxBlur(), alphaMask, getNearestPureBackgroundPixel...
    // 🟢 TRẢ VỀ: Ảnh thô 100% thu được từ cảm biến thấu kính camera vật lý
    return capturedBitmap 
}


💻 2. Cấu Hình Điều Khiển Thấu Kính Vật Lý Khi Chạm Khung Ngắm (MainActivity.kt)

Chúng ta sẽ sử dụng gói Android Camera2 API để gửi lệnh ép thấu kính dịch chuyển (Lens Vector Focus) dựa trên vị trí ngón tay chạm trên màn hình.

💻 Logic khóa nét vật lý theo tọa độ chạm:

import android.hardware.camera2.CaptureRequest
import android.hardware.camera2.params.MeteringRectangle
import android.hardware.camera2.CameraMetadata

fun applyHardwareTouchToFocus(
    previewRequestBuilder: CaptureRequest.Builder,
    captureSession: CameraCaptureSession,
    percentX: Float,
    percentY: Float
) {
    // 1. Chuyển đổi tỷ lệ chạm % về tọa độ ma trận cảm biến của phần cứng camera (Sensor Active Array)
    val sensorRegion = cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE)
    val sensorW = sensorRegion?.width() ?: 2000
    val sensorH = sensorRegion?.height() ?: 1500

    val focusX = (percentX * sensorW).toInt().coerceIn(0, sensorW)
    val focusY = (percentY * sensorH).toInt().coerceIn(0, sensorH)

    // Thiết lập vùng lấy nét vật lý (kích thước ô vuông lấy mốc đo tiêu cự là 100x100 pixel)
    val focusAreaWeight = 1000 // Độ ưu tiên lấy nét cao nhất
    val focusRectangle = MeteringRectangle(
        Math.max(focusX - 50, 0),
        Math.max(focusY - 50, 0),
        100,
        100,
        focusAreaWeight
    )

    try {
        // 2. HỦY CHẾ ĐỘ TỰ ĐỘNG LẤY NÉT KHÔNG ĐỊNH HƯỚNG (CANCEL AF)
        previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL)
        captureSession.capture(previewRequestBuilder.build(), null, null)

        // 3. ÉP CAMERA ĐIỀU KHIỂN ĐỘNG CƠ THẤU KÍNH ĐẾN TỌA ĐỘ VẬT LÝ MỚI
        previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO)
        previewRequestBuilder.set(CaptureRequest.CONTROL_AF_REGIONS, arrayOf(focusRectangle))
        previewRequestBuilder.set(CaptureRequest.CONTROL_AE_REGIONS, arrayOf(focusRectangle)) // Đồng bộ đo sáng tại điểm chạm
        
        // Kích hoạt lệnh di chuyển thấu kính (START AF TRIGGER)
        previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START)
        
        // Gửi lệnh xuống phần cứng camera cập nhật Preview lập tức
        captureSession.setRepeatingRequest(previewRequestBuilder.build(), null, null)
        
    } catch (e: Exception) {
        e.printStackTrace()
    }
}


3. Đồng Bộ Sang Tầng Giao Diện Người Dùng (CameraScreen.dart)

Trên tầng giao diện Flutter, gỡ bỏ hoàn toàn các widget bộ lọc phần mềm như BackdropFilter hay Container đục lỗ làm mờ cũ (vì chúng gây lag và không đúng thực tế). Chỉ giữ lại Vòng tròn lấy nét vật lý:

Widget _buildHardwareFocusViewfinder() {
  return GestureDetector(
    onTapUp: (TapUpDetails details) {
      final RenderBox box = context.findRenderObject() as RenderBox;
      final Offset localPosition = box.globalToLocal(details.globalPosition);
      
      double percentX = (localPosition.dx / box.size.width).clamp(0.0, 1.0);
      double percentY = (localPosition.dy / box.size.height).clamp(0.0, 1.0);

      setState(() {
        _focusTouchPosition = localPosition;
      });

      // Gọi lệnh Native ép mô-tơ thấu kính di chuyển vật lý
      _sendHardwareFocusCommand(percentX, percentY);

      Future.delayed(const Duration(milliseconds: 800), () {
        if (mounted) setState(() { _focusTouchPosition = null; });
      });
    },
    child: Stack(
      children: [
        CameraPreview(_cameraController), // Kính ngắm chạy thuần 100% phần cứng, 60 FPS tuyệt đối trơn tru
        if (_focusTouchPosition != null)
          Positioned(
            left: _focusTouchPosition!.dx - 25,
            top: _focusTouchPosition!.dy - 25,
            child: const HardwareFocusRing(), // Hiện vòng tròn màu vàng/xanh khóa nét nhiếp ảnh
          ),
      ],
    ),
  );
}


📅 4. Checklist Xác Minh Sau Khi Sửa Lỗi (Verification Checklist)

  • Xác minh 1: Chạm vào khuôn mặt. Thấu kính camera sẽ di chuyển lấy nét vào mặt. Kiểm tra phần áo ba lỗ và hai bên vai xem đã nét đều và tự nhiên theo cơ thể chưa (Yêu cầu: Không còn vệt mờ phần mềm đè lên vai).
  • Xác minh 2: Chạm vào song sắt cửa sổ ở hậu cảnh phía sau. Thấu kính vật lý sẽ lùi ra sau lấy nét vào cửa sổ, khiến khuôn mặt bạn mờ nhòe đi một cách quang học tự nhiên nhất. (Yêu cầu: Phần viền đầu tóc không còn các vệt bóc tách AI màu xám loang lổ).
  • Kiểm thử độ lag: Vuốt màn hình và bấm lấy nét liên tục xem Viewfinder có bị giật hay đứng hình không. (Yêu cầu: Khung hình chạy mượt mà 60 FPS ổn định hoàn toàn do CPU không còn phải gánh bất kỳ vòng lặp pixel nào).