9.9 KiB
Dựa trên các vấn đề bạn vừa nêu, nguyên nhân cốt lõi khiến các thông số (Brightness, Contrast, Highlight, Shadow) tăng giảm không rõ rệt là do công thức tính toán pixel tuyến tính (/ 255.0f) đang bị bó hẹp trong dải biên độ quá nhỏ, không đủ mạnh để tạo ra sự đột biến về mặt thị giác trên cảm biến ảnh độ phân giải cao. Ngoài ra, việc các thông số này chưa hiển thị trên kính ngắm là do ứng dụng chưa tích hợp bộ lọc màu vào luồng render thời gian thực của camera preview.
Dưới đây là kế hoạch Markdown chi tiết để nâng cấp thang đo ma trận màu sắc phi tuyến tính nhằm tạo ra sự thay đổi tương phản, sáng tối cực kỳ rõ rệt, đồng thời tích hợp trực tiếp hiệu ứng này lên kính ngắm (Viewfinder) thời gian thực.
🛠️ KẾ HOẠCH FIX LỖI: HIỂN THỊ PRESET ĐỘNG TRÊN VIEWFINDER VÀ KHUẾCH ĐẠI BIÊN ĐỘ MÀU SẮC
Mục tiêu cốt lõi:
- Khuếch đại biên độ (High-Impact Color Engine): Thay đổi công thức tính toán từ tuyến tính sang phi tuyến tính (dạng lũy thừa hoặc nhân hệ số dốc) để Brightness, Contrast, Highlight, Shadow thay đổi cực kỳ rõ rệt, đáp ứng đúng yêu cầu nghệ thuật của ảnh.
- Hiển thị Real-time trên Viewfinder: Tích hợp bộ xử lý màu sắc trực tiếp vào luồng render của camera trước khi xuất ra màn hình kính ngắm.
- Đồng bộ ảnh chụp: Đảm bảo khi bấm Shutter, thông số đang hiển thị trên kính ngắm được áp chính xác 100% vào ảnh lưu.
📐 1. Tích Hợp Hiệu Ứng Preset Lên Kính Ngắm Viewfinder (CameraScreen.dart)
Để người dùng thấy rõ sự thay đổi màu sắc ngay khi trượt thanh cấu hình hoặc chọn Preset, chúng ta sử dụng widget ColorFiltered kết hợp với Ma trận màu (Color Filter Matrix) ở tầng Flutter. Phương pháp này tận dụng GPU của điện thoại để render màu trực tiếp, đảm bảo Viewfinder luôn đạt 60 FPS mượt mà không bị lag.
// Hàm khởi tạo ma trận màu 4x5 từ các thông số Preset để ép GPU hiển thị thời gian thực
List<double> _generateColorMatrix({
required double brightness, // Thang từ -1.0 đến 1.0
required double contrast, // Thang từ 0.5 đến 2.5
required double saturation, // Thang từ 0.0 đến 2.0
}) {
// Khuếch đại thông số Brightness hệ số 255 để ăn trực tiếp vào ma trận phần cứng
final double b = brightness * 255.0;
final double c = contrast;
final double s = saturation;
// Ma trận biến đổi màu chuẩn đồ họa máy tính
return [
c * (0.213 + 0.787 * s), c * (0.715 - 0.715 * s), c * (0.072 - 0.072 * s), 0, b,
c * (0.213 - 0.213 * s), c * (0.715 + 0.285 * s), c * (0.072 - 0.072 * s), 0, b,
c * (0.213 - 0.213 * s), c * (0.715 - 0.715 * s), c * (0.072 + 0.928 * s), 0, b,
0, 0, 0, 1, 0,
];
}
Widget _buildPresetLiveViewfinder() {
return AspectRatio(
aspectRatio: 3 / 4,
child: ColorFiltered(
// Áp ma trận màu động lên toàn bộ luồng camera hiển thị
colorFilter: ColorFilter.matrix(_generateColorMatrix(
brightness: _currentPreset.brightness,
contrast: _currentPreset.contrast,
saturation: _currentPreset.saturation,
)),
child: CameraPreview(_cameraController), // Kính ngắm camera chạy bằng phần cứng mượt mà
),
);
}
💻 2. Tái Cấu Trúc Thuật Toán Màu Sắc Để Tăng Giảm Cực Kỳ Rõ Rệt (MainActivity.kt)
Dưới đây là thuật toán toán học mới được viết lại cho tầng xử lý ảnh gốc (MainActivity.kt). Chúng ta thay đổi quy luật tính toán: Sử dụng hàm dốc phi tuyến tính để ép Highlight/Shadow thay đổi rõ rệt và khuếch đại biên độ Brightness/Contrast.
fun applyHighImpactPreset(inputBitmap: Bitmap, preset: ColorPreset): Bitmap {
val w = inputBitmap.width
val h = inputBitmap.height
val outputBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
// KHUẾCH ĐẠI BIÊN ĐỘ: Nhân hệ số cường độ tác động để thông số thay đổi rõ rệt
val enhancedBrightness = preset.brightness * 1.5f // Tăng lực cho Brightness
val enhancedContrast = preset.contrast // Giữ nguyên dải Contrast chuẩn [0.5 - 2.5]
for (y in 0 until h) {
for (x in 0 until w) {
val pixel = inputBitmap.getPixel(x, y)
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. CẢI TIẾN BRIGHTNESS & CONTRAST (Áp dụng công thức dốc phi tuyến tính)
// Lấy mốc 0.5f làm tâm điểm phối màu tương phản, tăng biên độ rõ rệt
r = (r - 0.5f) * enhancedContrast + 0.5f + enhancedBrightness
g = (g - 0.5f) * enhancedContrast + 0.5f + enhancedBrightness
b = (b - 0.5f) * enhancedContrast + 0.5f + enhancedBrightness
// Tính toán độ sáng thực tế (Luminance) của pixel hiện tại sau khi chỉnh Brightness
val luminance = 0.2126f * r + 0.7152f * g + 0.0722f * b
// 2. SỬA LỖI HIGHLIGHT: Dùng hàm S-Curve phi tuyến tính thay vì cộng trừ thông thường
// Giúp vùng sáng được cứu (nếu giảm) hoặc tăng sáng rõ rệt (nếu tăng) mà không bị bết màu
if (luminance > 0.5f) {
// Tính toán trọng số dốc: Pixel càng sáng, tác động Highlight càng mạnh
val highlightFactor = Math.pow(((luminance - 0.5f) / 0.5f).toDouble(), 1.5).toFloat()
// Nếu preset.highlight âm (-): dìm vùng sáng để cứu chi tiết
// Nếu preset.highlight dương (+): đẩy vùng sáng lên rực rỡ hẳn
r += preset.highlight * highlightFactor * 0.4f
g += preset.highlight * highlightFactor * 0.4f
b += preset.highlight * highlightFactor * 0.4f
}
// 3. SỬA LỖI SHADOW: Tương tự như Highlight, dùng hàm lũy thừa ngược cho vùng tối
if (luminance < 0.6f) {
// Pixel càng tối (gần 0.0f), trọng số dốc Shadow càng đạt cực đại
val shadowFactor = Math.pow(((0.6f - luminance) / 0.6f).toDouble(), 1.5).toFloat()
// Nếu preset.shadow dương (+): Cứu sáng vùng tối rõ rệt, phơi sáng chi tiết bóng râm
// Nếu preset.shadow âm (-): Dìm sâu vùng tối để tạo độ sâu, tương phản khối gắt
r += preset.shadow * shadowFactor * 0.5f
g += preset.shadow * shadowFactor * 0.5f
b += preset.shadow * shadowFactor * 0.5f
}
// 4. XỬ LÝ SẮC ĐỘ (Saturation)
val finalLuminance = (0.2126f * r + 0.7152f * g + 0.0722f * b).coerceIn(0.0f, 1.0f)
r = finalLuminance + (r - finalLuminance) * preset.saturation
g = finalLuminance + (g - finalLuminance) * preset.saturation
b = finalLuminance + (b - finalLuminance) * preset.saturation
// Kiểm soát biên độ bảo vệ hạt màu không vượt quá giới hạn [0, 255]
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
}
⚡ 3. Đồng Bộ Áp Dụng Khi Nhấn Shutter Chụp Ảnh
Để đảm bảo ảnh lưu vào thư viện giống hệt những gì người dùng nhìn thấy trên kính ngắm, cấu trúc luồng Coroutine ngầm khi bấm Shutter sẽ nhận trực tiếp tham số cấu hình Preset hiện tại:
fun onShutterPressedWithPreset(capturedBitmap: Bitmap, activePreset: ColorPreset) {
// Luồng ngầm xử lý lưu ảnh
GlobalScope.launch(Dispatchers.Default) {
// Áp dụng thuật toán High-Impact vừa nâng cấp vào ảnh gốc độ phân giải cao
val filteredPhoto = applyHighImpactPreset(capturedBitmap, activePreset)
// Lưu ảnh vào Bộ sưu tập hệ thống
saveToSystemGallery(filteredPhoto)
filteredPhoto.recycle()
}
}
📅 4. Checklist Xác Minh Bản Sửa Đổi (Verification Checklist)
- Kiểm thử thay đổi trên Viewfinder: Chọn một Preset bất kỳ (Ví dụ: Đen trắng hoặc Vintage). Quan sát màn hình kính ngắm xem màu sắc có thay đổi ngay lập tức theo thời gian thực hay không.
- Kiểm thử cường độ Brightness / Contrast: Tăng thử thông số Brightness lên
+0.5hoặc Contrast lên1.8. (Yêu cầu: Ảnh phải sáng bừng lên hoặc độ tương phản khối gắt hiện rõ lập tức, không còn hiện tượng "nhẹ nhẹ" không rõ ràng). - Kiểm thử tính năng Highlight: Chụp một khung cảnh có cửa sổ trời rất sáng. Giảm Highlight xuống
-0.6. (Yêu cầu: Vùng sáng quá mức quanh cửa sổ phải được dìm dịu đi rõ rệt, hiện rõ lại chi tiết vân khung cửa sắt). - Kiểm thử tính năng Shadow: Tìm góc tối trong phòng, tăng Shadow lên
+0.7. (Yêu cầu: Các chi tiết khuất trong góc tối phải được phơi sáng bật lên rõ ràng, không còn bị bết đen góc ảnh).