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
@@ -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()