diff --git a/android-project/app/src/main/java/com/photobooth/app/MainActivity.kt b/android-project/app/src/main/java/com/photobooth/app/MainActivity.kt index 9f4a584..b8042e5 100644 --- a/android-project/app/src/main/java/com/photobooth/app/MainActivity.kt +++ b/android-project/app/src/main/java/com/photobooth/app/MainActivity.kt @@ -1952,25 +1952,31 @@ class MainActivity : AppCompatActivity() { private fun blurBitmap(src: android.graphics.Bitmap, radius: Int): android.graphics.Bitmap { val w = src.width val h = src.height - val output = android.graphics.Bitmap.createBitmap(w, h, android.graphics.Bitmap.Config.ARGB_8888) - val pixels = IntArray(w * h) - src.getPixels(pixels, 0, w, 0, 0, w, h) + // Resolution-independent downscaling for consistent blur appearance and extremely high performance + val targetW = 120 + val targetH = (h * targetW / w).coerceAtLeast(1) - val temp = IntArray(w * h) - val r = radius.coerceAtLeast(1) + val downscaled = android.graphics.Bitmap.createScaledBitmap(src, targetW, targetH, true) + 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 // Horizontal pass - for (y in 0 until h) { - val rowOffset = y * w + for (y in 0 until targetH) { + val rowOffset = y * targetW var rSum = 0 var gSum = 0 var bSum = 0 // Initialize window sum for (dx in -r..r) { - val px = dx.coerceIn(0, w - 1) + val px = dx.coerceIn(0, targetW - 1) val pixel = pixels[rowOffset + px] rSum += (pixel shr 16) 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) // Slide window - for (x in 1 until w) { - val oldPixelX = (x - 1 - r).coerceIn(0, w - 1) - val newPixelX = (x + r).coerceIn(0, w - 1) + for (x in 1 until targetW) { + val oldPixelX = (x - 1 - r).coerceIn(0, targetW - 1) + val newPixelX = (x + r).coerceIn(0, targetW - 1) val oldPixel = pixels[rowOffset + oldPixelX] val newPixel = pixels[rowOffset + newPixelX] @@ -1995,15 +2001,15 @@ class MainActivity : AppCompatActivity() { } // Vertical pass - for (x in 0 until w) { + for (x in 0 until targetW) { var rSum = 0 var gSum = 0 var bSum = 0 // Initialize window sum for (dy in -r..r) { - val py = dy.coerceIn(0, h - 1) - val pixel = temp[py * w + x] + val py = dy.coerceIn(0, targetH - 1) + val pixel = temp[py * targetW + x] rSum += (pixel shr 16) and 0xFF gSum += (pixel shr 8) 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) // Slide window - for (y in 1 until h) { - val oldPixelY = (y - 1 - r).coerceIn(0, h - 1) - val newPixelY = (y + r).coerceIn(0, h - 1) + for (y in 1 until targetH) { + val oldPixelY = (y - 1 - r).coerceIn(0, targetH - 1) + val newPixelY = (y + r).coerceIn(0, targetH - 1) - val oldPixel = temp[oldPixelY * w + x] - val newPixel = temp[newPixelY * w + x] + val oldPixel = temp[oldPixelY * targetW + x] + val newPixel = temp[newPixelY * targetW + x] rSum += ((newPixel shr 16) and 0xFF) - ((oldPixel shr 16) and 0xFF) gSum += ((newPixel shr 8) and 0xFF) - ((oldPixel shr 8) 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) - return output + val tinyBlurred = android.graphics.Bitmap.createBitmap(targetW, targetH, android.graphics.Bitmap.Config.ARGB_8888) + 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() {