|
hue查找:整体偏差不会很大,但是对于亮度较高存在误差,精准度不够
lab查找:整体一般,但是精准度较好,不过算法复杂,增加耗时
hue色相查找存在误差,在有限的256色中,匹配的规则需要调整
这里使用lab算法提高精准度
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)
}
View Code
对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)
)
}
View Code
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
}
}
View Code
从上面图中结果可以明显看到,大部分图片中,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
}
}
View Code
在lab精度不够时,可能结果偏差较大,但是hue偏差不会很大,hue只是精度不够,所以这里兼容两种方案
在lab误差较大时,直接采用hue方案计算结果,这样直接弥补了自身的缺点,比如图一中误差小,采用lab方案,提高了精准度,而在图二精度不够时,采用hue,避免误差偏差较大
这里需要注意,hue色相查找需要调整排序,原生排序对于黑白纯色判断效果不佳
Palette.from(bitmap).maximumColorCount(24).clearFilters().generate(),所以这里过滤了黑白色,同时修改了原生的排序,按照新的计算方式取的主色
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)
}
View Code
最终方案是融合方案,不过精度任然存在误差,但本身转换过程结果只有256色,所以不可能达到完美,相比原图取色结果精度是较高
![]() ![]()
import android.graphics.Bitmap
import android.graphics.Color
import androidx.palette.graphics.Palette
import androidx.palette.graphics.Palette.Swatch
import java.lang.Math.toDegrees
import java.lang.Math.toRadians
import kotlin.math.abs
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.exp
import kotlin.math.pow
import kotlin.math.sin
import kotlin.math.sqrt
object 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)
}
}
View Code
![]() ![]()
{
"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
}
]
}
color_rgb_256.json
![]() ![]()
import android.graphics.Bitmap
import android.graphics.Color
import android.util.Log
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.reflect.TypeToken
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import java.util.concurrent.CopyOnWriteArrayList
import kotlin.math.abs
object 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.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.hue() - bgHue)
if (currentDiff < minDiff) {
minDiff = currentDiff
result = tableList
}
}
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)
}
View Code
来源:https://www.cnblogs.com/LiuZhen/p/19157538 |