Files
photobooth-app/31_FIX_UXUI.md
T

9.2 KiB

Dưới đây là kế hoạch Markdown chi tiết giúp bạn tách biệt rạch ròi giữa Hiệu ứng xóa phông AILấy nét vật lý phần cứng, giải quyết tận gốc lỗi lag ngầm, đồng thời tái cấu trúc lại hệ thống quản lý thuộc tính Presets để bổ sung đầy đủ các thông số ánh sáng và sắc độ.


📊 KẾ HOẠCH TOÀN DIỆN: ĐỒNG BỘ FOCUS VẬT LÝ, TỐI ƯU LUỒNG BLUR AI VÀ CẤU TRÚC LẠI PRESETS

Mục tiêu cốt lõi:

  1. Triệt tiêu lag ngầm khi tắt Blur AI: Sửa lỗi hệ thống vẫn chạy ngầm vòng lặp pixel đồ họa của multiclass_segmenter.tflite gây lag mặc dù giao diện không hiển thị hiệu ứng.
  2. Đồng bộ Focus vật lý: Giữ nguyên cơ chế lấy nét quang học qua Camera2 API, loại bỏ hoàn toàn sự can thiệp của thuật toán mờ phần mềm lên vật thể.
  3. Mở rộng thuộc tính Presets: Định nghĩa lại cấu trúc dữ liệu cấu hình màu sắc, bổ sung các thông số chuyên sâu: Highlight, Shadow, Saturation, Contrast, Brightness.

🛠️ 1. Khắc Phục Lỗi Lag Ngầm Của Bộ Xử Lý Blur AI (MainActivity.kt)

Nguyên nhân gây lag: Khi bạn tắt chế độ xóa nền AI nhưng tăng thước trượt Blur, ứng dụng hiển thị như thể không dùng AI, nhưng ở tầng mã nguồn Native, điều kiện kiểm tra (if/else) chưa chuẩn khiến hàm processMediaPipeOnlyBokeh() vẫn bị gọi liên tục dưới nền, ép CPU xử lý ma trận ByteBuffer vô ích.

🟢 Giải pháp: Thiết lập cờ trạng thái hiệu ứng (Feature Flags) để ngắt luồng AI tuyệt đối

// Định nghĩa các chế độ hoạt động của ứng dụng Photobooth
enum class BackgroundMode {
    DEFAULT,       // Giữ nguyên ảnh gốc thô của Camera
    HARDWARE_DOF,  // Chỉ dùng Focus vật lý của thấu kính
    AI_BLUR        // Dùng AI tách nền và làm mờ phần mềm
}

var currentBackgroundMode = BackgroundMode.DEFAULT

fun handleImageStreamPipeline(capturedBitmap: Bitmap, rawMaskBuffer: ByteBuffer, rulerValue: Int): Bitmap {
    // SỬA LỖI LAG NGẦM: Nếu không bật chế độ AI_BLUR hoặc thước trượt bằng 0, 
    // trả về ngay lập tức ảnh thô, chặn đứng hoàn toàn vòng lặp CPU.
    if (currentBackgroundMode != BackgroundMode.AI_BLUR || rulerValue == 0) {
        return capturedBitmap 
    }

    // Chỉ khi bật AI_BLUR và rulerValue > 0 thì mới gánh luồng xử lý nặng này
    return processMediaPipeOnlyBokeh(capturedBitmap, rawMaskBuffer, rulerValue)
}


🎯 2. Quy Hoạch Luồng Focus Vật Lý Khi Chạm Khung Ngắm

Khi chuyển sang tab "Focus Vật lý", chúng ta chuyển trạng thái hệ thống về BackgroundMode.HARDWARE_DOF. Lúc này, hệ thống bỏ qua toàn bộ mặt nạ phân lớp của Google MediaPipe, giao phó 100% việc tạo độ sâu trường ảnh (DoF) tự nhiên cho mô-tơ thấu kính của thiết bị di động.

  • Viewfinder: Gọi lệnh applyHardwareTouchToFocus() qua Camera2 API gửi tọa độ vùng đo tiêu cự MeteringRectangle xuống phần cứng thấu kính.
  • Ảnh xuất xưởng: Nhận trực tiếp Bitmap thô từ cảm biến camera để lưu, đảm bảo ảnh trung thực như máy ảnh cơ, không bị lỗi lẹm viền hay tạo bóng ma (Ghost Shadow) quanh cơ thể.

🎨 3. Cấu Trúc Lại Hệ Thống Thuộc Tính Presets Màu Sắc

Để các Presets (bộ lọc màu) chỉnh sửa đúng các thuộc tính ánh sáng và sắc độ, chúng ta cần chuyển đổi cấu trúc dữ liệu từ dạng chuỗi (String) đơn giản sang một đối tượng cấu hình có cấu trúc dữ liệu cụ thể (Structured Configuration Class).

📐 3.1. Thiết kế Model Dữ Liệu Bộ Màu (PresetModel.kt)

data class ColorPreset(
    val id: String,
    val name: String,
    // --- Thuộc tính ánh sáng ---
    val brightness: Float, // -1.0f đến 1.0f (Mặc định 0.0f)
    val contrast: Float,   // 0.5f đến 2.0f  (Mặc định 1.0f)
    val highlight: Float,  // -1.0f đến 1.0f (Mặc định 0.0f) -> Mới bổ sung
    val shadow: Float,     // -1.0f đến 1.0f (Mặc định 0.0f) -> Mới bổ sung
    // --- Thuộc tính sắc độ & màu sắc ---
    val saturation: Float, // 0.0f đến 2.0f  (Mặc định 1.0f)
    val temperature: Float // -0.5f đến 0.5f (Mặc định 0.0f) (Ấm/Lạnh)
)

