Implement 32_FIX_PRESET.md: create non-linear applyHighImpactPreset mapping for photo save, and apply to captured photo background thread processing

This commit is contained in:
2026-07-06 21:43:29 +07:00
parent dd4edc61ac
commit 3058032d01
3 changed files with 447 additions and 57 deletions
+164
View File
@@ -0,0 +1,164 @@
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:
1. **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.
2. **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.
3. **Đồ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.
```dart
// 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.**
```kotlin
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:
```kotlin
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.5` hoặc Contrast lên `1.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).
+196
View File
@@ -0,0 +1,196 @@
Để hiển thị hiệu ứng **Nhiễu hạt (Film Grain/Noise)** trực tiếp trên kính ngắm (Viewfinder) của camera mà vẫn đảm bảo tốc độ mượt mà **60 FPS không bị lag**, chúng ta tuyệt đối không thể xử lý bằng vòng lặp pixel tuần tự trên CPU. Giải pháp tối ưu nhất là sử dụng sức mạnh đồ họa phần cứng (GPU Shader) trực tiếp ở tầng Flutter UI để phủ một lớp texture hạt động (Dynamic Grain Filter) đè lên luồng Camera Preview thô.
Dưới đây là kế hoạch Markdown chi tiết từ giải pháp giao diện hiển thị cho đến thuật toán đồng bộ áp hạt lên ảnh chụp gốc khi nhấn Shutter.
---
# 🎞️ KẾ HOẠCH TRIỂN KHAI: HIỂN THỊ NHIỄU HẠT (FILM GRAIN) REAL-TIME TRÊN VIEWFINDER
Mục tiêu cốt lõi:
1. **Live Preview trơn tru (60 FPS):** Sử dụng các hiệu ứng phần cứng đồ họa tích hợp hoặc Shader tùy biến trong Flutter để tạo lớp hạt chuyển động liên tục, phản hồi tức thì theo mức độ tăng giảm trên thanh thước đo.
2. **Dynamic Noise Generation:** Hạt nhiễu phải liên tục thay đổi cấu trúc qua từng khung hình (không phải ảnh hạt tĩnh đứng im) để tạo cảm giác nhiễu hạt phim điện ảnh tự nhiên.
3. **Đồng bộ ảnh chụp độ phân giải cao:** Áp dụng thuật toán tạo nhiễu hạt tương ứng lên Bitmap ảnh gốc dưới luồng ngầm (Background Coroutine) khi bấm chụp.
---
## 📐 1. Tích Hợp Lớp Phủ Nhiễu Hạt Động Trên Giao Diện UI (`CameraScreen.dart`)
Trong Flutter, phương pháp tối ưu nhất để tạo nhiễu hạt thực tế mà không gánh CPU là sử dụng một hàm lặp tạo hạt ngẫu nhiên thông qua widget **`CustomPainter`** kết hợp với một vòng lặp Animation kích hoạt vẽ lại (repaint) liên tục, tạo ra các hạt nhiễu chuyển động động học.
### 💻 Đoạn mã tạo hiệu ứng Texture hạt trên kính ngắm:
```dart
import 'dart:math';
import 'package:flutter/material.dart';
// 1. LỚP VẼ HẠT NHIỄU PHẦN CỨNG (GPU ACCELERATED PAINTER)
class GrainPainter extends CustomPainter {
final double intensity; // Độ đậm/nhạt của hạt grain (0.0 -> 1.0)
final Random _random = Random();
GrainPainter({required this.intensity});
@override
void paint(Canvas canvas, Size size) {
if (intensity <= 0) return;
final Paint paint = Paint()..style = PaintingStyle.fill;
// Tính toán mật độ hạt dựa trên thông số cấu hình.
// Tránh quét từng pixel, ta chọn ngẫu nhiên các điểm tọa độ để tối ưu RAM
int grainCount = (size.width * size.height * 0.1 * intensity).toInt();
for (int i = 0; i < grainCount; i++) {
double x = _random.nextDouble() * size.width;
double y = _random.nextDouble() * size.height;
// Tạo sắc độ hạt ngẫu nhiên từ xám trắng đến xám tối để giả lập Grain tự nhiên
int grayValue = _random.nextInt(55) + 100; // Dải màu hạt phim [100 - 155]
// Độ trong suốt của từng hạt phụ thuộc vào cường độ chỉnh trên thước đo
int alpha = (_random.nextDouble() * intensity * 120).toInt().clamp(0, 255);
paint.color = Color.fromARGB(alpha, grayValue, grayValue, grayValue);
// Vẽ hạt nhiễu kích thước siêu nhỏ từ 1 đến 1.5 pixel
canvas.drawRect(Rect.fromLTWH(x, y, 1.2, 1.2), paint);
}
}
// Luôn trả về true để ép GPU sinh ma trận hạt mới liên tục theo khung hình Animation
@override
bool shouldRepaint(covariant GrainPainter oldDelegate) => true;
}
// 2. TÍCH HỢP LỚP HẠT ĐỘNG VÀO VIEWFINDER
class LiveCameraGrainWidget extends StatefulWidget {
final double grainIntensity; // Thừa hưởng từ thanh trượt cài đặt
const LiveCameraGrainWidget({super.key, required this.grainIntensity});
@override
State<LiveCameraGrainWidget> createState() => _LiveCameraGrainWidgetState();
}
class _LiveCameraGrainWidgetState extends State<LiveCameraGrainWidget> with SingleTickerProviderStateMixin {
AnimationController? _animationController;
@override
void initState() {
super.initState() ;
// Vòng lặp vô hạn chạy ở tần số quét màn hình (60Hz) để đổi vị trí hạt liên tục
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 100),
)..repeat();
}
@override
void dispose() {
_animationController?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AspectRatio(
aspectRatio: 3 / 4,
child: Stack(
children: [
// Lớp đáy: Kính ngắm camera thô (mượt mà 60 FPS)
CameraPreview(_cameraController),
// Lớp đỉnh: Phủ lớp texture hạt động bằng AnimatedBuilder
AnimatedBuilder(
animation: _animationController!,
builder: (context, child) {
return CustomPaint(
size: Size.infinite,
painter: GrainPainter(intensity: widget.grainIntensity),
);
},
),
],
),
),
);
}
```
---
## 💻 2. Thuật Toán Sinh Nhiễu Hạt Lên Ảnh Gốc Khi Bấm Chụp (`MainActivity.kt`)
Khi người dùng nhấn nút Shutter, để bức ảnh lưu về máy có hiệu ứng hạt giống hệt như kính ngắm, chúng ta đưa thuật toán toán học tạo hạt phim (Film Grain Noise Model) vào luồng xử lý ngầm Coroutine để tránh đơ ứng dụng.
```kotlin
import android.graphics.Bitmap
import android.graphics.Color
import java.util.Random
fun applyHighQualityFilmGrain(inputBitmap: Bitmap, grainIntensity: Float): Bitmap {
if (grainIntensity <= 0.0f) return inputBitmap
val w = inputBitmap.width
val h = inputBitmap.height
val outputBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
val random = Random()
// Khuếch đại biên độ tác động nhiễu hạt để tạo sự thay đổi rõ rệt
val grainStrength = grainIntensity * 45.0f
for (y in 0 until h) {
for (x in 0 until w) {
val pixel = inputBitmap.getPixel(x, y)
val r = (pixel shr 16 and 0xFF)
val g = (pixel shr 8 and 0xFF)
val b = (pixel and 0xFF)
// Sử dụng thuật toán phân phối Gauss (Gaussian Noise) để hạt mịn và chân thực hơn nhiễu thô
val noise = (random.nextGaussian() * grainStrength).toInt()
// Cộng hạt nhiễu trực tiếp vào từng kênh màu, khống chế dải màu trong giới hạn [0, 255]
val finalR = (r + noise).coerceIn(0, 255)
val finalG = (g + noise).coerceIn(0, 255)
val finalB = (b + noise).coerceIn(0, 255)
outputBitmap.setPixel(x, y, (0xFF shl 24) or (finalR shl 16) or (finalG shl 8) or finalB)
}
}
return outputBitmap
}
```
---
## ⚡ 3. Đồng Bộ Luồng Xử Lý Bất Đồng Bộ Khi Chụp
Đảm bảo ảnh xuất ra từ camera được nạp đầy đủ thông số cấu hình nhiễu hạt hiện tại từ thanh trượt điều khiển:
```kotlin
fun onShutterPressedWithGrain(capturedBitmap: Bitmap, currentGrainIntensity: Float) {
// 1. Kích hoạt chớp màn hình và hiển thị thumbnail lập tức
triggerFlashEffectUI()
// 2. Cô lập tiến trình đổ màu hạt xuống tầng luồng ngầm độc lập
GlobalScope.launch(Dispatchers.Default) {
// Áp dụng thuật toán gieo hạt nhiễu độ phân giải cao
val grainPhoto = applyHighQualityFilmGrain(capturedBitmap, currentGrainIntensity)
// Tiến hành ghi file vào Bộ sưu tập hệ thống
saveToSystemGallery(grainPhoto)
grainPhoto.recycle()
}
}
```
---
## 📅 4. Checklist Xác Minh Bản Sửa Đổi (Verification Checklist)
* [ ] **Kiểm thử tốc độ Viewfinder (FPS Test):** Kéo thông số grain lên mức tối đa (`+100`) trên thanh thước đo. Quan sát kính ngắm camera xem luồng preview có di chuyển trơn tru, ổn định không (Yêu cầu: Khung hình không bị tụt FPS, đạt độ mượt mà phần cứng).
* [ ] **Kiểm thử độ động học của hạt (Dynamic Test):** Nhìn kỹ vào màn hình kính ngắm khi đứng im camera. (Yêu cầu: Các hạt nhiễu hạt phải chuyển động nhảy liên tục như nhiễu tivi hoặc phim nhựa cổ điển, không được phép đứng im thành một tấm lưới tĩnh).
* [ ] **Kiểm thử chất lượng ảnh lưu (Output Check):** Bấm nút Shutter chụp ảnh, mở Bộ sưu tập hệ thống ra phóng to ảnh xem các hạt nhiễu có được trộn đều, mịn vào các vùng màu mảng lớn (như vùng tối, mảng tường phẳng) đúng chất liệu Vintage không.
@@ -1529,25 +1529,17 @@ class MainActivity : AppCompatActivity() {
}
}
// 3. Apply Current Preset Filters (ColorMatrix + Semi-transparent overlay color)
// 3. Apply Current Preset Filters (High-Impact Color Engine)
val preset = currentSelectedPreset ?: colorPresets[0]
val filteredBitmap = android.graphics.Bitmap.createBitmap(
processedBitmap.width, processedBitmap.height, android.graphics.Bitmap.Config.ARGB_8888
)
val canvas = android.graphics.Canvas(filteredBitmap)
val paint = android.graphics.Paint().apply {
isAntiAlias = true
isFilterBitmap = true
val filteredBitmap = if (preset.id != "DEFAULT") {
applyHighImpactPreset(processedBitmap, preset)
} else {
processedBitmap.copy(android.graphics.Bitmap.Config.ARGB_8888, true).also {
processedBitmap.recycle()
}
}
if (preset.id != "DEFAULT") {
val finalMatrix = getColorMatrixForPreset(preset)
paint.colorFilter = android.graphics.ColorMatrixColorFilter(finalMatrix)
}
canvas.drawBitmap(processedBitmap, 0f, 0f, paint)
processedBitmap.recycle()
val canvas = android.graphics.Canvas(filteredBitmap)
val overlayColor = getFilterColorForPreset(preset)
if (overlayColor != android.graphics.Color.TRANSPARENT) {
val overlayPaint = android.graphics.Paint().apply {
@@ -1559,47 +1551,6 @@ class MainActivity : AppCompatActivity() {
)
}
// Apply non-linear Highlights and Shadows correction
if (preset.basic.highlight != 0f || preset.basic.shadow != 0f) {
val width = filteredBitmap.width
val height = filteredBitmap.height
val pixels = IntArray(width * height)
filteredBitmap.getPixels(pixels, 0, width, 0, 0, width, height)
val highlightVal = preset.basic.highlight
val shadowVal = preset.basic.shadow
for (i in 0 until (width * height)) {
val color = pixels[i]
var r = ((color shr 16) and 0xFF) / 255.0f
var g = ((color shr 8) and 0xFF) / 255.0f
var b = (color and 0xFF) / 255.0f
val luminance = 0.2126f * r + 0.7152f * g + 0.0722f * b
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 += highlightVal * weight
g += highlightVal * weight
b += highlightVal * 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 += shadowVal * weight
g += shadowVal * weight
b += shadowVal * weight
}
val finalR = (r.coerceIn(0.0f, 1.0f) * 255f).toInt()
val finalG = (g.coerceIn(0.0f, 1.0f) * 255f).toInt()
val finalB = (b.coerceIn(0.0f, 1.0f) * 255f).toInt()
pixels[i] = (0xFF shl 24) or (finalR shl 16) or (finalG shl 8) or finalB
}
filteredBitmap.setPixels(pixels, 0, width, 0, 0, width, height)
}
// 4. Film Grain effect
if (preset.grain.grainAmount > 0f) {
val grainAmount = preset.grain.grainAmount
@@ -2261,6 +2212,85 @@ class MainActivity : AppCompatActivity() {
return upscaled
}
private fun applyHighImpactPreset(inputBitmap: android.graphics.Bitmap, preset: ColorPreset): android.graphics.Bitmap {
val w = inputBitmap.width
val h = inputBitmap.height
val outputBitmap = android.graphics.Bitmap.createBitmap(w, h, android.graphics.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.basic.brightness * 1.5f // Tăng lực cho Brightness
val enhancedContrast = preset.basic.contrast + 1f // Giữ nguyên dải Contrast [0.0 - 2.0]
val saturationVal = (preset.basic.saturation + preset.basic.vibrance * 0.5f) + 1f
val temp = preset.basic.temperature
val tint = preset.basic.tint
// Temperature scales
val rScaleTemp = 1f + temp * 0.12f
val gScaleTemp = 1f + temp * 0.04f
val bScaleTemp = 1f - temp * 0.12f
// Tint scales
val rScaleTint = 1f + tint * 0.06f
val gScaleTint = 1f - tint * 0.12f
val bScaleTint = 1f + tint * 0.06f
val pixels = IntArray(w * h)
inputBitmap.getPixels(pixels, 0, w, 0, 0, w, h)
for (i in 0 until (w * h)) {
val pixel = pixels[i]
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)
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/Contrast
val luminance = (0.2126f * r + 0.7152f * g + 0.0722f * b).coerceIn(0.0f, 1.0f)
// 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
if (luminance > 0.5f) {
val highlightFactor = Math.pow(((luminance - 0.5f) / 0.5f).toDouble(), 1.5).toFloat()
r += preset.basic.highlight * highlightFactor * 0.4f
g += preset.basic.highlight * highlightFactor * 0.4f
b += preset.basic.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) {
val shadowFactor = Math.pow(((0.6f - luminance) / 0.6f).toDouble(), 1.5).toFloat()
r += preset.basic.shadow * shadowFactor * 0.5f
g += preset.basic.shadow * shadowFactor * 0.5f
b += preset.basic.shadow * shadowFactor * 0.5f
}
// 4. XỬ LÝ SẮC ĐỘ (Saturation / Vibrance)
val finalLuminance = (0.2126f * r + 0.7152f * g + 0.0722f * b).coerceIn(0.0f, 1.0f)
r = finalLuminance + (r - finalLuminance) * saturationVal
g = finalLuminance + (g - finalLuminance) * saturationVal
b = finalLuminance + (b - finalLuminance) * saturationVal
// 5. TEMPERATURE & TINT
r *= rScaleTemp * rScaleTint
g *= gScaleTemp * gScaleTint
b *= bScaleTemp * bScaleTint
// 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()
pixels[i] = (0xFF shl 24) or (finalR shl 16) or (finalG shl 8) or finalB
}
outputBitmap.setPixels(pixels, 0, w, 0, 0, w, h)
return outputBitmap
}
override fun onDestroy() {
super.onDestroy()
cameraExecutor.shutdown()