冲刺流星官方中文版
12.3G · 2025-10-23
hue查找:整体偏差不会很大,但是对于亮度较高存在误差,精准度不够
lab查找:整体一般,但是精准度较好,不过算法复杂,增加耗时
hue色相查找存在误差,在有限的256色中,匹配的规则需要调整
这里使用lab算法提高精准度
fun rGBToLab(r: Int, g: Int, b: Int): DoubleArray { // 处理负值(如-2563864) val labR = if (r < 0) 0f else r / 255f val labG = if (g < 0) 0f else g / 255f val labB = if (b < 0) 0f else b / 255f // 线性化处理(sRGB转换) val rLin = if (labR <= 0.04045) labR / 12.92 else ((labR + 0.055) / 1.055).pow(2.4) val gLin = if (labG <= 0.04045) labG / 12.92 else ((labG + 0.055) / 1.055).pow(2.4) val bLin = if (labB <= 0.04045) labB / 12.92 else ((labB + 0.055) / 1.055).pow(2.4) // XYZ转换(D65标准光源) val x = 0.4124564 * rLin + 0.3575761 * gLin + 0.1804375 * bLin val y = 0.2126729 * rLin + 0.7151522 * gLin + 0.0721750 * bLin val z = 0.0193339 * rLin + 0.1191920 * gLin + 0.9503041 * bLin // Lab转换 val xn = 0.95047f val yn = 1.0f val zn = 1.08883f val fX = x / xn val fY = y / yn val fZ = z / zn val l = 116f * fY.pow(1 / 3.0) - 16 val a = 500f * (fX.pow(1 / 3.0) - fY.pow(1 / 3.0)) val b = 200f * (fY.pow(1 / 3.0) - fZ.pow(1 / 3.0)) return doubleArrayOf(l, a, b) }
对rgb进行转换,这里是简化版,如果需要精确要求极高,需要完善
处理流程跟方案一一样,使用集合缓存
接着使用 ΔE76色差公式 查找色值(另外还有ΔEab欧式距离算法,CIEDE2000色差公式,CIE76)
fun deltaE76(lab1: DoubleArray, lab2: DoubleArray): Double { val (l1, a1, b1) = lab1 val (l2, a2, b2) = lab2 // 计算中间变量 val c1 = sqrt(a1.pow(2) + b1.pow(2)) val c2 = sqrt(a2.pow(2) + b2.pow(2)) val cAvg = (c1 + c2) / 2 val g = 0.5 * (1 - sqrt(cAvg.pow(7) / (cAvg.pow(7) + 25f.pow(7)))) val a1p = a1 * (1 + g) val a2p = a2 * (1 + g) val c1p = sqrt(a1p.pow(2) + b1.pow(2)) val c2p = sqrt(a2p.pow(2) + b2.pow(2)) val h1p = Math.toDegrees(kotlin.math.atan2(b1, a1p)).let { if (it < 0) it + 360 else it } val h2p = Math.toDegrees(kotlin.math.atan2(b2, a2p)).let { if (it < 0) it + 360 else it } // 色差计算 val deltaLp = l2 - l1 val deltaCp = c2p - c1p val deltaHp = when { c1p * c2p == 0.0 -> 0.0 abs(h2p - h1p) <= 180 -> h2p - h1p h2p <= h1p -> h2p - h1p + 360 else -> h2p - h1p - 360 } val deltaHpS = 2 * sqrt(c1p * c2p) * sin(Math.toRadians(deltaHp) / 2) // 加权计算 val lAvg = (l1 + l2) / 2 val cpAvg = (c1p + c2p) / 2 val hpAvg = when { c1p * c2p == 0.0 -> h1p + h2p abs(h1p - h2p) > 180 -> (h1p + h2p + 360) / 2 else -> (h1p + h2p) / 2 }.let { if (it >= 360) it - 360 else it } val t = 1 - 0.17 * cos(Math.toRadians(hpAvg - 30)) + 0.24 * cos(Math.toRadians(2 * hpAvg)) + 0.32 * cos(Math.toRadians(3 * hpAvg + 6)) - 0.20 * cos(Math.toRadians(4 * hpAvg - 63)) val sl = 1 + (0.015 * (lAvg - 50).pow(2)) / sqrt(20 + (lAvg - 50).pow(2)) val sc = 1 + 0.045 * cpAvg val sh = 1 + 0.015 * cpAvg * t val deltaTheta = 30 * exp(-((hpAvg - 275) / 25).pow(2)) val rc = 2 * sqrt(cpAvg.pow(7) / (cpAvg.pow(7) + 25f.pow(7))) val rt = -rc * sin(2 * Math.toRadians(deltaTheta)) return sqrt( (deltaLp / sl).pow(2) + (deltaCp / sc).pow(2) + (deltaHpS / sh).pow(2) + rt * (deltaCp / sc) * (deltaHpS / sh) ) }
var minIndex = 0 var minDistance = Double.MAX_VALUE var minLab = doubleArrayOf() tableLabPalette.forEachIndexed { index, lab -> val distance = ColorEabLabUtils.deltaE76(lab, list) if (distance < minDistance) { minDistance = distance minLab = lab minIndex = index } }
从上面图中结果可以明显看到,大部分图片中,hue原方案精准度对比lab查找,是有差距的
但是缺点也明显,在某些图片上表现不好,可以看到原方案hue更精准
经过大量调整算法,优化算法,测试下来发现,如果在不通过底层显示屏直接转换色值情况下,都存在误差
而刚好这两种查找结果有一定互补作用,那是否可以取各自的优势,结合结果,达到一个平衡
private fun findLabColor(list: DoubleArray, hue: Float): Int { // 寻找最近颜色(简化版) var minIndex = 0 var minDistance = Double.MAX_VALUE var minLab = doubleArrayOf() tableLabPalette.forEachIndexed { index, lab -> val distance = ColorEabLabUtils.deltaE76(lab, list) if (distance < minDistance) { minDistance = distance minLab = lab minIndex = index } } return if (minDistance < 26) { tableColorList[minIndex] } else { findColor(hue).colorTip } }
在lab精度不够时,可能结果偏差较大,但是hue偏差不会很大,hue只是精度不够,所以这里兼容两种方案
在lab误差较大时,直接采用hue方案计算结果,这样直接弥补了自身的缺点,比如图一中误差小,采用lab方案,提高了精准度,而在图二精度不够时,采用hue,避免误差偏差较大
最终方案是融合方案,不过精度任然存在误差,但本身转换过程结果只有256色,所以不可能达到完美,相比原图取色结果精度是较高
import android.graphics.Bitmapimport android.graphics.Colorimport androidx.palette.graphics.Paletteimport androidx.palette.graphics.Palette.Swatchimport java.lang.Math.toDegreesimport java.lang.Math.toRadiansimport kotlin.math.absimport kotlin.math.atan2import kotlin.math.cosimport kotlin.math.expimport kotlin.math.powimport kotlin.math.sinimport kotlin.math.sqrtobject ColorEabLabUtils { 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) } // RGB转Lab ********* fun rGBToLab(r: Int, g: Int, b: Int): DoubleArray { // 处理负值(如-2563864) val labR = if (r < 0) 0f else r / 255f val labG = if (g < 0) 0f else g / 255f val labB = if (b < 0) 0f else b / 255f // 线性化处理(sRGB转换) val rLin = if (labR <= 0.04045) labR / 12.92 else ((labR + 0.055) / 1.055).pow(2.4) val gLin = if (labG <= 0.04045) labG / 12.92 else ((labG + 0.055) / 1.055).pow(2.4) val bLin = if (labB <= 0.04045) labB / 12.92 else ((labB + 0.055) / 1.055).pow(2.4) // XYZ转换(D65标准光源) val x = 0.4124564 * rLin + 0.3575761 * gLin + 0.1804375 * bLin val y = 0.2126729 * rLin + 0.7151522 * gLin + 0.0721750 * bLin val z = 0.0193339 * rLin + 0.1191920 * gLin + 0.9503041 * bLin // Lab转换 val xn = 0.95047f val yn = 1.0f val zn = 1.08883f val fX = x / xn val fY = y / yn val fZ = z / zn val l = 116f * fY.pow(1 / 3.0) - 16 val a = 500f * (fX.pow(1 / 3.0) - fY.pow(1 / 3.0)) val b = 200f * (fY.pow(1 / 3.0) - fZ.pow(1 / 3.0)) return doubleArrayOf(l, a, b) } // ΔEab算法 fun deltaEab(lab1: DoubleArray, lab2: DoubleArray): Double { val dL = lab1[0] - lab2[0] val da = lab1[1] - lab2[1] val db = lab1[2] - lab2[2] return sqrt(dL.pow(2) + da.pow(2) + db.pow(2)) } // 改进的RGB转Lab(包含伽马校正和D65白点) fun rgbToLab(r: Int, g: Int, b: Int): DoubleArray { // 处理负值(如-2563864) val labR = if (r < 0) 0f else r / 255f val labG = if (g < 0) 0f else g / 255f val labB = if (b < 0) 0f else b / 255f // sRGB伽马校正 val rLin = if (labR <= 0.04045) labR / 12.92 else ((labR + 0.055) / 1.055).pow(2.4) val gLin = if (labG <= 0.04045) labG / 12.92 else ((labG + 0.055) / 1.055).pow(2.4) val bLin = if (labB <= 0.04045) labB / 12.92 else ((labB + 0.055) / 1.055).pow(2.4) // D65标准光源XYZ转换 val x = 0.4124564 * rLin + 0.3575761 * gLin + 0.1804375 * bLin val y = 0.2126729 * rLin + 0.7151522 * gLin + 0.0721750 * bLin val z = 0.0193339 * rLin + 0.1191920 * gLin + 0.9503041 * bLin // Lab转换(包含阈值处理) val xn = 0.95047 val yn = 1.0 val zn = 1.08883 val fX = x / xn val fY = y / yn val fZ = z / zn val l = if (fY > 0.008856) 116 * fY.pow(1 / 3.0) - 16 else 903.3 * fY val a = 500 * (fX.pow(1 / 3.0) - fY.pow(1 / 3.0)) val b = 200 * (fY.pow(1 / 3.0) - fZ.pow(1 / 3.0)) return doubleArrayOf(l, a, b) } // 改进的ΔE76色差公式(包含亮度权重) ********* fun deltaE76(lab1: DoubleArray, lab2: DoubleArray): Double { val (l1, a1, b1) = lab1 val (l2, a2, b2) = lab2 // 计算中间变量 val c1 = sqrt(a1.pow(2) + b1.pow(2)) val c2 = sqrt(a2.pow(2) + b2.pow(2)) val cAvg = (c1 + c2) / 2 val g = 0.5 * (1 - sqrt(cAvg.pow(7) / (cAvg.pow(7) + 25f.pow(7)))) val a1p = a1 * (1 + g) val a2p = a2 * (1 + g) val c1p = sqrt(a1p.pow(2) + b1.pow(2)) val c2p = sqrt(a2p.pow(2) + b2.pow(2)) val h1p = Math.toDegrees(kotlin.math.atan2(b1, a1p)).let { if (it < 0) it + 360 else it } val h2p = Math.toDegrees(kotlin.math.atan2(b2, a2p)).let { if (it < 0) it + 360 else it } // 色差计算 val deltaLp = l2 - l1 val deltaCp = c2p - c1p val deltaHp = when { c1p * c2p == 0.0 -> 0.0 abs(h2p - h1p) <= 180 -> h2p - h1p h2p <= h1p -> h2p - h1p + 360 else -> h2p - h1p - 360 } val deltaHpS = 2 * sqrt(c1p * c2p) * sin(Math.toRadians(deltaHp) / 2) // 加权计算 val lAvg = (l1 + l2) / 2 val cpAvg = (c1p + c2p) / 2 val hpAvg = when { c1p * c2p == 0.0 -> h1p + h2p abs(h1p - h2p) > 180 -> (h1p + h2p + 360) / 2 else -> (h1p + h2p) / 2 }.let { if (it >= 360) it - 360 else it } val t = 1 - 0.17 * cos(Math.toRadians(hpAvg - 30)) + 0.24 * cos(Math.toRadians(2 * hpAvg)) + 0.32 * cos(Math.toRadians(3 * hpAvg + 6)) - 0.20 * cos(Math.toRadians(4 * hpAvg - 63)) val sl = 1 + (0.015 * (lAvg - 50).pow(2)) / sqrt(20 + (lAvg - 50).pow(2)) val sc = 1 + 0.045 * cpAvg val sh = 1 + 0.015 * cpAvg * t val deltaTheta = 30 * exp(-((hpAvg - 275) / 25).pow(2)) val rc = 2 * sqrt(cpAvg.pow(7) / (cpAvg.pow(7) + 25f.pow(7))) val rt = -rc * sin(2 * Math.toRadians(deltaTheta)) return sqrt( (deltaLp / sl).pow(2) + (deltaCp / sc).pow(2) + (deltaHpS / sh).pow(2) + rt * (deltaCp / sc) * (deltaHpS / sh) ) } /** * CIEDE2000色差公式实现 * 更符合人眼感知的颜色差异计算 * @param lab1 第一个颜色的Lab值 * @param lab2 第二个颜色的Lab值 * @return Double 色差值(ΔE越小颜色越接近) */ fun deltaE2000(lab1: DoubleArray, lab2: DoubleArray): Double { val (l1, a1, b1) = lab1 val (l2, a2, b2) = lab2 // 步骤1:计算色度 val c1 = sqrt(a1.pow(2) + b1.pow(2)) val c2 = sqrt(a2.pow(2) + b2.pow(2)) val cAvg = (c1 + c2) / 2.0 // 步骤2:计算补偿因子 val g = 0.5 * (1.0 - sqrt(cAvg.pow(7) / (cAvg.pow(7) + 25.0.pow(7)))) val a1Prime = a1 * (1.0 + g) val a2Prime = a2 * (1.0 + g) val c1Prime = sqrt(a1Prime.pow(2) + b1.pow(2)) val c2Prime = sqrt(a2Prime.pow(2) + b2.pow(2)) val cBarPrime = (c1Prime + c2Prime) / 2.0 // 步骤3:计算色调角 val h1Prime = toDegrees(atan2(b1, a1Prime)).let { if (it < 0) it + 360.0 else it } val h2Prime = toDegrees(atan2(b2, a2Prime)).let { if (it < 0) it + 360.0 else it } // 步骤4:计算基本差异 val deltaLPrime = l2 - l1 val deltaCPrime = c2Prime - c1Prime // 步骤5:计算色调差 var deltaHPrime = 0.0 if (c1Prime * c2Prime != 0.0) { val deltaHPrimeRaw = when { abs(h2Prime - h1Prime) <= 180.0 -> h2Prime - h1Prime h2Prime <= h1Prime -> h2Prime - h1Prime + 360.0 else -> h2Prime - h1Prime - 360.0 } deltaHPrime = 2.0 * sqrt(c1Prime * c2Prime) * sin(toRadians(deltaHPrimeRaw) / 2.0) } // 步骤6:计算加权平均值 val lBar = (l1 + l2) / 2.0 val cBar = (c1 + c2) / 2.0 // 步骤7:计算色调平均值 val hBarPrime = when { abs(h1Prime - h2Prime) > 180.0 -> (h1Prime + h2Prime + 360.0) / 2.0 else -> (h1Prime + h2Prime) / 2.0 } // 步骤8:计算补偿项 val t = 1.0 - 0.17 * cos(toRadians(hBarPrime - 30.0)) + 0.24 * cos(toRadians(2.0 * hBarPrime)) + 0.32 * cos(toRadians(3.0 * hBarPrime + 6.0)) - 0.20 * cos(toRadians(4.0 * hBarPrime - 63.0)) // 步骤9:计算旋转项 val deltaTheta = 30.0 * exp(-((hBarPrime - 275.0) / 25.0).pow(2)) val rc = 2.0 * sqrt(cBarPrime.pow(7) / (cBarPrime.pow(7) + 25.0.pow(7))) val rt = -rc * sin(2.0 * toRadians(deltaTheta)) // 步骤10:计算权重因子 val sl = 1.0 + (0.015 * (lBar - 50.0).pow(2) / sqrt(20.0 + (lBar - 50.0).pow(2))) val sc = 1.0 + 0.045 * cBarPrime val sh = 1.0 + 0.015 * cBarPrime * t // 步骤11:最终色差计算 val term1 = (deltaLPrime / sl).pow(2) val term2 = (deltaCPrime / sc).pow(2) val term3 = (deltaHPrime / sh).pow(2) val term4 = rt * (deltaCPrime / sc) * (deltaHPrime / sh) return sqrt(term1 + term2 + term3 + term4) } // 优化的伽马校正函数 fun preciseGammaExpand(c: Double) = if (c <= 0.04045) { c / 12.92 } else { ((c + 0.055) / 1.055).pow(2.4) } // 优化的Lab转换函数 fun preciseF(t: Double) = if (t > 0.008856) { t.pow(1.0 / 3.0) } else { (7.787 * t) + (16.0 / 116.0) } /** * 优化的RGB转Lab转换方法 * 使用更精确的浮点数计算和标准化处理 */ fun rgbToLabOptimized(r: Int, g: Int, b: Int): DoubleArray { // 使用更精确的归一化 val rNormalized = r / 255.0 val gNormalized = g / 255.0 val bNormalized = b / 255.0 // 应用伽马校正 val rLinear = preciseGammaExpand(rNormalized) val gLinear = preciseGammaExpand(gNormalized) val bLinear = preciseGammaExpand(bNormalized) // 使用D65标准光源的精确XYZ转换矩阵 val x = 0.4124564 * rLinear + 0.3575761 * gLinear + 0.1804375 * bLinear val y = 0.2126729 * rLinear + 0.7151522 * gLinear + 0.0721750 * bLinear val z = 0.0193339 * rLinear + 0.1191920 * gLinear + 0.9503041 * bLinear // 使用CIE标准参考白点 val refX = 0.95047 val refY = 1.00000 val refZ = 1.08883 // 计算相对值 val xRelative = x / refX val yRelative = y / refY val zRelative = z / refZ val fx = preciseF(xRelative) val fy = preciseF(yRelative) val fz = preciseF(zRelative) // 计算Lab值 val l = (116.0 * fy) - 16.0 val a = 500.0 * (fx - fy) val bLab = 200.0 * (fy - fz) return doubleArrayOf(l, a, bLab) } /** * 完整的CIEDE2000色差公式实现 * 包含所有补偿项和旋转项 */ fun deltaE2000Complete(lab1: DoubleArray, lab2: DoubleArray): Double { val (l1, a1, b1) = lab1 val (l2, a2, b2) = lab2 // 步骤1:计算色度值 val c1 = sqrt(a1.pow(2) + b1.pow(2)) val c2 = sqrt(a2.pow(2) + b2.pow(2)) // 步骤2:计算平均值 val lBar = (l1 + l2) / 2.0 val cBar = (c1 + c2) / 2.0 // 步骤3:计算补偿因子 val g = 0.5 * (1.0 - sqrt(cBar.pow(7) / (cBar.pow(7) + 25.0.pow(7)))) // 步骤4:计算调整后的a'值 val a1Prime = a1 * (1.0 + g) val a2Prime = a2 * (1.0 + g) // 步骤5:计算调整后的色度值 val c1Prime = sqrt(a1Prime.pow(2) + b1.pow(2)) val c2Prime = sqrt(a2Prime.pow(2) + b2.pow(2)) val cBarPrime = (c1Prime + c2Prime) / 2.0 // 步骤6:计算色调角 val h1Prime = toDegrees(atan2(b1, a1Prime)).let { if (it < 0) it + 360.0 else it } val h2Prime = toDegrees(atan2(b2, a2Prime)).let { if (it < 0) it + 360.0 else it } // 步骤7:计算基本差异 val deltaLPrime = l2 - l1 val deltaCPrime = c2Prime - c1Prime // 步骤8:计算色调差 var deltaHPrime = 0.0 if (c1Prime != 0.0 && c2Prime != 0.0) { val deltaHPrimeRaw = when { abs(h2Prime - h1Prime) <= 180.0 -> h2Prime - h1Prime h2Prime <= h1Prime -> h2Prime - h1Prime + 360.0 else -> h2Prime - h1Prime - 360.0 } deltaHPrime = 2.0 * sqrt(c1Prime * c2Prime) * sin(toRadians(deltaHPrimeRaw) / 2.0) } // 步骤9:计算色调平均值 val hBarPrime = when { abs(h1Prime - h2Prime) > 180.0 -> (h1Prime + h2Prime + 360.0) / 2.0 else -> (h1Prime + h2Prime) / 2.0 } // 步骤10:计算补偿项 val t = 1.0 - 0.17 * cos(toRadians(hBarPrime - 30.0)) + 0.24 * cos(toRadians(2.0 * hBarPrime)) + 0.32 * cos(toRadians(3.0 * hBarPrime + 6.0)) - 0.20 * cos(toRadians(4.0 * hBarPrime - 63.0)) // 步骤11:计算旋转项 val deltaTheta = 30.0 * exp(-((hBarPrime - 275.0) / 25.0).pow(2)) val rc = 2.0 * sqrt(cBarPrime.pow(7) / (cBarPrime.pow(7) + 25.0.pow(7))) val rt = -rc * sin(2.0 * toRadians(deltaTheta)) // 步骤12:计算权重因子 val sl = 1.0 + (0.015 * (lBar - 50.0).pow(2) / sqrt(20.0 + (lBar - 50.0).pow(2))) val sc = 1.0 + 0.045 * cBarPrime val sh = 1.0 + 0.015 * cBarPrime * t // 步骤13:最终色差计算 val term1 = (deltaLPrime / sl).pow(2) val term2 = (deltaCPrime / sc).pow(2) val term3 = (deltaHPrime / sh).pow(2) val term4 = rt * (deltaCPrime / sc) * (deltaHPrime / sh) return sqrt(term1 + term2 + term3 + term4) }}
{ "list": [ { "r": 0, "g": 84, "b": 255 }, { "r": 18, "g": 92, "b": 255 }, { "r": 65, "g": 82, "b": 255 }, { "r": 90, "g": 81, "b": 255 }, { "r": 110, "g": 80, "b": 255 }, { "r": 127, "g": 78, "b": 255 }, { "r": 142, "g": 77, "b": 255 }, { "r": 157, "g": 76, "b": 255 }, { "r": 170, "g": 74, "b": 255 }, { "r": 197, "g": 70, "b": 255 }, { "r": 209, "g": 68, "b": 255 }, { "r": 237, "g": 62, "b": 255 }, { "r": 255, "g": 51, "b": 244 }, { "r": 255, "g": 35, "b": 219 }, { "r": 255, "g": 11, "b": 196 }, { "r": 255, "g": 0, "b": 176 }, { "r": 255, "g": 48, "b": 158 }, { "r": 255, "g": 19, "b": 142 }, { "r": 255, "g": 15, "b": 122 }, { "r": 255, "g": 0, "b": 99 }, { "r": 255, "g": 6, "b": 75 }, { "r": 255, "g": 9, "b": 55 }, { "r": 255, "g": 6, "b": 0 }, { "r": 255, "g": 28, "b": 0 }, { "r": 255, "g": 62, "b": 0 }, { "r": 255, "g": 85, "b": 0 }, { "r": 255, "g": 105, "b": 0 }, { "r": 255, "g": 125, "b": 0 }, { "r": 255, "g": 155, "b": 5 }, { "r": 255, "g": 171, "b": 41 }, { "r": 255, "g": 180, "b": 15 }, { "r": 255, "g": 192, "b": 19 }, { "r": 255, "g": 206, "b": 45 }, { "r": 255, "g": 218, "b": 27 }, { "r": 255, "g": 231, "b": 30 }, { "r": 255, "g": 244, "b": 34 }, { "r": 251, "g": 255, "b": 37 }, { "r": 235, "g": 255, "b": 38 }, { "r": 219, "g": 255, "b": 39 }, { "r": 203, "g": 255, "b": 40 }, { "r": 185, "g": 255, "b": 41 }, { "r": 165, "g": 255, "b": 42 }, { "r": 142, "g": 255, "b": 42 }, { "r": 109, "g": 255, "b": 29 }, { "r": 69, "g": 255, "b": 44 }, { "r": 24, "g": 255, "b": 46 }, { "r": 22, "g": 255, "b": 32 }, { "r": 0, "g": 255, "b": 102 }, { "r": 0, "g": 255, "b": 136 }, { "r": 6, "g": 209, "b": 128 }, { "r": 0, "g": 255, "b": 189 }, { "r": 0, "g": 255, "b": 212 }, { "r": 0, "g": 255, "b": 234 }, { "r": 0, "g": 254, "b": 255 }, { "r": 0, "g": 234, "b": 255 }, { "r": 0, "g": 200, "b": 255 }, { "r": 0, "g": 186, "b": 255 }, { "r": 0, "g": 173, "b": 255 }, { "r": 72, "g": 164, "b": 237 }, { "r": 2, "g": 146, "b": 255 }, { "r": 0, "g": 138, "b": 255 }, { "r": 0, "g": 127, "b": 255 }, { "r": 0, "g": 106, "b": 255 }, { "r": 255, "g": 255, "b": 255 }, { "r": 78, "g": 85, "b": 255 }, { "r": 100, "g": 150, "b": 255 }, { "r": 98, "g": 204, "b": 255 }, { "r": 103, "g": 172, "b": 156 }, { "r": 84, "g": 255, "b": 167 }, { "r": 58, "g": 255, "b": 112 }, { "r": 52, "g": 255, "b": 88 }, { "r": 149, "g": 95, "b": 255 }, { "r": 162, "g": 152, "b": 255 }, { "r": 171, "g": 228, "b": 255 }, { "r": 159, "g": 255, "b": 201 }, { "r": 130, "g": 255, "b": 142 }, { "r": 108, "g": 255, "b": 99 }, { "r": 97, "g": 255, "b": 74 }, { "r": 79, "g": 255, "b": 41 }, { "r": 236, "g": 101, "b": 254 }, { "r": 254, "g": 169, "b": 255 }, { "r": 225, "g": 255, "b": 170 }, { "r": 184, "g": 255, "b": 155 }, { "r": 155, "g": 255, "b": 75 }, { "r": 133, "g": 255, "b": 45 }, { "r": 255, "g": 86, "b": 196 }, { "r": 255, "g": 130, "b": 171 }, { "r": 255, "g": 175, "b": 146 }, { "r": 255, "g": 221, "b": 119 }, { "r": 235, "g": 255, "b": 95 }, { "r": 203, "g": 255, "b": 51 }, { "r": 255, "g": 68, "b": 138 }, { "r": 255, "g": 104, "b": 117 }, { "r": 255, "g": 140, "b": 95 }, { "r": 255, "g": 187, "b": 85 }, { "r": 255, "g": 217, "b": 49 }, { "r": 255, "g": 25, "b": 115 }, { "r": 255, "g": 40, "b": 96 }, { "r": 255, "g": 85, "b": 78 }, { "r": 255, "g": 116, "b": 59 }, { "r": 255, "g": 148, "b": 40 }, { "r": 255, "g": 45, "b": 62 }, { "r": 255, "g": 81, "b": 54 }, { "r": 255, "g": 98, "b": 33 }, { "r": 255, "g": 22, "b": 36 }, { "r": 255, "g": 49, "b": 35 }, { "r": 255, "g": 32, "b": 23 }, { "r": 35, "g": 55, "b": 255 }, { "r": 35, "g": 73, "b": 255 }, { "r": 35, "g": 94, "b": 255 }, { "r": 35, "g": 116, "b": 255 }, { "r": 65, "g": 145, "b": 255 }, { "r": 35, "g": 182, "b": 255 }, { "r": 35, "g": 214, "b": 255 }, { "r": 2, "g": 251, "b": 255 }, { "r": 0, "g": 255, "b": 223 }, { "r": 0, "g": 255, "b": 203 }, { "r": 22, "g": 255, "b": 168 }, { "r": 54, "g": 255, "b": 144 }, { "r": 58, "g": 55, "b": 255 }, { "r": 59, "g": 86, "b": 255 }, { "r": 61, "g": 120, "b": 255 }, { "r": 64, "g": 175, "b": 255 }, { "r": 66, "g": 225, "b": 255 }, { "r": 65, "g": 255, "b": 246 }, { "r": 83, "g": 255, "b": 211 }, { "r": 51, "g": 255, "b": 181 }, { "r": 46, "g": 255, "b": 156 }, { "r": 41, "g": 255, "b": 134 }, { "r": 37, "g": 255, "b": 115 }, { "r": 83, "g": 56, "b": 255 }, { "r": 84, "g": 67, "b": 255 }, { "r": 85, "g": 77, "b": 255 }, { "r": 86, "g": 104, "b": 255 }, { "r": 89, "g": 113, "b": 255 }, { "r": 90, "g": 126, "b": 255 }, { "r": 94, "g": 170, "b": 255 }, { "r": 102, "g": 242, "b": 255 }, { "r": 84, "g": 255, "b": 193 }, { "r": 68, "g": 255, "b": 140 }, { "r": 56, "g": 255, "b": 101 }, { "r": 110, "g": 58, "b": 255 }, { "r": 112, "g": 68, "b": 255 }, { "r": 116, "g": 91, "b": 255 }, { "r": 120, "g": 117, "b": 255 }, { "r": 124, "g": 145, "b": 255 }, { "r": 130, "g": 176, "b": 255 }, { "r": 149, "g": 222, "b": 255 }, { "r": 142, "g": 253, "b": 255 }, { "r": 127, "g": 255, "b": 216 }, { "r": 114, "g": 255, "b": 183 }, { "r": 103, "g": 255, "b": 156 }, { "r": 94, "g": 255, "b": 132 }, { "r": 86, "g": 255, "b": 111 }, { "r": 78, "g": 255, "b": 93 }, { "r": 72, "g": 255, "b": 77 }, { "r": 87, "g": 255, "b": 71 }, { "r": 62, "g": 255, "b": 51 }, { "r": 143, "g": 70, "b": 255 }, { "r": 140, "g": 112, "b": 255 }, { "r": 170, "g": 186, "b": 255 }, { "r": 178, "g": 255, "b": 240 }, { "r": 143, "g": 255, "b": 169 }, { "r": 118, "g": 255, "b": 119 }, { "r": 100, "g": 255, "b": 82 }, { "r": 85, "g": 255, "b": 53 }, { "r": 74, "g": 255, "b": 30 }, { "r": 178, "g": 72, "b": 255 }, { "r": 192, "g": 105, "b": 255 }, { "r": 195, "g": 127, "b": 255 }, { "r": 205, "g": 160, "b": 255 }, { "r": 216, "g": 198, "b": 255 }, { "r": 191, "g": 255, "b": 186 }, { "r": 173, "g": 255, "b": 155 }, { "r": 162, "g": 255, "b": 125 }, { "r": 143, "g": 255, "b": 106 }, { "r": 130, "g": 255, "b": 91 }, { "r": 121, "g": 255, "b": 71 }, { "r": 112, "g": 255, "b": 56 }, { "r": 104, "g": 255, "b": 43 }, { "r": 93, "g": 255, "b": 37 }, { "r": 217, "g": 74, "b": 255 }, { "r": 242, "g": 140, "b": 255 }, { "r": 255, "g": 199, "b": 240 }, { "r": 203, "g": 255, "b": 140 }, { "r": 169, "g": 255, "b": 94 }, { "r": 143, "g": 255, "b": 59 }, { "r": 124, "g": 255, "b": 33 }, { "r": 255, "g": 84, "b": 255 }, { "r": 255, "g": 112, "b": 255 }, { "r": 255, "g": 123, "b": 222 }, { "r": 255, "g": 148, "b": 208 }, { "r": 255, "g": 166, "b": 205 }, { "r": 255, "g": 194, "b": 204 }, { "r": 255, "g": 224, "b": 166 }, { "r": 255, "g": 251, "b": 151 }, { "r": 233, "g": 255, "b": 125 }, { "r": 210, "g": 255, "b": 91 }, { "r": 194, "g": 255, "b": 81 }, { "r": 179, "g": 255, "b": 63 }, { "r": 154, "g": 255, "b": 32 }, { "r": 158, "g": 255, "b": 37 }, { "r": 255, "g": 65, "b": 208 }, { "r": 255, "g": 108, "b": 184 }, { "r": 255, "g": 152, "b": 159 }, { "r": 255, "g": 198, "b": 133 }, { "r": 255, "g": 253, "b": 95 }, { "r": 220, "g": 255, "b": 67 }, { "r": 188, "g": 255, "b": 36 }, { "r": 255, "g": 57, "b": 176 }, { "r": 255, "g": 76, "b": 164 }, { "r": 255, "g": 96, "b": 153 }, { "r": 255, "g": 125, "b": 150 }, { "r": 255, "g": 136, "b": 130 }, { "r": 255, "g": 166, "b": 126 }, { "r": 255, "g": 176, "b": 106 }, { "r": 255, "g": 197, "b": 94 }, { "r": 255, "g": 223, "b": 88 }, { "r": 255, "g": 240, "b": 69 }, { "r": 248, "g": 255, "b": 56 }, { "r": 228, "g": 255, "b": 38 }, { "r": 255, "g": 36, "b": 167 }, { "r": 255, "g": 88, "b": 134 }, { "r": 255, "g": 122, "b": 106 }, { "r": 255, "g": 160, "b": 77 }, { "r": 255, "g": 207, "b": 86 }, { "r": 255, "g": 237, "b": 37 }, { "r": 255, "g": 41, "b": 143 }, { "r": 255, "g": 61, "b": 116 }, { "r": 255, "g": 84, "b": 111 }, { "r": 255, "g": 94, "b": 96 }, { "r": 255, "g": 110, "b": 86 }, { "r": 255, "g": 127, "b": 76 }, { "r": 255, "g": 144, "b": 65 }, { "r": 255, "g": 162, "b": 55 }, { "r": 255, "g": 179, "b": 44 }, { "r": 255, "g": 197, "b": 33 }, { "r": 255, "g": 70, "b": 88 }, { "r": 254, "g": 107, "b": 64 }, { "r": 255, "g": 132, "b": 50 }, { "r": 255, "g": 164, "b": 30 }, { "r": 255, "g": 22, "b": 98 }, { "r": 255, "g": 50, "b": 80 }, { "r": 254, "g": 89, "b": 64 }, { "r": 255, "g": 92, "b": 54 }, { "r": 255, "g": 121, "b": 36 }, { "r": 255, "g": 138, "b": 37 }, { "r": 255, "g": 111, "b": 25 }, { "r": 255, "g": 41, "b": 54 }, { "r": 255, "g": 53, "b": 46 }, { "r": 255, "g": 78, "b": 30 }, { "r": 255, "g": 61, "b": 28 }, { "r": 255, "g": 39, "b": 37 }, { "r": 255, "g": 57, "b": 18 }, { "r": 255, "g": 29, "b": 15 }, { "r": 255, "g": 14, "b": 8 } ]}
import android.graphics.Bitmapimport android.graphics.Colorimport android.util.Logimport com.blankj.utilcode.util.GsonUtilsimport com.blankj.utilcode.util.ScreenUtilsimport com.blankj.utilcode.util.Utilsimport com.google.gson.Gsonimport com.google.gson.reflect.TypeTokenimport kotlinx.coroutines.Dispatchersimport kotlinx.coroutines.MainScopeimport kotlinx.coroutines.launchimport java.util.concurrent.CopyOnWriteArrayListimport kotlin.math.absobject AmbientLightColorPickManager { private const val TAG = "AmbientLightColorPickManager" private var scope = MainScope() private val mWidth = ScreenUtils.getScreenWidth() / 2 private val mHeight = ScreenUtils.getScreenHeight() / 2 // 256色 private val tableList = mutableListOf<ColorTableBean>() // 原图色 private val originalList = CopyOnWriteArrayList<ColorTableBean>() var test1Listener: ((Int) -> Unit)? = null var test2Listener: ((Int, Int, Int) -> Unit)? = null @JvmStatic fun init() { log("$TAG init") scope.launch(Dispatchers.IO) { initHsvColor() } } private fun initHsvColor() { tableList.clear() runCatching { val json = SharedPreferencesUtils.getRGB256HsvColor(Utils.getApp()) val listType = object : TypeToken<MutableList<ColorTableBean>>() {}.type Gson().fromJson<MutableList<ColorTableBean>>(json, listType)?.let { tableList.addAll(it) log("initHsvColor xml list size=${tableList.size}") } }.getOrElse { Log.e(TAG, "initHsvColor Exception ${it.message}") } if (tableList.isEmpty()) { saveHsvColor().let { if (it.isNotEmpty()) { tableList.addAll(it) } } log("initHsvColor json list size=${tableList.size}") } } /** 将本地rgb色值转换成hsv保存到本地 */ private fun saveHsvColor(): MutableList<ColorTableBean> { log("saveHsvColor") val hsvList = mutableListOf<ColorTableBean>() 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) for (i in 0 until bean.list.size) { bean.list[i].apply { val myColor = Color.rgb(r, g, b) val hsvColors = FloatArray(3) Color.colorToHSV(myColor, hsvColors) val lab = ColorEabLabUtils.rGBToLab(r, g, b) val bean = ColorTableBean(hsvColors, lab, myColor) hsvList.add(bean) } } 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 (originalList.isEmpty()) { Log.w(TAG, "setAmbientLight hueList is null") return@launch } if (index < 0 || index >= originalList.size) { Log.w(TAG, "setAmbientLight 索引异常") return@launch } // 氛围灯取色 setBytesFunctionValue(index) } } @JvmStatic fun switchLight(isOn: Boolean) { log("switchLight isOn=$isOn") } /** 初始化资源 */ @JvmStatic fun loadData(displayId: Int, pictures: List<String>) { if (displayId != DisplayParameter.DISPLAY_CSD.displayId) return log("loadData pictures size=${pictures.size} pictures $pictures") originalList.clear() for ((_, picture) in pictures.withIndex()) { runCatching { val bitmap = GlideCacheUtils.loadImageAsBitmap(picture, mWidth, mHeight) originalList.add(generate(bitmap)) }.getOrElse { Log.e(TAG, "loadData exception ${it.message}") } } log("loadData hueList size=${originalList.size}") } private fun setFunctionValue(functionId: Int, value: Int, zone: Int) { } private fun setBytesFunctionValue(index: Int) { try { originalList[index].let { test1Listener?.invoke(it.color) test2Listener?.invoke( findColor(it.hue()).colorTip, findLabColor(it), findTestColor(it.labArray) ) } } catch (e: Exception) { Log.e(TAG, "setBytesFunctionValue Exception $e") } } private fun findColor(bgHue: Float): ColorTipBean { if (tableList.isEmpty()) { Log.w(TAG, "findColor hsvList is null") return ColorTipBean(Color.WHITE) } var result = tableList[0] var minDiff = abs(result.hue() - bgHue) for (i in 0 until tableList.size) { val currentDiff = abs(tableList[i].hue() - bgHue) if (currentDiff < minDiff) { minDiff = currentDiff result = tableList[i] } } log("findColor bgHue=$bgHue,minDiff=$minDiff,result=$result") return ColorTipBean(result.color) } private fun findLabColor(bean: ColorTableBean): Int { var color = Color.WHITE var minDiff = Double.MAX_VALUE tableList.forEachIndexed { index, it -> val distance = ColorEabLabUtils.deltaE76(it.labArray, bean.labArray) if (distance < minDiff) { minDiff = distance color = it.color } } log("findLabColor minDiff=$minDiff,color=[${bean.color},$color]") return if (minDiff < 26) color else findColor(bean.hue()).colorTip } private fun findTestColor(list: DoubleArray): Int { var color = Color.WHITE var minDiff = Double.MAX_VALUE tableList.forEachIndexed { index, it -> val distance = ColorEabLabUtils.deltaE76(it.labArray, list) if (distance < minDiff) { minDiff = distance color = it.color } } return color } private fun getColors(bean: ColorTableBean): ByteArray { val result = mutableListOf<ColorTipBean>() val colorBean = ColorTipBean(findLabColor(bean)) result.add(colorBean) result.add(colorBean) result.add(colorBean) val json = GsonUtils.toJson(ColorLightBean(result).list) log("setBytesFunctionValue json=$json") return json.toByteArray() } private fun generate(newMap: Bitmap): ColorTableBean { val dominantColor = ColorEabLabUtils.getPerceptuallyDominantColor(newMap) val hsvArray = FloatArray(3) Color.colorToHSV(dominantColor, hsvArray) val labArray = ColorEabLabUtils.rGBToLab(dominantColor) return ColorTableBean(hsvArray, labArray, dominantColor) } private fun log(str: String) = Log.d(TAG, str)}
荣耀转型 AI 终端生态公司:方飞公布 1 x 3 x N 战略,AI 研发累计投入超 100 亿元
OSAT、模拟芯片两领域巨头牵手:日月光将收购 ADI 槟城制造工厂