Files
photobooth-app/19_FIX_BG.md

7.2 KiB

Dựa trên các vấn đề bạn vừa nêu, nguyên nhân cốt lõi nằm ở việc quản lý trạng thái (State Management) giữa các tab chưa độc lập (khiến bộ lọc này đè/xóa bộ lọc kia) và ngưỡng lọc AI (Mask Thresholding) quá gắt ở vùng biên.

Dưới đây là kế hoạch Markdown chi tiết để khắc phục triệt để các lỗi trên trong dự án photobooth-app.


🛠️ KẾ HOẠCH KHẮC PHỤC LỖI: GIỮ TRẠNG THÁI NỀN, TỐI ƯU BIÊN AI & GỠ BỎ TOAST THÔNG BÁO

Mục tiêu chính:

  1. Sửa lỗi mất nền: Giữ nguyên ảnh nền đã chèn (BG Replace) khi người dùng quay lại tab Xóa nền (Portrait Blur), cho phép vừa thay nền vừa làm mờ (Blur) chính nền mới đó theo giá trị Ruler.
  2. Sửa lỗi cắt nhầm tai/cổ/tóc: Nới lỏng vùng đệm Alpha, áp dụng bộ lọc mượt viền (Anti-aliasing) để tránh AI "gọt" mất các chi tiết cơ thể.
  3. Gỡ bỏ UI thừa: Xóa hoàn toàn hàm hiển thị thông báo "Đã lưu vào thư viện".

🔄 1. Sửa Lỗi Mất Ảnh Nền Khi Tăng Giảm Mức Độ Blur

🔴 Nguyên nhân:

Trong logic cũ, biến trạng thái _selectedBgMode chỉ nhận một giá trị duy nhất tại một thời điểm (blur hoặc replace). Khi bạn quay lại chỉnh blur, hệ thống chuyển _selectedBgMode = BackgroundMode.blur và vô tình làm mất đường dẫn ảnh nền _selectedBgAssetPath.

🟢 Giải pháp: tách biệt hai trạng thái cấu hình song song

Thay vì dùng enum loại trừ nhau, ta quản lý độc lập: Có thay nền hay không?Mức độ làm mờ nền là bao nhiêu?

// Cấu trúc trạng thái mới (Tách biệt độc lập)
bool _isBgReplacementActive = false; // Bật/tắt chế độ thay nền
String? _selectedBgAssetPath;        // Đường dẫn ảnh nền nếu có
double _blurIntensity = 0.0;          // Mức độ blur nền (Áp dụng cho cả nền gốc hoặc nền mới)

💻 Cập nhật lại Logic xử lý Phông nền trong Engine:

// Trong hàm xử lý đồ họa ngầm:
img.Image bgImage;

if (_isBgReplacementActive && _selectedBgAssetPath != null) {
  // 1. Nếu người dùng ĐÃ CHỌN THAY NỀN -> Lấy ảnh nền mới làm nền
  final ByteData bgData = await rootBundle.load(_selectedBgAssetPath!);
  bgImage = img.decodeImage(bgData.buffer.asUint8List())!;
  bgImage = img.copyResize(bgImage, width: w, height: h);
} else {
  // 2. Nếu KHÔNG thay nền -> Lấy chính ảnh chụp gốc làm nền (Xóa phông mặc định)
  bgImage = img.copyResize(foregroundImage, width: w, height: h);
}

// KHẮC PHỤC LỖI: Dù là nền gốc hay nền mới, nếu thanh trượt Blur > 0 thì tiến hành làm mờ nền đó
if (_blurIntensity > 0) {
  int blurRadius = (_blurIntensity * 25).toInt().clamp(1, 25);
  bgImage = img.gaussianBlur(bgImage, radius: blurRadius);
}

// Tiếp tục đưa bgImage vào vòng lặp trộn Alpha Blending với Foreground người/vật...


👤 2. Sửa Lỗi AI Cắt Nhầm Tóc, Tai, Cổ, Vai Của Đối Tượng

🔴 Nguyên nhân:

Mô hình SelfieSegmenter trả về mảng confidences chứa độ chính xác từ 0.0 đến 1.0. Lỗi cắt lẹm vào tai, cổ hoặc vai xảy ra do thuật toán đang lấy giá trị cứng ở mức quá cao (ví dụ: alpha > 0.5 mới giữ lại), khiến các vùng chuyển tiếp hoặc vùng bị sụt giảm chi tiết do ánh sáng bị AI nhận nhầm là phông nền.

