|
|
|
@@ -78,6 +78,23 @@ class MainActivity : AppCompatActivity() {
|
|
|
|
|
private var cachedMaskWidth = 0
|
|
|
|
|
private var cachedMaskHeight = 0
|
|
|
|
|
|
|
|
|
|
// Đị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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private var currentBackgroundMode = BackgroundMode.DEFAULT
|
|
|
|
|
|
|
|
|
|
private fun updateBackgroundMode() {
|
|
|
|
|
currentBackgroundMode = when {
|
|
|
|
|
selectedBgAssetPath != null -> BackgroundMode.AI_BLUR
|
|
|
|
|
blurIntensity > 0f || isPortraitBlurModeOpen -> BackgroundMode.HARDWARE_DOF
|
|
|
|
|
else -> BackgroundMode.DEFAULT
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun getImageSegmenter(): ImageSegmenter? {
|
|
|
|
|
synchronized(aiInferenceLock) {
|
|
|
|
|
if (!isAiSupported) return null
|
|
|
|
@@ -207,6 +224,8 @@ class MainActivity : AppCompatActivity() {
|
|
|
|
|
|
|
|
|
|
val brightness = extractJsonDoubleField(content, "brightness").toFloat()
|
|
|
|
|
val contrast = extractJsonDoubleField(content, "contrast").toFloat()
|
|
|
|
|
val highlight = extractJsonDoubleField(content, "highlight").toFloat()
|
|
|
|
|
val shadow = extractJsonDoubleField(content, "shadow").toFloat()
|
|
|
|
|
val saturation = extractJsonDoubleField(content, "saturation").toFloat()
|
|
|
|
|
val vibrance = extractJsonDoubleField(content, "vibrance").toFloat()
|
|
|
|
|
val temperature = extractJsonDoubleField(content, "temperature").toFloat()
|
|
|
|
@@ -229,7 +248,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
|
|
name = name,
|
|
|
|
|
themeCategory = category,
|
|
|
|
|
isEditable = true,
|
|
|
|
|
basic = BasicAdjustments(brightness, contrast, saturation, vibrance, temperature, tint),
|
|
|
|
|
basic = BasicAdjustments(brightness, contrast, saturation, vibrance, temperature, tint, highlight, shadow),
|
|
|
|
|
advanced = AdvancedEffects(clarity, dehaze, vignetteAmount),
|
|
|
|
|
toneCurve = ToneCurveEmulation(fadedBlackLevel, shadowsTintR, shadowsTintG, shadowsTintB),
|
|
|
|
|
grain = FilmGrain(grainAmount, grainSize)
|
|
|
|
@@ -272,6 +291,8 @@ class MainActivity : AppCompatActivity() {
|
|
|
|
|
val attributeItems = listOf(
|
|
|
|
|
AttributeNode(EditAttribute.BRIGHTNESS, "Brightness (Độ sáng)", R.drawable.ic_attr_brightness),
|
|
|
|
|
AttributeNode(EditAttribute.CONTRAST, "Contrast (Tương phản)", R.drawable.ic_attr_contrast),
|
|
|
|
|
AttributeNode(EditAttribute.HIGHLIGHT, "Highlight (Vùng sáng)", R.drawable.ic_attr_highlight),
|
|
|
|
|
AttributeNode(EditAttribute.SHADOW, "Shadow (Vùng tối)", R.drawable.ic_attr_shadow),
|
|
|
|
|
AttributeNode(EditAttribute.SATURATION, "Saturation (Bão hòa màu)", R.drawable.ic_attr_saturation),
|
|
|
|
|
AttributeNode(EditAttribute.VIBRANCE, "Vibrance (Bão hòa thông minh)", R.drawable.ic_attr_vibrance),
|
|
|
|
|
AttributeNode(EditAttribute.TEMPERATURE, "Temperature (Nhiệt màu)", R.drawable.ic_attr_temperature),
|
|
|
|
@@ -305,6 +326,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
|
|
)
|
|
|
|
|
backgroundOptionAdapter = BackgroundOptionAdapter(backgroundOptions) { option ->
|
|
|
|
|
selectedBgAssetPath = option.assetPath
|
|
|
|
|
updateBackgroundMode()
|
|
|
|
|
}
|
|
|
|
|
binding.rvBackgroundOptions.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
|
|
|
|
|
binding.rvBackgroundOptions.adapter = backgroundOptionAdapter
|
|
|
|
@@ -320,6 +342,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
|
|
binding.panelRulerView.onValueChangeListener = { valInt ->
|
|
|
|
|
binding.tvPanelRulerValue.text = "$valInt"
|
|
|
|
|
blurIntensity = valInt / 100f
|
|
|
|
|
updateBackgroundMode()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -454,8 +477,8 @@ class MainActivity : AppCompatActivity() {
|
|
|
|
|
))
|
|
|
|
|
finalMatrix.postConcat(contrastMatrix)
|
|
|
|
|
|
|
|
|
|
// 3. Brightness (Độ sáng Exposure thực tế)
|
|
|
|
|
val brightnessOffset = preset.basic.brightness * 60f
|
|
|
|
|
// 3. Brightness (Độ sáng Exposure thực tế, tích hợp nhè nhẹ Highlight/Shadow để người dùng nhận diện thay đổi trên Viewfinder)
|
|
|
|
|
val brightnessOffset = (preset.basic.brightness + (preset.basic.highlight + preset.basic.shadow) * 0.15f) * 60f
|
|
|
|
|
val brightnessMatrix = android.graphics.ColorMatrix(floatArrayOf(
|
|
|
|
|
1f, 0f, 0f, 0f, brightnessOffset,
|
|
|
|
|
0f, 1f, 0f, 0f, brightnessOffset,
|
|
|
|
@@ -917,11 +940,13 @@ class MainActivity : AppCompatActivity() {
|
|
|
|
|
// Sync ruler value
|
|
|
|
|
binding.panelRulerView.value = (blurIntensity * 100f).toInt()
|
|
|
|
|
binding.tvPanelRulerValue.text = "${(blurIntensity * 100f).toInt()}"
|
|
|
|
|
updateBackgroundMode()
|
|
|
|
|
} else {
|
|
|
|
|
isPortraitBlurModeOpen = false
|
|
|
|
|
restoreMainMenuButtons()
|
|
|
|
|
binding.viewLeftDockBackground.visibility = android.view.View.GONE
|
|
|
|
|
binding.panelRulerContainer.visibility = android.view.View.GONE
|
|
|
|
|
updateBackgroundMode()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -948,11 +973,13 @@ class MainActivity : AppCompatActivity() {
|
|
|
|
|
binding.rvBackgroundOptions.visibility = android.view.View.VISIBLE
|
|
|
|
|
binding.rvHorizontalTimeline.visibility = android.view.View.GONE
|
|
|
|
|
binding.panelRulerContainer.visibility = android.view.View.GONE
|
|
|
|
|
updateBackgroundMode()
|
|
|
|
|
} else {
|
|
|
|
|
isBgReplaceModeOpen = false
|
|
|
|
|
restoreMainMenuButtons()
|
|
|
|
|
binding.viewLeftDockBackground.visibility = android.view.View.GONE
|
|
|
|
|
binding.rvBackgroundOptions.visibility = android.view.View.GONE
|
|
|
|
|
updateBackgroundMode()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -1039,6 +1066,8 @@ class MainActivity : AppCompatActivity() {
|
|
|
|
|
return when (currentAttribute) {
|
|
|
|
|
EditAttribute.BRIGHTNESS -> preset.basic.brightness
|
|
|
|
|
EditAttribute.CONTRAST -> preset.basic.contrast
|
|
|
|
|
EditAttribute.HIGHLIGHT -> preset.basic.highlight
|
|
|
|
|
EditAttribute.SHADOW -> preset.basic.shadow
|
|
|
|
|
EditAttribute.SATURATION -> preset.basic.saturation
|
|
|
|
|
EditAttribute.VIBRANCE -> preset.basic.vibrance
|
|
|
|
|
EditAttribute.TEMPERATURE -> preset.basic.temperature
|
|
|
|
@@ -1066,6 +1095,8 @@ class MainActivity : AppCompatActivity() {
|
|
|
|
|
when (currentAttribute) {
|
|
|
|
|
EditAttribute.BRIGHTNESS -> preset.basic.brightness = value
|
|
|
|
|
EditAttribute.CONTRAST -> preset.basic.contrast = value
|
|
|
|
|
EditAttribute.HIGHLIGHT -> preset.basic.highlight = value
|
|
|
|
|
EditAttribute.SHADOW -> preset.basic.shadow = value
|
|
|
|
|
EditAttribute.SATURATION -> preset.basic.saturation = value
|
|
|
|
|
EditAttribute.VIBRANCE -> preset.basic.vibrance = value
|
|
|
|
|
EditAttribute.TEMPERATURE -> preset.basic.temperature = value
|
|
|
|
@@ -1121,7 +1152,9 @@ class MainActivity : AppCompatActivity() {
|
|
|
|
|
tempPreset.basic.saturation,
|
|
|
|
|
tempPreset.basic.vibrance,
|
|
|
|
|
tempPreset.basic.temperature,
|
|
|
|
|
tempPreset.basic.tint
|
|
|
|
|
tempPreset.basic.tint,
|
|
|
|
|
tempPreset.basic.highlight,
|
|
|
|
|
tempPreset.basic.shadow
|
|
|
|
|
),
|
|
|
|
|
advanced = AdvancedEffects(
|
|
|
|
|
tempPreset.advanced.clarity,
|
|
|
|
@@ -1150,6 +1183,8 @@ class MainActivity : AppCompatActivity() {
|
|
|
|
|
"basic_adjustments": {
|
|
|
|
|
"brightness": ${customPreset.basic.brightness},
|
|
|
|
|
"contrast": ${customPreset.basic.contrast},
|
|
|
|
|
"highlight": ${customPreset.basic.highlight},
|
|
|
|
|
"shadow": ${customPreset.basic.shadow},
|
|
|
|
|
"saturation": ${customPreset.basic.saturation},
|
|
|
|
|
"vibrance": ${customPreset.basic.vibrance},
|
|
|
|
|
"temperature": ${customPreset.basic.temperature},
|
|
|
|
@@ -1211,7 +1246,8 @@ class MainActivity : AppCompatActivity() {
|
|
|
|
|
.build()
|
|
|
|
|
|
|
|
|
|
imageAnalysis.setAnalyzer(cameraExecutor) { imageProxy ->
|
|
|
|
|
val isBgActive = (selectedBgAssetPath != null) || (blurIntensity > 0f)
|
|
|
|
|
// SỬA LỖI LAG NGẦM: Chỉ chạy MediaPipe phân đoạn AI khi thực sự cần dùng chế độ AI_BLUR
|
|
|
|
|
val isBgActive = currentBackgroundMode == BackgroundMode.AI_BLUR
|
|
|
|
|
if (isBgActive) {
|
|
|
|
|
val rawProxyBitmap = imageProxy.toBitmap()
|
|
|
|
|
val rotationDegrees = imageProxy.imageInfo.rotationDegrees
|
|
|
|
@@ -1523,6 +1559,47 @@ 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
|
|
|
|
@@ -1718,20 +1795,11 @@ class MainActivity : AppCompatActivity() {
|
|
|
|
|
capturedImage: android.graphics.Bitmap,
|
|
|
|
|
isStreamMode: Boolean = false
|
|
|
|
|
): android.graphics.Bitmap {
|
|
|
|
|
val isBgActive = (selectedBgAssetPath != null) || (blurIntensity > 0f)
|
|
|
|
|
if (!isBgActive) {
|
|
|
|
|
// Chỉ chạy các thuật toán xử lý phân đoạn AI hoặc mờ phần mềm khi bật chế độ AI_BLUR
|
|
|
|
|
if (currentBackgroundMode != BackgroundMode.AI_BLUR) {
|
|
|
|
|
return capturedImage
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Tắt hoàn toàn thuật toán xử lý mờ/trộn nhân tạo cho Portrait Blur (khi selectedBgAssetPath == null)
|
|
|
|
|
// và trả về ảnh thô 100% để sử dụng tiêu cự thấu kính vật lý của camera.
|
|
|
|
|
if (selectedBgAssetPath == null && blurIntensity > 0f) {
|
|
|
|
|
if (!isStreamMode) {
|
|
|
|
|
// Trong chế độ chụp ảnh: Trả về ảnh thô ngay lập tức, không chạy phân đoạn AI
|
|
|
|
|
return capturedImage
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!isAiSupported) {
|
|
|
|
|
// Khi không hỗ trợ AI và chỉ cần Portrait Blur, trả về ảnh thô để dùng thấu kính vật lý
|
|
|
|
|
if (selectedBgAssetPath == null && blurIntensity > 0f) {
|
|
|
|
|