Fix 34_FIX_BG.md: implement runSimulatedBackgroundProcessing fallback to support high-quality portrait blur and background replacement on JNI-less emulators
This commit is contained in:
@@ -1736,16 +1736,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isAiSupported) {
|
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ý
|
return runSimulatedBackgroundProcessing(capturedImage)
|
||||||
if (selectedBgAssetPath == null && blurIntensity > 0f) {
|
|
||||||
return capturedImage
|
|
||||||
}
|
|
||||||
if (blurIntensity > 0f) {
|
|
||||||
val opticalIntensity = Math.pow(blurIntensity.toDouble(), 2.5).toFloat()
|
|
||||||
val radius = (40 * opticalIntensity).toInt().coerceIn(1, 40)
|
|
||||||
return blurBitmap(capturedImage, radius)
|
|
||||||
}
|
|
||||||
return capturedImage
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BỆNH PHÁP KHẮC PHỤC TRÌỆT ĐỂ LAG: Nếu là luồng Live Viewfinder, không chạy mô hình AI nặng
|
// BỆNH PHÁP KHẮC PHỤC TRÌỆT ĐỂ LAG: Nếu là luồng Live Viewfinder, không chạy mô hình AI nặng
|
||||||
@@ -1876,15 +1867,9 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// Ignore and fallback
|
return runSimulatedBackgroundProcessing(capturedImage)
|
||||||
}
|
}
|
||||||
|
return runSimulatedBackgroundProcessing(capturedImage)
|
||||||
if (blurIntensity > 0f) {
|
|
||||||
val opticalIntensity = Math.pow(blurIntensity.toDouble(), 2.5).toFloat()
|
|
||||||
val radius = (25 * opticalIntensity).toInt().coerceIn(1, 25)
|
|
||||||
return blurBitmap(capturedImage, radius)
|
|
||||||
}
|
|
||||||
return capturedImage
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -2010,15 +1995,9 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// Ignore and fallback
|
return runSimulatedBackgroundProcessing(capturedImage)
|
||||||
}
|
}
|
||||||
|
return runSimulatedBackgroundProcessing(capturedImage)
|
||||||
if (blurIntensity > 0f) {
|
|
||||||
val opticalIntensity = Math.pow(blurIntensity.toDouble(), 2.5).toFloat()
|
|
||||||
val radius = (40 * opticalIntensity).toInt().coerceIn(1, 40)
|
|
||||||
return blurBitmap(capturedImage, radius)
|
|
||||||
}
|
|
||||||
return capturedImage
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getNearestBackgroundPixel(
|
private fun getNearestBackgroundPixel(
|
||||||
@@ -2337,6 +2316,122 @@ class MainActivity : AppCompatActivity() {
|
|||||||
return outputBitmap
|
return outputBitmap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun runSimulatedBackgroundProcessing(capturedImage: android.graphics.Bitmap): android.graphics.Bitmap {
|
||||||
|
val w = capturedImage.width
|
||||||
|
val h = capturedImage.height
|
||||||
|
val alphaMask = FloatArray(w * h)
|
||||||
|
|
||||||
|
// Giả lập mặt nạ bằng hình bầu dục lấy nét mềm ở trung tâm (mô phỏng chân dung)
|
||||||
|
val cx = w * 0.5f
|
||||||
|
val cy = h * 0.52f
|
||||||
|
val rx = w * 0.28f
|
||||||
|
val ry = h * 0.38f
|
||||||
|
for (y in 0 until h) {
|
||||||
|
val dy = (y - cy) / ry
|
||||||
|
val rowOffset = y * w
|
||||||
|
for (x in 0 until w) {
|
||||||
|
val dx = (x - cx) / rx
|
||||||
|
val distSq = dx * dx + dy * dy
|
||||||
|
alphaMask[rowOffset + x] = if (distSq < 1.0f) {
|
||||||
|
val t = 1.0f - distSq
|
||||||
|
t * t * (3f - 2f * t) // smoothstep
|
||||||
|
} else {
|
||||||
|
0.0f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TẠO HÌNH NỀN (Background Canvas)
|
||||||
|
val baseBg = if (selectedBgAssetPath != null) {
|
||||||
|
try {
|
||||||
|
val bgInputStream = assets.open(selectedBgAssetPath!!)
|
||||||
|
val decodedBg = android.graphics.BitmapFactory.decodeStream(bgInputStream)
|
||||||
|
if (decodedBg != null) {
|
||||||
|
val tempScaled = android.graphics.Bitmap.createScaledBitmap(decodedBg, w, h, true)
|
||||||
|
if (tempScaled != decodedBg) decodedBg.recycle()
|
||||||
|
tempScaled
|
||||||
|
} else {
|
||||||
|
val solidBg = android.graphics.Bitmap.createBitmap(w, h, android.graphics.Bitmap.Config.ARGB_8888)
|
||||||
|
solidBg.eraseColor(android.graphics.Color.BLACK)
|
||||||
|
solidBg
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
val solidBg = android.graphics.Bitmap.createBitmap(w, h, android.graphics.Bitmap.Config.ARGB_8888)
|
||||||
|
solidBg.eraseColor(android.graphics.Color.BLACK)
|
||||||
|
solidBg
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Triệt tiêu Ghost Shadow bằng Inpainting Dilate
|
||||||
|
val cleanBg = android.graphics.Bitmap.createBitmap(w, h, android.graphics.Bitmap.Config.ARGB_8888)
|
||||||
|
for (y in 0 until h) {
|
||||||
|
for (x in 0 until w) {
|
||||||
|
val idx = y * w + x
|
||||||
|
val alpha = alphaMask[idx]
|
||||||
|
if (alpha > 0.01f) {
|
||||||
|
cleanBg.setPixel(x, y, getNearestBackgroundPixel(capturedImage, alphaMask, x, y, w, h))
|
||||||
|
} else {
|
||||||
|
cleanBg.setPixel(x, y, capturedImage.getPixel(x, y))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cleanBg
|
||||||
|
}
|
||||||
|
|
||||||
|
// LÀM MỜ NỀN PHÂN CẤP (Optical DoF Blur)
|
||||||
|
val bgPixels = IntArray(w * h)
|
||||||
|
var blurredBg: android.graphics.Bitmap? = null
|
||||||
|
if (blurIntensity > 0f) {
|
||||||
|
val opticalIntensity = Math.pow(blurIntensity.toDouble(), 2.5).toFloat()
|
||||||
|
val radius = (40 * opticalIntensity).toInt().coerceIn(1, 40)
|
||||||
|
blurredBg = blurBitmap(baseBg, radius)
|
||||||
|
blurredBg.getPixels(bgPixels, 0, w, 0, 0, w, h)
|
||||||
|
} else {
|
||||||
|
baseBg.getPixels(bgPixels, 0, w, 0, 0, w, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (baseBg != capturedImage) {
|
||||||
|
baseBg.recycle()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TRỘN HÒA QUYỆN ĐỐI TƯỢNG (Alpha Blending)
|
||||||
|
val fgPixels = IntArray(w * h)
|
||||||
|
val outputPixels = IntArray(w * h)
|
||||||
|
capturedImage.getPixels(fgPixels, 0, w, 0, 0, w, h)
|
||||||
|
|
||||||
|
for (idx in 0 until (w * h)) {
|
||||||
|
val alpha = alphaMask[idx]
|
||||||
|
val fgPixel = fgPixels[idx]
|
||||||
|
var fgR = (fgPixel shr 16) and 0xFF
|
||||||
|
var fgG = (fgPixel shr 8) and 0xFF
|
||||||
|
var fgB = fgPixel and 0xFF
|
||||||
|
|
||||||
|
val bgPixel = bgPixels[idx]
|
||||||
|
val bgR = (bgPixel shr 16) and 0xFF
|
||||||
|
val bgG = (bgPixel shr 8) and 0xFF
|
||||||
|
val bgB = bgPixel and 0xFF
|
||||||
|
|
||||||
|
// Color decontamination for edges
|
||||||
|
if (alpha > 0f && alpha < 1.0f) {
|
||||||
|
val grayIntensity = (fgR + fgG + fgB) / 3f
|
||||||
|
fgR = (fgR * alpha + grayIntensity * (1f - alpha)).toInt()
|
||||||
|
fgG = (fgG * alpha + grayIntensity * (1f - alpha)).toInt()
|
||||||
|
fgB = (fgB * alpha + grayIntensity * (1f - alpha)).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
val outR = (fgR * alpha + bgR * (1f - alpha)).toInt().coerceIn(0, 255)
|
||||||
|
val outG = (fgG * alpha + bgG * (1f - alpha)).toInt().coerceIn(0, 255)
|
||||||
|
val outB = (fgB * alpha + bgB * (1f - alpha)).toInt().coerceIn(0, 255)
|
||||||
|
|
||||||
|
outputPixels[idx] = (0xFF shl 24) or (outR shl 16) or (outG shl 8) or outB
|
||||||
|
}
|
||||||
|
|
||||||
|
blurredBg?.recycle()
|
||||||
|
|
||||||
|
val outputBitmap = android.graphics.Bitmap.createBitmap(w, h, android.graphics.Bitmap.Config.ARGB_8888)
|
||||||
|
outputBitmap.setPixels(outputPixels, 0, w, 0, 0, w, h)
|
||||||
|
return outputBitmap
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
cameraExecutor.shutdown()
|
cameraExecutor.shutdown()
|
||||||
|
|||||||
Reference in New Issue
Block a user