Smooth blur: replace O(N*r) box blur with O(N) sliding window box blur and scale intensity linearly from 1 to 40
This commit is contained in:
@@ -1664,7 +1664,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
if (!isAiSupported) {
|
if (!isAiSupported) {
|
||||||
if (blurIntensity > 0f) {
|
if (blurIntensity > 0f) {
|
||||||
val radius = (25 * blurIntensity).toInt().coerceIn(1, 25)
|
val radius = (40 * blurIntensity).toInt().coerceIn(1, 40)
|
||||||
return blurBitmap(capturedImage, radius)
|
return blurBitmap(capturedImage, radius)
|
||||||
}
|
}
|
||||||
return capturedImage
|
return capturedImage
|
||||||
@@ -1723,7 +1723,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
val bgPixels = IntArray(w * h)
|
val bgPixels = IntArray(w * h)
|
||||||
var blurredBg: android.graphics.Bitmap? = null
|
var blurredBg: android.graphics.Bitmap? = null
|
||||||
if (blurIntensity > 0f) {
|
if (blurIntensity > 0f) {
|
||||||
val radius = (15 * blurIntensity).toInt().coerceIn(1, 25)
|
val radius = (25 * blurIntensity).toInt().coerceIn(1, 25)
|
||||||
blurredBg = blurBitmap(baseBg, radius)
|
blurredBg = blurBitmap(baseBg, radius)
|
||||||
blurredBg.getPixels(bgPixels, 0, w, 0, 0, w, h)
|
blurredBg.getPixels(bgPixels, 0, w, 0, 0, w, h)
|
||||||
} else {
|
} else {
|
||||||
@@ -1778,7 +1778,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (blurIntensity > 0f) {
|
if (blurIntensity > 0f) {
|
||||||
val radius = (15 * blurIntensity).toInt().coerceIn(1, 15)
|
val radius = (25 * blurIntensity).toInt().coerceIn(1, 25)
|
||||||
return blurBitmap(capturedImage, radius)
|
return blurBitmap(capturedImage, radius)
|
||||||
}
|
}
|
||||||
return capturedImage
|
return capturedImage
|
||||||
@@ -1835,7 +1835,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
val bgPixels = IntArray(w * h)
|
val bgPixels = IntArray(w * h)
|
||||||
var blurredBg: android.graphics.Bitmap? = null
|
var blurredBg: android.graphics.Bitmap? = null
|
||||||
if (blurIntensity > 0f) {
|
if (blurIntensity > 0f) {
|
||||||
val radius = (25 * blurIntensity).toInt().coerceIn(1, 25)
|
val radius = (40 * blurIntensity).toInt().coerceIn(1, 40)
|
||||||
blurredBg = blurBitmap(baseBg, radius)
|
blurredBg = blurBitmap(baseBg, radius)
|
||||||
blurredBg.getPixels(bgPixels, 0, w, 0, 0, w, h)
|
blurredBg.getPixels(bgPixels, 0, w, 0, 0, w, h)
|
||||||
} else {
|
} else {
|
||||||
@@ -1890,7 +1890,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (blurIntensity > 0f) {
|
if (blurIntensity > 0f) {
|
||||||
val radius = (25 * blurIntensity).toInt().coerceIn(1, 25)
|
val radius = (40 * blurIntensity).toInt().coerceIn(1, 40)
|
||||||
return blurBitmap(capturedImage, radius)
|
return blurBitmap(capturedImage, radius)
|
||||||
}
|
}
|
||||||
return capturedImage
|
return capturedImage
|
||||||
@@ -1950,74 +1950,84 @@ 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 scaleFactor = 4
|
|
||||||
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 downscaledW = (w / scaleFactor).coerceAtLeast(1)
|
val pixels = IntArray(w * h)
|
||||||
val downscaledH = (h / scaleFactor).coerceAtLeast(1)
|
src.getPixels(pixels, 0, w, 0, 0, w, h)
|
||||||
val downscaledBitmap = android.graphics.Bitmap.createScaledBitmap(src, downscaledW, downscaledH, true)
|
|
||||||
|
|
||||||
val r = (radius / scaleFactor).coerceAtLeast(1)
|
val temp = IntArray(w * h)
|
||||||
|
val r = radius.coerceAtLeast(1)
|
||||||
|
val windowSize = r * 2 + 1
|
||||||
|
|
||||||
val width = downscaledBitmap.width
|
// Horizontal pass
|
||||||
val height = downscaledBitmap.height
|
for (y in 0 until h) {
|
||||||
val pixels = IntArray(width * height)
|
val rowOffset = y * w
|
||||||
downscaledBitmap.getPixels(pixels, 0, width, 0, 0, width, height)
|
var rSum = 0
|
||||||
downscaledBitmap.recycle()
|
var gSum = 0
|
||||||
|
var bSum = 0
|
||||||
val blurredPixels = IntArray(width * height)
|
|
||||||
|
// Initialize window sum
|
||||||
for (y in 0 until height) {
|
for (dx in -r..r) {
|
||||||
for (x in 0 until width) {
|
val px = dx.coerceIn(0, w - 1)
|
||||||
var rSum = 0
|
val pixel = pixels[rowOffset + px]
|
||||||
var gSum = 0
|
rSum += (pixel shr 16) and 0xFF
|
||||||
var bSum = 0
|
gSum += (pixel shr 8) and 0xFF
|
||||||
var count = 0
|
bSum += pixel and 0xFF
|
||||||
for (dx in -r..r) {
|
}
|
||||||
val px = x + dx
|
temp[rowOffset] = (0xFF shl 24) or ((rSum / windowSize) shl 16) or ((gSum / windowSize) shl 8) or (bSum / windowSize)
|
||||||
if (px in 0 until width) {
|
|
||||||
val color = pixels[y * width + px]
|
// Slide window
|
||||||
rSum += android.graphics.Color.red(color)
|
for (x in 1 until w) {
|
||||||
gSum += android.graphics.Color.green(color)
|
val oldPixelX = (x - 1 - r).coerceIn(0, w - 1)
|
||||||
bSum += android.graphics.Color.blue(color)
|
val newPixelX = (x + r).coerceIn(0, w - 1)
|
||||||
count++
|
|
||||||
}
|
val oldPixel = pixels[rowOffset + oldPixelX]
|
||||||
}
|
val newPixel = pixels[rowOffset + newPixelX]
|
||||||
val idx = y * width + x
|
|
||||||
blurredPixels[idx] = android.graphics.Color.rgb(rSum / count, gSum / count, bSum / count)
|
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)
|
||||||
|
|
||||||
|
temp[rowOffset + x] = (0xFF shl 24) or ((rSum / windowSize) shl 16) or ((gSum / windowSize) shl 8) or (bSum / windowSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val finalPixels = IntArray(width * height)
|
// Vertical pass
|
||||||
for (x in 0 until width) {
|
for (x in 0 until w) {
|
||||||
for (y in 0 until height) {
|
var rSum = 0
|
||||||
var rSum = 0
|
var gSum = 0
|
||||||
var gSum = 0
|
var bSum = 0
|
||||||
var bSum = 0
|
|
||||||
var count = 0
|
// Initialize window sum
|
||||||
for (dy in -r..r) {
|
for (dy in -r..r) {
|
||||||
val py = y + dy
|
val py = dy.coerceIn(0, h - 1)
|
||||||
if (py in 0 until height) {
|
val pixel = temp[py * w + x]
|
||||||
val color = blurredPixels[py * width + x]
|
rSum += (pixel shr 16) and 0xFF
|
||||||
rSum += android.graphics.Color.red(color)
|
gSum += (pixel shr 8) and 0xFF
|
||||||
gSum += android.graphics.Color.green(color)
|
bSum += pixel and 0xFF
|
||||||
bSum += android.graphics.Color.blue(color)
|
}
|
||||||
count++
|
pixels[x] = (0xFF shl 24) or ((rSum / windowSize) shl 16) or ((gSum / windowSize) shl 8) or (bSum / windowSize)
|
||||||
}
|
|
||||||
}
|
// Slide window
|
||||||
val idx = y * width + x
|
for (y in 1 until h) {
|
||||||
finalPixels[idx] = android.graphics.Color.rgb(rSum / count, gSum / count, bSum / count)
|
val oldPixelY = (y - 1 - r).coerceIn(0, h - 1)
|
||||||
|
val newPixelY = (y + r).coerceIn(0, h - 1)
|
||||||
|
|
||||||
|
val oldPixel = temp[oldPixelY * w + x]
|
||||||
|
val newPixel = temp[newPixelY * w + 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val tinyBlurred = android.graphics.Bitmap.createBitmap(width, height, android.graphics.Bitmap.Config.ARGB_8888)
|
output.setPixels(pixels, 0, w, 0, 0, w, h)
|
||||||
tinyBlurred.setPixels(finalPixels, 0, width, 0, 0, width, height)
|
return output
|
||||||
|
|
||||||
val upscaledDest = android.graphics.Bitmap.createScaledBitmap(tinyBlurred, w, h, true)
|
|
||||||
tinyBlurred.recycle()
|
|
||||||
|
|
||||||
return upscaledDest
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
|||||||
Reference in New Issue
Block a user