Add bitmap safety in applyTiltShiftEffect()
Track all intermediate bitmaps with nullable variables and recycle them in a finally block. This prevents native memory leaks when an OOM or other exception occurs mid-processing. Variables are set to null after recycle or handoff to the caller. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f53d6f0b1b
commit
593f2c5b1f
1 changed files with 63 additions and 45 deletions
|
|
@ -152,63 +152,81 @@ class ImageCaptureHandler(
|
|||
/**
|
||||
* Applies tilt-shift blur effect to a bitmap.
|
||||
* Supports both linear and radial modes.
|
||||
*
|
||||
* All intermediate bitmaps are tracked and recycled in a finally block
|
||||
* so that an OOM or other exception does not leak native memory.
|
||||
*/
|
||||
private fun applyTiltShiftEffect(source: Bitmap, params: BlurParameters): Bitmap {
|
||||
val width = source.width
|
||||
val height = source.height
|
||||
|
||||
// Create output bitmap
|
||||
val result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
var result: Bitmap? = null
|
||||
var scaled: Bitmap? = null
|
||||
var blurred: Bitmap? = null
|
||||
var blurredFullSize: Bitmap? = null
|
||||
var mask: Bitmap? = null
|
||||
|
||||
// For performance, we use a scaled-down version for blur and composite
|
||||
val scaleFactor = 4 // Blur a 1/4 size image for speed
|
||||
val blurredWidth = width / scaleFactor
|
||||
val blurredHeight = height / scaleFactor
|
||||
try {
|
||||
result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
|
||||
// Create scaled bitmap for blur
|
||||
val scaled = Bitmap.createScaledBitmap(source, blurredWidth, blurredHeight, true)
|
||||
val scaleFactor = 4
|
||||
val blurredWidth = width / scaleFactor
|
||||
val blurredHeight = height / scaleFactor
|
||||
|
||||
// Apply stack blur (fast approximation)
|
||||
val blurred = stackBlur(scaled, (params.blurAmount * 25).toInt().coerceIn(1, 25))
|
||||
scaled.recycle()
|
||||
scaled = Bitmap.createScaledBitmap(source, blurredWidth, blurredHeight, true)
|
||||
|
||||
// Scale blurred back up
|
||||
val blurredFullSize = Bitmap.createScaledBitmap(blurred, width, height, true)
|
||||
blurred.recycle()
|
||||
blurred = stackBlur(scaled, (params.blurAmount * 25).toInt().coerceIn(1, 25))
|
||||
scaled.recycle()
|
||||
scaled = null
|
||||
|
||||
// Create gradient mask based on tilt-shift parameters
|
||||
val mask = createGradientMask(width, height, params)
|
||||
blurredFullSize = Bitmap.createScaledBitmap(blurred, width, height, true)
|
||||
blurred.recycle()
|
||||
blurred = null
|
||||
|
||||
// Composite: blend original with blurred based on mask
|
||||
val pixels = IntArray(width * height)
|
||||
val blurredPixels = IntArray(width * height)
|
||||
val maskPixels = IntArray(width * height)
|
||||
mask = createGradientMask(width, height, params)
|
||||
|
||||
source.getPixels(pixels, 0, width, 0, 0, width, height)
|
||||
blurredFullSize.getPixels(blurredPixels, 0, width, 0, 0, width, height)
|
||||
mask.getPixels(maskPixels, 0, width, 0, 0, width, height)
|
||||
// Composite: blend original with blurred based on mask
|
||||
val pixels = IntArray(width * height)
|
||||
val blurredPixels = IntArray(width * height)
|
||||
val maskPixels = IntArray(width * height)
|
||||
|
||||
blurredFullSize.recycle()
|
||||
mask.recycle()
|
||||
source.getPixels(pixels, 0, width, 0, 0, width, height)
|
||||
blurredFullSize.getPixels(blurredPixels, 0, width, 0, 0, width, height)
|
||||
mask.getPixels(maskPixels, 0, width, 0, 0, width, height)
|
||||
|
||||
for (i in pixels.indices) {
|
||||
val maskAlpha = (maskPixels[i] and 0xFF) / 255f
|
||||
val origR = (pixels[i] shr 16) and 0xFF
|
||||
val origG = (pixels[i] shr 8) and 0xFF
|
||||
val origB = pixels[i] and 0xFF
|
||||
val blurR = (blurredPixels[i] shr 16) and 0xFF
|
||||
val blurG = (blurredPixels[i] shr 8) and 0xFF
|
||||
val blurB = blurredPixels[i] and 0xFF
|
||||
blurredFullSize.recycle()
|
||||
blurredFullSize = null
|
||||
mask.recycle()
|
||||
mask = null
|
||||
|
||||
val r = (origR * (1 - maskAlpha) + blurR * maskAlpha).toInt()
|
||||
val g = (origG * (1 - maskAlpha) + blurG * maskAlpha).toInt()
|
||||
val b = (origB * (1 - maskAlpha) + blurB * maskAlpha).toInt()
|
||||
for (i in pixels.indices) {
|
||||
val maskAlpha = (maskPixels[i] and 0xFF) / 255f
|
||||
val origR = (pixels[i] shr 16) and 0xFF
|
||||
val origG = (pixels[i] shr 8) and 0xFF
|
||||
val origB = pixels[i] and 0xFF
|
||||
val blurR = (blurredPixels[i] shr 16) and 0xFF
|
||||
val blurG = (blurredPixels[i] shr 8) and 0xFF
|
||||
val blurB = blurredPixels[i] and 0xFF
|
||||
|
||||
pixels[i] = (0xFF shl 24) or (r shl 16) or (g shl 8) or b
|
||||
val r = (origR * (1 - maskAlpha) + blurR * maskAlpha).toInt()
|
||||
val g = (origG * (1 - maskAlpha) + blurG * maskAlpha).toInt()
|
||||
val b = (origB * (1 - maskAlpha) + blurB * maskAlpha).toInt()
|
||||
|
||||
pixels[i] = (0xFF shl 24) or (r shl 16) or (g shl 8) or b
|
||||
}
|
||||
|
||||
result.setPixels(pixels, 0, width, 0, 0, width, height)
|
||||
|
||||
val output = result
|
||||
result = null // prevent finally from recycling the returned bitmap
|
||||
return output
|
||||
} finally {
|
||||
result?.recycle()
|
||||
scaled?.recycle()
|
||||
blurred?.recycle()
|
||||
blurredFullSize?.recycle()
|
||||
mask?.recycle()
|
||||
}
|
||||
|
||||
result.setPixels(pixels, 0, width, 0, 0, width, height)
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue