import android.graphics.Bitmap
import android.graphics.Color
import android.util.Log
import androidx.palette.graphics.Palette
import androidx.palette.graphics.Palette.Swatch
import com.blankj.utilcode.util.GsonUtils
import com.blankj.utilcode.util.ScreenUtils
import com.blankj.utilcode.util.Utils
import com.google.gson.Gson
import com.google.gson.annotations.SerializedName
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import java.util.Collections
import java.util.concurrent.CopyOnWriteArrayList
import kotlin.math.abs
import kotlin.math.sqrt
import androidx.core.graphics.get
object AmbientLightColorPickManager {
private const val TAG = "AmbientLightColorPickManager"
private var scope = MainScope()
private val mWidth = ScreenUtils.getScreenWidth() / 2
private val mHeight = ScreenUtils.getScreenHeight() / 2
private val hsvTableList = mutableListOf<HsvColor>()
private val hueList = CopyOnWriteArrayList<FloatArray>()
private val test1List = CopyOnWriteArrayList<FloatArray>()
private val test2List = CopyOnWriteArrayList<FloatArray>()
private val test3List = CopyOnWriteArrayList<FloatArray>()
var test1Listener: ((Int, Int, Int) -> Unit)? = null
var test2Listener: ((Int, Int, Int) -> Unit)? = null
@JvmStatic
fun init() {
log("$TAG init")
scope.launch(Dispatchers.IO) {
hsvTableList.clear()
initHsvColor()
}
}
private fun initHsvColor() {
if (hsvTableList.isEmpty()) {
runCatching {
val json = SharedPreferencesUtils.getRGB256HsvColor(Utils.getApp())
val listType = object : TypeToken<MutableList<HsvColor>>() {}.type
Gson().fromJson<MutableList<HsvColor>>(json, listType)?.let {
hsvTableList.addAll(it)
log("initHsvColor xml list size=${hsvTableList.size}")
}
}.getOrElse {
Log.e(TAG, "initHsvColor Exception ${it.message}")
}
}
if (hsvTableList.isEmpty()) {
saveHsvColor().let {
if (it.isNotEmpty()) {
hsvTableList.addAll(it)
}
}
log("initHsvColor json list size=${hsvTableList.size}")
}
}
/** 将本地rgb色值转换成hsv保存到本地 */
private fun saveHsvColor(): MutableList<HsvColor> {
log("saveHsvColor")
val hsvList = mutableListOf<HsvColor>()
runCatching {
val assetManager = Utils.getApp().assets
val file = assetManager.open("color_rgb_256.json")
val jsonStr = file.bufferedReader().readText()
file.close()
val bean = Gson().fromJson(jsonStr, AmbientLightList::class.java)
val hsvColors = FloatArray(3)
for (i in 0 until bean.list.size) {
bean.list.apply {
val myColor = Color.rgb(r, g, b)
Color.colorToHSV(myColor, hsvColors)
hsvList.add(HsvColor(hsvColors[0], hsvColors[1], hsvColors[2]))
}
}
val json = Gson().toJson(hsvList)
log("saveHsvColor hsvListSize=${hsvList.size}")
SharedPreferencesUtils.setRGB256HsvColor(Utils.getApp(), json)
}.getOrElse {
Log.e(TAG, "saveHsvColor Exception ${it.message}")
}
return hsvList
}
/** 设置氛围灯 */
@JvmStatic
fun setAmbientLight(displayId: Int, index: Int) {
if (displayId != DisplayParameter.DISPLAY_CSD.displayId) return
log("setAmbientLight displayId=$displayId")
scope.launch(Dispatchers.IO) {
if (hueList.isEmpty()) {
Log.w(TAG, "setAmbientLight hueList is null")
return@launch
}
if (index < 0 || index >= hueList.size) {
Log.w(TAG, "setAmbientLight 索引异常")
return@launch
}
// 氛围灯取色
setBytesFunctionValue(index)
}
}
@JvmStatic
fun switchLight(isOn: Boolean) {
log("switchLight isOn=$isOn")
}
private fun findColor(bgHue: Float): ColorTipBean {
if (hsvTableList.isEmpty()) {
Log.w(TAG, "findColor hsvList is null")
return ColorTipBean(Color.WHITE)
}
var result = hsvTableList[0]
var minDiff = abs(result.hue - bgHue)
for (i in 0 until hsvTableList.size) {
val currentDiff = abs(hsvTableList.hue - bgHue)
if (currentDiff < minDiff) {
minDiff = currentDiff
result = hsvTableList
}
}
log("findColor bgHue=$bgHue,result=$result")
return ColorTipBean(
Color.HSVToColor(floatArrayOf(result.hue, result.saturation, result.value))
)
}
/** 初始化资源 */
@JvmStatic
fun loadData(displayId: Int, pictures: List<String>) {
if (displayId != DisplayParameter.DISPLAY_CSD.displayId) return
log("loadData pictures size=${pictures.size} pictures $pictures")
hueList.clear()
test1List.clear()
test2List.clear()
test3List.clear()
for ((index, picture) in pictures.withIndex()) {
runCatching {
val bitmap = GlideCacheUtils.loadImageAsBitmap(picture, mWidth, mHeight)
testGenerate(bitmap)
val result = generate(bitmap)
hueList.add(result)
log("loadData add index=$index,colors=${GsonUtils.toJson(result)}")
}.getOrElse {
Log.e(TAG, "loadData exception ${it.message}")
}
}
log("loadData hueList size=${hueList.size}")
}
private fun setFunctionValue(functionId: Int, value: Int, zone: Int) {
try {
AdapterCarManager.iCarFunction.setFunctionValue(functionId, zone, value)
} catch (e: Exception) {
Log.e(TAG, "setFunctionValue Exception $e")
}
}
private fun setBytesFunctionValue(index: Int) {
try {
test1Listener?.invoke(
Color.HSVToColor(test1List[index]),
Color.HSVToColor(test2List[index]),
Color.HSVToColor(test3List[index]),
)
test2Listener?.invoke(
findColor(test1List[index][0]).colorTip,
findColor(test2List[index][0]).colorTip,
findColor(test3List[index][0]).colorTip,
)
} catch (e: Exception) {
Log.e(TAG, "setBytesFunctionValue Exception $e")
}
}
private fun getColors(list: FloatArray): ByteArray {
val result = mutableListOf<ColorTipBean>()
list.forEach {
result.add(findColor(it))
}
val json = GsonUtils.toJson(LightColorBean(result).list)
log("setBytesFunctionValue json=$json")
return json.toByteArray()
}
private fun generate(newMap: Bitmap): FloatArray {
val result = FloatArray(3)
Log.w(TAG, "------generate start")
val dominantColor = getPerceptuallyDominantColor(newMap)
val hsvColorArray = FloatArray(3)
val hsv = colorToHSV(dominantColor, hsvColorArray)
result.fill(hsv)
Log.d(TAG, "dominantColor $dominantColor, hsv ${GsonUtils.toJson(hsvColorArray)}")
return result
}
private fun testGenerate(newMap: Bitmap) {
// 评分公式
val dominantColor1 = getPerceptuallyDominantColor(newMap)
val hsvColorArray1 = FloatArray(3)
colorToHSV(dominantColor1, hsvColorArray1)
test1List.add(hsvColorArray1)
// 主色
Palette.from(newMap).maximumColorCount(24).clearFilters().generate().apply {
val hsvColorArray2 = FloatArray(3)
val dominantColor2 = getDominantColor(Color.WHITE)
colorToHSV(dominantColor2, hsvColorArray2)
test2List.add(hsvColorArray2)
}
// 评分优化公式
val dominantColor3 = getPerceptuallyDominantColor1(newMap)
val hsvColorArray3 = FloatArray(3)
colorToHSV(dominantColor3, hsvColorArray3)
test3List.add(hsvColorArray3)
}
fun getPerceptuallyDominantColor(bitmap: Bitmap): Int {
val palette = Palette.from(bitmap).maximumColorCount(24).clearFilters().generate()
val swatches = palette.swatches
if (swatches.isEmpty()) return Color.WHITE
var bestSwatch: Swatch? = null
var maxScore = 0f
for (swatch in swatches) {
val hsl = swatch.getHsl()
val saturation = hsl[1] // 饱和度 (0-1)
val luminance = hsl[2] // 亮度 (0-1)
val population = swatch.population
// 评分公式:人口占比 * 饱和度 * 亮度因子
// 亮度因子确保避免过暗或过亮的颜色(0.1-0.9为理想范围)
val luminanceFactor = 1f - abs(luminance - 0.5f) * 1.8f
val score = population * saturation * luminanceFactor
if (score > maxScore) {
maxScore = score
bestSwatch = swatch
}
}
return bestSwatch?.rgb ?: palette.getDominantColor(Color.WHITE)
}
private fun isClear(bitmap: Bitmap): Boolean {
val totalPixels = bitmap.width * bitmap.height
var blackCount = 0.0
var whiteCount = 0.0
for (x in 0 until bitmap.width) {
for (y in 0 until bitmap.height) {
val pixel = bitmap[x, y]
if (pixel == Color.BLACK) {
blackCount++
}
if (pixel == Color.WHITE) {
whiteCount++
}
}
}
val blackRatio = blackCount / totalPixels
val whiteRatio = whiteCount / totalPixels
val isClear = blackRatio > 0.3 || whiteRatio > 0.3
Log.d(TAG, "isClear=$isClear totalPixels=$totalPixels,blackCount=$blackCount, blackRatio=${String.format("%.2f", blackRatio)},whiteRatio=${String.format("%.2f", whiteRatio)}")
return isClear
}
private fun calculateSwatchScore(
hue: Float,
saturation: Float,
luminance: Float,
population: Float
): Float {
// 1. 人口权重 (标准化)
val populationWeight = population / 1000000f
// 2. 饱和度权重 - 适度重视但不过度
val saturationWeight = sqrt(saturation) // 使用平方根降低过高饱和度的优势
// 3. 亮度权重 - 偏好中等亮度范围
val luminanceWeight = when {
luminance < 0.15f -> 0.2f // 太暗的惩罚
luminance > 0.85f -> 0.3f // 太亮的惩罚
else -> 1.0f - abs(luminance - 0.5f) * 1.5f
}
// 4. 色相权重 - 可选:降低过于鲜艳的红色/蓝色的优势
val hueWeight = when {
// 红色范围 (330-30度)
(hue >= 330f || hue <= 30f) -> 0.8f
// 蓝色范围 (210-270度)
hue in 210f..270f -> 0.9f
else -> 1.0f
}
return populationWeight * saturationWeight * luminanceWeight * hueWeight
}
fun getPerceptuallyDominantColor1(bitmap: Bitmap): Int {
val palette = Palette.from(bitmap)
.maximumColorCount(24)
.clearFilters()
.generate()
val swatches = palette.swatches
if (swatches.isEmpty()) return Color.WHITE
var bestSwatch: Swatch? = null
var maxScore = 0f
for (swatch in swatches) {
val hsl = swatch.hsl
val hue = hsl[0] // 色相 (0-360)
val saturation = hsl[1] // 饱和度 (0-1)
val luminance = hsl[2] // 亮度 (0-1)
val population = swatch.population.toFloat()
// 改进的评分公式
val score = calculateSwatchScore(hue, saturation, luminance, population)
if (score > maxScore) {
maxScore = score
bestSwatch = swatch
}
}
return bestSwatch?.rgb ?: palette.getDominantColor(Color.WHITE)
}
private fun colorToHSV(rgb: Int, hsvColorArray: FloatArray): Float {
Color.colorToHSV(rgb, hsvColorArray)
return hsvColorArray[0]
}
private fun log(str: String) = Log.d(TAG, str)
data class LightColorBean(
val list: List<ColorTipBean>
)
data class ColorTipBean(
@SerializedName("ColorTip")
var colorTip: Int,
)
}