Sensor Alignment: 방향 화살표 → arrow_transparent.png 교체 (rotate + tint)

자산:
  - drawable/arrow_transparent.png 신규 (132×112 RGBA, 우향 thin arrow)

코드:
  - drawLine 3개로 구성하던 화살표 → drawImage(arrowBitmap) 한 번으로 교체
  - 4방향은 rotate(degrees, pivot) 로 처리:
      right=0° / down=90° / left=180° / up=-90°
  - ColorFilter.tint(#E64A19 × arrowAlpha) — 일러스트 톤 매칭 + blink 유지
  - arrowDrawW=90f, 높이는 icon aspect(132:112)로 비례
  - center 위치는 bladder oval edge + sensorHalfH + gap + arrowSize/2

기존 blink 로직(scanCount % 2 → animateFloatAsState 180ms)은 그대로 동작 —
tint color 의 alpha 에 arrowAlpha 를 곱하므로 PNG 도 매 refresh 깜빡임.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-15 14:19:06 +09:00
parent a6b973b284
commit c7d4555535
2 changed files with 57 additions and 32 deletions
@@ -368,6 +368,13 @@ fun PlacementGuideView(appState: AppState) {
com.example.medilightv2android.R.drawable.sensor_device,
).asImageBitmap()
}
// 방향 화살표 PNG (132x112, 우향). 회전 + tint 로 4방향 + 색감 통일.
val arrowBitmap = remember {
android.graphics.BitmapFactory.decodeResource(
context.resources,
com.example.medilightv2android.R.drawable.arrow_transparent,
).asImageBitmap()
}
Canvas(modifier = Modifier.fillMaxSize()) {
val w = size.width
val h = size.height
@@ -416,41 +423,59 @@ fun PlacementGuideView(appState: AppState) {
alpha = 0.95f,
)
// ── 방향 화살표 (센서/타원 바깥) — 매 refresh 마다 alpha 깜빡임 ──
// 색감 #FF5722 → #E64A19 (deep orange 700, 일러스트와 톤 맞춤)
// StrokeCap.Round로 끝부분 둥글게 + 굵기 9→10 (가독성)
// ── 방향 화살표 (PNG icon, rotate + tint) — 매 refresh 마다 alpha 깜빡임 ──
// arrow_transparent.png 는 우향(→) 132×112. 회전으로 4방향 처리.
// tint = deep orange 700 (#E64A19) — 일러스트 톤 맞춤 + alpha 로 blink.
if (!greenZone) {
val arrowColor = Color(0xFFE64A19).copy(alpha = arrowAlpha)
val arrowLen = 70f
val arrowHead = 28f
val sw = 10f
val cap = androidx.compose.ui.graphics.StrokeCap.Round
val arrowDrawW = 90f
val arrowDrawH = arrowDrawW * (arrowBitmap.height.toFloat() / arrowBitmap.width.toFloat())
val sensorHalfH = h * 0.08f
val gap = 20f
when {
directionIcon.contains("up") -> {
val ay = bladderCy - bladderRy - sensorHalfH - 20f
drawLine(arrowColor, Offset(cx, ay + arrowLen), Offset(cx, ay), strokeWidth = sw, cap = cap)
drawLine(arrowColor, Offset(cx - arrowHead, ay + arrowHead), Offset(cx, ay), strokeWidth = sw, cap = cap)
drawLine(arrowColor, Offset(cx + arrowHead, ay + arrowHead), Offset(cx, ay), strokeWidth = sw, cap = cap)
}
directionIcon.contains("down") -> {
val ay = bladderCy + bladderRy + sensorHalfH + 20f
drawLine(arrowColor, Offset(cx, ay - arrowLen), Offset(cx, ay), strokeWidth = sw, cap = cap)
drawLine(arrowColor, Offset(cx - arrowHead, ay - arrowHead), Offset(cx, ay), strokeWidth = sw, cap = cap)
drawLine(arrowColor, Offset(cx + arrowHead, ay - arrowHead), Offset(cx, ay), strokeWidth = sw, cap = cap)
}
directionIcon.contains("left") -> {
val ax = cx - bladderRx - 30f
drawLine(arrowColor, Offset(ax + arrowLen, bladderCy), Offset(ax, bladderCy), strokeWidth = sw, cap = cap)
drawLine(arrowColor, Offset(ax + arrowHead, bladderCy - arrowHead), Offset(ax, bladderCy), strokeWidth = sw, cap = cap)
drawLine(arrowColor, Offset(ax + arrowHead, bladderCy + arrowHead), Offset(ax, bladderCy), strokeWidth = sw, cap = cap)
}
directionIcon.contains("right") -> {
val ax = cx + bladderRx + 30f
drawLine(arrowColor, Offset(ax - arrowLen, bladderCy), Offset(ax, bladderCy), strokeWidth = sw, cap = cap)
drawLine(arrowColor, Offset(ax - arrowHead, bladderCy - arrowHead), Offset(ax, bladderCy), strokeWidth = sw, cap = cap)
drawLine(arrowColor, Offset(ax - arrowHead, bladderCy + arrowHead), Offset(ax, bladderCy), strokeWidth = sw, cap = cap)
// 화살표 center 위치 + 회전 각도 (icon 이 우향이므로 right=0°)
data class ArrowPlacement(val cxa: Float, val cya: Float, val rot: Float)
val placement: ArrowPlacement? = when {
directionIcon.contains("up") -> ArrowPlacement(
cx,
bladderCy - bladderRy - sensorHalfH - gap - arrowDrawH / 2f,
-90f,
)
directionIcon.contains("down") -> ArrowPlacement(
cx,
bladderCy + bladderRy + sensorHalfH + gap + arrowDrawH / 2f,
90f,
)
directionIcon.contains("left") -> ArrowPlacement(
cx - bladderRx - gap - arrowDrawW / 2f,
bladderCy,
180f,
)
directionIcon.contains("right") -> ArrowPlacement(
cx + bladderRx + gap + arrowDrawW / 2f,
bladderCy,
0f,
)
else -> null
}
if (placement != null) {
val tint = androidx.compose.ui.graphics.ColorFilter.tint(
Color(0xFFE64A19).copy(alpha = arrowAlpha)
)
rotate(degrees = placement.rot, pivot = Offset(placement.cxa, placement.cya)) {
drawImage(
image = arrowBitmap,
srcOffset = androidx.compose.ui.unit.IntOffset.Zero,
srcSize = androidx.compose.ui.unit.IntSize(arrowBitmap.width, arrowBitmap.height),
dstOffset = androidx.compose.ui.unit.IntOffset(
(placement.cxa - arrowDrawW / 2f).toInt(),
(placement.cya - arrowDrawH / 2f).toInt(),
),
dstSize = androidx.compose.ui.unit.IntSize(
arrowDrawW.toInt(),
arrowDrawH.toInt(),
),
colorFilter = tint,
)
}
}
}