Browse Source

fix: implement bitmap memory management for CRITICAL-002

- Add proper bitmap cleanup in ScreenCaptureService.processImage()
- Fix bitmap leaks in convertImageToMat() with try-finally blocks
- Ensure bitmap.recycle() called in all exception paths
- Add safe cleanup in EnhancedOCR.performBasicOCR()
- Wrap OCR bitmap operations with proper resource management

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
critical-002-bitmap-memory
Quildra 5 months ago
parent
commit
ca512afd4f
  1. 26
      app/src/main/java/com/quillstudios/pokegoalshelper/EnhancedOCR.kt
  2. 46
      app/src/main/java/com/quillstudios/pokegoalshelper/ScreenCaptureService.kt

26
app/src/main/java/com/quillstudios/pokegoalshelper/EnhancedOCR.kt

@ -119,9 +119,13 @@ class EnhancedOCR(private val context: Context) {
}
// Method 4: Class-specific OCR optimization
val classOptimizedText = performClassSpecificOCR(croppedMat, detection.className)
if (classOptimizedText.isNotEmpty()) {
candidates.add(Pair(classOptimizedText, 1.3f)) // Highest weight for class-specific
try {
val classOptimizedText = performClassSpecificOCR(croppedMat, detection.className)
if (classOptimizedText.isNotEmpty()) {
candidates.add(Pair(classOptimizedText, 1.3f)) // Highest weight for class-specific
}
} catch (e: Exception) {
Log.e(TAG, "❌ Error in class-specific OCR", e)
}
} catch (e: Exception) {
@ -193,21 +197,23 @@ class EnhancedOCR(private val context: Context) {
// - Firebase ML Text Recognition
// - Custom OCR model
try {
var bitmap: Bitmap? = null
return try {
// Convert Mat to Bitmap for OCR processing
val bitmap = Bitmap.createBitmap(mat.cols(), mat.rows(), Bitmap.Config.ARGB_8888)
bitmap = Bitmap.createBitmap(mat.cols(), mat.rows(), Bitmap.Config.ARGB_8888)
Utils.matToBitmap(mat, bitmap)
// TODO: Integrate with actual OCR library
// For now, return a placeholder based on image properties
val result = simulateOCRResult(bitmap)
bitmap.recycle()
return result
simulateOCRResult(bitmap)
} catch (e: Exception) {
Log.e(TAG, "❌ Error in basic OCR", e)
return ""
""
} finally {
// Always clean up bitmap
bitmap?.recycle()
}
}

46
app/src/main/java/com/quillstudios/pokegoalshelper/ScreenCaptureService.kt

@ -380,6 +380,9 @@ class ScreenCaptureService : Service() {
}
private fun processImage(image: Image) {
var bitmap: Bitmap? = null
var croppedBitmap: Bitmap? = null
try {
val planes = image.planes
val buffer = planes[0].buffer
@ -391,7 +394,7 @@ class ScreenCaptureService : Service() {
Log.d(TAG, "🖼️ CAPTURE DEBUG: screenSize=${screenWidth}x${screenHeight}, expected bitmap=${screenWidth + rowPadding / pixelStride}x${screenHeight}")
// Create bitmap from image
val bitmap = Bitmap.createBitmap(
bitmap = Bitmap.createBitmap(
screenWidth + rowPadding / pixelStride,
screenHeight,
Bitmap.Config.ARGB_8888
@ -401,7 +404,7 @@ class ScreenCaptureService : Service() {
Log.d(TAG, "🖼️ CAPTURE DEBUG: created bitmap=${bitmap.width}x${bitmap.height}")
// Convert to cropped bitmap if needed
val croppedBitmap = if (rowPadding == 0) {
croppedBitmap = if (rowPadding == 0) {
Log.d(TAG, "🖼️ CAPTURE DEBUG: No padding, using original bitmap")
bitmap
} else {
@ -437,14 +440,14 @@ class ScreenCaptureService : Service() {
analyzePokemonScreen(mat)
}
// Clean up
if (croppedBitmap != bitmap) {
croppedBitmap.recycle()
}
bitmap.recycle()
} catch (e: Exception) {
Log.e(TAG, "Error processing image", e)
} finally {
// Always clean up bitmaps in finally block to prevent leaks
if (croppedBitmap != null && croppedBitmap != bitmap) {
croppedBitmap.recycle()
}
bitmap?.recycle()
}
}
@ -747,12 +750,16 @@ class ScreenCaptureService : Service() {
Utils.matToBitmap(processedRoi, bitmap)
// Use ML Kit OCR
val extractedText = performOCR(bitmap, detection.className)
val extractedText = try {
performOCR(bitmap, detection.className)
} finally {
// Always clean up bitmap after OCR
bitmap.recycle()
}
// Clean up
roi.release()
processedRoi.release()
bitmap.recycle()
if (extractedText != null) {
Log.i(TAG, "✅ YOLO SUCCESS: ${detection.className} = '$extractedText' (conf: ${String.format("%.2f", detection.confidence)})")
@ -1149,6 +1156,10 @@ class ScreenCaptureService : Service() {
}
private fun convertImageToMat(image: Image): Mat? {
var bitmap: Bitmap? = null
var croppedBitmap: Bitmap? = null
var mat: Mat? = null
return try {
val planes = image.planes
val buffer = planes[0].buffer
@ -1157,7 +1168,7 @@ class ScreenCaptureService : Service() {
val rowPadding = rowStride - pixelStride * screenWidth
// Create bitmap from image
val bitmap = Bitmap.createBitmap(
bitmap = Bitmap.createBitmap(
screenWidth + rowPadding / pixelStride,
screenHeight,
Bitmap.Config.ARGB_8888
@ -1165,29 +1176,36 @@ class ScreenCaptureService : Service() {
bitmap.copyPixelsFromBuffer(buffer)
// Crop bitmap to remove padding if needed
val croppedBitmap = if (rowPadding == 0) {
croppedBitmap = if (rowPadding == 0) {
bitmap
} else {
val cropped = Bitmap.createBitmap(bitmap, 0, 0, screenWidth, screenHeight)
bitmap.recycle() // Clean up original
bitmap = null // Mark as recycled
cropped
}
// Convert bitmap to Mat
val mat = Mat()
mat = Mat()
Utils.bitmapToMat(croppedBitmap, mat)
// Convert from RGBA to BGR (OpenCV format for proper color channel handling)
val bgrMat = Mat()
Imgproc.cvtColor(mat, bgrMat, Imgproc.COLOR_RGBA2BGR)
// Clean up
// Clean up intermediate resources
mat.release()
croppedBitmap.recycle()
bgrMat
} catch (e: Exception) {
Log.e(TAG, "❌ Error converting image to Mat", e)
// Clean up on error
mat?.release()
if (croppedBitmap != null && croppedBitmap != bitmap) {
croppedBitmap.recycle()
}
bitmap?.recycle()
null
}
}

Loading…
Cancel
Save