🟢 Giải pháp: nới rộng vùng đệm (Soft Boundary) và tối ưu toán học viền

Ta sẽ hạ thấp ngưỡng nhận diện tối thiểu cho các vùng cơ thể, đồng thời áp dụng hàm Smoothstep dốc mềm để bảo vệ tai, cổ, vai và tóc không bị "gọt" mất.

// VÒNG LẶP TRỘN ẢNH CẢI TIẾN TRÁNH CẮT LẸM
for (int y = 0; y < h; y++) {
  for (int x = 0; x < w; x++) {
    final int idx = y * w + x;
    double alpha = confidences[idx]; // Điểm số từ AI

    // --- THUẬT TOÁN BẢO VỆ CHI TIẾT TAI, CỔ, VAI ---
    // Thay vì cắt cứng ở 0.5, ta nới rộng vùng nhận diện sang dải rộng hơn (từ 0.15 đến 0.8)
    if (alpha > 0.15 && alpha < 0.8) {
      // Hàm nội suy bậc 3 giúp làm mịn vùng biên tiếp giáp, giữ lại phần tai/tóc mảnh
      alpha = cubicSmoothStep(0.15, 0.8, alpha);
    } else if (alpha >= 0.8) {
      alpha = 1.0; // Giữ nguyên 100% chi tiết cơ thể người/vật
    } else {
      alpha = 0.0; // Chắc chắn là nền
    }

    // Tiến hành pha màu Alpha Blending giữa Foreground (Người) và Background (Nền đã xử lý)
    final fgPixel = foreground.getPixel(x, y);
    final bgPixel = bgImage.getPixel(x, y);

    final int outR = (fgPixel.r * alpha + bgPixel.r * (1.0 - alpha)).toInt();
    final int outG = (fgPixel.g * alpha + bgPixel.g * (1.0 - alpha)).toInt();
    final int outB = (fgPixel.b * alpha + bgPixel.b * (1.0 - alpha)).toInt();

    output.setPixel(x, y, img.ColorRgb8(outR, outG, outB));
  }
}

// Hàm toán học phụ trợ Smoothstep bậc cao (Cubic) để tạo biên mượt hơn nữa
double cubicSmoothStep(double edge0, double edge1, double x) {
  final double t = ((x - edge0) / (edge1 - edge0)).clamp(0.0, 1.0);
  return t * t * (3.0 - 2.0 * t);
}


🔇 3. Xóa Bỏ Thông Báo "Đã lưu vào thư viện" Sau Khi Chụp Ảnh

🔴 Nguyên nhân:

Sau khi luồng Gal.putImage hoàn thành, hệ thống đang gọi một hàm thông báo (có thể là _showSuccessToast(), _showSuccessOverlay(), hoặc SnackBar).

🟢 Giải pháp:

Rà soát lại phần cuối của hàm xử lý chuỗi ảnh captureAndProcessImagePipeline()xóa bỏ/comment dòng lệnh kích hoạt thông báo này để giao diện chụp ảnh sạch sẽ hoàn toàn.

// TRONG PIPELINE XỬ LÝ SAU KHI CHỤP
await Gal.putImage(tempFile.path); // Lưu ảnh âm thầm dưới nền thành công
await tempFile.delete();           // Giải phóng bộ nhớ tạm

// ❌ XÓA HOẶC COMMENT BỎ DÒNG LỆNH NÀY:
// _showSuccessToast(); 
// ScaffoldMessenger.of(context).showSnackBar(...);

print("Xử lý và lưu ảnh hoàn tất im lặng."); 


📅 4. Checklist Kiểm Thử Sau Khi Sửa Lỗi (Testing Checklist)

  • Test giữ nền: Chọn một ảnh nền bất kỳ (Nền 1), sau đó chuyển sang tab Xóa nền trượt Slider/Swipe trên màn hình xem ảnh nền có bị mất đi không. (Yêu cầu: Nền 1 vẫn giữ nguyên và mờ dần đi theo tay vuốt).
  • Test cắt viền: Chụp nghiêng góc để lộ rõ tai, vai, và tóc xù. Phóng to ảnh kiểm tra xem phần tai có bị lẹm méo hay mất một phần vai/cổ không.
  • Test thông báo: Nhấn nút Shutter chụp 3 tấm liên tiếp, quan sát màn hình xem có hiện bất kỳ chữ hay hộp thoại thông báo nào che mắt người dùng hay không. (Yêu cầu: Chỉ chớp trắng 1 cái và ảnh thu nhỏ bay về nút Media ở góc dưới).