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, val maxValue: Float ) object SilhouetteManager { private val originals = ConcurrentHashMap() private val invertedPurple = ConcurrentHashMap() private val weightedMasks = ConcurrentHashMap() 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 ) } }