197 lines
6.7 KiB
Kotlin
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
|
|
)
|
|
}
|
|
|
|
}
|