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.
|
* Applies tilt-shift blur effect to a bitmap.
|
||||||
* Supports both linear and radial modes.
|
* 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 {
|
private fun applyTiltShiftEffect(source: Bitmap, params: BlurParameters): Bitmap {
|
||||||
val width = source.width
|
val width = source.width
|
||||||
val height = source.height
|
val height = source.height
|
||||||
|
|
||||||
// Create output bitmap
|
var result: Bitmap? = null
|
||||||
val result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
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
|
try {
|
||||||
val scaleFactor = 4 // Blur a 1/4 size image for speed
|
result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||||
val blurredWidth = width / scaleFactor
|
|
||||||
val blurredHeight = height / scaleFactor
|
|
||||||
|
|
||||||
// Create scaled bitmap for blur
|
val scaleFactor = 4
|
||||||
val scaled = Bitmap.createScaledBitmap(source, blurredWidth, blurredHeight, true)
|
val blurredWidth = width / scaleFactor
|
||||||
|
val blurredHeight = height / scaleFactor
|
||||||
|
|
||||||
// Apply stack blur (fast approximation)
|
scaled = Bitmap.createScaledBitmap(source, blurredWidth, blurredHeight, true)
|
||||||
val blurred = stackBlur(scaled, (params.blurAmount * 25).toInt().coerceIn(1, 25))
|
|
||||||
scaled.recycle()
|
|
||||||
|
|
||||||
// Scale blurred back up
|
blurred = stackBlur(scaled, (params.blurAmount * 25).toInt().coerceIn(1, 25))
|
||||||
val blurredFullSize = Bitmap.createScaledBitmap(blurred, width, height, true)
|
scaled.recycle()
|
||||||
blurred.recycle()
|
scaled = null
|
||||||
|
|
||||||
// Create gradient mask based on tilt-shift parameters
|
blurredFullSize = Bitmap.createScaledBitmap(blurred, width, height, true)
|
||||||
val mask = createGradientMask(width, height, params)
|
blurred.recycle()
|
||||||
|
blurred = null
|
||||||
|
|
||||||
// Composite: blend original with blurred based on mask
|
mask = createGradientMask(width, height, params)
|
||||||
val pixels = IntArray(width * height)
|
|
||||||
val blurredPixels = IntArray(width * height)
|
|
||||||
val maskPixels = IntArray(width * height)
|
|
||||||
|
|
||||||
source.getPixels(pixels, 0, width, 0, 0, width, height)
|
// Composite: blend original with blurred based on mask
|
||||||
blurredFullSize.getPixels(blurredPixels, 0, width, 0, 0, width, height)
|
val pixels = IntArray(width * height)
|
||||||
mask.getPixels(maskPixels, 0, width, 0, 0, width, height)
|
val blurredPixels = IntArray(width * height)
|
||||||
|
val maskPixels = IntArray(width * height)
|
||||||
|
|
||||||
blurredFullSize.recycle()
|
source.getPixels(pixels, 0, width, 0, 0, width, height)
|
||||||
mask.recycle()
|
blurredFullSize.getPixels(blurredPixels, 0, width, 0, 0, width, height)
|
||||||
|
mask.getPixels(maskPixels, 0, width, 0, 0, width, height)
|
||||||
|
|
||||||
for (i in pixels.indices) {
|
blurredFullSize.recycle()
|
||||||
val maskAlpha = (maskPixels[i] and 0xFF) / 255f
|
blurredFullSize = null
|
||||||
val origR = (pixels[i] shr 16) and 0xFF
|
mask.recycle()
|
||||||
val origG = (pixels[i] shr 8) and 0xFF
|
mask = null
|
||||||
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
|
|
||||||
|
|
||||||
val r = (origR * (1 - maskAlpha) + blurR * maskAlpha).toInt()
|
for (i in pixels.indices) {
|
||||||
val g = (origG * (1 - maskAlpha) + blurG * maskAlpha).toInt()
|
val maskAlpha = (maskPixels[i] and 0xFF) / 255f
|
||||||
val b = (origB * (1 - maskAlpha) + blurB * maskAlpha).toInt()
|
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