💻 3.2. Thuật Toán Áp Dụng Thông Số Preset Lên Điểm Ảnh (Color Toning Engine)

Thuật toán xử lý ma trận màu sắc dưới đây nhận cấu hình từ các thuộc tính trên để biến đổi hệ màu RGB của bức ảnh:

fun applyAdvancedPreset(inputBitmap: Bitmap, preset: ColorPreset): Bitmap {
    val w = inputBitmap.width
    val h = inputBitmap.height
    val outputBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)

    for (y in 0 until h) {
        for (x in 0 until w) {
            val pixel = inputBitmap.getPixel(x, y)
            
            // Trích xuất kênh màu gốc
            var r = (pixel shr 16 and 0xFF) / 255.0f
            var g = (pixel shr 8 and 0xFF) / 255.0f
            var b = (pixel and 0xFF) / 255.0f

            // 1. XỬ LÝ ÁNH SÁNG (Brightness & Contrast)
            r = (r - 0.5f) * preset.contrast + 0.5f + preset.brightness
            g = (g - 0.5f) * preset.contrast + 0.5f + preset.brightness
            b = (b - 0.5f) * preset.contrast + 0.5f + preset.brightness

            // Tính toán độ sáng tổng thể của pixel (Luminance) để định vị vùng Highlight/Shadow
            val luminance = 0.2126f * r + 0.7152f * g + 0.0722f * b

            // 2. BỔ SUNG: XỬ LÝ HIGHLIGHT & SHADOW
            if (luminance > 0.6f) { 
                // Vùng sáng (Highlight): Tăng/Giảm độ sáng dựa trên thông số highlight của Preset
                val weight = (luminance - 0.6f) / 0.4f
                r += preset.highlight * weight
                g += preset.highlight * weight
                b += preset.highlight * weight
            } else if (luminance < 0.4f) {
                // Vùng tối (Shadow): Bù sáng hoặc dìm tối dựa trên thông số shadow của Preset
                val weight = (0.4f - luminance) / 0.4f
                r += preset.shadow * weight
                g += preset.shadow * weight
                b += preset.shadow * weight
            }

            // 3. XỬ LÝ SẮC ĐỘ (Saturation)
            val gray = luminance
            r = gray + (r - gray) * preset.saturation
            g = gray + (g - gray) * preset.saturation
            b = gray + (b - gray) * preset.saturation

            // Ép dải màu về giới hạn an toàn [0.0, 1.0] trước khi đóng gói lại pixel
            val finalR = (r.coerceIn(0.0f, 1.0f) * 255).toInt()
            val finalG = (g.coerceIn(0.0f, 1.0f) * 255).toInt()
            val finalB = (b.coerceIn(0.0f, 1.0f) * 255).toInt()

            outputBitmap.setPixel(x, y, (0xFF shl 24) or (finalR shl 16) or (finalG shl 8) or finalB)
        }
    }
    return outputBitmap
}


🌐 4. Quản Lý Đồng Bộ File Định Cấu Hình Mẫu (PresetsStore)

Đối với tính năng Tải thêm từ Server trực tuyến (Online Store) qua nút ... đã lên kế hoạch từ trước, file cấu hình mẫu manifest.json trên Server của bạn sẽ được bổ sung các thuộc tính mới đồng bộ như sau:

{
  "presets": [
    {
      "id": "preset_kodak_gold",
      "name": "Kodak Gold 200",
      "url": "https://yourserver.com/assets/presets/kodak_gold.json",
      "version": 1,
      "properties": {
        "brightness": 0.05,
        "contrast": 1.15,
        "highlight": -0.10,
        "shadow": 0.15,
        "saturation": 1.20,
        "temperature": 0.08
      }
    }
  ]
}


📅 5. Kịch Bản Kiểm Thử Đảm Bảo Chất Lượng (QA Checklist)

  • Kiểm thử triệt tiêu lag ngầm (Lag Isolation Test): Tắt tính năng Xóa nền AI (chuyển sang chế độ Mặc định/Focus vật lý). Kéo thanh trượt Ruler lên tối đa +100 và di chuyển máy liên tục. (Yêu cầu: Kính ngắm phải mượt mà tuyệt đối 60 FPS, không được khựng giật vì hàm xử lý AI lúc này đã bị khóa chặn hoàn toàn bằng cờ trạng thái).
  • Kiểm thử Focus vật lý (Hardware Lens Shift): Chạm vào các vùng khoảng cách khác nhau (Gần/Xa) trên Viewfinder xem ống kính camera có dịch chuyển mô-tơ lấy nét vật lý chính xác không, ảnh thu được không được phép có vết cắt giả tạo hay răng cưa phần mềm xung quanh tóc.
  • Kiểm thử thông số Highlight/Shadow trên Preset: Áp dụng một bộ màu có thông số highlight: -0.3shadow: +0.2 lên một bức ảnh có vùng trời quá sáng và vùng tối khuất bóng. (Yêu cầu: Chi tiết vùng mây trên trời được dìm sáng rõ nét lại, vùng tối khuất sau lưng được cứu sáng mượt mà).