AnimalRating/app/src/main/java/com/example/livingai/utils/SilhouetteManager.kt

197 lines
6.7 KiB
Kotlin

package com.example.livingai.utils
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.util.Log
import com.example.livingai.R
import java.util.concurrent.ConcurrentHashMap
import kotlin.math.min
data class SignedMask(
val mask: Array<FloatArray>,
val maxValue: Float
)
object SilhouetteManager {
private val originals = ConcurrentHashMap<String, Bitmap>()
private val invertedPurple = ConcurrentHashMap<String, Bitmap>()
private val weightedMasks = ConcurrentHashMap<String, SignedMask>()
fun getOriginal(name: String): Bitmap? = originals[name]
fun getInvertedPurple(name: String): Bitmap? = invertedPurple[name]
fun getWeightedMask(name: String): SignedMask? = weightedMasks[name]
fun initialize(context: Context, width: Int, height: Int) {
val resources = context.resources
val silhouetteList = mapOf(
"front" to R.drawable.front_silhouette,
"back" to R.drawable.back_silhouette,
"left" to R.drawable.left_silhouette,
"right" to R.drawable.right_silhouette,
"leftangle" to R.drawable.leftangle_silhouette,
"rightangle" to R.drawable.rightangle_silhouette,
"angleview" to R.drawable.angleview_silhouette
)
silhouetteList.forEach { (name, resId) ->
val bmp = BitmapFactory.decodeResource(resources, resId)
originals[name] = bmp
// Fit image appropriately (front/back = W/H, others rotated)
val fitted = if (name == "front" || name == "back")
createInvertedPurpleBitmap(bmp, width, height)
else
createInvertedPurpleBitmap(bmp, height, width)
invertedPurple[name] = fitted
weightedMasks[name] = createSignedWeightedMask(fitted, fadeInside = 10, fadeOutside = 20)
Log.d("Silhouette", "Loaded mask: $name (${fitted.width} x ${fitted.height})")
}
}
// ------------------------------------------------------------------------
// STEP 1: Create "inverted purple" mask (transparent object becomes purple)
// ------------------------------------------------------------------------
private fun createInvertedPurpleBitmap(
src: Bitmap,
targetWidth: Int,
targetHeight: Int
): Bitmap {
val w = src.width
val h = src.height
val pixels = IntArray(w * h)
src.getPixels(pixels, 0, w, 0, 0, w, h)
val purple = Color.argb(255, 128, 0, 128)
for (i in pixels.indices) {
val alpha = pixels[i] ushr 24
pixels[i] = if (alpha == 0) purple else 0x00000000
}
val inverted = Bitmap.createBitmap(pixels, w, h, Bitmap.Config.ARGB_8888)
return Bitmap.createScaledBitmap(inverted, targetWidth, targetHeight, true)
}
/**
* Creates a signed weighted mask in range [-1, +1]
*
* +1 : deep inside object
* 0 : object boundary
* -1 : far outside object
*/
fun createSignedWeightedMask(
bitmap: Bitmap,
fadeInside: Int = 10,
fadeOutside: Int = 20
): SignedMask {
val w = bitmap.width
val h = bitmap.height
val pixels = IntArray(w * h)
bitmap.getPixels(pixels, 0, w, 0, 0, w, h)
fun idx(x: Int, y: Int) = y * w + x
// --------------------------------------------------------------------
// 1. Binary mask
// --------------------------------------------------------------------
val inside = IntArray(w * h)
for (i in pixels.indices) {
inside[i] = if ((pixels[i] ushr 24) > 0) 1 else 0
}
// --------------------------------------------------------------------
// 2. Distance transform (inside → outside)
// --------------------------------------------------------------------
val distInside = IntArray(w * h) { Int.MAX_VALUE }
for (i in inside.indices) if (inside[i] == 0) distInside[i] = 0
for (y in 0 until h) {
for (x in 0 until w) {
val i = idx(x, y)
var best = distInside[i]
if (x > 0) best = min(best, distInside[idx(x - 1, y)] + 1)
if (y > 0) best = min(best, distInside[idx(x, y - 1)] + 1)
distInside[i] = best
}
}
for (y in h - 1 downTo 0) {
for (x in w - 1 downTo 0) {
val i = idx(x, y)
var best = distInside[i]
if (x < w - 1) best = min(best, distInside[idx(x + 1, y)] + 1)
if (y < h - 1) best = min(best, distInside[idx(x, y + 1)] + 1)
distInside[i] = best
}
}
// --------------------------------------------------------------------
// 3. Distance transform (outside → inside)
// --------------------------------------------------------------------
val distOutside = IntArray(w * h) { Int.MAX_VALUE }
for (i in inside.indices) if (inside[i] == 1) distOutside[i] = 0
for (y in 0 until h) {
for (x in 0 until w) {
val i = idx(x, y)
var best = distOutside[i]
if (x > 0) best = min(best, distOutside[idx(x - 1, y)] + 1)
if (y > 0) best = min(best, distOutside[idx(x, y - 1)] + 1)
distOutside[i] = best
}
}
for (y in h - 1 downTo 0) {
for (x in w - 1 downTo 0) {
val i = idx(x, y)
var best = distOutside[i]
if (x < w - 1) best = min(best, distOutside[idx(x + 1, y)] + 1)
if (y < h - 1) best = min(best, distOutside[idx(x, y + 1)] + 1)
distOutside[i] = best
}
}
// --------------------------------------------------------------------
// 4. Build signed mask + track max value
// --------------------------------------------------------------------
val result = Array(h) { FloatArray(w) }
var maxValue = Float.NEGATIVE_INFINITY
for (y in 0 until h) {
for (x in 0 until w) {
val i = idx(x, y)
val weight = if (inside[i] == 1) {
val d = distInside[i]
if (d >= fadeInside) 1f
else d.toFloat() / fadeInside
} else {
val d = distOutside[i]
(-d.toFloat() / fadeOutside).coerceAtLeast(-1f)
}
result[y][x] = weight
if (weight > maxValue) maxValue = weight
}
}
return SignedMask(
mask = result,
maxValue = maxValue
)
}
}