From 013582f650c2c6e2a3ae313b4ef43a8b2899d9d4 Mon Sep 17 00:00:00 2001 From: TTolsun <112919054+TTolsun@users.noreply.github.com> Date: Sat, 24 Oct 2026 15:27:01 +0900 Subject: [PATCH] Preserve fine foreground edges in App Bokeh --- .omm/data-flow/description.md | 2 + .../camera-engines/app-bokeh/description.md | 2 + .../dev/halcamera/camera/AppBokehChecks.kt | 45 +++-- .../dev/halcamera/camera/AppBokehProcessor.kt | 1 + .../java/dev/halcamera/camera/BokehMask.kt | 58 +++++++ .../dev/halcamera/camera/RelativeDepth.kt | 3 +- .../dev/halcamera/camera/BokehMaskTest.kt | 48 ++++++ docs/.site-manifest.json | 2 +- docs/architecture.html | 28 ++-- docs/design/APP-BOKEH.md | 9 +- docs/validation/app-bokeh.md | 18 +- guide/_bindings.yaml | 2 + guide/_content/architecture/module-roles.md | 3 +- guide/_content/architecture/runtime-flow.md | 3 +- guide/_reference/engine.md | 18 +- guide/_reference/evidence.md | 6 +- guide/_reference/troubleshooting.md | 6 +- guide/architecture.md | 28 ++-- tools/docgen/state/evidence.json | 156 ++++++++++-------- 19 files changed, 302 insertions(+), 136 deletions(-) create mode 100644 app/src/main/java/dev/halcamera/camera/BokehMask.kt create mode 100644 app/src/test/java/dev/halcamera/camera/BokehMaskTest.kt diff --git a/.omm/data-flow/description.md b/.omm/data-flow/description.md index 719b0331..440e3b33 100644 --- a/.omm/data-flow/description.md +++ b/.omm/data-flow/description.md @@ -17,3 +17,5 @@ Multi 모드의 ConcurrentSession은 독립 장치마다 출력과 저장을 관 Portrait는 Depth 메뉴에서 선택하지만 별도 확장 세션의 컬러 미리보기와 OEM JPEG를 받습니다. PortraitCameraEngine은 JPEG를 MediaLibrary로 저장하고 효과·해상도·시각만 촬영 JSON에 남깁니다. 확장 콜백은 벤치마크나 일반 CaptureResult 지표에 섞지 않습니다. App Bokeh는 일반 Camera2 Preview+JPEG 세션에서 원본 JPEG를 저장한 뒤 RGB로 디코딩합니다. MiDaS 모델의 Float 상대 깊이와 사용자 선택 지점을 DepthBokeh에 전달하여 배경을 흐리고, 보케 JPEG·깊이 PNG·처리 메타데이터를 MediaLibrary로 저장합니다. HAL Depth나 분석용 YUV를 입력으로 사용하지 않습니다. + +배경 흐림 가중치는 원본 밝기 경계로 보정한 뒤 색 표본과 최종 합성에 동일하게 사용합니다. 깊이 PNG에는 보정 마스크가 아닌 모델 상대 깊이를 저장하며, 메타데이터의 renderer로 합성 방식을 구별합니다. diff --git a/.omm/overall-architecture/camera-engines/app-bokeh/description.md b/.omm/overall-architecture/camera-engines/app-bokeh/description.md index 8f352c62..28a976cb 100644 --- a/.omm/overall-architecture/camera-engines/app-bokeh/description.md +++ b/.omm/overall-architecture/camera-engines/app-bokeh/description.md @@ -2,6 +2,8 @@ App Bokeh는 일반 Camera2의 Preview와 JPEG 두 출력을 사용한다. MainA AppBokehProcessor는 원본을 EXIF 방향에 맞추고 긴 변을 최대 1600픽셀로 제한한다. MiDaS v2.1 모델은 RGB 사진 한 장에서 256×256 Float 상대 역깊이를 추론한다. RelativeDepth와 DepthBokeh는 Android 의존성 없이 깊이 정규화·선택 위치 표본·배경 혼합과 blur를 계산한다. 결과는 JPEG로 재인코딩하며 별도 깊이 PNG와 모델·원본·선택 위치·강도 메타데이터를 저장한다. 원본은 수정하지 않는다. +BokehMask는 원본 밝기를 guidance로 사용하는 국소 선형 필터로 깊이에서 얻은 흐림 가중치를 보정한다. 기존 가중치보다 흐림을 늘리지 않으며 보정한 가중치를 색 표본과 최종 합성에 함께 사용한다. 깊이 PNG는 원래 모델 출력을 유지하고 메타데이터는 renderer=guided_luma_mask_v1을 기록한다. 가는 경계의 손실을 줄이지만 피사체 분할이나 가려진 배경 복원은 아니다. + 편집기의 worker가 추론·렌더·저장을 처리한다. 변경 세대가 지난 렌더는 표시하지 않고 화면 종료 후에는 UI를 갱신하지 않는다. 화면 복귀는 보관한 원본 URI로 편집을 다시 시작하며 이전 강도·선택 위치는 초기화한다. 상대 깊이는 실제 거리나 센서 DEPTH16이 아니다. Benchmark와 incident ZIP에는 이미지 픽셀을 넣지 않는다. 편집기는 Live 선택창과 같은 제목·닫기 아이콘, 흰색 선택 테두리와 접근성 선택 상태를 사용한다. 이미지와 저장 버튼은 Gallery의 Look 토큰을 재사용한다. 강도 조절은 Bokeh 결과로 돌아가며 Save Bokeh는 비교 화면과 관계없이 보케 복사본을 저장한다. 슬라이더는 최소 48dp 높이를 제공한다. diff --git a/app/src/androidTest/java/dev/halcamera/camera/AppBokehChecks.kt b/app/src/androidTest/java/dev/halcamera/camera/AppBokehChecks.kt index 473ea2bb..d8214036 100644 --- a/app/src/androidTest/java/dev/halcamera/camera/AppBokehChecks.kt +++ b/app/src/androidTest/java/dev/halcamera/camera/AppBokehChecks.kt @@ -24,27 +24,37 @@ object AppBokehChecks { .addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK)) val editor = main { dev.halcamera.ui.AppBokehEditor(activity, Uri.fromFile(File(path))) {}.also { it.show() } } try { - val deadline = android.os.SystemClock.uptimeMillis() + 25_000 - while (!main { button("Save Bokeh").isEnabled }) { - check(android.os.SystemClock.uptimeMillis() < deadline) - android.os.SystemClock.sleep(100) + fun awaitReady() { + val deadline = android.os.SystemClock.uptimeMillis() + 25_000 + while (!main { button("Save Bokeh").isEnabled }) { + check(android.os.SystemClock.uptimeMillis() < deadline) + android.os.SystemClock.sleep(100) + } } + awaitReady() main { check(button("Bokeh").isSelected) button("Original").performClick() check(button("Original").isSelected && !button("Bokeh").isSelected) button("Depth").performClick() check(button("Depth").isSelected) - val slider = nodes(root()).filterIsInstance().first() - check(slider.height >= (48 * activity.resources.displayMetrics.density).toInt()) - check(slider.performAccessibilityAction(android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_PROGRESS.id, - android.os.Bundle().apply { putFloat(android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_PROGRESS_VALUE, 50f) })) - check(button("Bokeh").isSelected && !button("Depth").isSelected) } - while (!main { button("Save Bokeh").isEnabled }) { - check(android.os.SystemClock.uptimeMillis() < deadline) - android.os.SystemClock.sleep(100) + for (strength in listOf(0, 50, 100)) { + main { + val slider = nodes(root()).filterIsInstance().first() + check(slider.height >= (48 * activity.resources.displayMetrics.density).toInt()) + check(slider.performAccessibilityAction(android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_PROGRESS.id, + android.os.Bundle().apply { putFloat(android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_PROGRESS_VALUE, strength.toFloat()) })) + check(button("Bokeh").isSelected && !button("Depth").isSelected) + } + awaitReady() + main { + check(nodes(root()).filterIsInstance().any { it.text.startsWith("Blur $strength%") }) + } } + main { check(button("Save Bokeh").performClick()) } + awaitReady() + main { check(nodes(root()).filterIsInstance().any { it.text.startsWith("Saved Bokeh JPEG + depth PNG.") }) } main { val view = root() val bitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888) @@ -55,7 +65,7 @@ object AppBokehChecks { bitmap.recycle() check(nodes(view).first { it.contentDescription == "Close App Bokeh" }.performClick()) } - return "APP_BOKEH_UI_OK selected comparison, accessible strength, result after adjustment, shared close control" + return "APP_BOKEH_UI_OK selected comparison, accessible strength 0/50/100, saved Bokeh, shared close control" } finally { main { editor.dismiss(); activity.finish() } } } @@ -68,8 +78,15 @@ object AppBokehChecks { val dir = File(context.getExternalFilesDir(null), "app-bokeh-check").apply { mkdirs() } File(dir, "original.png").outputStream().use { scene.original.compress(Bitmap.CompressFormat.PNG, 100, it) } File(dir, "depth.png").outputStream().use { AppBokehProcessor.depthBitmap(scene.depth).compress(Bitmap.CompressFormat.PNG, 100, it) } + // Exact model floats allow a before/after renderer comparison without re-inference. + java.io.DataOutputStream(File(dir, "depth.f32").outputStream()).use { output -> + scene.depth.values.forEach(output::writeFloat) + } + val renderTimes = mutableListOf() for (strength in listOf(50, 100)) { + val start = android.os.SystemClock.elapsedRealtime() val rendered = processor.render(scene, focusX, focusY, strength) + renderTimes += android.os.SystemClock.elapsedRealtime() - start File(dir, "bokeh-$strength.jpg").outputStream().use { rendered.compress(Bitmap.CompressFormat.JPEG, 95, it) } rendered.recycle() } @@ -82,6 +99,6 @@ object AppBokehChecks { artifacts.filter { it.mime.startsWith("image/") }.forEach { artifact -> context.contentResolver.openInputStream(artifact.uri)!!.use { check(BitmapFactory.decodeStream(it) != null) } } - return "APP_BOKEH_OK source=${scene.original.width}x${scene.original.height} depth=256x256 focus=$focusX,$focusY loadAndInferenceMs=${scene.elapsedMs} saved=${artifacts.joinToString { it.name }}" + return "APP_BOKEH_OK source=${scene.original.width}x${scene.original.height} depth=256x256 focus=$focusX,$focusY loadAndInferenceMs=${scene.elapsedMs} render50And100Ms=$renderTimes saved=${artifacts.joinToString { it.name }}" } } diff --git a/app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt b/app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt index ff7b9e46..f063c261 100644 --- a/app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt +++ b/app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt @@ -90,6 +90,7 @@ class AppBokehProcessor(context: Context) { "model" to "MiDaS v2.1 model_opt", "modelSha256" to MODEL_SHA256, "depthKind" to "relative_inverse_depth", "depthNear" to "white", "metricDepth" to false, "focusX" to x, "focusY" to y, "focusDepth" to scene.depth.focus(x, y), "strength" to strength, + "renderer" to "guided_luma_mask_v1", "width" to result.width, "height" to result.height, "inferenceMs" to scene.elapsedMs, "outputs" to listOf(jpeg.name, depth.name)) val json = save("${name}_metadata.json", "application/json") { it.write(org.json.JSONObject(metadata).toString(2).toByteArray()) } diff --git a/app/src/main/java/dev/halcamera/camera/BokehMask.kt b/app/src/main/java/dev/halcamera/camera/BokehMask.kt new file mode 100644 index 00000000..6d2877b9 --- /dev/null +++ b/app/src/main/java/dev/halcamera/camera/BokehMask.kt @@ -0,0 +1,58 @@ +package dev.halcamera.camera + +import kotlin.math.roundToInt + +/** Refines coarse blur weights against the source image before either sampling or compositing blur. */ +internal object BokehMask { + fun refine(pixels: IntArray, width: Int, height: Int, coarse: FloatArray): FloatArray { + // Local linear guided filter (He et al., ECCV 2010). Use full-resolution luminance so + // structures narrower than one model pixel can influence the compositing boundary. + val radius = (maxOf(width, height) * .035f).roundToInt().coerceAtLeast(1) + val guide = FloatArray(pixels.size) { i -> + val p = pixels[i] + (.299f * ((p ushr 16) and 255) + .587f * ((p ushr 8) and 255) + .114f * (p and 255)) / 255f + } + val meanGuide = mean(guide, width, height, radius) + val meanMask = mean(coarse, width, height, radius) + val variance = mean(FloatArray(pixels.size) { guide[it] * guide[it] }, width, height, radius) + val covariance = mean(FloatArray(pixels.size) { guide[it] * coarse[it] }, width, height, radius) + for (i in pixels.indices) { + val a = (covariance[i] - meanGuide[i] * meanMask[i]) / + ((variance[i] - meanGuide[i] * meanGuide[i]).coerceAtLeast(0f) + .001f) + covariance[i] = a + variance[i] = meanMask[i] - a * meanGuide[i] + } + val meanA = mean(covariance, width, height, radius) + val meanB = mean(variance, width, height, radius) + return FloatArray(pixels.size) { i -> + // Never add blur to a plane the depth model already protects. Constant-background + // regions remain unchanged; refinement only recovers detail near a depth boundary. + minOf(coarse[i], (meanA[i] * guide[i] + meanB[i]).coerceIn(0f, 1f)) + } + } + + /** One box mean with clipped borders, O(pixels) work independent of radius. */ + private fun mean(input: FloatArray, width: Int, height: Int, radius: Int): FloatArray { + val horizontal = FloatArray(input.size) + for (y in 0 until height) { + var sum = 0.0 + for (x in 0..minOf(radius, width - 1)) sum += input[y * width + x] + for (x in 0 until width) { + horizontal[y * width + x] = (sum / (minOf(width - 1, x + radius) - maxOf(0, x - radius) + 1)).toFloat() + if (x - radius >= 0) sum -= input[y * width + x - radius] + if (x + radius + 1 < width) sum += input[y * width + x + radius + 1] + } + } + val output = FloatArray(input.size) + for (x in 0 until width) { + var sum = 0.0 + for (y in 0..minOf(radius, height - 1)) sum += horizontal[y * width + x] + for (y in 0 until height) { + output[y * width + x] = (sum / (minOf(height - 1, y + radius) - maxOf(0, y - radius) + 1)).toFloat() + if (y - radius >= 0) sum -= horizontal[(y - radius) * width + x] + if (y + radius + 1 < height) sum += horizontal[(y + radius + 1) * width + x] + } + } + return output + } +} diff --git a/app/src/main/java/dev/halcamera/camera/RelativeDepth.kt b/app/src/main/java/dev/halcamera/camera/RelativeDepth.kt index 65570fd1..43a46724 100644 --- a/app/src/main/java/dev/halcamera/camera/RelativeDepth.kt +++ b/app/src/main/java/dev/halcamera/camera/RelativeDepth.kt @@ -47,8 +47,9 @@ object DepthBokeh { require(focusX.isFinite() && focusY.isFinite()) if (strength == 0) return pixels.copyOf() val focus = depth.focus(focusX, focusY) - val weights = FloatArray(pixels.size) { i -> amount(depth.at((i % width).toFloat() / (width - 1).coerceAtLeast(1), + val coarseWeights = FloatArray(pixels.size) { i -> amount(depth.at((i % width).toFloat() / (width - 1).coerceAtLeast(1), (i / width).toFloat() / (height - 1).coerceAtLeast(1)), focus) } + val weights = BokehMask.refine(pixels, width, height, coarseWeights) val radius = (maxOf(width, height) * .018f * strength / 100).roundToInt().coerceAtLeast(1) val blurredWeight = blur(weights, width, height, radius) val channels = arrayOf(16, 8, 0).map { shift -> diff --git a/app/src/test/java/dev/halcamera/camera/BokehMaskTest.kt b/app/src/test/java/dev/halcamera/camera/BokehMaskTest.kt new file mode 100644 index 00000000..2a9b339d --- /dev/null +++ b/app/src/test/java/dev/halcamera/camera/BokehMaskTest.kt @@ -0,0 +1,48 @@ +package dev.halcamera.camera + +import org.junit.Assert.* +import org.junit.Test + +class BokehMaskTest { + @Test fun `thin foreground extensions survive a missing coarse depth edge in both contrast directions`() { + val w = 256; val h = 128 + for (brightSubject in listOf(false, true)) { + fun gray(value: Int) = -0x1000000 or (value shl 16) or (value shl 8) or value + val subject = if (brightSubject) 220 else 35 + val pixels = IntArray(w * h) { i -> + val x = i % w; val y = i / w + gray(if (x < 72 || (x < 96 && y in 63..65)) subject + else if ((x + y) % 2 == 0) { if (brightSubject) 15 else 240 } + else { if (brightSubject) 47 else 208 }) + } + // The model misses the narrow extension entirely; source pixels still show it. + val depth = RelativeDepth(w, h, FloatArray(w * h) { if (it % w < 72) 1f else 0f }) + val result = DepthBokeh.render(pixels, w, h, depth, .1f, .5f, 100) + val recovered = result[64 * w + 77] and 255 + val backgroundMean = if (brightSubject) 31 else 224 + assertTrue("thin extension value=$recovered subject=$subject", + kotlin.math.abs(recovered - subject) < kotlin.math.abs(backgroundMean - subject) / 2) + // A disconnected high-contrast background texture must still be blurred. + val far = result[64 * w + 180] and 255 + assertTrue(far in if (brightSubject) 25..37 else 218..230) + for (y in 0 until h) for (x in 0 until 72) assertEquals(pixels[y * w + x], result[y * w + x]) + } + } + + @Test fun `uniform background weight does not copy image texture into the blur mask`() { + val w = 80; val h = 50 + val pixels = IntArray(w * h) { if (it % 3 == 0) -1 else 0xff123456.toInt() } + for (weight in listOf(0f, .4f, 1f)) { + val result = BokehMask.refine(pixels, w, h, FloatArray(pixels.size) { weight }) + result.forEach { assertEquals(weight, it, .00001f) } + } + } + + @Test fun `flat guidance and narrow images stay finite and cannot increase blur`() { + for ((w, h) in listOf(1 to 1, 1 to 64, 64 to 1, 20 to 30)) { + val coarse = FloatArray(w * h) { (it % 11) / 10f } + val result = BokehMask.refine(IntArray(w * h) { 0xff808080.toInt() }, w, h, coarse) + result.forEachIndexed { i, value -> assertTrue(value.isFinite() && value in 0f..coarse[i]) } + } + } +} diff --git a/docs/.site-manifest.json b/docs/.site-manifest.json index 0dbace3a..fa8bc1e7 100644 --- a/docs/.site-manifest.json +++ b/docs/.site-manifest.json @@ -4,7 +4,7 @@ ".nojekyll": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "_inputs/decisions.html": "aadc3cad5fb3b8e37af148aa62ae79f9be0ba19a076fd2837f85c471b1629ded", "_inputs/device-verification.yaml": "1852f48d52f75733b4db973418fc7fad1f0f9d340a8b4b555cf9d49aa7016215", - "architecture.html": "d5c30ef33736f4f609f3e39368a542fd6ec687796b894913dee4ce7be62e49a2", + "architecture.html": "b3de159e6d839d09d50c1c006d6d3ea43f69848c9ae23fb69ce6a7f10d61bd14", "assets/docflow-design.css": "1cc4f34d8df94abdcf70ab2e9c51e456d8ef7f52283a2ef3adb986a6bd38d44c", "assets/media/automation/bridge.png": "bdbd7cbdc28010caa9b8e320e2d311efd35bb2f66cbb9b5cb322af638b7ccbff", "assets/media/automation/opening-sensor.mp4": "8e26f5eeeb0b92cd241b2235aab7ffd122cbf2ac94b66c37fafb440da4ae1178", diff --git a/docs/architecture.html b/docs/architecture.html index 56f5e606..454de1a5 100644 --- a/docs/architecture.html +++ b/docs/architecture.html @@ -113,7 +113,7 @@

코드를 처음 읽는 순서

근거와 검토 정보
  • 근거 파일: app/src/main/java/dev/halcamera/cli/CommandCoordinator.kt, tools/halcam/halcam/cli.py, app/src/main/java/dev/halcamera/MainActivity.kt, app/src/main/java/dev/halcamera/ConcurrentCameraActivity.kt, app/src/main/java/dev/halcamera/WorkbenchActivity.kt, app/src/main/java/dev/halcamera/camera/CameraEngine.kt, app/src/main/java/dev/halcamera/telemetry/Telemetry.kt, app/src/main/java/dev/halcamera/telemetry/FlightRecorder.kt, app/src/main/java/dev/halcamera/benchmark/domain/BenchmarkRunner.kt, app/src/main/java/dev/halcamera/benchmark/domain/RunAssembler.kt, app/src/main/java/dev/halcamera/benchmark/BenchmarkActivity.kt, app/src/main/java/dev/halcamera/benchmark/HistoryActivity.kt, app/src/main/java/dev/halcamera/benchmark/domain/RegressionDetector.kt
  • 근거 수준: 코드 확인
  • -
  • 검토 2026-10-24 @ 9e71f282 · codex-app-bokeh-ui-review
  • +
  • 검토 2026-10-24 @ 2157ebe2 · codex-bokeh-edge-review

전체 구조도

화살표는 코드를 사용하는 방향입니다. 이벤트와 파일이 이동하는 순서는 데이터 흐름에서 확인하세요.

@@ -214,7 +214,7 @@

코드를 처음 읽는 순서

  • close(done) 완료 전에 다음 카메라를 열지 않습니다.

  • -
  • AppBokehProcessor는 일반 JPEG를 RGB로 읽고 기기 안에서 상대 깊이를 추정합니다. RelativeDepth와 DepthBokeh는 깊이 정규화·선택 지점·배경 흐림을 순수 Kotlin으로 계산합니다. MainAppBokehBridge와 AppBokehEditor가 촬영 후 편집을 연결하며 원본과 보케 결과를 별도로 보관합니다.

    +
  • AppBokehProcessor는 일반 JPEG를 RGB로 읽고 기기 안에서 상대 깊이를 추정합니다. RelativeDepth와 DepthBokeh는 깊이 정규화·선택 지점·배경 흐림을 순수 Kotlin으로 계산합니다. BokehMask는 원본 밝기 경계로 흐림 가중치를 보정해 가는 구조물의 손실을 줄입니다. MainAppBokehBridge와 AppBokehEditor가 촬영 후 편집을 연결하며 원본과 보케 결과를 별도로 보관합니다.

  • 엔진 계약(MediaCapture, LiveTuning, TouchMetering)과 두 엔진의 구현, 두 엔진의 차이는 Engine Comparison에 있습니다.

  • @@ -294,9 +294,9 @@

    코드를 처음 읽는 순서

    ui/RecentMediaButton은 받은 썸네일을 원형으로 그리며, 항목이 동영상이면 가운데에 재생 표시를 겹쳐 접근성 설명뿐 아니라 화면으로도 종류를 알 수 있게 합니다.

    근거와 검토 정보
      -
    • 근거 파일: app/src/main/java/dev/halcamera/MainAppBokehBridge.kt, app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt, app/src/main/java/dev/halcamera/camera/RelativeDepth.kt, app/src/main/java/dev/halcamera/ui/AppBokehEditor.kt, app/src/main/java/dev/halcamera/camera/MultiSession.kt, app/src/main/java/dev/halcamera/camera/CameraXMultiSession.kt, app/src/main/java/dev/halcamera/camera/MultiRecording.kt, app/src/main/java/dev/halcamera/camera/SplitDragGesture.kt, app/src/main/java/dev/halcamera/cli/CommandCoordinator.kt, app/src/main/java/dev/halcamera/cli/LiveController.kt, app/src/main/java/dev/halcamera/cli/BenchmarkController.kt, app/src/main/java/dev/halcamera/cli/CliLibrary.kt, app/src/main/java/dev/halcamera/MainCliBridge.kt, app/src/main/java/dev/halcamera/DualCliBridge.kt, app/src/main/java/dev/halcamera/camera/CameraEngine.kt, app/src/main/java/dev/halcamera/camera/BenchmarkRecorder.kt, app/src/main/java/dev/halcamera/benchmark/platform/EnvironmentProbe.kt, app/src/main/java/dev/halcamera/benchmark/BenchmarkActivity.kt, app/src/main/java/dev/halcamera/benchmark/HistoryActivity.kt, app/src/main/java/dev/halcamera/benchmark/domain/RunIndex.kt, app/src/main/java/dev/halcamera/benchmark/domain/BenchmarkCsv.kt, app/src/main/java/dev/halcamera/benchmark/platform/BenchmarkReport.kt, app/src/main/java/dev/halcamera/benchmark/domain/BenchmarkReportCodec.kt, app/src/main/java/dev/halcamera/telemetry/FlightRecorder.kt, app/src/main/java/dev/halcamera/MainActivity.kt, app/src/main/java/dev/halcamera/camera/PortraitCameraEngine.kt, app/src/main/java/dev/halcamera/ConcurrentCameraActivity.kt, app/src/main/java/dev/halcamera/camera/ConcurrentSession.kt, app/src/main/java/dev/halcamera/camera/RecentMediaThumbnail.kt, app/src/main/java/dev/halcamera/ui/RecentMediaButton.kt, app/src/main/java/dev/halcamera/ui/Look.kt, app/src/main/java/dev/halcamera/cts/recording/BasicRecordingRules.kt, app/src/main/java/dev/halcamera/cts/CtsEntryActivity.kt, app/src/main/java/dev/halcamera/cts/vendored/VendoredCaseActivity.kt, app/src/main/java/dev/halcamera/cts/vendored/VendoredCtsListActivity.kt, app/src/main/java/dev/halcamera/cts/CtsCaseActivity.kt, app/src/main/java/dev/halcamera/cts/CtsCaseListActivity.kt, app/src/main/java/dev/halcamera/cts/suite/CtsChecklistActivity.kt, app/src/main/java/dev/halcamera/cts/suite/CtsSuiteRunActivity.kt, app/src/main/java/dev/halcamera/cts/suite/SuitePlan.kt, app/src/main/java/dev/halcamera/cts/suite/SuiteReport.kt, app/src/main/java/dev/halcamera/cts/CtsCatalog.kt, app/src/main/java/dev/halcamera/cts/CtsRunner.kt, app/src/main/java/dev/halcamera/cts/CameraCaseRunner.kt, app/src/main/java/dev/halcamera/cts/Camera2Ops.kt, app/src/main/java/dev/halcamera/cts/onoff/FastOnOffRules.kt, app/src/main/java/dev/halcamera/cts/switching/SwitchingRules.kt, app/src/main/java/dev/halcamera/cts/sizes/AllSizeOnOffRules.kt, app/src/main/java/dev/halcamera/cts/combination/StillPreviewCombinationRules.kt, app/src/main/java/dev/halcamera/cts/snapshot/VideoSnapshotRules.kt, app/src/main/java/dev/halcamera/CameraProbeActivity.kt, app/src/main/java/dev/halcamera/camera/CameraProbe.kt, app/src/main/java/dev/halcamera/camera/CameraProbeReader.kt
    • +
    • 근거 파일: app/src/main/java/dev/halcamera/MainAppBokehBridge.kt, app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt, app/src/main/java/dev/halcamera/camera/RelativeDepth.kt, app/src/main/java/dev/halcamera/camera/BokehMask.kt, app/src/main/java/dev/halcamera/ui/AppBokehEditor.kt, app/src/main/java/dev/halcamera/camera/MultiSession.kt, app/src/main/java/dev/halcamera/camera/CameraXMultiSession.kt, app/src/main/java/dev/halcamera/camera/MultiRecording.kt, app/src/main/java/dev/halcamera/camera/SplitDragGesture.kt, app/src/main/java/dev/halcamera/cli/CommandCoordinator.kt, app/src/main/java/dev/halcamera/cli/LiveController.kt, app/src/main/java/dev/halcamera/cli/BenchmarkController.kt, app/src/main/java/dev/halcamera/cli/CliLibrary.kt, app/src/main/java/dev/halcamera/MainCliBridge.kt, app/src/main/java/dev/halcamera/DualCliBridge.kt, app/src/main/java/dev/halcamera/camera/CameraEngine.kt, app/src/main/java/dev/halcamera/camera/BenchmarkRecorder.kt, app/src/main/java/dev/halcamera/benchmark/platform/EnvironmentProbe.kt, app/src/main/java/dev/halcamera/benchmark/BenchmarkActivity.kt, app/src/main/java/dev/halcamera/benchmark/HistoryActivity.kt, app/src/main/java/dev/halcamera/benchmark/domain/RunIndex.kt, app/src/main/java/dev/halcamera/benchmark/domain/BenchmarkCsv.kt, app/src/main/java/dev/halcamera/benchmark/platform/BenchmarkReport.kt, app/src/main/java/dev/halcamera/benchmark/domain/BenchmarkReportCodec.kt, app/src/main/java/dev/halcamera/telemetry/FlightRecorder.kt, app/src/main/java/dev/halcamera/MainActivity.kt, app/src/main/java/dev/halcamera/camera/PortraitCameraEngine.kt, app/src/main/java/dev/halcamera/ConcurrentCameraActivity.kt, app/src/main/java/dev/halcamera/camera/ConcurrentSession.kt, app/src/main/java/dev/halcamera/camera/RecentMediaThumbnail.kt, app/src/main/java/dev/halcamera/ui/RecentMediaButton.kt, app/src/main/java/dev/halcamera/ui/Look.kt, app/src/main/java/dev/halcamera/cts/recording/BasicRecordingRules.kt, app/src/main/java/dev/halcamera/cts/CtsEntryActivity.kt, app/src/main/java/dev/halcamera/cts/vendored/VendoredCaseActivity.kt, app/src/main/java/dev/halcamera/cts/vendored/VendoredCtsListActivity.kt, app/src/main/java/dev/halcamera/cts/CtsCaseActivity.kt, app/src/main/java/dev/halcamera/cts/CtsCaseListActivity.kt, app/src/main/java/dev/halcamera/cts/suite/CtsChecklistActivity.kt, app/src/main/java/dev/halcamera/cts/suite/CtsSuiteRunActivity.kt, app/src/main/java/dev/halcamera/cts/suite/SuitePlan.kt, app/src/main/java/dev/halcamera/cts/suite/SuiteReport.kt, app/src/main/java/dev/halcamera/cts/CtsCatalog.kt, app/src/main/java/dev/halcamera/cts/CtsRunner.kt, app/src/main/java/dev/halcamera/cts/CameraCaseRunner.kt, app/src/main/java/dev/halcamera/cts/Camera2Ops.kt, app/src/main/java/dev/halcamera/cts/onoff/FastOnOffRules.kt, app/src/main/java/dev/halcamera/cts/switching/SwitchingRules.kt, app/src/main/java/dev/halcamera/cts/sizes/AllSizeOnOffRules.kt, app/src/main/java/dev/halcamera/cts/combination/StillPreviewCombinationRules.kt, app/src/main/java/dev/halcamera/cts/snapshot/VideoSnapshotRules.kt, app/src/main/java/dev/halcamera/CameraProbeActivity.kt, app/src/main/java/dev/halcamera/camera/CameraProbe.kt, app/src/main/java/dev/halcamera/camera/CameraProbeReader.kt
    • 근거 수준: 코드 확인
    • -
    • 검토 2026-10-24 @ 9e71f282 · codex-app-bokeh-ui-review
    • +
    • 검토 2026-10-24 @ 2157ebe2 · codex-bokeh-edge-review

    CLI 책임을 따라가세요

    CliCommands는 명령별 필수 인자·허용 옵션·기본 제한·실행 대상과 Live에서 실행되는 방식(flow)·녹화 여부를 한곳에서 정의하고 hello.command_specs로 제공합니다.

    @@ -315,7 +315,7 @@

    주요 실행 흐름

    Live 셔터 조작은 MainActivity에서 선택한 엔진의 촬영·녹화 동작으로 이어집니다.

    Live의 Depth 보기는 DepthCameraEngine → ImageReader → DepthFrame → DepthMapView로 연결합니다. 컬러 촬영과 분리된 보기이며, Telemetry에는 버퍼의 시각·크기·형식과 capture 콜백만 기록합니다. 맵 픽셀을 벤치마크나 incident ZIP의 입력으로 전달하지 않습니다. 사용 순서는 Depth 맵 보기에 있습니다.

    같은 Depth 메뉴의 Portrait는 PortraitCameraEngine → CameraExtensionSession → JPEG ImageReader → MediaLibrary로 연결합니다. 제조사 Bokeh가 적용된 컬러 사진을 저장하며 별도의 센서 Depth 맵을 받지 않습니다. 확장 처리 이벤트는 일반 Camera2 프레임 지표와 구분하고, 사진 픽셀은 측정 파일에 넣지 않습니다.

    -

    App Bokeh는 일반 Camera2의 Preview+JPEG로 촬영한 원본을 저장한 뒤 MainAppBokehBridge → AppBokehEditor → AppBokehProcessor로 전달합니다. JPEG를 RGB로 디코딩하고 MiDaS 모델이 256×256 Float 상대 깊이를 추론합니다. 사용자가 선택한 깊이보다 먼 영역을 앱에서 흐린 뒤 보케 JPEG·깊이 PNG·처리 메타데이터를 별도 저장합니다. 원본은 그대로 유지하며 분석용 YUV·HAL Depth·제조사 보케 버퍼는 사용하지 않습니다.

    +

    App Bokeh는 일반 Camera2의 Preview+JPEG로 촬영한 원본을 저장한 뒤 MainAppBokehBridge → AppBokehEditor → AppBokehProcessor로 전달합니다. JPEG를 RGB로 디코딩하고 MiDaS 모델이 256×256 Float 상대 깊이를 추론합니다. 사용자가 선택한 깊이보다 먼 영역의 흐림 가중치를 원본 밝기 경계로 보정한 뒤 배경을 흐립니다. 보케 JPEG·깊이 PNG·처리 메타데이터를 별도 저장하며 깊이 PNG에는 합성 마스크가 아닌 모델 출력을 담습니다. 원본은 그대로 유지하며 분석용 YUV·HAL Depth·제조사 보케 버퍼는 사용하지 않습니다.

    벤치마크는 별도 화면인 BenchmarkActivity가 준비를 마친 뒤 BenchmarkRunner.start()를 호출하여 시작합니다.

    Telemetry.callback(...)이 반환한 Camera2 콜백의 onCaptureStarted는 프레임워크 신호를 기록하며, Live 셔터와 벤치마크 시작을 연결하는 메서드가 아닙니다.

    실행에서 저장까지

    @@ -442,9 +442,9 @@

    CLI 요청과 결과 수집

    이전 CameraX 그래프가 닫히기 전에 MainActivity가 다시 카메라를 열지 않도록 합니다.

    근거와 검토 정보
      -
    • 근거 파일: app/src/main/java/dev/halcamera/MainAppBokehBridge.kt, app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt, app/src/main/java/dev/halcamera/ui/AppBokehEditor.kt, app/src/main/java/dev/halcamera/cli/CliProvider.kt, app/src/main/java/dev/halcamera/cli/CommandCoordinator.kt, tools/halcam/halcam/cli.py, tools/halcam/halcam/download.py, app/src/main/java/dev/halcamera/MainActivity.kt, app/src/main/java/dev/halcamera/camera/PortraitCameraEngine.kt, app/src/main/java/dev/halcamera/GalleryActivity.kt, app/src/main/java/dev/halcamera/camera/MediaLibrary.kt, app/src/main/java/dev/halcamera/camera/RecentMediaThumbnail.kt, app/src/main/java/dev/halcamera/telemetry/Telemetry.kt, app/src/main/java/dev/halcamera/telemetry/FlightRecorder.kt, app/src/main/java/dev/halcamera/metrics/MetricExtractor.kt, app/src/main/java/dev/halcamera/benchmark/domain/BenchmarkRunner.kt, app/src/main/java/dev/halcamera/benchmark/domain/RunAssembler.kt, app/src/main/java/dev/halcamera/benchmark/domain/RunValidity.kt, app/src/main/java/dev/halcamera/benchmark/domain/RunRetention.kt, app/src/main/java/dev/halcamera/benchmark/platform/BenchmarkReport.kt, app/src/main/java/dev/halcamera/benchmark/domain/BenchmarkReportCodec.kt, app/src/main/java/dev/halcamera/benchmark/domain/BenchmarkEvaluator.kt, app/src/main/java/dev/halcamera/benchmark/BenchmarkActivity.kt, app/src/main/java/dev/halcamera/benchmark/platform/LaunchDiagnostics.kt, app/src/main/java/dev/halcamera/benchmark/domain/RegressionDetector.kt
    • +
    • 근거 파일: app/src/main/java/dev/halcamera/MainAppBokehBridge.kt, app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt, app/src/main/java/dev/halcamera/camera/BokehMask.kt, app/src/main/java/dev/halcamera/ui/AppBokehEditor.kt, app/src/main/java/dev/halcamera/cli/CliProvider.kt, app/src/main/java/dev/halcamera/cli/CommandCoordinator.kt, tools/halcam/halcam/cli.py, tools/halcam/halcam/download.py, app/src/main/java/dev/halcamera/MainActivity.kt, app/src/main/java/dev/halcamera/camera/PortraitCameraEngine.kt, app/src/main/java/dev/halcamera/GalleryActivity.kt, app/src/main/java/dev/halcamera/camera/MediaLibrary.kt, app/src/main/java/dev/halcamera/camera/RecentMediaThumbnail.kt, app/src/main/java/dev/halcamera/telemetry/Telemetry.kt, app/src/main/java/dev/halcamera/telemetry/FlightRecorder.kt, app/src/main/java/dev/halcamera/metrics/MetricExtractor.kt, app/src/main/java/dev/halcamera/benchmark/domain/BenchmarkRunner.kt, app/src/main/java/dev/halcamera/benchmark/domain/RunAssembler.kt, app/src/main/java/dev/halcamera/benchmark/domain/RunValidity.kt, app/src/main/java/dev/halcamera/benchmark/domain/RunRetention.kt, app/src/main/java/dev/halcamera/benchmark/platform/BenchmarkReport.kt, app/src/main/java/dev/halcamera/benchmark/domain/BenchmarkReportCodec.kt, app/src/main/java/dev/halcamera/benchmark/domain/BenchmarkEvaluator.kt, app/src/main/java/dev/halcamera/benchmark/BenchmarkActivity.kt, app/src/main/java/dev/halcamera/benchmark/platform/LaunchDiagnostics.kt, app/src/main/java/dev/halcamera/benchmark/domain/RegressionDetector.kt
    • 근거 수준: 코드 확인
    • -
    • 검토 2026-10-24 @ 9e71f282 · codex-app-bokeh-ui-review
    • +
    • 검토 2026-10-24 @ 2157ebe2 · codex-bokeh-edge-review

    이벤트와 결과가 전달되는 경로

    graph TB
    @@ -680,7 +680,7 @@ 

    CLI의 접근과 완료 경계

    근거와 검토 정보
    • 근거 파일: app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt, app/src/main/java/dev/halcamera/camera/RelativeDepth.kt, app/src/main/java/dev/halcamera/ui/AppBokehEditor.kt, app/src/main/java/dev/halcamera/camera/MultiSession.kt, app/src/main/java/dev/halcamera/camera/CameraXMultiSession.kt, app/src/main/java/dev/halcamera/camera/MultiRecording.kt, app/src/main/java/dev/halcamera/camera/MicrophoneCapture.kt, app/src/main/java/dev/halcamera/camera/TimestampedVideoRecording.kt, app/src/main/java/dev/halcamera/camera/DeviceCompositor.kt, app/src/main/java/dev/halcamera/cli/CliProvider.kt, app/src/main/java/dev/halcamera/cli/CommandCoordinator.kt, app/src/main/java/dev/halcamera/cli/CommandStore.kt, app/src/main/java/dev/halcamera/camera/CameraEngine.kt, app/src/main/java/dev/halcamera/camera/PortraitCameraEngine.kt, app/src/main/java/dev/halcamera/camera/ConcurrentSession.kt, app/src/main/java/dev/halcamera/camera/CameraEndpointResolver.kt, app/src/main/java/dev/halcamera/telemetry/IncidentExporter.kt, app/src/main/java/dev/halcamera/benchmark/domain/BenchmarkRunner.kt, app/src/main/java/dev/halcamera/benchmark/domain/RunValidity.kt, app/src/main/java/dev/halcamera/benchmark/domain/RegressionRules.kt, app/src/main/java/dev/halcamera/benchmark/platform/BenchmarkReport.kt, app/src/main/java/dev/halcamera/benchmark/domain/BenchmarkReportCodec.kt, app/src/main/java/dev/halcamera/benchmark/platform/BenchmarkStore.kt, app/src/main/java/dev/halcamera/benchmark/domain/BenchmarkIndex.kt, app/src/main/java/dev/halcamera/benchmark/domain/AtomicFiles.kt
    • 근거 수준: 코드 확인
    • -
    • 검토 2026-10-24 @ 9e71f282 · codex-app-bokeh-ui-review
    • +
    • 검토 2026-10-24 @ 2157ebe2 · codex-bokeh-edge-review

    변경 후 검증

    Android 의존성이 없는 러너와 평가 로직은 JVM 단위 테스트로 확인할 수 있습니다. 저장소 루트에서 실행하세요.

    @@ -720,12 +720,12 @@

    미완성 기능과 추가 검증

    구조 원본 data-flow 최신 -검토 2026-10-24 @ e4de2762 · codex-app-bokeh-code-review +검토 2026-10-24 @ 2157ebe2 · codex-bokeh-edge-review 구조 원본 overall-architecture 최신 -검토 2026-10-24 @ 9e71f282 · codex-app-bokeh-ui-review +검토 2026-10-24 @ 2157ebe2 · codex-bokeh-edge-review 구조 원본 state-transitions @@ -750,22 +750,22 @@

    미완성 기능과 추가 검증

    원고 overview 최신 -검토 2026-10-24 @ 9e71f282 · codex-app-bokeh-ui-review +검토 2026-10-24 @ 2157ebe2 · codex-bokeh-edge-review 원고 module-roles 최신 -검토 2026-10-24 @ 9e71f282 · codex-app-bokeh-ui-review +검토 2026-10-24 @ 2157ebe2 · codex-bokeh-edge-review 원고 runtime-flow 최신 -검토 2026-10-24 @ 9e71f282 · codex-app-bokeh-ui-review +검토 2026-10-24 @ 2157ebe2 · codex-bokeh-edge-review 원고 constraints 최신 -검토 2026-10-24 @ 9e71f282 · codex-app-bokeh-ui-review +검토 2026-10-24 @ 2157ebe2 · codex-bokeh-edge-review

    표의 최신은 기록된 근거와 원고가 검토 이후 바뀌지 않았다는 뜻입니다. 모든 기기에서 동작을 검증했다는 뜻은 아닙니다.

    diff --git a/docs/design/APP-BOKEH.md b/docs/design/APP-BOKEH.md index 398a6040..25e5a5e2 100644 --- a/docs/design/APP-BOKEH.md +++ b/docs/design/APP-BOKEH.md @@ -9,11 +9,14 @@ App Bokeh 진입 시 Camera2의 일반 Preview와 촬영 JPEG 두 출력만 구 1. `AppBokehProcessor`가 EXIF 방향을 반영하고 긴 변을 최대 1600픽셀로 줄인다. 원본 파일은 수정하지 않는다. 2. RGB를 256×256으로 리사이즈하고 공식 Android 예제와 같은 `(channel - 115) / 58` 정규화를 적용한다. 3. LiteRT Interpreter의 CPU 스레드 4개로 MiDaS 상대 역깊이를 추론한다. 상·하위 2% 범위로 0–1 정규화한다. 값이 클수록 가까우며 실제 거리로 해석하지 않는다. -4. 사용자가 선택한 지점 주변 3×3 깊이의 중앙값을 기준으로 더 먼 영역에 연속적인 혼합 가중치를 부여한다. 전경 색이 배경으로 번지는 현상을 줄이기 위해 배경 가중치로 색을 정규화한 세 번의 분리형 box blur를 적용한다. 완전한 광학적 보케나 가려진 배경의 복원은 보장하지 않는다. -5. JPEG, 깊이 PNG, 원본 URI와 모델 해시를 포함한 메타데이터를 `MediaLibrary.transaction`으로 저장한다. 원본은 이미 독립적으로 저장되어 있어 처리·저장 실패에도 남는다. Benchmark/incident에는 픽셀을 추가하지 않는다. +4. 사용자가 선택한 지점 주변 3×3 깊이의 중앙값을 기준으로 더 먼 영역에 연속적인 혼합 가중치를 부여한다. `BokehMask`는 처리용 원본의 밝기를 guidance로 사용하는 guided filter로 가중치 경계를 보정한다. 반경은 긴 변의 3.5%(최소 1픽셀), 정규화 밝기의 분산 안정화 항은 0.001이다. 보정값과 기존 값 중 작은 값을 사용하므로 깊이 모델이 보호한 영역에 흐림을 추가하지 않는다. +5. 보정한 같은 가중치를 배경 색 표본과 최종 합성에 모두 사용한다. 전경 색이 배경으로 번지는 현상을 줄이기 위해 배경 가중치로 색을 정규화한 세 번의 분리형 box blur를 적용한다. 완전한 광학적 보케나 가려진 배경의 복원은 보장하지 않는다. +6. JPEG, 깊이 PNG, 원본 URI와 모델 해시를 포함한 메타데이터를 `MediaLibrary.transaction`으로 저장한다. 메타데이터의 `renderer: guided_luma_mask_v1`으로 경계 보정 방식을 구별한다. 깊이 PNG는 보정 전 모델의 상대 깊이이며 합성 마스크가 아니다. 원본은 이미 독립적으로 저장되어 있어 처리·저장 실패에도 남는다. Benchmark/incident에는 픽셀을 추가하지 않는다. 모델 추론과 렌더·저장은 편집기 전용 worker에서 실행한다. 슬라이더 변경 시 이전 세대의 결과는 화면에 적용하지 않는다. Activity 종료 시 편집기를 닫고 후속 UI 갱신을 막는다. 화면 재진입 시 원본 URI로 다시 계산하며 편집 매개변수는 초기화한다. +경계 보정은 [He·Sun·Tang의 Guided Image Filtering](https://people.csail.mit.edu/kaiming/eccv10/index.html)의 국소 선형 모델을 사용한다. 밝기·가중치의 평균과 공분산, 계수의 평균을 분리형 box mean으로 구해 반경에 관계없이 픽셀 수에 비례하는 연산량을 유지한다. 중간 버퍼도 픽셀 수에 비례한다. + ## 모델과 재현 가능한 빌드 - 공식 배포: [MiDaS v2.1](https://github.com/isl-org/MiDaS/releases/tag/v2_1), `model_opt.tflite`, 66,338,288 bytes. @@ -28,4 +31,6 @@ App Bokeh 진입 시 Camera2의 일반 Preview와 촬영 JPEG 두 출력만 구 깊이는 단일 이미지 추정이므로 인물·사물 모두 사용할 수 있지만 모든 장면의 정확성을 보장하지 않는다. 실시간 효과, 전경 흐림, 모델 학습, 센서 깊이와의 융합은 이 경로에서 수행하지 않는다. CPU 추론 시간과 메모리 사용은 기기에 따라 다르다. 강도 0의 픽셀 동일성은 방향 보정·축소 후의 처리용 원본을 기준으로 하며, JPEG 재인코딩까지 무손실이라는 뜻이 아니다. +원본의 밝기 경계 보정은 깊이 경계 주변의 가는 구조물을 보존하는 데 도움을 주지만 피사체 분할은 아니다. 깊이 모델이 넓게 놓친 구조물이나 배경과 밝기가 비슷한 잎·머리카락은 복원하지 못할 수 있다. 경계 주변의 배경 무늬도 일부 선명해질 수 있으며, 가려진 배경을 새로 생성하지 않는다. + 사용 절차는 [Live 가이드](../../guide/live.md)를 참고한다. diff --git a/docs/validation/app-bokeh.md b/docs/validation/app-bokeh.md index 43e945cd..149f76c1 100644 --- a/docs/validation/app-bokeh.md +++ b/docs/validation/app-bokeh.md @@ -1,6 +1,6 @@ # App Bokeh 실기기 검증 -2026-10-24에 SM-S936N, Android 16(API 36), 후면 카메라 ID 0에서 확인했다. 제조사 Bokeh와 센서 DEPTH16 없이 사물 사진에 배경 흐림을 적용하고 원본을 보존하는 경로를 검증했다. 사진과 화면 캡처는 로컬 검증 자료로만 보관하며 저장소와 PR에 올리지 않는다. +2026-10-24에 SM-S936N, 후면 카메라 ID 0에서 확인했다. 제조사 Bokeh와 센서 DEPTH16 없이 사물 사진에 배경 흐림을 적용하고 원본을 보존하는 경로를 검증했다. 사진과 화면 캡처는 로컬 검증 자료로만 보관하며 저장소와 PR에 올리지 않는다. OS는 아래 경계 보정 검사 때 기기 속성을 직접 조회한 Android 17(API 37)을 기준으로 기록한다. ## 실기기 결과 @@ -31,4 +31,20 @@ PR UI/UX 리뷰에서는 Live 선택창과 같은 제목·닫기 아이콘·흰 이번 새 장면은 후면 사물 한 장이다. 다른 기기·전면·인물·역광·반사체·얇은 경계의 품질을 검증한 것으로 해석하지 않는다. 마우스 주변과 가까운 책상은 보존됐지만 책상 뒤쪽 경계처럼 깊이가 바뀌는 곳에는 흐림이 번지는 부분이 있다. 단안 추정과 저해상도 깊이 맵의 한계이며, 정밀한 피사체 마스크나 광학 보케와 같지 않다. 최대 출력 긴 변은 1600픽셀이며 실시간 미리보기 효과는 제공하지 않는다. +## 가는 잎 경계 보정 재검증 + +같은 날 설치된 main `2157ebe2`에서 촬영한 역광 화분·창밖 건물 사진을 그대로 사용했다. 새 장면을 촬영하거나 강도를 줄여 비교하지 않았다. 개선 전후 모두 처리용 원본 900×1600, 중심 초점 `(0.5, 0.5)`, 강도 100, 같은 MiDaS 모델이다. 기기에서 다시 조회한 `ro.build.version.release=17`, `ro.build.version.sdk=37`을 기록하며, 앞선 Android 16 표기를 정정한다. + +| 검사 | 결과 | +| --- | --- | +| 잎 끝 비교 | 원본·개선 전·개선 후를 같은 위치에서 확대했다. 이전에 배경과 함께 흐려지던 잎 끝의 대비가 더 남았다. | +| 잎 영역 수치 | 처리용 원본의 `(180,250)`부터 `(390,420)` 미만 영역에서 원본 밝기 140 미만인 5,321픽셀을 고정해 비교했다. 밝기 평균 절대 오차는 32.81에서 23.11로 약 29.6% 감소했다. 0–255 밝기에서 계산한 이 사진의 국소 비교값이며 일반적인 품질 점수는 아니다. | +| 배경 유지 | 먼 건물의 `(700,80)`부터 `(870,360)` 미만 영역에서 수평 인접 밝기 차의 평균은 원본 3.36, 개선 전 1.23, 개선 후 1.37이었다. 배경 흐림은 유지됐지만 경계 주변 난간 일부는 더 선명해졌다. | +| 기기 처리 | 동일 원본에 강도 0·50·80·100을 적용했다. 강도 0의 `Bitmap.sameAs`와 저장한 이미지 재디코딩 검사가 통과했다. | +| 편집기 | 같은 사진을 편집기에 전달한 기기 검사에서 Original·Depth 전환, 접근성 강도 0·50·100 변경, Bokeh 결과 복귀, Save Bokeh 저장, 닫기를 확인했다. `APP_BOKEH_UI_OK`와 저장 완료 화면을 확인했다. | +| 저장 계약 | UI 저장 JSON의 강도 100, 초점 `(0.5,0.5)`, `renderer=guided_luma_mask_v1`, 결과 900×1600을 확인했다. 깊이 PNG는 256×256이며 원본 JPEG의 SHA-256은 전후 동일했다. | +| 시간 | 첫 처리 검사에서 로드·추론 950 ms, 강도 50·100 렌더 2264·1776 ms를 관찰했다. UI 검사 로드·추론은 608 ms였다. 단일 기기의 관찰값이며 저장 시간이나 성능 보장값이 아니다. | + +이번 비교는 저해상도 깊이 경계를 원본 밝기로 보정한 효과를 확인한다. 깊이가 잘못된 아주 가는 잎 끝은 일부 여전히 흐려지며, 밝기가 비슷한 경계나 다른 기기·인물 머리카락까지 검증한 것은 아니다. 공개 회귀 검사는 밝고 어두운 가는 전경, 분리된 배경 무늬의 흐림, 보호한 전경의 픽셀 동일성, 일정한 깊이 가중치에서의 무늬 전이 방지, 폭·높이 1과 평탄한 밝기를 다룬다. + 처리·모델·스트림 계약은 [App Bokeh 설계](../design/APP-BOKEH.md)에 있다. diff --git a/guide/_bindings.yaml b/guide/_bindings.yaml index e95aab17..915fee83 100644 --- a/guide/_bindings.yaml +++ b/guide/_bindings.yaml @@ -136,6 +136,7 @@ sources: - app/src/main/java/dev/halcamera/MainAppBokehBridge.kt - app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt - app/src/main/java/dev/halcamera/camera/RelativeDepth.kt + - app/src/main/java/dev/halcamera/camera/BokehMask.kt - app/src/main/java/dev/halcamera/ui/AppBokehEditor.kt camera-engines/camera2-engine: evidence: @@ -505,6 +506,7 @@ sources: - app/src/main/java/dev/halcamera/MainAppBokehBridge.kt - app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt - app/src/main/java/dev/halcamera/camera/RelativeDepth.kt + - app/src/main/java/dev/halcamera/camera/BokehMask.kt - app/src/main/java/dev/halcamera/GalleryActivity.kt - app/src/main/java/dev/halcamera/MainActivity.kt - app/src/main/java/dev/halcamera/benchmark/BenchmarkActivity.kt diff --git a/guide/_content/architecture/module-roles.md b/guide/_content/architecture/module-roles.md index 65401b7f..2760e1bf 100644 --- a/guide/_content/architecture/module-roles.md +++ b/guide/_content/architecture/module-roles.md @@ -5,6 +5,7 @@ sources: - app/src/main/java/dev/halcamera/MainAppBokehBridge.kt - app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt - app/src/main/java/dev/halcamera/camera/RelativeDepth.kt + - app/src/main/java/dev/halcamera/camera/BokehMask.kt - app/src/main/java/dev/halcamera/ui/AppBokehEditor.kt - app/src/main/java/dev/halcamera/camera/MultiSession.kt - app/src/main/java/dev/halcamera/camera/CameraXMultiSession.kt @@ -102,7 +103,7 @@ verifications: [] - 엔진 계약, Camera2·CameraX 구현, 엔드포인트 열거를 제공합니다. `DepthCameraEngine`은 컬러 출력 없이 DEPTH16 맵만 받으며 `DepthFrame`이 순수 Kotlin으로 표시용 표본을 만듭니다. - `PortraitCameraEngine`은 LIVE의 제조사 BOKEH 전용 세션입니다. MainPortraitBridge가 JPEG 크기를 선택하고 MainDepthBridge가 Depth Map과 Portrait의 진입점을 구분합니다. 이 경로는 벤치마크를 실행하거나 센서 Depth 맵을 저장하지 않습니다. - `close(done)` 완료 전에 다음 카메라를 열지 않습니다. -- `AppBokehProcessor`는 일반 JPEG를 RGB로 읽고 기기 안에서 상대 깊이를 추정합니다. `RelativeDepth`와 `DepthBokeh`는 깊이 정규화·선택 지점·배경 흐림을 순수 Kotlin으로 계산합니다. `MainAppBokehBridge`와 `AppBokehEditor`가 촬영 후 편집을 연결하며 원본과 보케 결과를 별도로 보관합니다. +- `AppBokehProcessor`는 일반 JPEG를 RGB로 읽고 기기 안에서 상대 깊이를 추정합니다. `RelativeDepth`와 `DepthBokeh`는 깊이 정규화·선택 지점·배경 흐림을 순수 Kotlin으로 계산합니다. `BokehMask`는 원본 밝기 경계로 흐림 가중치를 보정해 가는 구조물의 손실을 줄입니다. `MainAppBokehBridge`와 `AppBokehEditor`가 촬영 후 편집을 연결하며 원본과 보케 결과를 별도로 보관합니다. - 엔진 계약(`MediaCapture`, `LiveTuning`, `TouchMetering`)과 두 엔진의 구현, 두 엔진의 차이는 [Engine Comparison](engine.md)에 있습니다. - 벤치마크 RECORD 단계의 recorder 상태 기계는 `BenchmarkRecorder`가 담당하고 `Camera2Engine`은 그 호출을 위임합니다. - 카메라를 화면에 적는 이름(`Camera · 0 (Wide · Rear)`)도 이 패키지의 `CameraLabel` 하나가 만들며, Live·Benchmark·Probe·CTS가 모두 그것을 부릅니다. diff --git a/guide/_content/architecture/runtime-flow.md b/guide/_content/architecture/runtime-flow.md index 1ffd8764..832413ef 100644 --- a/guide/_content/architecture/runtime-flow.md +++ b/guide/_content/architecture/runtime-flow.md @@ -4,6 +4,7 @@ confidence: code sources: - app/src/main/java/dev/halcamera/MainAppBokehBridge.kt - app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt + - app/src/main/java/dev/halcamera/camera/BokehMask.kt - app/src/main/java/dev/halcamera/ui/AppBokehEditor.kt - app/src/main/java/dev/halcamera/cli/CliProvider.kt - app/src/main/java/dev/halcamera/cli/CommandCoordinator.kt @@ -47,7 +48,7 @@ Live의 Depth 보기는 `DepthCameraEngine → ImageReader → DepthFrame → De 같은 Depth 메뉴의 Portrait는 `PortraitCameraEngine → CameraExtensionSession → JPEG ImageReader → MediaLibrary`로 연결합니다. 제조사 Bokeh가 적용된 컬러 사진을 저장하며 별도의 센서 Depth 맵을 받지 않습니다. 확장 처리 이벤트는 일반 Camera2 프레임 지표와 구분하고, 사진 픽셀은 측정 파일에 넣지 않습니다. -App Bokeh는 일반 Camera2의 Preview+JPEG로 촬영한 원본을 저장한 뒤 `MainAppBokehBridge → AppBokehEditor → AppBokehProcessor`로 전달합니다. JPEG를 RGB로 디코딩하고 MiDaS 모델이 256×256 Float 상대 깊이를 추론합니다. 사용자가 선택한 깊이보다 먼 영역을 앱에서 흐린 뒤 보케 JPEG·깊이 PNG·처리 메타데이터를 별도 저장합니다. 원본은 그대로 유지하며 분석용 YUV·HAL Depth·제조사 보케 버퍼는 사용하지 않습니다. +App Bokeh는 일반 Camera2의 Preview+JPEG로 촬영한 원본을 저장한 뒤 `MainAppBokehBridge → AppBokehEditor → AppBokehProcessor`로 전달합니다. JPEG를 RGB로 디코딩하고 MiDaS 모델이 256×256 Float 상대 깊이를 추론합니다. 사용자가 선택한 깊이보다 먼 영역의 흐림 가중치를 원본 밝기 경계로 보정한 뒤 배경을 흐립니다. 보케 JPEG·깊이 PNG·처리 메타데이터를 별도 저장하며 깊이 PNG에는 합성 마스크가 아닌 모델 출력을 담습니다. 원본은 그대로 유지하며 분석용 YUV·HAL Depth·제조사 보케 버퍼는 사용하지 않습니다. 벤치마크는 별도 화면인 `BenchmarkActivity`가 준비를 마친 뒤 `BenchmarkRunner.start()`를 호출하여 시작합니다. diff --git a/guide/_reference/engine.md b/guide/_reference/engine.md index 075beabc..d87f28ad 100644 --- a/guide/_reference/engine.md +++ b/guide/_reference/engine.md @@ -62,7 +62,7 @@ PIP와 Multi는 Android 11 이상에서 제공합니다. 저장물과 설정 방 - 근거 파일: `app/src/main/java/dev/halcamera/camera/MultiSession.kt`, `app/src/main/java/dev/halcamera/camera/CameraXMultiSession.kt`, `app/src/main/java/dev/halcamera/camera/MultiRecording.kt`, `app/src/main/java/dev/halcamera/camera/Camera2Engine.kt`, `app/src/main/java/dev/halcamera/camera/Camera2StillCapture.kt`, `app/src/main/java/dev/halcamera/camera/Camera2LiveRecorder.kt`, `app/src/main/java/dev/halcamera/camera/TouchMeterRequests.kt`, `app/src/main/java/dev/halcamera/camera/CameraXEngine.kt`, `app/src/main/java/dev/halcamera/camera/CameraXPipSession.kt`, `app/src/main/java/dev/halcamera/camera/CameraXStillCapture.kt`, `app/src/main/java/dev/halcamera/camera/CameraXLiveRecorder.kt`, `app/src/main/java/dev/halcamera/camera/CameraXControls.kt`, `app/src/main/java/dev/halcamera/MainActivity.kt`, `app/src/main/java/dev/halcamera/MainAppBokehBridge.kt`, `app/src/main/java/dev/halcamera/camera/PortraitCameraEngine.kt`, `app/src/main/java/dev/halcamera/camera/ConcurrentSession.kt`, `app/src/main/java/dev/halcamera/camera/LivePipSession.kt`, `app/src/main/java/dev/halcamera/ConcurrentCameraActivity.kt` - 근거 수준: 코드 확인 -- 검토 2026-10-24 @ `9e71f282` · codex-app-bokeh-ui-review +- 검토 2026-10-24 @ `2157ebe2` · codex-bokeh-edge-review @@ -225,7 +225,7 @@ Live 제어와 터치 측광은 다음 이벤트를 추가로 남깁니다. - 근거 파일: `app/src/main/java/dev/halcamera/camera/CameraEngine.kt`, `app/src/main/java/dev/halcamera/camera/Camera2Engine.kt`, `app/src/main/java/dev/halcamera/camera/CameraXEngine.kt`, `app/src/main/java/dev/halcamera/camera/DepthCameraEngine.kt`, `app/src/main/java/dev/halcamera/MainActivity.kt`, `app/src/main/java/dev/halcamera/MainAppBokehBridge.kt`, `app/src/main/java/dev/halcamera/camera/PortraitCameraEngine.kt`, `app/src/main/java/dev/halcamera/ui/LiveControlBar.kt`, `app/src/main/java/dev/halcamera/ui/FocusRing.kt`, `app/src/main/java/dev/halcamera/camera/LiveControls.kt`, `app/src/main/java/dev/halcamera/benchmark/BenchmarkActivity.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/StartCardPresenter.kt`, `app/src/main/java/dev/halcamera/cli/LiveController.kt`, `app/src/main/java/dev/halcamera/telemetry/Telemetry.kt` - 근거 수준: 코드 확인 -- 검토 2026-10-24 @ `9e71f282` · codex-app-bokeh-ui-review +- 검토 2026-10-24 @ `2157ebe2` · codex-bokeh-edge-review @@ -613,7 +613,7 @@ Camera2에서 짧게 터치한 경우입니다. 긴 누르기는 별도의 AE - 근거 파일: `app/src/main/java/dev/halcamera/camera/Camera2Engine.kt`, `app/src/main/java/dev/halcamera/MainAppBokehBridge.kt`, `app/src/main/java/dev/halcamera/camera/PortraitCameraEngine.kt`, `app/src/main/java/dev/halcamera/camera/LivePipSession.kt`, `app/src/main/java/dev/halcamera/camera/PipSessionQuery.kt`, `app/src/main/java/dev/halcamera/camera/CameraOpenRetry.kt`, `app/src/main/java/dev/halcamera/camera/CameraReleaseWait.kt`, `app/src/main/java/dev/halcamera/camera/LiveStreamSettings.kt`, `app/src/main/java/dev/halcamera/camera/LiveStabilization.kt`, `app/src/main/java/dev/halcamera/camera/LiveStreamCapabilities.kt`, `app/src/main/java/dev/halcamera/camera/LiveSessionCheck.kt`, `app/src/main/java/dev/halcamera/camera/Camera2StillCapture.kt`, `app/src/main/java/dev/halcamera/camera/Camera2LiveRecorder.kt`, `app/src/main/java/dev/halcamera/camera/Camera2VideoSnapshot.kt`, `app/src/main/java/dev/halcamera/camera/VideoSnapshot.kt`, `app/src/main/java/dev/halcamera/camera/BenchmarkRecorder.kt`, `app/src/main/java/dev/halcamera/camera/PreviewBufferRelay.kt`, `app/src/main/java/dev/halcamera/camera/RecordingBufferRelay.kt`, `app/src/main/java/dev/halcamera/camera/StreamConfiguration.kt`, `app/src/main/java/dev/halcamera/camera/StillPair.kt`, `app/src/main/java/dev/halcamera/camera/YuvPacking.kt`, `app/src/main/java/dev/halcamera/camera/RawFrame.kt`, `app/src/main/java/dev/halcamera/camera/DngOutput.kt`, `app/src/main/java/dev/halcamera/camera/StillEncoding.kt`, `app/src/main/java/dev/halcamera/camera/MediaLibrary.kt`, `app/src/main/java/dev/halcamera/camera/LiveControls.kt`, `app/src/main/java/dev/halcamera/camera/LiveControlRequests.kt`, `app/src/main/java/dev/halcamera/camera/ManualControls.kt`, `app/src/main/java/dev/halcamera/camera/ManualControlRequests.kt`, `app/src/main/java/dev/halcamera/camera/TouchMeter.kt`, `app/src/main/java/dev/halcamera/camera/TouchMeterRequests.kt` - 근거 수준: 코드 확인 -- 검토 2026-10-24 @ `9e71f282` · codex-app-bokeh-ui-review +- 검토 2026-10-24 @ `2157ebe2` · codex-bokeh-edge-review @@ -832,7 +832,7 @@ AE 재잠금은 Camera2와 같은 [AeRelock 상태도](#노출은-언제-다시- - 근거 파일: `app/src/main/java/dev/halcamera/camera/CameraXEngine.kt`, `app/src/main/java/dev/halcamera/camera/CameraXPipSession.kt`, `app/src/main/java/dev/halcamera/camera/PipSessionQuery.kt`, `app/src/main/java/dev/halcamera/camera/CameraXStillCapture.kt`, `app/src/main/java/dev/halcamera/camera/CameraXLiveRecorder.kt`, `app/src/main/java/dev/halcamera/camera/CameraXVideoSnapshot.kt`, `app/src/main/java/dev/halcamera/camera/VideoSnapshot.kt`, `app/src/main/java/dev/halcamera/camera/CameraXControls.kt`, `app/src/main/java/dev/halcamera/camera/StillEncoding.kt`, `app/src/main/java/dev/halcamera/camera/YuvPacking.kt`, `app/src/main/java/dev/halcamera/camera/MediaLibrary.kt`, `app/src/main/java/dev/halcamera/camera/StreamConfiguration.kt`, `app/src/main/java/dev/halcamera/camera/LiveControls.kt`, `app/src/main/java/dev/halcamera/camera/TouchMeter.kt`, `app/build.gradle.kts` - 근거 수준: 코드 확인 -- 검토 2026-10-24 @ `9e71f282` · codex-app-bokeh-ui-review +- 검토 2026-10-24 @ `2157ebe2` · codex-bokeh-edge-review @@ -852,11 +852,11 @@ AE 재잠금은 Camera2와 같은 [AeRelock 상태도](#노출은-언제-다시- | 항목 | 최신성 | 검토 | | --- | --- | --- | -| 구조 원본 `overall-architecture` | 최신 | 검토 2026-10-24 @ `9e71f282` · codex-app-bokeh-ui-review | -| 원고 `contract` | 최신 | 검토 2026-10-24 @ `9e71f282` · codex-app-bokeh-ui-review | -| 원고 `camera2` | 최신 | 검토 2026-10-24 @ `9e71f282` · codex-app-bokeh-ui-review | -| 원고 `camerax` | 최신 | 검토 2026-10-24 @ `9e71f282` · codex-app-bokeh-ui-review | -| 원고 `comparison` | 최신 | 검토 2026-10-24 @ `9e71f282` · codex-app-bokeh-ui-review | +| 구조 원본 `overall-architecture` | 최신 | 검토 2026-10-24 @ `2157ebe2` · codex-bokeh-edge-review | +| 원고 `contract` | 최신 | 검토 2026-10-24 @ `2157ebe2` · codex-bokeh-edge-review | +| 원고 `camera2` | 최신 | 검토 2026-10-24 @ `2157ebe2` · codex-bokeh-edge-review | +| 원고 `camerax` | 최신 | 검토 2026-10-24 @ `2157ebe2` · codex-bokeh-edge-review | +| 원고 `comparison` | 최신 | 검토 2026-10-24 @ `2157ebe2` · codex-bokeh-edge-review | diff --git a/guide/_reference/evidence.md b/guide/_reference/evidence.md index 133c3d10..bfcb3a3c 100644 --- a/guide/_reference/evidence.md +++ b/guide/_reference/evidence.md @@ -80,7 +80,7 @@ Galaxy S25+·Android 16에서 2026년 9월 17~19일, versionCode 106~108로 수 - 근거 파일: `app/src/main/java/dev/halcamera/camera/CameraXControls.kt`, `app/src/main/java/dev/halcamera/camera/CameraXStillCapture.kt`, `app/src/main/java/dev/halcamera/camera/CameraXLiveRecorder.kt` - 기기 검증: `V-002` - 근거 수준: 기기 검증 -- 검토 2026-10-24 @ `9e71f282` · codex-app-bokeh-ui-review +- 검토 2026-10-24 @ `2157ebe2` · codex-bokeh-edge-review @@ -98,8 +98,8 @@ Galaxy S25+·Android 16에서 2026년 9월 17~19일, versionCode 106~108로 수 | 항목 | 최신성 | 검토 | | --- | --- | --- | -| 구조 원본 `overall-architecture` | 최신 | 검토 2026-10-24 @ `9e71f282` · codex-app-bokeh-ui-review | -| 원고 `device-notes` | 최신 | 검토 2026-10-24 @ `9e71f282` · codex-app-bokeh-ui-review | +| 구조 원본 `overall-architecture` | 최신 | 검토 2026-10-24 @ `2157ebe2` · codex-bokeh-edge-review | +| 원고 `device-notes` | 최신 | 검토 2026-10-24 @ `2157ebe2` · codex-bokeh-edge-review | diff --git a/guide/_reference/troubleshooting.md b/guide/_reference/troubleshooting.md index 4169b40d..5389b638 100644 --- a/guide/_reference/troubleshooting.md +++ b/guide/_reference/troubleshooting.md @@ -140,7 +140,7 @@ PC의 대기 시간이 끝나도 앱 작업은 계속될 수 있습니다. 요 - 근거 파일: `app/src/main/java/dev/halcamera/cli/CommandStore.kt`, `tools/halcam/halcam/cli.py`, `app/src/main/java/dev/halcamera/MainActivity.kt`, `app/src/main/java/dev/halcamera/camera/CameraXStillCapture.kt`, `app/src/main/java/dev/halcamera/camera/Camera2Engine.kt`, `app/src/main/java/dev/halcamera/camera/ManualControlRequests.kt`, `app/src/main/java/dev/halcamera/telemetry/Telemetry.kt`, `app/src/main/java/dev/halcamera/telemetry/FlightRecorder.kt`, `app/src/main/java/dev/halcamera/metrics/MetricExtractor.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/RunAssembler.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/RunValidity.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/BenchmarkEvaluator.kt`, `app/src/main/java/dev/halcamera/benchmark/BenchmarkActivity.kt`, `app/src/main/java/dev/halcamera/benchmark/HistoryActivity.kt` - 근거 수준: 코드 확인 -- 검토 2026-10-24 @ `9e71f282` · codex-app-bokeh-ui-review +- 검토 2026-10-24 @ `2157ebe2` · codex-bokeh-edge-review @@ -172,9 +172,9 @@ PC의 대기 시간이 끝나도 앱 작업은 계속될 수 있습니다. 요 | 항목 | 최신성 | 검토 | | --- | --- | --- | -| 구조 원본 `data-flow` | 최신 | 검토 2026-10-24 @ `e4de2762` · codex-app-bokeh-code-review | +| 구조 원본 `data-flow` | 최신 | 검토 2026-10-24 @ `2157ebe2` · codex-bokeh-edge-review | | 구조 원본 `state-transitions` | 최신 | 검토 2026-10-24 @ `9e71f282` · codex-app-bokeh-ui-review | -| 원고 `layer-isolation` | 최신 | 검토 2026-10-24 @ `9e71f282` · codex-app-bokeh-ui-review | +| 원고 `layer-isolation` | 최신 | 검토 2026-10-24 @ `2157ebe2` · codex-bokeh-edge-review | diff --git a/guide/architecture.md b/guide/architecture.md index e19dc34c..10adf29d 100644 --- a/guide/architecture.md +++ b/guide/architecture.md @@ -57,7 +57,7 @@ PC에서 같은 기능을 실행하려면 [CLI](cli.md)를 사용합니다. `Cli - 근거 파일: `app/src/main/java/dev/halcamera/cli/CommandCoordinator.kt`, `tools/halcam/halcam/cli.py`, `app/src/main/java/dev/halcamera/MainActivity.kt`, `app/src/main/java/dev/halcamera/ConcurrentCameraActivity.kt`, `app/src/main/java/dev/halcamera/WorkbenchActivity.kt`, `app/src/main/java/dev/halcamera/camera/CameraEngine.kt`, `app/src/main/java/dev/halcamera/telemetry/Telemetry.kt`, `app/src/main/java/dev/halcamera/telemetry/FlightRecorder.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/BenchmarkRunner.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/RunAssembler.kt`, `app/src/main/java/dev/halcamera/benchmark/BenchmarkActivity.kt`, `app/src/main/java/dev/halcamera/benchmark/HistoryActivity.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/RegressionDetector.kt` - 근거 수준: 코드 확인 -- 검토 2026-10-24 @ `9e71f282` · codex-app-bokeh-ui-review +- 검토 2026-10-24 @ `2157ebe2` · codex-bokeh-edge-review @@ -138,7 +138,7 @@ flowchart TB - 엔진 계약, Camera2·CameraX 구현, 엔드포인트 열거를 제공합니다. `DepthCameraEngine`은 컬러 출력 없이 DEPTH16 맵만 받으며 `DepthFrame`이 순수 Kotlin으로 표시용 표본을 만듭니다. - `PortraitCameraEngine`은 LIVE의 제조사 BOKEH 전용 세션입니다. MainPortraitBridge가 JPEG 크기를 선택하고 MainDepthBridge가 Depth Map과 Portrait의 진입점을 구분합니다. 이 경로는 벤치마크를 실행하거나 센서 Depth 맵을 저장하지 않습니다. - `close(done)` 완료 전에 다음 카메라를 열지 않습니다. -- `AppBokehProcessor`는 일반 JPEG를 RGB로 읽고 기기 안에서 상대 깊이를 추정합니다. `RelativeDepth`와 `DepthBokeh`는 깊이 정규화·선택 지점·배경 흐림을 순수 Kotlin으로 계산합니다. `MainAppBokehBridge`와 `AppBokehEditor`가 촬영 후 편집을 연결하며 원본과 보케 결과를 별도로 보관합니다. +- `AppBokehProcessor`는 일반 JPEG를 RGB로 읽고 기기 안에서 상대 깊이를 추정합니다. `RelativeDepth`와 `DepthBokeh`는 깊이 정규화·선택 지점·배경 흐림을 순수 Kotlin으로 계산합니다. `BokehMask`는 원본 밝기 경계로 흐림 가중치를 보정해 가는 구조물의 손실을 줄입니다. `MainAppBokehBridge`와 `AppBokehEditor`가 촬영 후 편집을 연결하며 원본과 보케 결과를 별도로 보관합니다. - 엔진 계약(`MediaCapture`, `LiveTuning`, `TouchMetering`)과 두 엔진의 구현, 두 엔진의 차이는 [Engine Comparison](engine.md)에 있습니다. - 벤치마크 RECORD 단계의 recorder 상태 기계는 `BenchmarkRecorder`가 담당하고 `Camera2Engine`은 그 호출을 위임합니다. - 카메라를 화면에 적는 이름(`Camera · 0 (Wide · Rear)`)도 이 패키지의 `CameraLabel` 하나가 만들며, Live·Benchmark·Probe·CTS가 모두 그것을 부릅니다. @@ -252,9 +252,9 @@ Camera2의 RAW (DNG)는 `RawFrame`이 RAW 샘플을 복사하고 `DngOutput`이
    근거와 검토 정보 -- 근거 파일: `app/src/main/java/dev/halcamera/MainAppBokehBridge.kt`, `app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt`, `app/src/main/java/dev/halcamera/camera/RelativeDepth.kt`, `app/src/main/java/dev/halcamera/ui/AppBokehEditor.kt`, `app/src/main/java/dev/halcamera/camera/MultiSession.kt`, `app/src/main/java/dev/halcamera/camera/CameraXMultiSession.kt`, `app/src/main/java/dev/halcamera/camera/MultiRecording.kt`, `app/src/main/java/dev/halcamera/camera/SplitDragGesture.kt`, `app/src/main/java/dev/halcamera/cli/CommandCoordinator.kt`, `app/src/main/java/dev/halcamera/cli/LiveController.kt`, `app/src/main/java/dev/halcamera/cli/BenchmarkController.kt`, `app/src/main/java/dev/halcamera/cli/CliLibrary.kt`, `app/src/main/java/dev/halcamera/MainCliBridge.kt`, `app/src/main/java/dev/halcamera/DualCliBridge.kt`, `app/src/main/java/dev/halcamera/camera/CameraEngine.kt`, `app/src/main/java/dev/halcamera/camera/BenchmarkRecorder.kt`, `app/src/main/java/dev/halcamera/benchmark/platform/EnvironmentProbe.kt`, `app/src/main/java/dev/halcamera/benchmark/BenchmarkActivity.kt`, `app/src/main/java/dev/halcamera/benchmark/HistoryActivity.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/RunIndex.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/BenchmarkCsv.kt`, `app/src/main/java/dev/halcamera/benchmark/platform/BenchmarkReport.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/BenchmarkReportCodec.kt`, `app/src/main/java/dev/halcamera/telemetry/FlightRecorder.kt`, `app/src/main/java/dev/halcamera/MainActivity.kt`, `app/src/main/java/dev/halcamera/camera/PortraitCameraEngine.kt`, `app/src/main/java/dev/halcamera/ConcurrentCameraActivity.kt`, `app/src/main/java/dev/halcamera/camera/ConcurrentSession.kt`, `app/src/main/java/dev/halcamera/camera/RecentMediaThumbnail.kt`, `app/src/main/java/dev/halcamera/ui/RecentMediaButton.kt`, `app/src/main/java/dev/halcamera/ui/Look.kt`, `app/src/main/java/dev/halcamera/cts/recording/BasicRecordingRules.kt`, `app/src/main/java/dev/halcamera/cts/CtsEntryActivity.kt`, `app/src/main/java/dev/halcamera/cts/vendored/VendoredCaseActivity.kt`, `app/src/main/java/dev/halcamera/cts/vendored/VendoredCtsListActivity.kt`, `app/src/main/java/dev/halcamera/cts/CtsCaseActivity.kt`, `app/src/main/java/dev/halcamera/cts/CtsCaseListActivity.kt`, `app/src/main/java/dev/halcamera/cts/suite/CtsChecklistActivity.kt`, `app/src/main/java/dev/halcamera/cts/suite/CtsSuiteRunActivity.kt`, `app/src/main/java/dev/halcamera/cts/suite/SuitePlan.kt`, `app/src/main/java/dev/halcamera/cts/suite/SuiteReport.kt`, `app/src/main/java/dev/halcamera/cts/CtsCatalog.kt`, `app/src/main/java/dev/halcamera/cts/CtsRunner.kt`, `app/src/main/java/dev/halcamera/cts/CameraCaseRunner.kt`, `app/src/main/java/dev/halcamera/cts/Camera2Ops.kt`, `app/src/main/java/dev/halcamera/cts/onoff/FastOnOffRules.kt`, `app/src/main/java/dev/halcamera/cts/switching/SwitchingRules.kt`, `app/src/main/java/dev/halcamera/cts/sizes/AllSizeOnOffRules.kt`, `app/src/main/java/dev/halcamera/cts/combination/StillPreviewCombinationRules.kt`, `app/src/main/java/dev/halcamera/cts/snapshot/VideoSnapshotRules.kt`, `app/src/main/java/dev/halcamera/CameraProbeActivity.kt`, `app/src/main/java/dev/halcamera/camera/CameraProbe.kt`, `app/src/main/java/dev/halcamera/camera/CameraProbeReader.kt` +- 근거 파일: `app/src/main/java/dev/halcamera/MainAppBokehBridge.kt`, `app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt`, `app/src/main/java/dev/halcamera/camera/RelativeDepth.kt`, `app/src/main/java/dev/halcamera/camera/BokehMask.kt`, `app/src/main/java/dev/halcamera/ui/AppBokehEditor.kt`, `app/src/main/java/dev/halcamera/camera/MultiSession.kt`, `app/src/main/java/dev/halcamera/camera/CameraXMultiSession.kt`, `app/src/main/java/dev/halcamera/camera/MultiRecording.kt`, `app/src/main/java/dev/halcamera/camera/SplitDragGesture.kt`, `app/src/main/java/dev/halcamera/cli/CommandCoordinator.kt`, `app/src/main/java/dev/halcamera/cli/LiveController.kt`, `app/src/main/java/dev/halcamera/cli/BenchmarkController.kt`, `app/src/main/java/dev/halcamera/cli/CliLibrary.kt`, `app/src/main/java/dev/halcamera/MainCliBridge.kt`, `app/src/main/java/dev/halcamera/DualCliBridge.kt`, `app/src/main/java/dev/halcamera/camera/CameraEngine.kt`, `app/src/main/java/dev/halcamera/camera/BenchmarkRecorder.kt`, `app/src/main/java/dev/halcamera/benchmark/platform/EnvironmentProbe.kt`, `app/src/main/java/dev/halcamera/benchmark/BenchmarkActivity.kt`, `app/src/main/java/dev/halcamera/benchmark/HistoryActivity.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/RunIndex.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/BenchmarkCsv.kt`, `app/src/main/java/dev/halcamera/benchmark/platform/BenchmarkReport.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/BenchmarkReportCodec.kt`, `app/src/main/java/dev/halcamera/telemetry/FlightRecorder.kt`, `app/src/main/java/dev/halcamera/MainActivity.kt`, `app/src/main/java/dev/halcamera/camera/PortraitCameraEngine.kt`, `app/src/main/java/dev/halcamera/ConcurrentCameraActivity.kt`, `app/src/main/java/dev/halcamera/camera/ConcurrentSession.kt`, `app/src/main/java/dev/halcamera/camera/RecentMediaThumbnail.kt`, `app/src/main/java/dev/halcamera/ui/RecentMediaButton.kt`, `app/src/main/java/dev/halcamera/ui/Look.kt`, `app/src/main/java/dev/halcamera/cts/recording/BasicRecordingRules.kt`, `app/src/main/java/dev/halcamera/cts/CtsEntryActivity.kt`, `app/src/main/java/dev/halcamera/cts/vendored/VendoredCaseActivity.kt`, `app/src/main/java/dev/halcamera/cts/vendored/VendoredCtsListActivity.kt`, `app/src/main/java/dev/halcamera/cts/CtsCaseActivity.kt`, `app/src/main/java/dev/halcamera/cts/CtsCaseListActivity.kt`, `app/src/main/java/dev/halcamera/cts/suite/CtsChecklistActivity.kt`, `app/src/main/java/dev/halcamera/cts/suite/CtsSuiteRunActivity.kt`, `app/src/main/java/dev/halcamera/cts/suite/SuitePlan.kt`, `app/src/main/java/dev/halcamera/cts/suite/SuiteReport.kt`, `app/src/main/java/dev/halcamera/cts/CtsCatalog.kt`, `app/src/main/java/dev/halcamera/cts/CtsRunner.kt`, `app/src/main/java/dev/halcamera/cts/CameraCaseRunner.kt`, `app/src/main/java/dev/halcamera/cts/Camera2Ops.kt`, `app/src/main/java/dev/halcamera/cts/onoff/FastOnOffRules.kt`, `app/src/main/java/dev/halcamera/cts/switching/SwitchingRules.kt`, `app/src/main/java/dev/halcamera/cts/sizes/AllSizeOnOffRules.kt`, `app/src/main/java/dev/halcamera/cts/combination/StillPreviewCombinationRules.kt`, `app/src/main/java/dev/halcamera/cts/snapshot/VideoSnapshotRules.kt`, `app/src/main/java/dev/halcamera/CameraProbeActivity.kt`, `app/src/main/java/dev/halcamera/camera/CameraProbe.kt`, `app/src/main/java/dev/halcamera/camera/CameraProbeReader.kt` - 근거 수준: 코드 확인 -- 검토 2026-10-24 @ `9e71f282` · codex-app-bokeh-ui-review +- 검토 2026-10-24 @ `2157ebe2` · codex-bokeh-edge-review
    @@ -296,7 +296,7 @@ Live의 Depth 보기는 `DepthCameraEngine → ImageReader → DepthFrame → De 같은 Depth 메뉴의 Portrait는 `PortraitCameraEngine → CameraExtensionSession → JPEG ImageReader → MediaLibrary`로 연결합니다. 제조사 Bokeh가 적용된 컬러 사진을 저장하며 별도의 센서 Depth 맵을 받지 않습니다. 확장 처리 이벤트는 일반 Camera2 프레임 지표와 구분하고, 사진 픽셀은 측정 파일에 넣지 않습니다. -App Bokeh는 일반 Camera2의 Preview+JPEG로 촬영한 원본을 저장한 뒤 `MainAppBokehBridge → AppBokehEditor → AppBokehProcessor`로 전달합니다. JPEG를 RGB로 디코딩하고 MiDaS 모델이 256×256 Float 상대 깊이를 추론합니다. 사용자가 선택한 깊이보다 먼 영역을 앱에서 흐린 뒤 보케 JPEG·깊이 PNG·처리 메타데이터를 별도 저장합니다. 원본은 그대로 유지하며 분석용 YUV·HAL Depth·제조사 보케 버퍼는 사용하지 않습니다. +App Bokeh는 일반 Camera2의 Preview+JPEG로 촬영한 원본을 저장한 뒤 `MainAppBokehBridge → AppBokehEditor → AppBokehProcessor`로 전달합니다. JPEG를 RGB로 디코딩하고 MiDaS 모델이 256×256 Float 상대 깊이를 추론합니다. 사용자가 선택한 깊이보다 먼 영역의 흐림 가중치를 원본 밝기 경계로 보정한 뒤 배경을 흐립니다. 보케 JPEG·깊이 PNG·처리 메타데이터를 별도 저장하며 깊이 PNG에는 합성 마스크가 아닌 모델 출력을 담습니다. 원본은 그대로 유지하며 분석용 YUV·HAL Depth·제조사 보케 버퍼는 사용하지 않습니다. 벤치마크는 별도 화면인 `BenchmarkActivity`가 준비를 마친 뒤 `BenchmarkRunner.start()`를 호출하여 시작합니다. @@ -477,9 +477,9 @@ CliLaunchActivity는 Dual·Multi에서 Live로 돌아갈 때 CliNavigationHost
    근거와 검토 정보 -- 근거 파일: `app/src/main/java/dev/halcamera/MainAppBokehBridge.kt`, `app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt`, `app/src/main/java/dev/halcamera/ui/AppBokehEditor.kt`, `app/src/main/java/dev/halcamera/cli/CliProvider.kt`, `app/src/main/java/dev/halcamera/cli/CommandCoordinator.kt`, `tools/halcam/halcam/cli.py`, `tools/halcam/halcam/download.py`, `app/src/main/java/dev/halcamera/MainActivity.kt`, `app/src/main/java/dev/halcamera/camera/PortraitCameraEngine.kt`, `app/src/main/java/dev/halcamera/GalleryActivity.kt`, `app/src/main/java/dev/halcamera/camera/MediaLibrary.kt`, `app/src/main/java/dev/halcamera/camera/RecentMediaThumbnail.kt`, `app/src/main/java/dev/halcamera/telemetry/Telemetry.kt`, `app/src/main/java/dev/halcamera/telemetry/FlightRecorder.kt`, `app/src/main/java/dev/halcamera/metrics/MetricExtractor.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/BenchmarkRunner.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/RunAssembler.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/RunValidity.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/RunRetention.kt`, `app/src/main/java/dev/halcamera/benchmark/platform/BenchmarkReport.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/BenchmarkReportCodec.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/BenchmarkEvaluator.kt`, `app/src/main/java/dev/halcamera/benchmark/BenchmarkActivity.kt`, `app/src/main/java/dev/halcamera/benchmark/platform/LaunchDiagnostics.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/RegressionDetector.kt` +- 근거 파일: `app/src/main/java/dev/halcamera/MainAppBokehBridge.kt`, `app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt`, `app/src/main/java/dev/halcamera/camera/BokehMask.kt`, `app/src/main/java/dev/halcamera/ui/AppBokehEditor.kt`, `app/src/main/java/dev/halcamera/cli/CliProvider.kt`, `app/src/main/java/dev/halcamera/cli/CommandCoordinator.kt`, `tools/halcam/halcam/cli.py`, `tools/halcam/halcam/download.py`, `app/src/main/java/dev/halcamera/MainActivity.kt`, `app/src/main/java/dev/halcamera/camera/PortraitCameraEngine.kt`, `app/src/main/java/dev/halcamera/GalleryActivity.kt`, `app/src/main/java/dev/halcamera/camera/MediaLibrary.kt`, `app/src/main/java/dev/halcamera/camera/RecentMediaThumbnail.kt`, `app/src/main/java/dev/halcamera/telemetry/Telemetry.kt`, `app/src/main/java/dev/halcamera/telemetry/FlightRecorder.kt`, `app/src/main/java/dev/halcamera/metrics/MetricExtractor.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/BenchmarkRunner.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/RunAssembler.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/RunValidity.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/RunRetention.kt`, `app/src/main/java/dev/halcamera/benchmark/platform/BenchmarkReport.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/BenchmarkReportCodec.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/BenchmarkEvaluator.kt`, `app/src/main/java/dev/halcamera/benchmark/BenchmarkActivity.kt`, `app/src/main/java/dev/halcamera/benchmark/platform/LaunchDiagnostics.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/RegressionDetector.kt` - 근거 수준: 코드 확인 -- 검토 2026-10-24 @ `9e71f282` · codex-app-bokeh-ui-review +- 검토 2026-10-24 @ `2157ebe2` · codex-bokeh-edge-review
    @@ -756,7 +756,7 @@ Live 카메라 종료 후 벤치마크 화면으로 이동하고 기존 측정· - 근거 파일: `app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt`, `app/src/main/java/dev/halcamera/camera/RelativeDepth.kt`, `app/src/main/java/dev/halcamera/ui/AppBokehEditor.kt`, `app/src/main/java/dev/halcamera/camera/MultiSession.kt`, `app/src/main/java/dev/halcamera/camera/CameraXMultiSession.kt`, `app/src/main/java/dev/halcamera/camera/MultiRecording.kt`, `app/src/main/java/dev/halcamera/camera/MicrophoneCapture.kt`, `app/src/main/java/dev/halcamera/camera/TimestampedVideoRecording.kt`, `app/src/main/java/dev/halcamera/camera/DeviceCompositor.kt`, `app/src/main/java/dev/halcamera/cli/CliProvider.kt`, `app/src/main/java/dev/halcamera/cli/CommandCoordinator.kt`, `app/src/main/java/dev/halcamera/cli/CommandStore.kt`, `app/src/main/java/dev/halcamera/camera/CameraEngine.kt`, `app/src/main/java/dev/halcamera/camera/PortraitCameraEngine.kt`, `app/src/main/java/dev/halcamera/camera/ConcurrentSession.kt`, `app/src/main/java/dev/halcamera/camera/CameraEndpointResolver.kt`, `app/src/main/java/dev/halcamera/telemetry/IncidentExporter.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/BenchmarkRunner.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/RunValidity.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/RegressionRules.kt`, `app/src/main/java/dev/halcamera/benchmark/platform/BenchmarkReport.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/BenchmarkReportCodec.kt`, `app/src/main/java/dev/halcamera/benchmark/platform/BenchmarkStore.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/BenchmarkIndex.kt`, `app/src/main/java/dev/halcamera/benchmark/domain/AtomicFiles.kt` - 근거 수준: 코드 확인 -- 검토 2026-10-24 @ `9e71f282` · codex-app-bokeh-ui-review +- 검토 2026-10-24 @ `2157ebe2` · codex-bokeh-edge-review @@ -808,16 +808,16 @@ Android 의존성이 없는 러너와 평가 로직은 JVM 단위 테스트로 | 항목 | 최신성 | 검토 | | --- | --- | --- | -| 구조 원본 `data-flow` | 최신 | 검토 2026-10-24 @ `e4de2762` · codex-app-bokeh-code-review | -| 구조 원본 `overall-architecture` | 최신 | 검토 2026-10-24 @ `9e71f282` · codex-app-bokeh-ui-review | +| 구조 원본 `data-flow` | 최신 | 검토 2026-10-24 @ `2157ebe2` · codex-bokeh-edge-review | +| 구조 원본 `overall-architecture` | 최신 | 검토 2026-10-24 @ `2157ebe2` · codex-bokeh-edge-review | | 구조 원본 `state-transitions` | 최신 | 검토 2026-10-24 @ `9e71f282` · codex-app-bokeh-ui-review | | 구조 원본 `ui-camera-label` | 최신 | 검토 2026-10-10 @ `93385443` · Codex (mode reset, labels and numeric ID selection review) | | 구조 원본 `ui-tool-handoff` | 최신 | 검토 2026-10-24 @ `e4de2762` · codex-app-bokeh-code-review | | 구조 원본 `ui-zoom` | 최신 | 검토 2026-10-24 @ `e4de2762` · codex-app-bokeh-code-review | -| 원고 `overview` | 최신 | 검토 2026-10-24 @ `9e71f282` · codex-app-bokeh-ui-review | -| 원고 `module-roles` | 최신 | 검토 2026-10-24 @ `9e71f282` · codex-app-bokeh-ui-review | -| 원고 `runtime-flow` | 최신 | 검토 2026-10-24 @ `9e71f282` · codex-app-bokeh-ui-review | -| 원고 `constraints` | 최신 | 검토 2026-10-24 @ `9e71f282` · codex-app-bokeh-ui-review | +| 원고 `overview` | 최신 | 검토 2026-10-24 @ `2157ebe2` · codex-bokeh-edge-review | +| 원고 `module-roles` | 최신 | 검토 2026-10-24 @ `2157ebe2` · codex-bokeh-edge-review | +| 원고 `runtime-flow` | 최신 | 검토 2026-10-24 @ `2157ebe2` · codex-bokeh-edge-review | +| 원고 `constraints` | 최신 | 검토 2026-10-24 @ `2157ebe2` · codex-bokeh-edge-review | diff --git a/tools/docgen/state/evidence.json b/tools/docgen/state/evidence.json index 3213d160..2b1d2d23 100644 --- a/tools/docgen/state/evidence.json +++ b/tools/docgen/state/evidence.json @@ -6,11 +6,11 @@ "state": "fresh" }, "accepted": { - "codeHash": "e5526928cc6bb406", - "modelHash": "d7c4aa2c4c6325a1", + "codeHash": "690052961a9babd0", + "modelHash": "bcf36073a8b8bddb", "at": "2026-10-24", - "commit": "9e71f282", - "reviewer": "codex-app-bokeh-ui-review", + "commit": "2157ebe2", + "reviewer": "codex-bokeh-edge-review", "files": { "app/build.gradle.kts": "f253a1187163f8d464f7eb2ae00426c64806ad75", "app/src/main/AndroidManifest.xml": "394f1ceb67558a1400f691c3eb3157f7be783960", @@ -65,8 +65,9 @@ "app/src/main/java/dev/halcamera/benchmark/platform/StreamSpecs.kt": "a3dc42ba6008f47189702c40a9ee45186f6daafb", "app/src/main/java/dev/halcamera/benchmark/platform/SubjectPrefs.kt": "1e9d5597619ea4b4e107cd08702c4ccba2cb41b7", "app/src/main/java/dev/halcamera/benchmark/platform/ThermalTracker.kt": "5572c02cd6ed7ad1f8fcd9285c02b03cb3f60ee1", - "app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt": "ff7b9e4699d391cabb01c714dd83b56ab2359681", + "app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt": "f063c261e34102ed1b22b4d94cb42560f5a606d8", "app/src/main/java/dev/halcamera/camera/BenchmarkRecorder.kt": "f15c520fa871ff94c5c1069ad962d36cf485b85b", + "app/src/main/java/dev/halcamera/camera/BokehMask.kt": "6d2877b930620a74b68c1fb118e7d8ffa21b8009", "app/src/main/java/dev/halcamera/camera/BracketCapture.kt": "ed6366635f5e466394c416a2b9efb1cff767dd4d", "app/src/main/java/dev/halcamera/camera/BracketFiles.kt": "36c1b51465b9e503d6e4632fad6119079f55f687", "app/src/main/java/dev/halcamera/camera/BracketFusion.kt": "b7a3a4312da292a04c4fd13d6e061375b6624532", @@ -164,7 +165,7 @@ "app/src/main/java/dev/halcamera/camera/RecordingBufferRelay.kt": "d40ba1bb593c5f55c4f12ebf963f0a2b259de1b0", "app/src/main/java/dev/halcamera/camera/RecordingTimebase.kt": "9c2c60e3fa71dea4deeb8cdc973f443e8c329300", "app/src/main/java/dev/halcamera/camera/RecordingTimeline.kt": "5f18000163c7e4568b3e4ba1581a3a1856d0597c", - "app/src/main/java/dev/halcamera/camera/RelativeDepth.kt": "65570fd109f49490b091cb8e9d2b2ded990a0c39", + "app/src/main/java/dev/halcamera/camera/RelativeDepth.kt": "43a467245d69486944c160e7a57f6cf21ceb6043", "app/src/main/java/dev/halcamera/camera/RequestStreamRegistry.kt": "5673124cf2f38d9ff29bcb6e3907be44ae82e3c6", "app/src/main/java/dev/halcamera/camera/SampleClock.kt": "64397572af6bc204dce7c802c4077cbc4769a334", "app/src/main/java/dev/halcamera/camera/SnapshotRequest.kt": "86ea336a62641229a89f1e05963a2e42e63db030", @@ -300,11 +301,11 @@ "state": "fresh" }, "accepted": { - "codeHash": "abdeffda7f395e31", - "modelHash": "a3e8b80c9d1ac34b", + "codeHash": "bcc05f1ea5aec847", + "modelHash": "5154f1ef270bbb15", "at": "2026-10-24", - "commit": "e4de2762", - "reviewer": "codex-app-bokeh-code-review", + "commit": "2157ebe2", + "reviewer": "codex-bokeh-edge-review", "files": { "app/src/main/java/dev/halcamera/GalleryActivity.kt": "d220302a8e9dd392cf1560e5b28dc5aa6378202f", "app/src/main/java/dev/halcamera/MainActivity.kt": "b29ca903388af63b0218a32b5bcc2d8075479cef", @@ -324,14 +325,15 @@ "app/src/main/java/dev/halcamera/benchmark/domain/RunValidity.kt": "df32b7ff015c81cb44407884ea02deda965752df", "app/src/main/java/dev/halcamera/benchmark/platform/BenchmarkReport.kt": "cb8a23b1587af5faf4409d01b075f2a615987c62", "app/src/main/java/dev/halcamera/benchmark/platform/DeviceInstance.kt": "ba39a6256eeb87193f9a6560762dee274c4f776f", - "app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt": "ff7b9e4699d391cabb01c714dd83b56ab2359681", + "app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt": "f063c261e34102ed1b22b4d94cb42560f5a606d8", + "app/src/main/java/dev/halcamera/camera/BokehMask.kt": "6d2877b930620a74b68c1fb118e7d8ffa21b8009", "app/src/main/java/dev/halcamera/camera/Camera2Engine.kt": "a07b50648bd1812a3ae55e1b31494e963feb060c", "app/src/main/java/dev/halcamera/camera/CameraEngine.kt": "7441e69d8ef8394db42448a69abd9fa688167c70", "app/src/main/java/dev/halcamera/camera/CameraXEngine.kt": "c885e0bb38e481d45fda923dbea1d03e60a1a2b7", "app/src/main/java/dev/halcamera/camera/MediaLibrary.kt": "2fbb56c9e81761cea93f64109ec12497309cffe8", "app/src/main/java/dev/halcamera/camera/MediaTransaction.kt": "34f0121f9c418c8e49fb0326a99882ae6f447c94", "app/src/main/java/dev/halcamera/camera/RecentMediaThumbnail.kt": "c8ca57c719ca0df2bc002493a1568c3de1196390", - "app/src/main/java/dev/halcamera/camera/RelativeDepth.kt": "65570fd109f49490b091cb8e9d2b2ded990a0c39", + "app/src/main/java/dev/halcamera/camera/RelativeDepth.kt": "43a467245d69486944c160e7a57f6cf21ceb6043", "app/src/main/java/dev/halcamera/cli/BenchmarkController.kt": "1789489c62871f8bb25a4274e76c58c80414182f", "app/src/main/java/dev/halcamera/cli/CliArtifactRegistry.kt": "e01a37879d2b3638a95bcb2a54adf75ab4643ac8", "app/src/main/java/dev/halcamera/cli/CliLaunchActivity.kt": "117b88d8cb5458aaa05dabe4153e74c5dc832e45", @@ -403,11 +405,11 @@ "state": "fresh" }, "accepted": { - "codeHash": "4bd046519edd291b", - "modelHash": "61c84735e9b9b510", + "codeHash": "0853f026e6f074fb", + "modelHash": "6e2796cdabd98225", "at": "2026-10-24", - "commit": "9e71f282", - "reviewer": "codex-app-bokeh-ui-review", + "commit": "2157ebe2", + "reviewer": "codex-bokeh-edge-review", "files": { "app/build.gradle.kts": "f253a1187163f8d464f7eb2ae00426c64806ad75", "app/src/main/AndroidManifest.xml": "394f1ceb67558a1400f691c3eb3157f7be783960", @@ -462,8 +464,9 @@ "app/src/main/java/dev/halcamera/benchmark/platform/StreamSpecs.kt": "a3dc42ba6008f47189702c40a9ee45186f6daafb", "app/src/main/java/dev/halcamera/benchmark/platform/SubjectPrefs.kt": "1e9d5597619ea4b4e107cd08702c4ccba2cb41b7", "app/src/main/java/dev/halcamera/benchmark/platform/ThermalTracker.kt": "5572c02cd6ed7ad1f8fcd9285c02b03cb3f60ee1", - "app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt": "ff7b9e4699d391cabb01c714dd83b56ab2359681", + "app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt": "f063c261e34102ed1b22b4d94cb42560f5a606d8", "app/src/main/java/dev/halcamera/camera/BenchmarkRecorder.kt": "f15c520fa871ff94c5c1069ad962d36cf485b85b", + "app/src/main/java/dev/halcamera/camera/BokehMask.kt": "6d2877b930620a74b68c1fb118e7d8ffa21b8009", "app/src/main/java/dev/halcamera/camera/BracketCapture.kt": "ed6366635f5e466394c416a2b9efb1cff767dd4d", "app/src/main/java/dev/halcamera/camera/BracketFiles.kt": "36c1b51465b9e503d6e4632fad6119079f55f687", "app/src/main/java/dev/halcamera/camera/BracketFusion.kt": "b7a3a4312da292a04c4fd13d6e061375b6624532", @@ -561,7 +564,7 @@ "app/src/main/java/dev/halcamera/camera/RecordingBufferRelay.kt": "d40ba1bb593c5f55c4f12ebf963f0a2b259de1b0", "app/src/main/java/dev/halcamera/camera/RecordingTimebase.kt": "9c2c60e3fa71dea4deeb8cdc973f443e8c329300", "app/src/main/java/dev/halcamera/camera/RecordingTimeline.kt": "5f18000163c7e4568b3e4ba1581a3a1856d0597c", - "app/src/main/java/dev/halcamera/camera/RelativeDepth.kt": "65570fd109f49490b091cb8e9d2b2ded990a0c39", + "app/src/main/java/dev/halcamera/camera/RelativeDepth.kt": "43a467245d69486944c160e7a57f6cf21ceb6043", "app/src/main/java/dev/halcamera/camera/RequestStreamRegistry.kt": "5673124cf2f38d9ff29bcb6e3907be44ae82e3c6", "app/src/main/java/dev/halcamera/camera/SampleClock.kt": "64397572af6bc204dce7c802c4077cbc4769a334", "app/src/main/java/dev/halcamera/camera/SnapshotRequest.kt": "86ea336a62641229a89f1e05963a2e42e63db030", @@ -700,11 +703,11 @@ "state": "fresh" }, "accepted": { - "codeHash": "167e256bf008a70d", - "modelHash": "3291cd6bfd0a9310", + "codeHash": "f9d4ce43b7c1fa4e", + "modelHash": "61b6961213bb879a", "at": "2026-10-24", - "commit": "9e71f282", - "reviewer": "codex-app-bokeh-ui-review", + "commit": "2157ebe2", + "reviewer": "codex-bokeh-edge-review", "files": { "app/src/main/java/dev/halcamera/GalleryActivity.kt": "d220302a8e9dd392cf1560e5b28dc5aa6378202f", "app/src/main/java/dev/halcamera/MainActivity.kt": "b29ca903388af63b0218a32b5bcc2d8075479cef", @@ -725,7 +728,8 @@ "app/src/main/java/dev/halcamera/benchmark/platform/BenchmarkReport.kt": "cb8a23b1587af5faf4409d01b075f2a615987c62", "app/src/main/java/dev/halcamera/benchmark/platform/DeviceInstance.kt": "ba39a6256eeb87193f9a6560762dee274c4f776f", "app/src/main/java/dev/halcamera/benchmark/platform/LaunchDiagnostics.kt": "c406e4887c0617d8b57eb4b3c91263f427953372", - "app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt": "ff7b9e4699d391cabb01c714dd83b56ab2359681", + "app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt": "f063c261e34102ed1b22b4d94cb42560f5a606d8", + "app/src/main/java/dev/halcamera/camera/BokehMask.kt": "6d2877b930620a74b68c1fb118e7d8ffa21b8009", "app/src/main/java/dev/halcamera/camera/Camera2Engine.kt": "a07b50648bd1812a3ae55e1b31494e963feb060c", "app/src/main/java/dev/halcamera/camera/CameraEngine.kt": "7441e69d8ef8394db42448a69abd9fa688167c70", "app/src/main/java/dev/halcamera/camera/CameraXEngine.kt": "c885e0bb38e481d45fda923dbea1d03e60a1a2b7", @@ -733,7 +737,7 @@ "app/src/main/java/dev/halcamera/camera/MediaTransaction.kt": "34f0121f9c418c8e49fb0326a99882ae6f447c94", "app/src/main/java/dev/halcamera/camera/PortraitCameraEngine.kt": "487943447e62e1f101687a0ebc3967abb78a54a3", "app/src/main/java/dev/halcamera/camera/RecentMediaThumbnail.kt": "c8ca57c719ca0df2bc002493a1568c3de1196390", - "app/src/main/java/dev/halcamera/camera/RelativeDepth.kt": "65570fd109f49490b091cb8e9d2b2ded990a0c39", + "app/src/main/java/dev/halcamera/camera/RelativeDepth.kt": "43a467245d69486944c160e7a57f6cf21ceb6043", "app/src/main/java/dev/halcamera/cli/BenchmarkController.kt": "1789489c62871f8bb25a4274e76c58c80414182f", "app/src/main/java/dev/halcamera/cli/CliArtifactRegistry.kt": "e01a37879d2b3638a95bcb2a54adf75ab4643ac8", "app/src/main/java/dev/halcamera/cli/CliLaunchActivity.kt": "117b88d8cb5458aaa05dabe4153e74c5dc832e45", @@ -754,11 +758,11 @@ }, "content:architecture.md/module-roles": { "accepted": { - "codeHash": "e5526928cc6bb406", - "modelHash": "21de31d2606fef6b", + "codeHash": "690052961a9babd0", + "modelHash": "2f929b39123cb865", "at": "2026-10-24", - "commit": "9e71f282", - "reviewer": "codex-app-bokeh-ui-review", + "commit": "2157ebe2", + "reviewer": "codex-bokeh-edge-review", "files": { "app/build.gradle.kts": "f253a1187163f8d464f7eb2ae00426c64806ad75", "app/src/main/AndroidManifest.xml": "394f1ceb67558a1400f691c3eb3157f7be783960", @@ -813,8 +817,9 @@ "app/src/main/java/dev/halcamera/benchmark/platform/StreamSpecs.kt": "a3dc42ba6008f47189702c40a9ee45186f6daafb", "app/src/main/java/dev/halcamera/benchmark/platform/SubjectPrefs.kt": "1e9d5597619ea4b4e107cd08702c4ccba2cb41b7", "app/src/main/java/dev/halcamera/benchmark/platform/ThermalTracker.kt": "5572c02cd6ed7ad1f8fcd9285c02b03cb3f60ee1", - "app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt": "ff7b9e4699d391cabb01c714dd83b56ab2359681", + "app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt": "f063c261e34102ed1b22b4d94cb42560f5a606d8", "app/src/main/java/dev/halcamera/camera/BenchmarkRecorder.kt": "f15c520fa871ff94c5c1069ad962d36cf485b85b", + "app/src/main/java/dev/halcamera/camera/BokehMask.kt": "6d2877b930620a74b68c1fb118e7d8ffa21b8009", "app/src/main/java/dev/halcamera/camera/BracketCapture.kt": "ed6366635f5e466394c416a2b9efb1cff767dd4d", "app/src/main/java/dev/halcamera/camera/BracketFiles.kt": "36c1b51465b9e503d6e4632fad6119079f55f687", "app/src/main/java/dev/halcamera/camera/BracketFusion.kt": "b7a3a4312da292a04c4fd13d6e061375b6624532", @@ -912,7 +917,7 @@ "app/src/main/java/dev/halcamera/camera/RecordingBufferRelay.kt": "d40ba1bb593c5f55c4f12ebf963f0a2b259de1b0", "app/src/main/java/dev/halcamera/camera/RecordingTimebase.kt": "9c2c60e3fa71dea4deeb8cdc973f443e8c329300", "app/src/main/java/dev/halcamera/camera/RecordingTimeline.kt": "5f18000163c7e4568b3e4ba1581a3a1856d0597c", - "app/src/main/java/dev/halcamera/camera/RelativeDepth.kt": "65570fd109f49490b091cb8e9d2b2ded990a0c39", + "app/src/main/java/dev/halcamera/camera/RelativeDepth.kt": "43a467245d69486944c160e7a57f6cf21ceb6043", "app/src/main/java/dev/halcamera/camera/RequestStreamRegistry.kt": "5673124cf2f38d9ff29bcb6e3907be44ae82e3c6", "app/src/main/java/dev/halcamera/camera/SampleClock.kt": "64397572af6bc204dce7c802c4077cbc4769a334", "app/src/main/java/dev/halcamera/camera/SnapshotRequest.kt": "86ea336a62641229a89f1e05963a2e42e63db030", @@ -1048,11 +1053,11 @@ }, "content:architecture.md/constraints": { "accepted": { - "codeHash": "e5526928cc6bb406", - "modelHash": "36d19ef7ee11f149", + "codeHash": "690052961a9babd0", + "modelHash": "79fc78abde555f3b", "at": "2026-10-24", - "commit": "9e71f282", - "reviewer": "codex-app-bokeh-ui-review", + "commit": "2157ebe2", + "reviewer": "codex-bokeh-edge-review", "files": { "app/build.gradle.kts": "f253a1187163f8d464f7eb2ae00426c64806ad75", "app/src/main/AndroidManifest.xml": "394f1ceb67558a1400f691c3eb3157f7be783960", @@ -1107,8 +1112,9 @@ "app/src/main/java/dev/halcamera/benchmark/platform/StreamSpecs.kt": "a3dc42ba6008f47189702c40a9ee45186f6daafb", "app/src/main/java/dev/halcamera/benchmark/platform/SubjectPrefs.kt": "1e9d5597619ea4b4e107cd08702c4ccba2cb41b7", "app/src/main/java/dev/halcamera/benchmark/platform/ThermalTracker.kt": "5572c02cd6ed7ad1f8fcd9285c02b03cb3f60ee1", - "app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt": "ff7b9e4699d391cabb01c714dd83b56ab2359681", + "app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt": "f063c261e34102ed1b22b4d94cb42560f5a606d8", "app/src/main/java/dev/halcamera/camera/BenchmarkRecorder.kt": "f15c520fa871ff94c5c1069ad962d36cf485b85b", + "app/src/main/java/dev/halcamera/camera/BokehMask.kt": "6d2877b930620a74b68c1fb118e7d8ffa21b8009", "app/src/main/java/dev/halcamera/camera/BracketCapture.kt": "ed6366635f5e466394c416a2b9efb1cff767dd4d", "app/src/main/java/dev/halcamera/camera/BracketFiles.kt": "36c1b51465b9e503d6e4632fad6119079f55f687", "app/src/main/java/dev/halcamera/camera/BracketFusion.kt": "b7a3a4312da292a04c4fd13d6e061375b6624532", @@ -1206,7 +1212,7 @@ "app/src/main/java/dev/halcamera/camera/RecordingBufferRelay.kt": "d40ba1bb593c5f55c4f12ebf963f0a2b259de1b0", "app/src/main/java/dev/halcamera/camera/RecordingTimebase.kt": "9c2c60e3fa71dea4deeb8cdc973f443e8c329300", "app/src/main/java/dev/halcamera/camera/RecordingTimeline.kt": "5f18000163c7e4568b3e4ba1581a3a1856d0597c", - "app/src/main/java/dev/halcamera/camera/RelativeDepth.kt": "65570fd109f49490b091cb8e9d2b2ded990a0c39", + "app/src/main/java/dev/halcamera/camera/RelativeDepth.kt": "43a467245d69486944c160e7a57f6cf21ceb6043", "app/src/main/java/dev/halcamera/camera/RequestStreamRegistry.kt": "5673124cf2f38d9ff29bcb6e3907be44ae82e3c6", "app/src/main/java/dev/halcamera/camera/SampleClock.kt": "64397572af6bc204dce7c802c4077cbc4769a334", "app/src/main/java/dev/halcamera/camera/SnapshotRequest.kt": "86ea336a62641229a89f1e05963a2e42e63db030", @@ -1395,11 +1401,11 @@ "state": "fresh" }, "accepted": { - "codeHash": "e5526928cc6bb406", - "modelHash": "4ef7c17af745df2f", + "codeHash": "690052961a9babd0", + "modelHash": "9b425ff4ec7b793f", "at": "2026-10-24", - "commit": "9e71f282", - "reviewer": "codex-app-bokeh-ui-review", + "commit": "2157ebe2", + "reviewer": "codex-bokeh-edge-review", "files": { "app/build.gradle.kts": "f253a1187163f8d464f7eb2ae00426c64806ad75", "app/src/main/AndroidManifest.xml": "394f1ceb67558a1400f691c3eb3157f7be783960", @@ -1454,8 +1460,9 @@ "app/src/main/java/dev/halcamera/benchmark/platform/StreamSpecs.kt": "a3dc42ba6008f47189702c40a9ee45186f6daafb", "app/src/main/java/dev/halcamera/benchmark/platform/SubjectPrefs.kt": "1e9d5597619ea4b4e107cd08702c4ccba2cb41b7", "app/src/main/java/dev/halcamera/benchmark/platform/ThermalTracker.kt": "5572c02cd6ed7ad1f8fcd9285c02b03cb3f60ee1", - "app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt": "ff7b9e4699d391cabb01c714dd83b56ab2359681", + "app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt": "f063c261e34102ed1b22b4d94cb42560f5a606d8", "app/src/main/java/dev/halcamera/camera/BenchmarkRecorder.kt": "f15c520fa871ff94c5c1069ad962d36cf485b85b", + "app/src/main/java/dev/halcamera/camera/BokehMask.kt": "6d2877b930620a74b68c1fb118e7d8ffa21b8009", "app/src/main/java/dev/halcamera/camera/BracketCapture.kt": "ed6366635f5e466394c416a2b9efb1cff767dd4d", "app/src/main/java/dev/halcamera/camera/BracketFiles.kt": "36c1b51465b9e503d6e4632fad6119079f55f687", "app/src/main/java/dev/halcamera/camera/BracketFusion.kt": "b7a3a4312da292a04c4fd13d6e061375b6624532", @@ -1553,7 +1560,7 @@ "app/src/main/java/dev/halcamera/camera/RecordingBufferRelay.kt": "d40ba1bb593c5f55c4f12ebf963f0a2b259de1b0", "app/src/main/java/dev/halcamera/camera/RecordingTimebase.kt": "9c2c60e3fa71dea4deeb8cdc973f443e8c329300", "app/src/main/java/dev/halcamera/camera/RecordingTimeline.kt": "5f18000163c7e4568b3e4ba1581a3a1856d0597c", - "app/src/main/java/dev/halcamera/camera/RelativeDepth.kt": "65570fd109f49490b091cb8e9d2b2ded990a0c39", + "app/src/main/java/dev/halcamera/camera/RelativeDepth.kt": "43a467245d69486944c160e7a57f6cf21ceb6043", "app/src/main/java/dev/halcamera/camera/RequestStreamRegistry.kt": "5673124cf2f38d9ff29bcb6e3907be44ae82e3c6", "app/src/main/java/dev/halcamera/camera/SampleClock.kt": "64397572af6bc204dce7c802c4077cbc4769a334", "app/src/main/java/dev/halcamera/camera/SnapshotRequest.kt": "86ea336a62641229a89f1e05963a2e42e63db030", @@ -1689,11 +1696,11 @@ "state": "fresh" }, "accepted": { - "codeHash": "e5526928cc6bb406", - "modelHash": "e53b130506a00ed6", + "codeHash": "690052961a9babd0", + "modelHash": "14a684fd502007cf", "at": "2026-10-24", - "commit": "9e71f282", - "reviewer": "codex-app-bokeh-ui-review", + "commit": "2157ebe2", + "reviewer": "codex-bokeh-edge-review", "files": { "app/build.gradle.kts": "f253a1187163f8d464f7eb2ae00426c64806ad75", "app/src/main/AndroidManifest.xml": "394f1ceb67558a1400f691c3eb3157f7be783960", @@ -1748,8 +1755,9 @@ "app/src/main/java/dev/halcamera/benchmark/platform/StreamSpecs.kt": "a3dc42ba6008f47189702c40a9ee45186f6daafb", "app/src/main/java/dev/halcamera/benchmark/platform/SubjectPrefs.kt": "1e9d5597619ea4b4e107cd08702c4ccba2cb41b7", "app/src/main/java/dev/halcamera/benchmark/platform/ThermalTracker.kt": "5572c02cd6ed7ad1f8fcd9285c02b03cb3f60ee1", - "app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt": "ff7b9e4699d391cabb01c714dd83b56ab2359681", + "app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt": "f063c261e34102ed1b22b4d94cb42560f5a606d8", "app/src/main/java/dev/halcamera/camera/BenchmarkRecorder.kt": "f15c520fa871ff94c5c1069ad962d36cf485b85b", + "app/src/main/java/dev/halcamera/camera/BokehMask.kt": "6d2877b930620a74b68c1fb118e7d8ffa21b8009", "app/src/main/java/dev/halcamera/camera/BracketCapture.kt": "ed6366635f5e466394c416a2b9efb1cff767dd4d", "app/src/main/java/dev/halcamera/camera/BracketFiles.kt": "36c1b51465b9e503d6e4632fad6119079f55f687", "app/src/main/java/dev/halcamera/camera/BracketFusion.kt": "b7a3a4312da292a04c4fd13d6e061375b6624532", @@ -1847,7 +1855,7 @@ "app/src/main/java/dev/halcamera/camera/RecordingBufferRelay.kt": "d40ba1bb593c5f55c4f12ebf963f0a2b259de1b0", "app/src/main/java/dev/halcamera/camera/RecordingTimebase.kt": "9c2c60e3fa71dea4deeb8cdc973f443e8c329300", "app/src/main/java/dev/halcamera/camera/RecordingTimeline.kt": "5f18000163c7e4568b3e4ba1581a3a1856d0597c", - "app/src/main/java/dev/halcamera/camera/RelativeDepth.kt": "65570fd109f49490b091cb8e9d2b2ded990a0c39", + "app/src/main/java/dev/halcamera/camera/RelativeDepth.kt": "43a467245d69486944c160e7a57f6cf21ceb6043", "app/src/main/java/dev/halcamera/camera/RequestStreamRegistry.kt": "5673124cf2f38d9ff29bcb6e3907be44ae82e3c6", "app/src/main/java/dev/halcamera/camera/SampleClock.kt": "64397572af6bc204dce7c802c4077cbc4769a334", "app/src/main/java/dev/halcamera/camera/SnapshotRequest.kt": "86ea336a62641229a89f1e05963a2e42e63db030", @@ -1983,11 +1991,11 @@ "state": "fresh" }, "accepted": { - "codeHash": "e5526928cc6bb406", - "modelHash": "9db4e087c89fd6e3", + "codeHash": "690052961a9babd0", + "modelHash": "4a299f15a5ce9a2e", "at": "2026-10-24", - "commit": "9e71f282", - "reviewer": "codex-app-bokeh-ui-review", + "commit": "2157ebe2", + "reviewer": "codex-bokeh-edge-review", "files": { "app/build.gradle.kts": "f253a1187163f8d464f7eb2ae00426c64806ad75", "app/src/main/AndroidManifest.xml": "394f1ceb67558a1400f691c3eb3157f7be783960", @@ -2042,8 +2050,9 @@ "app/src/main/java/dev/halcamera/benchmark/platform/StreamSpecs.kt": "a3dc42ba6008f47189702c40a9ee45186f6daafb", "app/src/main/java/dev/halcamera/benchmark/platform/SubjectPrefs.kt": "1e9d5597619ea4b4e107cd08702c4ccba2cb41b7", "app/src/main/java/dev/halcamera/benchmark/platform/ThermalTracker.kt": "5572c02cd6ed7ad1f8fcd9285c02b03cb3f60ee1", - "app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt": "ff7b9e4699d391cabb01c714dd83b56ab2359681", + "app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt": "f063c261e34102ed1b22b4d94cb42560f5a606d8", "app/src/main/java/dev/halcamera/camera/BenchmarkRecorder.kt": "f15c520fa871ff94c5c1069ad962d36cf485b85b", + "app/src/main/java/dev/halcamera/camera/BokehMask.kt": "6d2877b930620a74b68c1fb118e7d8ffa21b8009", "app/src/main/java/dev/halcamera/camera/BracketCapture.kt": "ed6366635f5e466394c416a2b9efb1cff767dd4d", "app/src/main/java/dev/halcamera/camera/BracketFiles.kt": "36c1b51465b9e503d6e4632fad6119079f55f687", "app/src/main/java/dev/halcamera/camera/BracketFusion.kt": "b7a3a4312da292a04c4fd13d6e061375b6624532", @@ -2141,7 +2150,7 @@ "app/src/main/java/dev/halcamera/camera/RecordingBufferRelay.kt": "d40ba1bb593c5f55c4f12ebf963f0a2b259de1b0", "app/src/main/java/dev/halcamera/camera/RecordingTimebase.kt": "9c2c60e3fa71dea4deeb8cdc973f443e8c329300", "app/src/main/java/dev/halcamera/camera/RecordingTimeline.kt": "5f18000163c7e4568b3e4ba1581a3a1856d0597c", - "app/src/main/java/dev/halcamera/camera/RelativeDepth.kt": "65570fd109f49490b091cb8e9d2b2ded990a0c39", + "app/src/main/java/dev/halcamera/camera/RelativeDepth.kt": "43a467245d69486944c160e7a57f6cf21ceb6043", "app/src/main/java/dev/halcamera/camera/RequestStreamRegistry.kt": "5673124cf2f38d9ff29bcb6e3907be44ae82e3c6", "app/src/main/java/dev/halcamera/camera/SampleClock.kt": "64397572af6bc204dce7c802c4077cbc4769a334", "app/src/main/java/dev/halcamera/camera/SnapshotRequest.kt": "86ea336a62641229a89f1e05963a2e42e63db030", @@ -2277,11 +2286,11 @@ "state": "fresh" }, "accepted": { - "codeHash": "e5526928cc6bb406", - "modelHash": "159d4eedf4472bea", + "codeHash": "690052961a9babd0", + "modelHash": "0afec0df64d07620", "at": "2026-10-24", - "commit": "9e71f282", - "reviewer": "codex-app-bokeh-ui-review", + "commit": "2157ebe2", + "reviewer": "codex-bokeh-edge-review", "files": { "app/build.gradle.kts": "f253a1187163f8d464f7eb2ae00426c64806ad75", "app/src/main/AndroidManifest.xml": "394f1ceb67558a1400f691c3eb3157f7be783960", @@ -2336,8 +2345,9 @@ "app/src/main/java/dev/halcamera/benchmark/platform/StreamSpecs.kt": "a3dc42ba6008f47189702c40a9ee45186f6daafb", "app/src/main/java/dev/halcamera/benchmark/platform/SubjectPrefs.kt": "1e9d5597619ea4b4e107cd08702c4ccba2cb41b7", "app/src/main/java/dev/halcamera/benchmark/platform/ThermalTracker.kt": "5572c02cd6ed7ad1f8fcd9285c02b03cb3f60ee1", - "app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt": "ff7b9e4699d391cabb01c714dd83b56ab2359681", + "app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt": "f063c261e34102ed1b22b4d94cb42560f5a606d8", "app/src/main/java/dev/halcamera/camera/BenchmarkRecorder.kt": "f15c520fa871ff94c5c1069ad962d36cf485b85b", + "app/src/main/java/dev/halcamera/camera/BokehMask.kt": "6d2877b930620a74b68c1fb118e7d8ffa21b8009", "app/src/main/java/dev/halcamera/camera/BracketCapture.kt": "ed6366635f5e466394c416a2b9efb1cff767dd4d", "app/src/main/java/dev/halcamera/camera/BracketFiles.kt": "36c1b51465b9e503d6e4632fad6119079f55f687", "app/src/main/java/dev/halcamera/camera/BracketFusion.kt": "b7a3a4312da292a04c4fd13d6e061375b6624532", @@ -2435,7 +2445,7 @@ "app/src/main/java/dev/halcamera/camera/RecordingBufferRelay.kt": "d40ba1bb593c5f55c4f12ebf963f0a2b259de1b0", "app/src/main/java/dev/halcamera/camera/RecordingTimebase.kt": "9c2c60e3fa71dea4deeb8cdc973f443e8c329300", "app/src/main/java/dev/halcamera/camera/RecordingTimeline.kt": "5f18000163c7e4568b3e4ba1581a3a1856d0597c", - "app/src/main/java/dev/halcamera/camera/RelativeDepth.kt": "65570fd109f49490b091cb8e9d2b2ded990a0c39", + "app/src/main/java/dev/halcamera/camera/RelativeDepth.kt": "43a467245d69486944c160e7a57f6cf21ceb6043", "app/src/main/java/dev/halcamera/camera/RequestStreamRegistry.kt": "5673124cf2f38d9ff29bcb6e3907be44ae82e3c6", "app/src/main/java/dev/halcamera/camera/SampleClock.kt": "64397572af6bc204dce7c802c4077cbc4769a334", "app/src/main/java/dev/halcamera/camera/SnapshotRequest.kt": "86ea336a62641229a89f1e05963a2e42e63db030", @@ -2571,11 +2581,11 @@ "state": "fresh" }, "accepted": { - "codeHash": "844a3e7c556ad4ae", - "modelHash": "1c146246635748ea", + "codeHash": "b91b670b1e0bc558", + "modelHash": "36b9a74276977f57", "at": "2026-10-24", - "commit": "9e71f282", - "reviewer": "codex-app-bokeh-ui-review", + "commit": "2157ebe2", + "reviewer": "codex-bokeh-edge-review", "files": { "app/src/main/java/dev/halcamera/GalleryActivity.kt": "d220302a8e9dd392cf1560e5b28dc5aa6378202f", "app/src/main/java/dev/halcamera/MainActivity.kt": "b29ca903388af63b0218a32b5bcc2d8075479cef", @@ -2597,7 +2607,8 @@ "app/src/main/java/dev/halcamera/benchmark/domain/RunValidity.kt": "df32b7ff015c81cb44407884ea02deda965752df", "app/src/main/java/dev/halcamera/benchmark/platform/BenchmarkReport.kt": "cb8a23b1587af5faf4409d01b075f2a615987c62", "app/src/main/java/dev/halcamera/benchmark/platform/DeviceInstance.kt": "ba39a6256eeb87193f9a6560762dee274c4f776f", - "app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt": "ff7b9e4699d391cabb01c714dd83b56ab2359681", + "app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt": "f063c261e34102ed1b22b4d94cb42560f5a606d8", + "app/src/main/java/dev/halcamera/camera/BokehMask.kt": "6d2877b930620a74b68c1fb118e7d8ffa21b8009", "app/src/main/java/dev/halcamera/camera/Camera2Engine.kt": "a07b50648bd1812a3ae55e1b31494e963feb060c", "app/src/main/java/dev/halcamera/camera/CameraEngine.kt": "7441e69d8ef8394db42448a69abd9fa688167c70", "app/src/main/java/dev/halcamera/camera/CameraXEngine.kt": "c885e0bb38e481d45fda923dbea1d03e60a1a2b7", @@ -2607,7 +2618,7 @@ "app/src/main/java/dev/halcamera/camera/MediaTransaction.kt": "34f0121f9c418c8e49fb0326a99882ae6f447c94", "app/src/main/java/dev/halcamera/camera/PortraitCameraEngine.kt": "487943447e62e1f101687a0ebc3967abb78a54a3", "app/src/main/java/dev/halcamera/camera/RecentMediaThumbnail.kt": "c8ca57c719ca0df2bc002493a1568c3de1196390", - "app/src/main/java/dev/halcamera/camera/RelativeDepth.kt": "65570fd109f49490b091cb8e9d2b2ded990a0c39", + "app/src/main/java/dev/halcamera/camera/RelativeDepth.kt": "43a467245d69486944c160e7a57f6cf21ceb6043", "app/src/main/java/dev/halcamera/cli/BenchmarkController.kt": "1789489c62871f8bb25a4274e76c58c80414182f", "app/src/main/java/dev/halcamera/cli/CliArtifactRegistry.kt": "e01a37879d2b3638a95bcb2a54adf75ab4643ac8", "app/src/main/java/dev/halcamera/cli/CliCommand.kt": "590a408fec3e4c8fb08a9b0a3ca50efb2b10e609", @@ -2640,11 +2651,11 @@ "state": "fresh" }, "accepted": { - "codeHash": "e5526928cc6bb406", - "modelHash": "b9db0c76a101865b", + "codeHash": "690052961a9babd0", + "modelHash": "c1fb59f5bfe5a17f", "at": "2026-10-24", - "commit": "9e71f282", - "reviewer": "codex-app-bokeh-ui-review", + "commit": "2157ebe2", + "reviewer": "codex-bokeh-edge-review", "files": { "app/build.gradle.kts": "f253a1187163f8d464f7eb2ae00426c64806ad75", "app/src/main/AndroidManifest.xml": "394f1ceb67558a1400f691c3eb3157f7be783960", @@ -2699,8 +2710,9 @@ "app/src/main/java/dev/halcamera/benchmark/platform/StreamSpecs.kt": "a3dc42ba6008f47189702c40a9ee45186f6daafb", "app/src/main/java/dev/halcamera/benchmark/platform/SubjectPrefs.kt": "1e9d5597619ea4b4e107cd08702c4ccba2cb41b7", "app/src/main/java/dev/halcamera/benchmark/platform/ThermalTracker.kt": "5572c02cd6ed7ad1f8fcd9285c02b03cb3f60ee1", - "app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt": "ff7b9e4699d391cabb01c714dd83b56ab2359681", + "app/src/main/java/dev/halcamera/camera/AppBokehProcessor.kt": "f063c261e34102ed1b22b4d94cb42560f5a606d8", "app/src/main/java/dev/halcamera/camera/BenchmarkRecorder.kt": "f15c520fa871ff94c5c1069ad962d36cf485b85b", + "app/src/main/java/dev/halcamera/camera/BokehMask.kt": "6d2877b930620a74b68c1fb118e7d8ffa21b8009", "app/src/main/java/dev/halcamera/camera/BracketCapture.kt": "ed6366635f5e466394c416a2b9efb1cff767dd4d", "app/src/main/java/dev/halcamera/camera/BracketFiles.kt": "36c1b51465b9e503d6e4632fad6119079f55f687", "app/src/main/java/dev/halcamera/camera/BracketFusion.kt": "b7a3a4312da292a04c4fd13d6e061375b6624532", @@ -2798,7 +2810,7 @@ "app/src/main/java/dev/halcamera/camera/RecordingBufferRelay.kt": "d40ba1bb593c5f55c4f12ebf963f0a2b259de1b0", "app/src/main/java/dev/halcamera/camera/RecordingTimebase.kt": "9c2c60e3fa71dea4deeb8cdc973f443e8c329300", "app/src/main/java/dev/halcamera/camera/RecordingTimeline.kt": "5f18000163c7e4568b3e4ba1581a3a1856d0597c", - "app/src/main/java/dev/halcamera/camera/RelativeDepth.kt": "65570fd109f49490b091cb8e9d2b2ded990a0c39", + "app/src/main/java/dev/halcamera/camera/RelativeDepth.kt": "43a467245d69486944c160e7a57f6cf21ceb6043", "app/src/main/java/dev/halcamera/camera/RequestStreamRegistry.kt": "5673124cf2f38d9ff29bcb6e3907be44ae82e3c6", "app/src/main/java/dev/halcamera/camera/SampleClock.kt": "64397572af6bc204dce7c802c4077cbc4769a334", "app/src/main/java/dev/halcamera/camera/SnapshotRequest.kt": "86ea336a62641229a89f1e05963a2e42e63db030",