Implement resolution independent blur: fix differences between viewfinder and final photo, eliminate lag by blurring on a fixed target width of 120px

This commit is contained in:
2026-07-06 18:53:36 +07:00
parent 7e21d37ae2
commit c95f12cedc
@@ -1952,25 +1952,31 @@ class MainActivity : AppCompatActivity() {
private fun blurBitmap(src: android.graphics.Bitmap, radius: Int): android.graphics.Bitmap { private fun blurBitmap(src: android.graphics.Bitmap, radius: Int): android.graphics.Bitmap {
val w = src.width val w = src.width
val h = src.height val h = src.height
val output = android.graphics.Bitmap.createBitmap(w, h, android.graphics.Bitmap.Config.ARGB_8888)
val pixels = IntArray(w * h) // Resolution-independent downscaling for consistent blur appearance and extremely high performance
src.getPixels(pixels, 0, w, 0, 0, w, h) val targetW = 120
val targetH = (h * targetW / w).coerceAtLeast(1)
val temp = IntArray(w * h) val downscaled = android.graphics.Bitmap.createScaledBitmap(src, targetW, targetH, true)
val r = radius.coerceAtLeast(1) val r = radius.coerceIn(1, targetW / 2)
val pixels = IntArray(targetW * targetH)
downscaled.getPixels(pixels, 0, targetW, 0, 0, targetW, targetH)
downscaled.recycle()
val temp = IntArray(targetW * targetH)
val windowSize = r * 2 + 1 val windowSize = r * 2 + 1
// Horizontal pass // Horizontal pass
for (y in 0 until h) { for (y in 0 until targetH) {
val rowOffset = y * w val rowOffset = y * targetW
var rSum = 0 var rSum = 0
var gSum = 0 var gSum = 0
var bSum = 0 var bSum = 0
// Initialize window sum // Initialize window sum
for (dx in -r..r) { for (dx in -r..r) {
val px = dx.coerceIn(0, w - 1) val px = dx.coerceIn(0, targetW - 1)
val pixel = pixels[rowOffset + px] val pixel = pixels[rowOffset + px]
rSum += (pixel shr 16) and 0xFF rSum += (pixel shr 16) and 0xFF
gSum += (pixel shr 8) and 0xFF gSum += (pixel shr 8) and 0xFF
@@ -1979,9 +1985,9 @@ class MainActivity : AppCompatActivity() {
temp[rowOffset] = (0xFF shl 24) or ((rSum / windowSize) shl 16) or ((gSum / windowSize) shl 8) or (bSum / windowSize) temp[rowOffset] = (0xFF shl 24) or ((rSum / windowSize) shl 16) or ((gSum / windowSize) shl 8) or (bSum / windowSize)
// Slide window // Slide window
for (x in 1 until w) { for (x in 1 until targetW) {
val oldPixelX = (x - 1 - r).coerceIn(0, w - 1) val oldPixelX = (x - 1 - r).coerceIn(0, targetW - 1)
val newPixelX = (x + r).coerceIn(0, w - 1) val newPixelX = (x + r).coerceIn(0, targetW - 1)
val oldPixel = pixels[rowOffset + oldPixelX] val oldPixel = pixels[rowOffset + oldPixelX]
val newPixel = pixels[rowOffset + newPixelX] val newPixel = pixels[rowOffset + newPixelX]
@@ -1995,15 +2001,15 @@ class MainActivity : AppCompatActivity() {
} }
// Vertical pass // Vertical pass
for (x in 0 until w) { for (x in 0 until targetW) {
var rSum = 0 var rSum = 0
var gSum = 0 var gSum = 0
var bSum = 0 var bSum = 0
// Initialize window sum // Initialize window sum
for (dy in -r..r) { for (dy in -r..r) {
val py = dy.coerceIn(0, h - 1) val py = dy.coerceIn(0, targetH - 1)
val pixel = temp[py * w + x] val pixel = temp[py * targetW + x]
rSum += (pixel shr 16) and 0xFF rSum += (pixel shr 16) and 0xFF
gSum += (pixel shr 8) and 0xFF gSum += (pixel shr 8) and 0xFF
bSum += pixel and 0xFF bSum += pixel and 0xFF
@@ -2011,23 +2017,27 @@ class MainActivity : AppCompatActivity() {
pixels[x] = (0xFF shl 24) or ((rSum / windowSize) shl 16) or ((gSum / windowSize) shl 8) or (bSum / windowSize) pixels[x] = (0xFF shl 24) or ((rSum / windowSize) shl 16) or ((gSum / windowSize) shl 8) or (bSum / windowSize)
// Slide window // Slide window
for (y in 1 until h) { for (y in 1 until targetH) {
val oldPixelY = (y - 1 - r).coerceIn(0, h - 1) val oldPixelY = (y - 1 - r).coerceIn(0, targetH - 1)
val newPixelY = (y + r).coerceIn(0, h - 1) val newPixelY = (y + r).coerceIn(0, targetH - 1)
val oldPixel = temp[oldPixelY * w + x] val oldPixel = temp[oldPixelY * targetW + x]
val newPixel = temp[newPixelY * w + x] val newPixel = temp[newPixelY * targetW + x]
rSum += ((newPixel shr 16) and 0xFF) - ((oldPixel shr 16) and 0xFF) rSum += ((newPixel shr 16) and 0xFF) - ((oldPixel shr 16) and 0xFF)
gSum += ((newPixel shr 8) and 0xFF) - ((oldPixel shr 8) and 0xFF) gSum += ((newPixel shr 8) and 0xFF) - ((oldPixel shr 8) and 0xFF)
bSum += (newPixel and 0xFF) - (oldPixel and 0xFF) bSum += (newPixel and 0xFF) - (oldPixel and 0xFF)
pixels[y * w + x] = (0xFF shl 24) or ((rSum / windowSize) shl 16) or ((gSum / windowSize) shl 8) or (bSum / windowSize) pixels[y * targetW + x] = (0xFF shl 24) or ((rSum / windowSize) shl 16) or ((gSum / windowSize) shl 8) or (bSum / windowSize)
} }
} }
output.setPixels(pixels, 0, w, 0, 0, w, h) val tinyBlurred = android.graphics.Bitmap.createBitmap(targetW, targetH, android.graphics.Bitmap.Config.ARGB_8888)
return output tinyBlurred.setPixels(pixels, 0, targetW, 0, 0, targetW, targetH)
val upscaled = android.graphics.Bitmap.createScaledBitmap(tinyBlurred, w, h, true)
tinyBlurred.recycle()
return upscaled
} }
override fun onDestroy() { override fun onDestroy() {