This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

fix: broken focus area

+96 -27
+95 -25
posedetection/src/androidMain/kotlin/com/performancecoachlab/posedetection/camera/Utils.android.kt
··· 213 213 val analysisBitmap: Bitmap = baseBitmap.rotateIntoPooled(rotationDegrees) 214 214 215 215 // 2) MLKit image must match analysisBitmap coordinate space. Rotation is now 0. 216 + // IMPORTANT: focusArea is defined in normalized coordinates of the *displayed* (upright) frame, 217 + // so apply it with angle=0 in this already-rotated bitmap coordinate space. 216 218 val mlKitImage: InputImage? = poseDetector?.let { 217 - val masked = analysisBitmap.applyFocusAreaMaskPooled(focusArea, rotationDegrees) 218 - InputImage.fromBitmap(masked, 0) 219 + if (focusArea == null) { 220 + InputImage.fromBitmap(analysisBitmap, 0) 221 + } else { 222 + // Create the masked bitmap into a dedicated pool so we never overwrite analysisBitmap 223 + val out = PoseMaskBitmapPool.obtain( 224 + analysisBitmap.width, 225 + analysisBitmap.height, 226 + analysisBitmap.config ?: Bitmap.Config.ARGB_8888 227 + ) 228 + val canvas = Canvas(out) 229 + canvas.drawBitmap(analysisBitmap, 0f, 0f, null) 230 + 231 + // Apply mask in upright coordinates. 232 + out.applyFocusAreaMaskInPlace( 233 + focusArea = focusArea, 234 + angle = 0, 235 + ) 236 + 237 + InputImage.fromBitmap(out, 0) 238 + } 219 239 } 220 240 221 241 // 3) Tensor input: resize into pooled bitmap (avoid allocating each frame). ··· 439 459 } ?: this 440 460 } 441 461 442 - /** 443 - * More efficient variant of applyFocusAreaMask: draws into a pooled bitmap, avoiding Bitmap.copy(). 444 - * Returns a bitmap in the same size/coords as the receiver. 445 - */ 446 - fun Bitmap.applyFocusAreaMaskPooled(focusArea: Rect?, angle: Int = 0): Bitmap { 447 - if (focusArea == null) return this 462 + /** Pool solely for pose-masked bitmaps so we never overwrite the analysis bitmap used by object detection. */ 463 + private object PoseMaskBitmapPool { 464 + private var cached: Bitmap? = null 465 + private var cachedW: Int = 0 466 + private var cachedH: Int = 0 467 + private var cachedConfig: Bitmap.Config = Bitmap.Config.ARGB_8888 448 468 449 - val out = AnalysisBitmapPool.obtain(width, height, this.config ?: Bitmap.Config.ARGB_8888) 450 - val canvas = Canvas(out) 451 - // Draw the source into the pooled bitmap. 452 - canvas.drawBitmap(this, 0f, 0f, null) 469 + fun obtain(width: Int, height: Int, config: Bitmap.Config = Bitmap.Config.ARGB_8888): Bitmap { 470 + val bmp = cached 471 + return if ( 472 + bmp != null && 473 + !bmp.isRecycled && 474 + cachedW == width && 475 + cachedH == height && 476 + cachedConfig == config 477 + ) { 478 + bmp.eraseColor(android.graphics.Color.TRANSPARENT) 479 + bmp 480 + } else { 481 + createBitmap(width, height, config).also { newBmp -> 482 + cached = newBmp 483 + cachedW = width 484 + cachedH = height 485 + cachedConfig = config 486 + } 487 + } 488 + } 489 + } 453 490 491 + /** In-place masking onto the receiver bitmap; assumes the receiver already contains the frame pixels. */ 492 + private fun Bitmap.applyFocusAreaMaskInPlace(focusArea: Rect, angle: Int = 0) { 493 + val area = focusArea.normalizedClamped() 494 + if (area.isEffectivelyFullViewport()) return 495 + if (area.width <= 1e-4f || area.height <= 1e-4f) return 496 + 497 + val canvas = Canvas(this) 454 498 val paint = Paint().apply { color = android.graphics.Color.BLACK } 455 499 456 500 val transformedRect = when (angle % 360) { 457 501 90 -> Rect( 458 - left = focusArea.top, 459 - top = 1f - focusArea.right, 460 - right = focusArea.bottom, 461 - bottom = 1f - focusArea.left 502 + left = area.top, 503 + top = 1f - area.right, 504 + right = area.bottom, 505 + bottom = 1f - area.left 462 506 ) 463 507 464 508 180 -> Rect( 465 - left = 1f - focusArea.right, 466 - top = 1f - focusArea.bottom, 467 - right = 1f - focusArea.left, 468 - bottom = 1f - focusArea.top 509 + left = 1f - area.right, 510 + top = 1f - area.bottom, 511 + right = 1f - area.left, 512 + bottom = 1f - area.top 469 513 ) 470 514 471 515 270 -> Rect( 472 - left = 1f - focusArea.bottom, 473 - top = focusArea.left, 474 - right = 1f - focusArea.top, 475 - bottom = focusArea.right 516 + left = 1f - area.bottom, 517 + top = area.left, 518 + right = 1f - area.top, 519 + bottom = area.right 476 520 ) 477 521 478 - else -> focusArea 522 + else -> area 479 523 } 480 524 481 525 val focusRect = transformedRect.toGraphicsRect(width, height) ··· 484 528 if (focusRect.bottom < height) canvas.drawRect(0f, focusRect.bottom.toFloat(), width.toFloat(), height.toFloat(), paint) 485 529 if (focusRect.left > 0) canvas.drawRect(0f, focusRect.top.toFloat(), focusRect.left.toFloat(), focusRect.bottom.toFloat(), paint) 486 530 if (focusRect.right < width) canvas.drawRect(focusRect.right.toFloat(), focusRect.top.toFloat(), width.toFloat(), focusRect.bottom.toFloat(), paint) 531 + } 487 532 533 + // Keep applyFocusAreaMaskPooled for other call sites, but implement it via the in-place helper. 534 + fun Bitmap.applyFocusAreaMaskPooled(focusArea: Rect?, angle: Int = 0): Bitmap { 535 + if (focusArea == null) return this 536 + 537 + val out = AnalysisBitmapPool.obtain(width, height, this.config ?: Bitmap.Config.ARGB_8888) 538 + val canvas = Canvas(out) 539 + canvas.drawBitmap(this, 0f, 0f, null) 540 + 541 + out.applyFocusAreaMaskInPlace(focusArea, angle) 488 542 return out 489 543 } 490 544 ··· 509 563 Logger.d{ "ModelInfo.label: $cls not in labels" } 510 564 return "$cls" 511 565 } 566 + 567 + private fun Rect.isEffectivelyFullViewport(eps: Float = 1e-4f): Boolean { 568 + return left <= 0f + eps && top <= 0f + eps && right >= 1f - eps && bottom >= 1f - eps 569 + } 570 + 571 + private fun Rect.normalizedClamped(): Rect { 572 + val l = left.coerceIn(0f, 1f) 573 + val t = top.coerceIn(0f, 1f) 574 + val r = right.coerceIn(0f, 1f) 575 + val b = bottom.coerceIn(0f, 1f) 576 + val leftN = min(l, r) 577 + val rightN = max(l, r) 578 + val topN = min(t, b) 579 + val bottomN = max(t, b) 580 + return Rect(left = leftN, top = topN, right = rightN, bottom = bottomN) 581 + }
+1 -2
sample/composeApp/src/commonMain/kotlin/com/nate/posedetection/App.kt
··· 377 377 }, 378 378 objectModel = generalModel, 379 379 modifier = Modifier.weight(1f), 380 - // focusArea = Rect(0f,0f,0.1f,1f), 381 - focusArea = null, 380 + focusArea = Rect(0f,0f,1f,1f), 382 381 frontCamera = frontCamera, 383 382 useUltraWide = ultrawide, 384 383 recordingId = recordingId,