氛围灯动态屏保取色方案二
<p><strong>hue查找:</strong>整体偏差不会很大,但是对于亮度较高存在误差,精准度不够</p><p><strong>lab查找:</strong>整体一般,但是精准度较好,不过算法复杂,增加耗时</p>
<p>hue色相查找存在误差,在有限的256色中,匹配的规则需要调整</p>
<p>这里使用lab算法提高精准度</p>
<h4>RGB转Lab</h4>
<div class="cnblogs_code"><img src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_ddfb89f7-a904-471d-97ce-79dbfb7a3436" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_ddfb89f7-a904-471d-97ce-79dbfb7a3436" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 0, 1)">fun rGBToLab(r: Int, g: Int, b: Int): DoubleArray {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 处理负值(如-2563864)</span>
val labR = <span style="color: rgba(0, 0, 255, 1)">if</span> (r < 0) 0f <span style="color: rgba(0, 0, 255, 1)">else</span> r /<span style="color: rgba(0, 0, 0, 1)"> 255f
val labG </span>= <span style="color: rgba(0, 0, 255, 1)">if</span> (g < 0) 0f <span style="color: rgba(0, 0, 255, 1)">else</span> g /<span style="color: rgba(0, 0, 0, 1)"> 255f
val labB </span>= <span style="color: rgba(0, 0, 255, 1)">if</span> (b < 0) 0f <span style="color: rgba(0, 0, 255, 1)">else</span> b /<span style="color: rgba(0, 0, 0, 1)"> 255f
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 线性化处理(sRGB转换)</span>
val rLin = <span style="color: rgba(0, 0, 255, 1)">if</span> (labR <= 0.04045) labR / 12.92 <span style="color: rgba(0, 0, 255, 1)">else</span> ((labR + 0.055) / 1.055).pow(2.4<span style="color: rgba(0, 0, 0, 1)">)
val gLin </span>= <span style="color: rgba(0, 0, 255, 1)">if</span> (labG <= 0.04045) labG / 12.92 <span style="color: rgba(0, 0, 255, 1)">else</span> ((labG + 0.055) / 1.055).pow(2.4<span style="color: rgba(0, 0, 0, 1)">)
val bLin </span>= <span style="color: rgba(0, 0, 255, 1)">if</span> (labB <= 0.04045) labB / 12.92 <span style="color: rgba(0, 0, 255, 1)">else</span> ((labB + 0.055) / 1.055).pow(2.4<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> XYZ转换(D65标准光源)</span>
val x = 0.4124564 * rLin + 0.3575761 * gLin + 0.1804375 *<span style="color: rgba(0, 0, 0, 1)"> bLin
val y </span>= 0.2126729 * rLin + 0.7151522 * gLin + 0.0721750 *<span style="color: rgba(0, 0, 0, 1)"> bLin
val z </span>= 0.0193339 * rLin + 0.1191920 * gLin + 0.9503041 *<span style="color: rgba(0, 0, 0, 1)"> bLin
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Lab转换</span>
val xn = 0.95047f<span style="color: rgba(0, 0, 0, 1)">
val yn </span>= 1.0f<span style="color: rgba(0, 0, 0, 1)">
val zn </span>= 1.08883f<span style="color: rgba(0, 0, 0, 1)">
val fX </span>= x /<span style="color: rgba(0, 0, 0, 1)"> xn
val fY </span>= y /<span style="color: rgba(0, 0, 0, 1)"> yn
val fZ </span>= z /<span style="color: rgba(0, 0, 0, 1)"> zn
val l </span>= 116f * fY.pow(1 / 3.0) - 16<span style="color: rgba(0, 0, 0, 1)">
val a </span>= 500f * (fX.pow(1 / 3.0) - fY.pow(1 / 3.0<span style="color: rgba(0, 0, 0, 1)">))
val b </span>= 200f * (fY.pow(1 / 3.0) - fZ.pow(1 / 3.0<span style="color: rgba(0, 0, 0, 1)">))
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> doubleArrayOf(l, a, b)
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>对rgb进行转换,这里是简化版,如果需要精确要求极高,需要完善</p>
<p>处理流程跟方案一一样,使用集合缓存</p>
<p>接着使用 ΔE76色差公式 查找色值(另外还有ΔEab欧式距离算法,CIEDE2000色差公式,CIE76)</p>
<div class="cnblogs_code"><img id="code_img_closed_88a54cc7-01fc-48e4-bdf1-e4b48a600745" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_88a54cc7-01fc-48e4-bdf1-e4b48a600745" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_88a54cc7-01fc-48e4-bdf1-e4b48a600745" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 0, 1)">fun deltaE76(lab1: DoubleArray, lab2: DoubleArray): Double {
val (l1, a1, b1) </span>=<span style="color: rgba(0, 0, 0, 1)"> lab1
val (l2, a2, b2) </span>=<span style="color: rgba(0, 0, 0, 1)"> lab2
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 计算中间变量</span>
val c1 = sqrt(a1.pow(2) + b1.pow(2<span style="color: rgba(0, 0, 0, 1)">))
val c2 </span>= sqrt(a2.pow(2) + b2.pow(2<span style="color: rgba(0, 0, 0, 1)">))
val cAvg </span>= (c1 + c2) / 2<span style="color: rgba(0, 0, 0, 1)">
val g </span>= 0.5 * (1 - sqrt(cAvg.pow(7) / (cAvg.pow(7) + 25f.pow(7<span style="color: rgba(0, 0, 0, 1)">))))
val a1p </span>= a1 * (1 +<span style="color: rgba(0, 0, 0, 1)"> g)
val a2p </span>= a2 * (1 +<span style="color: rgba(0, 0, 0, 1)"> g)
val c1p </span>= sqrt(a1p.pow(2) + b1.pow(2<span style="color: rgba(0, 0, 0, 1)">))
val c2p </span>= sqrt(a2p.pow(2) + b2.pow(2<span style="color: rgba(0, 0, 0, 1)">))
val h1p </span>= Math.toDegrees(kotlin.math.atan2(b1, a1p)).let { <span style="color: rgba(0, 0, 255, 1)">if</span> (it < 0) it + 360 <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> it }
val h2p </span>= Math.toDegrees(kotlin.math.atan2(b2, a2p)).let { <span style="color: rgba(0, 0, 255, 1)">if</span> (it < 0) it + 360 <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> it }
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 色差计算</span>
val deltaLp = l2 -<span style="color: rgba(0, 0, 0, 1)"> l1
val deltaCp </span>= c2p -<span style="color: rgba(0, 0, 0, 1)"> c1p
val deltaHp </span>=<span style="color: rgba(0, 0, 0, 1)"> when {
c1p </span>* c2p == 0.0 -> 0.0<span style="color: rgba(0, 0, 0, 1)">
abs(h2p </span>- h1p) <= 180 -> h2p -<span style="color: rgba(0, 0, 0, 1)"> h1p
h2p </span><= h1p -> h2p - h1p + 360
<span style="color: rgba(0, 0, 255, 1)">else</span> -> h2p - h1p - 360<span style="color: rgba(0, 0, 0, 1)">
}
val deltaHpS </span>= 2 * sqrt(c1p * c2p) * sin(Math.toRadians(deltaHp) / 2<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 加权计算</span>
val lAvg = (l1 + l2) / 2<span style="color: rgba(0, 0, 0, 1)">
val cpAvg </span>= (c1p + c2p) / 2<span style="color: rgba(0, 0, 0, 1)">
val hpAvg </span>=<span style="color: rgba(0, 0, 0, 1)"> when {
c1p </span>* c2p == 0.0 -> h1p +<span style="color: rgba(0, 0, 0, 1)"> h2p
abs(h1p </span>- h2p) > 180 -> (h1p + h2p + 360) / 2
<span style="color: rgba(0, 0, 255, 1)">else</span> -> (h1p + h2p) / 2<span style="color: rgba(0, 0, 0, 1)">
}.let { </span><span style="color: rgba(0, 0, 255, 1)">if</span> (it >= 360) it - 360 <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> it }
val t </span>= 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<span style="color: rgba(0, 0, 0, 1)">))
val sl </span>= 1 + (0.015 * (lAvg - 50).pow(2)) / sqrt(20 + (lAvg - 50).pow(2<span style="color: rgba(0, 0, 0, 1)">))
val sc </span>= 1 + 0.045 *<span style="color: rgba(0, 0, 0, 1)"> cpAvg
val sh </span>= 1 + 0.015 * cpAvg *<span style="color: rgba(0, 0, 0, 1)"> t
val deltaTheta </span>= 30 * exp(-((hpAvg - 275) / 25).pow(2<span style="color: rgba(0, 0, 0, 1)">))
val rc </span>= 2 * sqrt(cpAvg.pow(7) / (cpAvg.pow(7) + 25f.pow(7<span style="color: rgba(0, 0, 0, 1)">)))
val rt </span>= -rc * sin(2 *<span style="color: rgba(0, 0, 0, 1)"> Math.toRadians(deltaTheta))
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> sqrt(
(deltaLp </span>/ sl).pow(2) +<span style="color: rgba(0, 0, 0, 1)">
(deltaCp </span>/ sc).pow(2) +<span style="color: rgba(0, 0, 0, 1)">
(deltaHpS </span>/ sh).pow(2) +<span style="color: rgba(0, 0, 0, 1)">
rt </span>* (deltaCp / sc) * (deltaHpS /<span style="color: rgba(0, 0, 0, 1)"> sh)
)
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<div class="cnblogs_code"><img id="code_img_closed_41dfc3e2-fe4d-45ad-ac26-68a720c9cb15" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_41dfc3e2-fe4d-45ad-ac26-68a720c9cb15" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_41dfc3e2-fe4d-45ad-ac26-68a720c9cb15" class="cnblogs_code_hide">
<pre>var minIndex = 0<span style="color: rgba(0, 0, 0, 1)">
var minDistance </span>=<span style="color: rgba(0, 0, 0, 1)"> Double.MAX_VALUE
var minLab </span>=<span style="color: rgba(0, 0, 0, 1)"> doubleArrayOf()
tableLabPalette.forEachIndexed { index, lab </span>-><span style="color: rgba(0, 0, 0, 1)">
val distance </span>=<span style="color: rgba(0, 0, 0, 1)"> ColorEabLabUtils.deltaE76(lab, list)
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (distance <<span style="color: rgba(0, 0, 0, 1)"> minDistance) {
minDistance </span>=<span style="color: rgba(0, 0, 0, 1)"> distance
minLab </span>=<span style="color: rgba(0, 0, 0, 1)"> lab
minIndex </span>=<span style="color: rgba(0, 0, 0, 1)"> index
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p><img alt="image" width="405" height="310" loading="lazy" src="https://img2024.cnblogs.com/blog/583064/202510/583064-20251022110239280-1062942616.png"></p>
<p>从上面图中结果可以明显看到,大部分图片中,hue原方案精准度对比lab查找,是有差距的</p>
<p>但是缺点也明显,在某些图片上表现不好,可以看到原方案hue更精准</p>
<p><img src="https://img2024.cnblogs.com/blog/583064/202510/583064-20251022110112007-1144896521.png"></p>
<p>经过大量调整算法,优化算法,测试下来发现,如果在不通过底层显示屏直接转换色值情况下,都存在误差</p>
<p>而刚好这两种查找结果有一定互补作用,那是否可以取各自的优势,结合结果,达到一个平衡</p>
<div class="cnblogs_code"><img src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_edfbdec2-85d5-46dc-bd2a-1570f4ff50bc" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_edfbdec2-85d5-46dc-bd2a-1570f4ff50bc" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> fun findLabColor(list: DoubleArray, hue: Float): Int {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 寻找最近颜色(简化版)</span>
var minIndex = 0<span style="color: rgba(0, 0, 0, 1)">
var minDistance </span>=<span style="color: rgba(0, 0, 0, 1)"> Double.MAX_VALUE
var minLab </span>=<span style="color: rgba(0, 0, 0, 1)"> doubleArrayOf()
tableLabPalette.forEachIndexed { index, lab </span>-><span style="color: rgba(0, 0, 0, 1)">
val distance </span>=<span style="color: rgba(0, 0, 0, 1)"> ColorEabLabUtils.deltaE76(lab, list)
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (distance <<span style="color: rgba(0, 0, 0, 1)"> minDistance) {
minDistance </span>=<span style="color: rgba(0, 0, 0, 1)"> distance
minLab </span>=<span style="color: rgba(0, 0, 0, 1)"> lab
minIndex </span>=<span style="color: rgba(0, 0, 0, 1)"> index
}
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (minDistance < 26<span style="color: rgba(0, 0, 0, 1)">) {
tableColorList
} </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
findColor(hue).colorTip
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>在lab精度不够时,可能结果偏差较大,但是hue偏差不会很大,hue只是精度不够,所以这里兼容两种方案</p>
<p>在lab误差较大时,直接采用hue方案计算结果,这样直接弥补了自身的缺点,比如图一中误差小,采用lab方案,提高了精准度,而在图二精度不够时,采用hue,避免误差偏差较大</p>
<p><img alt="image" width="404" height="360" loading="lazy" src="https://img2024.cnblogs.com/blog/583064/202510/583064-20251022111128634-2073798422.png"></p>
<p>这里需要注意,hue色相查找需要调整排序,原生排序对于黑白纯色判断效果不佳</p>
<p>Palette.from(bitmap).maximumColorCount(24).clearFilters().generate(),所以这里过滤了黑白色,同时修改了原生的排序,按照新的计算方式取的主色</p>
<div class="cnblogs_code"><img id="code_img_closed_95c2d12d-05c1-4268-8c23-20023e29a000" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_95c2d12d-05c1-4268-8c23-20023e29a000" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_95c2d12d-05c1-4268-8c23-20023e29a000" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 0, 1)">fun getPerceptuallyDominantColor(bitmap: Bitmap): Int {
val palette </span>= Palette.from(bitmap).maximumColorCount(24<span style="color: rgba(0, 0, 0, 1)">).clearFilters().generate()
val swatches </span>=<span style="color: rgba(0, 0, 0, 1)"> palette.swatches
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (swatches.isEmpty()) <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> Color.WHITE
var bestSwatch: Swatch</span>? = <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">
var maxScore </span>=<span style="color: rgba(0, 0, 0, 1)"> 0f
</span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> (swatch in swatches) {
val hsl </span>=<span style="color: rgba(0, 0, 0, 1)"> swatch.getHsl()
val saturation </span>= hsl <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 饱和度 (0-1)</span>
val luminance = hsl <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 亮度 (0-1)</span>
val population =<span style="color: rgba(0, 0, 0, 1)"> swatch.population
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 评分公式:人口占比 * 饱和度 * 亮度因子
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 亮度因子确保避免过暗或过亮的颜色(0.1-0.9为理想范围)</span>
val luminanceFactor = 1f - abs(luminance - 0.5f) * 1.8f<span style="color: rgba(0, 0, 0, 1)">
val score </span>= population * saturation *<span style="color: rgba(0, 0, 0, 1)"> luminanceFactor
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (score ><span style="color: rgba(0, 0, 0, 1)"> maxScore) {
maxScore </span>=<span style="color: rgba(0, 0, 0, 1)"> score
bestSwatch </span>=<span style="color: rgba(0, 0, 0, 1)"> swatch
}
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span> bestSwatch?.rgb ?<span style="color: rgba(0, 0, 0, 1)">: palette.getDominantColor(Color.WHITE)
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>最终方案是融合方案,不过精度任然存在误差,但本身转换过程结果只有256色,所以不可能达到完美,相比原图取色结果精度是较高</p>
<div class="cnblogs_code"><img id="code_img_closed_20b40641-7ff6-481b-9756-0d1f53994dd7" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_20b40641-7ff6-481b-9756-0d1f53994dd7" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_20b40641-7ff6-481b-9756-0d1f53994dd7" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.graphics.Bitmap
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.graphics.Color
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> androidx.palette.graphics.Palette
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> androidx.palette.graphics.Palette.Swatch
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> java.lang.Math.toDegrees
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> java.lang.Math.toRadians
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> kotlin.math.abs
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> kotlin.math.atan2
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> kotlin.math.cos
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> kotlin.math.exp
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> kotlin.math.pow
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> kotlin.math.sin
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> kotlin.math.sqrt
object ColorEabLabUtils {
fun getPerceptuallyDominantColor(bitmap: Bitmap): Int {
val palette </span>= Palette.from(bitmap).maximumColorCount(24<span style="color: rgba(0, 0, 0, 1)">).clearFilters().generate()
val swatches </span>=<span style="color: rgba(0, 0, 0, 1)"> palette.swatches
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (swatches.isEmpty()) <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> Color.WHITE
var bestSwatch: Swatch</span>? = <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">
var maxScore </span>=<span style="color: rgba(0, 0, 0, 1)"> 0f
</span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> (swatch in swatches) {
val hsl </span>=<span style="color: rgba(0, 0, 0, 1)"> swatch.getHsl()
val saturation </span>= hsl <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 饱和度 (0-1)</span>
val luminance = hsl <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 亮度 (0-1)</span>
val population =<span style="color: rgba(0, 0, 0, 1)"> swatch.population
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 评分公式:人口占比 * 饱和度 * 亮度因子
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 亮度因子确保避免过暗或过亮的颜色(0.1-0.9为理想范围)</span>
val luminanceFactor = 1f - abs(luminance - 0.5f) * 1.8f<span style="color: rgba(0, 0, 0, 1)">
val score </span>= population * saturation *<span style="color: rgba(0, 0, 0, 1)"> luminanceFactor
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (score ><span style="color: rgba(0, 0, 0, 1)"> maxScore) {
maxScore </span>=<span style="color: rgba(0, 0, 0, 1)"> score
bestSwatch </span>=<span style="color: rgba(0, 0, 0, 1)"> swatch
}
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span> bestSwatch?.rgb ?<span style="color: rgba(0, 0, 0, 1)">: palette.getDominantColor(Color.WHITE)
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> RGB转Lab *********</span>
<span style="color: rgba(0, 0, 0, 1)"> fun rGBToLab(r: Int, g: Int, b: Int): DoubleArray {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 处理负值(如-2563864)</span>
val labR = <span style="color: rgba(0, 0, 255, 1)">if</span> (r < 0) 0f <span style="color: rgba(0, 0, 255, 1)">else</span> r /<span style="color: rgba(0, 0, 0, 1)"> 255f
val labG </span>= <span style="color: rgba(0, 0, 255, 1)">if</span> (g < 0) 0f <span style="color: rgba(0, 0, 255, 1)">else</span> g /<span style="color: rgba(0, 0, 0, 1)"> 255f
val labB </span>= <span style="color: rgba(0, 0, 255, 1)">if</span> (b < 0) 0f <span style="color: rgba(0, 0, 255, 1)">else</span> b /<span style="color: rgba(0, 0, 0, 1)"> 255f
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 线性化处理(sRGB转换)</span>
val rLin = <span style="color: rgba(0, 0, 255, 1)">if</span> (labR <= 0.04045) labR / 12.92 <span style="color: rgba(0, 0, 255, 1)">else</span> ((labR + 0.055) / 1.055).pow(2.4<span style="color: rgba(0, 0, 0, 1)">)
val gLin </span>= <span style="color: rgba(0, 0, 255, 1)">if</span> (labG <= 0.04045) labG / 12.92 <span style="color: rgba(0, 0, 255, 1)">else</span> ((labG + 0.055) / 1.055).pow(2.4<span style="color: rgba(0, 0, 0, 1)">)
val bLin </span>= <span style="color: rgba(0, 0, 255, 1)">if</span> (labB <= 0.04045) labB / 12.92 <span style="color: rgba(0, 0, 255, 1)">else</span> ((labB + 0.055) / 1.055).pow(2.4<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> XYZ转换(D65标准光源)</span>
val x = 0.4124564 * rLin + 0.3575761 * gLin + 0.1804375 *<span style="color: rgba(0, 0, 0, 1)"> bLin
val y </span>= 0.2126729 * rLin + 0.7151522 * gLin + 0.0721750 *<span style="color: rgba(0, 0, 0, 1)"> bLin
val z </span>= 0.0193339 * rLin + 0.1191920 * gLin + 0.9503041 *<span style="color: rgba(0, 0, 0, 1)"> bLin
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Lab转换</span>
val xn = 0.95047f<span style="color: rgba(0, 0, 0, 1)">
val yn </span>= 1.0f<span style="color: rgba(0, 0, 0, 1)">
val zn </span>= 1.08883f<span style="color: rgba(0, 0, 0, 1)">
val fX </span>= x /<span style="color: rgba(0, 0, 0, 1)"> xn
val fY </span>= y /<span style="color: rgba(0, 0, 0, 1)"> yn
val fZ </span>= z /<span style="color: rgba(0, 0, 0, 1)"> zn
val l </span>= 116f * fY.pow(1 / 3.0) - 16<span style="color: rgba(0, 0, 0, 1)">
val a </span>= 500f * (fX.pow(1 / 3.0) - fY.pow(1 / 3.0<span style="color: rgba(0, 0, 0, 1)">))
val b </span>= 200f * (fY.pow(1 / 3.0) - fZ.pow(1 / 3.0<span style="color: rgba(0, 0, 0, 1)">))
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> doubleArrayOf(l, a, b)
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ΔEab算法</span>
<span style="color: rgba(0, 0, 0, 1)"> fun deltaEab(lab1: DoubleArray, lab2: DoubleArray): Double {
val dL </span>= lab1 - lab2
val da </span>= lab1 - lab2
val db </span>= lab1 - lab2
</span><span style="color: rgba(0, 0, 255, 1)">return</span> sqrt(dL.pow(2) + da.pow(2) + db.pow(2<span style="color: rgba(0, 0, 0, 1)">))
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 改进的RGB转Lab(包含伽马校正和D65白点)</span>
<span style="color: rgba(0, 0, 0, 1)"> fun rgbToLab(r: Int, g: Int, b: Int): DoubleArray {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 处理负值(如-2563864)</span>
val labR = <span style="color: rgba(0, 0, 255, 1)">if</span> (r < 0) 0f <span style="color: rgba(0, 0, 255, 1)">else</span> r /<span style="color: rgba(0, 0, 0, 1)"> 255f
val labG </span>= <span style="color: rgba(0, 0, 255, 1)">if</span> (g < 0) 0f <span style="color: rgba(0, 0, 255, 1)">else</span> g /<span style="color: rgba(0, 0, 0, 1)"> 255f
val labB </span>= <span style="color: rgba(0, 0, 255, 1)">if</span> (b < 0) 0f <span style="color: rgba(0, 0, 255, 1)">else</span> b /<span style="color: rgba(0, 0, 0, 1)"> 255f
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> sRGB伽马校正</span>
val rLin = <span style="color: rgba(0, 0, 255, 1)">if</span> (labR <= 0.04045) labR / 12.92 <span style="color: rgba(0, 0, 255, 1)">else</span> ((labR + 0.055) / 1.055).pow(2.4<span style="color: rgba(0, 0, 0, 1)">)
val gLin </span>= <span style="color: rgba(0, 0, 255, 1)">if</span> (labG <= 0.04045) labG / 12.92 <span style="color: rgba(0, 0, 255, 1)">else</span> ((labG + 0.055) / 1.055).pow(2.4<span style="color: rgba(0, 0, 0, 1)">)
val bLin </span>= <span style="color: rgba(0, 0, 255, 1)">if</span> (labB <= 0.04045) labB / 12.92 <span style="color: rgba(0, 0, 255, 1)">else</span> ((labB + 0.055) / 1.055).pow(2.4<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> D65标准光源XYZ转换</span>
val x = 0.4124564 * rLin + 0.3575761 * gLin + 0.1804375 *<span style="color: rgba(0, 0, 0, 1)"> bLin
val y </span>= 0.2126729 * rLin + 0.7151522 * gLin + 0.0721750 *<span style="color: rgba(0, 0, 0, 1)"> bLin
val z </span>= 0.0193339 * rLin + 0.1191920 * gLin + 0.9503041 *<span style="color: rgba(0, 0, 0, 1)"> bLin
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Lab转换(包含阈值处理)</span>
val xn = 0.95047<span style="color: rgba(0, 0, 0, 1)">
val yn </span>= 1.0<span style="color: rgba(0, 0, 0, 1)">
val zn </span>= 1.08883<span style="color: rgba(0, 0, 0, 1)">
val fX </span>= x /<span style="color: rgba(0, 0, 0, 1)"> xn
val fY </span>= y /<span style="color: rgba(0, 0, 0, 1)"> yn
val fZ </span>= z /<span style="color: rgba(0, 0, 0, 1)"> zn
val l </span>= <span style="color: rgba(0, 0, 255, 1)">if</span> (fY > 0.008856) 116 * fY.pow(1 / 3.0) - 16 <span style="color: rgba(0, 0, 255, 1)">else</span> 903.3 *<span style="color: rgba(0, 0, 0, 1)"> fY
val a </span>= 500 * (fX.pow(1 / 3.0) - fY.pow(1 / 3.0<span style="color: rgba(0, 0, 0, 1)">))
val b </span>= 200 * (fY.pow(1 / 3.0) - fZ.pow(1 / 3.0<span style="color: rgba(0, 0, 0, 1)">))
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> doubleArrayOf(l, a, b)
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 改进的ΔE76色差公式(包含亮度权重)*********</span>
<span style="color: rgba(0, 0, 0, 1)"> fun deltaE76(lab1: DoubleArray, lab2: DoubleArray): Double {
val (l1, a1, b1) </span>=<span style="color: rgba(0, 0, 0, 1)"> lab1
val (l2, a2, b2) </span>=<span style="color: rgba(0, 0, 0, 1)"> lab2
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 计算中间变量</span>
val c1 = sqrt(a1.pow(2) + b1.pow(2<span style="color: rgba(0, 0, 0, 1)">))
val c2 </span>= sqrt(a2.pow(2) + b2.pow(2<span style="color: rgba(0, 0, 0, 1)">))
val cAvg </span>= (c1 + c2) / 2<span style="color: rgba(0, 0, 0, 1)">
val g </span>= 0.5 * (1 - sqrt(cAvg.pow(7) / (cAvg.pow(7) + 25f.pow(7<span style="color: rgba(0, 0, 0, 1)">))))
val a1p </span>= a1 * (1 +<span style="color: rgba(0, 0, 0, 1)"> g)
val a2p </span>= a2 * (1 +<span style="color: rgba(0, 0, 0, 1)"> g)
val c1p </span>= sqrt(a1p.pow(2) + b1.pow(2<span style="color: rgba(0, 0, 0, 1)">))
val c2p </span>= sqrt(a2p.pow(2) + b2.pow(2<span style="color: rgba(0, 0, 0, 1)">))
val h1p </span>= Math.toDegrees(kotlin.math.atan2(b1, a1p)).let { <span style="color: rgba(0, 0, 255, 1)">if</span> (it < 0) it + 360 <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> it }
val h2p </span>= Math.toDegrees(kotlin.math.atan2(b2, a2p)).let { <span style="color: rgba(0, 0, 255, 1)">if</span> (it < 0) it + 360 <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> it }
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 色差计算</span>
val deltaLp = l2 -<span style="color: rgba(0, 0, 0, 1)"> l1
val deltaCp </span>= c2p -<span style="color: rgba(0, 0, 0, 1)"> c1p
val deltaHp </span>=<span style="color: rgba(0, 0, 0, 1)"> when {
c1p </span>* c2p == 0.0 -> 0.0<span style="color: rgba(0, 0, 0, 1)">
abs(h2p </span>- h1p) <= 180 -> h2p -<span style="color: rgba(0, 0, 0, 1)"> h1p
h2p </span><= h1p -> h2p - h1p + 360
<span style="color: rgba(0, 0, 255, 1)">else</span> -> h2p - h1p - 360<span style="color: rgba(0, 0, 0, 1)">
}
val deltaHpS </span>= 2 * sqrt(c1p * c2p) * sin(Math.toRadians(deltaHp) / 2<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 加权计算</span>
val lAvg = (l1 + l2) / 2<span style="color: rgba(0, 0, 0, 1)">
val cpAvg </span>= (c1p + c2p) / 2<span style="color: rgba(0, 0, 0, 1)">
val hpAvg </span>=<span style="color: rgba(0, 0, 0, 1)"> when {
c1p </span>* c2p == 0.0 -> h1p +<span style="color: rgba(0, 0, 0, 1)"> h2p
abs(h1p </span>- h2p) > 180 -> (h1p + h2p + 360) / 2
<span style="color: rgba(0, 0, 255, 1)">else</span> -> (h1p + h2p) / 2<span style="color: rgba(0, 0, 0, 1)">
}.let { </span><span style="color: rgba(0, 0, 255, 1)">if</span> (it >= 360) it - 360 <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> it }
val t </span>= 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<span style="color: rgba(0, 0, 0, 1)">))
val sl </span>= 1 + (0.015 * (lAvg - 50).pow(2)) / sqrt(20 + (lAvg - 50).pow(2<span style="color: rgba(0, 0, 0, 1)">))
val sc </span>= 1 + 0.045 *<span style="color: rgba(0, 0, 0, 1)"> cpAvg
val sh </span>= 1 + 0.015 * cpAvg *<span style="color: rgba(0, 0, 0, 1)"> t
val deltaTheta </span>= 30 * exp(-((hpAvg - 275) / 25).pow(2<span style="color: rgba(0, 0, 0, 1)">))
val rc </span>= 2 * sqrt(cpAvg.pow(7) / (cpAvg.pow(7) + 25f.pow(7<span style="color: rgba(0, 0, 0, 1)">)))
val rt </span>= -rc * sin(2 *<span style="color: rgba(0, 0, 0, 1)"> Math.toRadians(deltaTheta))
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> sqrt(
(deltaLp </span>/ sl).pow(2) +<span style="color: rgba(0, 0, 0, 1)">
(deltaCp </span>/ sc).pow(2) +<span style="color: rgba(0, 0, 0, 1)">
(deltaHpS </span>/ sh).pow(2) +<span style="color: rgba(0, 0, 0, 1)">
rt </span>* (deltaCp / sc) * (deltaHpS /<span style="color: rgba(0, 0, 0, 1)"> sh)
)
}
</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
* CIEDE2000色差公式实现
* 更符合人眼感知的颜色差异计算
* </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> lab1 第一个颜色的Lab值
* </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> lab2 第二个颜色的Lab值
* </span><span style="color: rgba(128, 128, 128, 1)">@return</span><span style="color: rgba(0, 128, 0, 1)"> Double 色差值(ΔE越小颜色越接近)
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
fun deltaE2000(lab1: DoubleArray, lab2: DoubleArray): Double {
val (l1, a1, b1) </span>=<span style="color: rgba(0, 0, 0, 1)"> lab1
val (l2, a2, b2) </span>=<span style="color: rgba(0, 0, 0, 1)"> lab2
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 步骤1:计算色度</span>
val c1 = sqrt(a1.pow(2) + b1.pow(2<span style="color: rgba(0, 0, 0, 1)">))
val c2 </span>= sqrt(a2.pow(2) + b2.pow(2<span style="color: rgba(0, 0, 0, 1)">))
val cAvg </span>= (c1 + c2) / 2.0
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 步骤2:计算补偿因子</span>
val g = 0.5 * (1.0 - sqrt(cAvg.pow(7) / (cAvg.pow(7) + 25.0.pow(7<span style="color: rgba(0, 0, 0, 1)">))))
val a1Prime </span>= a1 * (1.0 +<span style="color: rgba(0, 0, 0, 1)"> g)
val a2Prime </span>= a2 * (1.0 +<span style="color: rgba(0, 0, 0, 1)"> g)
val c1Prime </span>= sqrt(a1Prime.pow(2) + b1.pow(2<span style="color: rgba(0, 0, 0, 1)">))
val c2Prime </span>= sqrt(a2Prime.pow(2) + b2.pow(2<span style="color: rgba(0, 0, 0, 1)">))
val cBarPrime </span>= (c1Prime + c2Prime) / 2.0
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 步骤3:计算色调角</span>
val h1Prime = toDegrees(atan2(b1, a1Prime)).let { <span style="color: rgba(0, 0, 255, 1)">if</span> (it < 0) it + 360.0 <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> it }
val h2Prime </span>= toDegrees(atan2(b2, a2Prime)).let { <span style="color: rgba(0, 0, 255, 1)">if</span> (it < 0) it + 360.0 <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> it }
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 步骤4:计算基本差异</span>
val deltaLPrime = l2 -<span style="color: rgba(0, 0, 0, 1)"> l1
val deltaCPrime </span>= c2Prime -<span style="color: rgba(0, 0, 0, 1)"> c1Prime
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 步骤5:计算色调差</span>
var deltaHPrime = 0.0
<span style="color: rgba(0, 0, 255, 1)">if</span> (c1Prime * c2Prime != 0.0<span style="color: rgba(0, 0, 0, 1)">) {
val deltaHPrimeRaw </span>=<span style="color: rgba(0, 0, 0, 1)"> when {
abs(h2Prime </span>- h1Prime) <= 180.0 -> h2Prime -<span style="color: rgba(0, 0, 0, 1)"> h1Prime
h2Prime </span><= h1Prime -> h2Prime - h1Prime + 360.0
<span style="color: rgba(0, 0, 255, 1)">else</span> -> h2Prime - h1Prime - 360.0<span style="color: rgba(0, 0, 0, 1)">
}
deltaHPrime </span>= 2.0 * sqrt(c1Prime * c2Prime) * sin(toRadians(deltaHPrimeRaw) / 2.0<span style="color: rgba(0, 0, 0, 1)">)
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 步骤6:计算加权平均值</span>
val lBar = (l1 + l2) / 2.0<span style="color: rgba(0, 0, 0, 1)">
val cBar </span>= (c1 + c2) / 2.0
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 步骤7:计算色调平均值</span>
val hBarPrime =<span style="color: rgba(0, 0, 0, 1)"> when {
abs(h1Prime </span>- h2Prime) > 180.0 -> (h1Prime + h2Prime + 360.0) / 2.0
<span style="color: rgba(0, 0, 255, 1)">else</span> -> (h1Prime + h2Prime) / 2.0<span style="color: rgba(0, 0, 0, 1)">
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 步骤8:计算补偿项</span>
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<span style="color: rgba(0, 0, 0, 1)">))
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 步骤9:计算旋转项</span>
val deltaTheta = 30.0 * exp(-((hBarPrime - 275.0) / 25.0).pow(2<span style="color: rgba(0, 0, 0, 1)">))
val rc </span>= 2.0 * sqrt(cBarPrime.pow(7) / (cBarPrime.pow(7) + 25.0.pow(7<span style="color: rgba(0, 0, 0, 1)">)))
val rt </span>= -rc * sin(2.0 *<span style="color: rgba(0, 0, 0, 1)"> toRadians(deltaTheta))
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 步骤10:计算权重因子</span>
val sl = 1.0 + (0.015 * (lBar - 50.0).pow(2) / sqrt(20.0 + (lBar - 50.0).pow(2<span style="color: rgba(0, 0, 0, 1)">)))
val sc </span>= 1.0 + 0.045 *<span style="color: rgba(0, 0, 0, 1)"> cBarPrime
val sh </span>= 1.0 + 0.015 * cBarPrime *<span style="color: rgba(0, 0, 0, 1)"> t
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 步骤11:最终色差计算</span>
val term1 = (deltaLPrime / sl).pow(2<span style="color: rgba(0, 0, 0, 1)">)
val term2 </span>= (deltaCPrime / sc).pow(2<span style="color: rgba(0, 0, 0, 1)">)
val term3 </span>= (deltaHPrime / sh).pow(2<span style="color: rgba(0, 0, 0, 1)">)
val term4 </span>= rt * (deltaCPrime / sc) * (deltaHPrime /<span style="color: rgba(0, 0, 0, 1)"> sh)
</span><span style="color: rgba(0, 0, 255, 1)">return</span> sqrt(term1 + term2 + term3 +<span style="color: rgba(0, 0, 0, 1)"> term4)
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 优化的伽马校正函数</span>
fun preciseGammaExpand(c: Double) = <span style="color: rgba(0, 0, 255, 1)">if</span> (c <= 0.04045<span style="color: rgba(0, 0, 0, 1)">) {
c </span>/ 12.92<span style="color: rgba(0, 0, 0, 1)">
} </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
((c </span>+ 0.055) / 1.055).pow(2.4<span style="color: rgba(0, 0, 0, 1)">)
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 优化的Lab转换函数</span>
fun preciseF(t: Double) = <span style="color: rgba(0, 0, 255, 1)">if</span> (t > 0.008856<span style="color: rgba(0, 0, 0, 1)">) {
t.pow(</span>1.0 / 3.0<span style="color: rgba(0, 0, 0, 1)">)
} </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
(</span>7.787 * t) + (16.0 / 116.0<span style="color: rgba(0, 0, 0, 1)">)
}
</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
* 优化的RGB转Lab转换方法
* 使用更精确的浮点数计算和标准化处理
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
fun rgbToLabOptimized(r: Int, g: Int, b: Int): DoubleArray {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用更精确的归一化</span>
val rNormalized = r / 255.0<span style="color: rgba(0, 0, 0, 1)">
val gNormalized </span>= g / 255.0<span style="color: rgba(0, 0, 0, 1)">
val bNormalized </span>= b / 255.0
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 应用伽马校正</span>
val rLinear =<span style="color: rgba(0, 0, 0, 1)"> preciseGammaExpand(rNormalized)
val gLinear </span>=<span style="color: rgba(0, 0, 0, 1)"> preciseGammaExpand(gNormalized)
val bLinear </span>=<span style="color: rgba(0, 0, 0, 1)"> preciseGammaExpand(bNormalized)
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用D65标准光源的精确XYZ转换矩阵</span>
val x = 0.4124564 * rLinear + 0.3575761 * gLinear + 0.1804375 *<span style="color: rgba(0, 0, 0, 1)"> bLinear
val y </span>= 0.2126729 * rLinear + 0.7151522 * gLinear + 0.0721750 *<span style="color: rgba(0, 0, 0, 1)"> bLinear
val z </span>= 0.0193339 * rLinear + 0.1191920 * gLinear + 0.9503041 *<span style="color: rgba(0, 0, 0, 1)"> bLinear
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用CIE标准参考白点</span>
val refX = 0.95047<span style="color: rgba(0, 0, 0, 1)">
val refY </span>= 1.00000<span style="color: rgba(0, 0, 0, 1)">
val refZ </span>= 1.08883
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 计算相对值</span>
val xRelative = x /<span style="color: rgba(0, 0, 0, 1)"> refX
val yRelative </span>= y /<span style="color: rgba(0, 0, 0, 1)"> refY
val zRelative </span>= z /<span style="color: rgba(0, 0, 0, 1)"> refZ
val fx </span>=<span style="color: rgba(0, 0, 0, 1)"> preciseF(xRelative)
val fy </span>=<span style="color: rgba(0, 0, 0, 1)"> preciseF(yRelative)
val fz </span>=<span style="color: rgba(0, 0, 0, 1)"> preciseF(zRelative)
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 计算Lab值</span>
val l = (116.0 * fy) - 16.0<span style="color: rgba(0, 0, 0, 1)">
val a </span>= 500.0 * (fx -<span style="color: rgba(0, 0, 0, 1)"> fy)
val bLab </span>= 200.0 * (fy -<span style="color: rgba(0, 0, 0, 1)"> fz)
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> doubleArrayOf(l, a, bLab)
}
</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
* 完整的CIEDE2000色差公式实现
* 包含所有补偿项和旋转项
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
fun deltaE2000Complete(lab1: DoubleArray, lab2: DoubleArray): Double {
val (l1, a1, b1) </span>=<span style="color: rgba(0, 0, 0, 1)"> lab1
val (l2, a2, b2) </span>=<span style="color: rgba(0, 0, 0, 1)"> lab2
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 步骤1:计算色度值</span>
val c1 = sqrt(a1.pow(2) + b1.pow(2<span style="color: rgba(0, 0, 0, 1)">))
val c2 </span>= sqrt(a2.pow(2) + b2.pow(2<span style="color: rgba(0, 0, 0, 1)">))
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 步骤2:计算平均值</span>
val lBar = (l1 + l2) / 2.0<span style="color: rgba(0, 0, 0, 1)">
val cBar </span>= (c1 + c2) / 2.0
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 步骤3:计算补偿因子</span>
val g = 0.5 * (1.0 - sqrt(cBar.pow(7) / (cBar.pow(7) + 25.0.pow(7<span style="color: rgba(0, 0, 0, 1)">))))
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 步骤4:计算调整后的a'值</span>
val a1Prime = a1 * (1.0 +<span style="color: rgba(0, 0, 0, 1)"> g)
val a2Prime </span>= a2 * (1.0 +<span style="color: rgba(0, 0, 0, 1)"> g)
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 步骤5:计算调整后的色度值</span>
val c1Prime = sqrt(a1Prime.pow(2) + b1.pow(2<span style="color: rgba(0, 0, 0, 1)">))
val c2Prime </span>= sqrt(a2Prime.pow(2) + b2.pow(2<span style="color: rgba(0, 0, 0, 1)">))
val cBarPrime </span>= (c1Prime + c2Prime) / 2.0
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 步骤6:计算色调角</span>
val h1Prime =<span style="color: rgba(0, 0, 0, 1)"> toDegrees(atan2(b1, a1Prime)).let {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (it < 0) it + 360.0 <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> it
}
val h2Prime </span>=<span style="color: rgba(0, 0, 0, 1)"> toDegrees(atan2(b2, a2Prime)).let {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (it < 0) it + 360.0 <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> it
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 步骤7:计算基本差异</span>
val deltaLPrime = l2 -<span style="color: rgba(0, 0, 0, 1)"> l1
val deltaCPrime </span>= c2Prime -<span style="color: rgba(0, 0, 0, 1)"> c1Prime
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 步骤8:计算色调差</span>
var deltaHPrime = 0.0
<span style="color: rgba(0, 0, 255, 1)">if</span> (c1Prime != 0.0 && c2Prime != 0.0<span style="color: rgba(0, 0, 0, 1)">) {
val deltaHPrimeRaw </span>=<span style="color: rgba(0, 0, 0, 1)"> when {
abs(h2Prime </span>- h1Prime) <= 180.0 -> h2Prime -<span style="color: rgba(0, 0, 0, 1)"> h1Prime
h2Prime </span><= h1Prime -> h2Prime - h1Prime + 360.0
<span style="color: rgba(0, 0, 255, 1)">else</span> -> h2Prime - h1Prime - 360.0<span style="color: rgba(0, 0, 0, 1)">
}
deltaHPrime </span>= 2.0 * sqrt(c1Prime * c2Prime) * sin(toRadians(deltaHPrimeRaw) / 2.0<span style="color: rgba(0, 0, 0, 1)">)
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 步骤9:计算色调平均值</span>
val hBarPrime =<span style="color: rgba(0, 0, 0, 1)"> when {
abs(h1Prime </span>- h2Prime) > 180.0 -> (h1Prime + h2Prime + 360.0) / 2.0
<span style="color: rgba(0, 0, 255, 1)">else</span> -> (h1Prime + h2Prime) / 2.0<span style="color: rgba(0, 0, 0, 1)">
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 步骤10:计算补偿项</span>
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<span style="color: rgba(0, 0, 0, 1)">))
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 步骤11:计算旋转项</span>
val deltaTheta = 30.0 * exp(-((hBarPrime - 275.0) / 25.0).pow(2<span style="color: rgba(0, 0, 0, 1)">))
val rc </span>= 2.0 * sqrt(cBarPrime.pow(7) / (cBarPrime.pow(7) + 25.0.pow(7<span style="color: rgba(0, 0, 0, 1)">)))
val rt </span>= -rc * sin(2.0 *<span style="color: rgba(0, 0, 0, 1)"> toRadians(deltaTheta))
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 步骤12:计算权重因子</span>
val sl = 1.0 + (0.015 * (lBar - 50.0).pow(2) / sqrt(20.0 + (lBar - 50.0).pow(2<span style="color: rgba(0, 0, 0, 1)">)))
val sc </span>= 1.0 + 0.045 *<span style="color: rgba(0, 0, 0, 1)"> cBarPrime
val sh </span>= 1.0 + 0.015 * cBarPrime *<span style="color: rgba(0, 0, 0, 1)"> t
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 步骤13:最终色差计算</span>
val term1 = (deltaLPrime / sl).pow(2<span style="color: rgba(0, 0, 0, 1)">)
val term2 </span>= (deltaCPrime / sc).pow(2<span style="color: rgba(0, 0, 0, 1)">)
val term3 </span>= (deltaHPrime / sh).pow(2<span style="color: rgba(0, 0, 0, 1)">)
val term4 </span>= rt * (deltaCPrime / sc) * (deltaHPrime /<span style="color: rgba(0, 0, 0, 1)"> sh)
</span><span style="color: rgba(0, 0, 255, 1)">return</span> sqrt(term1 + term2 + term3 +<span style="color: rgba(0, 0, 0, 1)"> term4)
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<div class="cnblogs_code"><img id="code_img_closed_d44521c4-0af6-4442-8a19-02381ce7c747" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_d44521c4-0af6-4442-8a19-02381ce7c747" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_d44521c4-0af6-4442-8a19-02381ce7c747" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 0, 1)">{
"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
}
]
}</span></pre>
</div>
<span class="cnblogs_code_collapse">color_rgb_256.json</span></div>
<div class="cnblogs_code"><img id="code_img_closed_0de0e9cb-ea55-4535-b736-ac376a8ceff6" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_0de0e9cb-ea55-4535-b736-ac376a8ceff6" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_0de0e9cb-ea55-4535-b736-ac376a8ceff6" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.graphics.Bitmap
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.graphics.Color
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> android.util.Log
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> com.blankj.utilcode.util.GsonUtils
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> com.blankj.utilcode.util.ScreenUtils
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> com.blankj.utilcode.util.Utils
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> com.google.gson.Gson
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> com.google.gson.reflect.TypeToken
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> kotlinx.coroutines.Dispatchers
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> kotlinx.coroutines.MainScope
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> kotlinx.coroutines.launch
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> java.util.concurrent.CopyOnWriteArrayList
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> kotlin.math.abs
object AmbientLightColorPickManager {
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">const</span> val TAG = "AmbientLightColorPickManager"
<span style="color: rgba(0, 0, 255, 1)">private</span> var scope =<span style="color: rgba(0, 0, 0, 1)"> MainScope()
</span><span style="color: rgba(0, 0, 255, 1)">private</span> val mWidth = ScreenUtils.getScreenWidth() / 2
<span style="color: rgba(0, 0, 255, 1)">private</span> val mHeight = ScreenUtils.getScreenHeight() / 2
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 256色</span>
<span style="color: rgba(0, 0, 255, 1)">private</span> val tableList = mutableListOf<ColorTableBean><span style="color: rgba(0, 0, 0, 1)">()
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 原图色</span>
<span style="color: rgba(0, 0, 255, 1)">private</span> val originalList = CopyOnWriteArrayList<ColorTableBean><span style="color: rgba(0, 0, 0, 1)">()
var test1Listener: ((Int) </span>-> Unit)? = <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">
var test2Listener: ((Int, Int, Int) </span>-> Unit)? = <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">
@JvmStatic
fun init() {
log(</span>"$TAG init"<span style="color: rgba(0, 0, 0, 1)">)
scope.launch(Dispatchers.IO) {
initHsvColor()
}
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> fun initHsvColor() {
tableList.clear()
runCatching {
val json </span>=<span style="color: rgba(0, 0, 0, 1)"> SharedPreferencesUtils.getRGB256HsvColor(Utils.getApp())
val listType </span>= object : TypeToken<MutableList<ColorTableBean>><span style="color: rgba(0, 0, 0, 1)">() {}.type
Gson().fromJson</span><MutableList<ColorTableBean>>(json, listType)?<span style="color: rgba(0, 0, 0, 1)">.let {
tableList.addAll(it)
log(</span>"initHsvColor xml list size=${tableList.size}"<span style="color: rgba(0, 0, 0, 1)">)
}
}.getOrElse {
Log.e(TAG, </span>"initHsvColor Exception ${it.message}"<span style="color: rgba(0, 0, 0, 1)">)
}
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (tableList.isEmpty()) {
saveHsvColor().let {
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (it.isNotEmpty()) {
tableList.addAll(it)
}
}
log(</span>"initHsvColor json list size=${tableList.size}"<span style="color: rgba(0, 0, 0, 1)">)
}
}
</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> 将本地rgb色值转换成hsv保存到本地 </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">private</span> fun saveHsvColor(): MutableList<ColorTableBean><span style="color: rgba(0, 0, 0, 1)"> {
log(</span>"saveHsvColor"<span style="color: rgba(0, 0, 0, 1)">)
val hsvList </span>= mutableListOf<ColorTableBean><span style="color: rgba(0, 0, 0, 1)">()
runCatching {
val assetManager </span>=<span style="color: rgba(0, 0, 0, 1)"> Utils.getApp().assets
val file </span>= assetManager.open("color_rgb_256.json"<span style="color: rgba(0, 0, 0, 1)">)
val jsonStr </span>=<span style="color: rgba(0, 0, 0, 1)"> file.bufferedReader().readText()
file.close()
val bean </span>= Gson().fromJson(jsonStr, AmbientLightList::<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">.java)
</span><span style="color: rgba(0, 0, 255, 1)">for</span> (i in 0<span style="color: rgba(0, 0, 0, 1)"> until bean.list.size) {
bean.list.apply {
val myColor </span>=<span style="color: rgba(0, 0, 0, 1)"> Color.rgb(r, g, b)
val hsvColors </span>= FloatArray(3<span style="color: rgba(0, 0, 0, 1)">)
Color.colorToHSV(myColor, hsvColors)
val lab </span>=<span style="color: rgba(0, 0, 0, 1)"> ColorEabLabUtils.rGBToLab(r, g, b)
val bean </span>=<span style="color: rgba(0, 0, 0, 1)"> ColorTableBean(hsvColors, lab, myColor)
hsvList.add(bean)
}
}
val json </span>=<span style="color: rgba(0, 0, 0, 1)"> Gson().toJson(hsvList)
log(</span>"saveHsvColor hsvListSize=${hsvList.size}"<span style="color: rgba(0, 0, 0, 1)">)
SharedPreferencesUtils.setRGB256HsvColor(Utils.getApp(), json)
}.getOrElse {
Log.e(TAG, </span>"saveHsvColor Exception ${it.message}"<span style="color: rgba(0, 0, 0, 1)">)
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> hsvList
}
</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> 设置氛围灯 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
@JvmStatic
fun setAmbientLight(displayId: Int, index: Int) {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (displayId != DisplayParameter.DISPLAY_CSD.displayId) <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">
log(</span>"setAmbientLight displayId=$displayId"<span style="color: rgba(0, 0, 0, 1)">)
scope.launch(Dispatchers.IO) {
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (originalList.isEmpty()) {
Log.w(TAG, </span>"setAmbientLight hueList is null"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">@launch
}
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (index < 0 || index >=<span style="color: rgba(0, 0, 0, 1)"> originalList.size) {
Log.w(TAG, </span>"setAmbientLight 索引异常"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">@launch
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 氛围灯取色</span>
<span style="color: rgba(0, 0, 0, 1)"> setBytesFunctionValue(index)
}
}
@JvmStatic
fun switchLight(isOn: Boolean) {
log(</span>"switchLight isOn=$isOn"<span style="color: rgba(0, 0, 0, 1)">)
}
</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> 初始化资源 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
@JvmStatic
fun loadData(displayId: Int, pictures: List</span><String><span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (displayId != DisplayParameter.DISPLAY_CSD.displayId) <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">
log(</span>"loadData pictures size=${pictures.size} pictures $pictures"<span style="color: rgba(0, 0, 0, 1)">)
originalList.clear()
</span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> ((_, picture) in pictures.withIndex()) {
runCatching {
val bitmap </span>=<span style="color: rgba(0, 0, 0, 1)"> GlideCacheUtils.loadImageAsBitmap(picture, mWidth, mHeight)
originalList.add(generate(bitmap))
}.getOrElse {
Log.e(TAG, </span>"loadData exception ${it.message}"<span style="color: rgba(0, 0, 0, 1)">)
}
}
log(</span>"loadData hueList size=${originalList.size}"<span style="color: rgba(0, 0, 0, 1)">)
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> fun setFunctionValue(functionId: Int, value: Int, zone: Int) {
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> fun setBytesFunctionValue(index: Int) {
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
originalList.let {
test1Listener</span>?<span style="color: rgba(0, 0, 0, 1)">.invoke(it.color)
test2Listener</span>?<span style="color: rgba(0, 0, 0, 1)">.invoke(
findColor(it.hue()).colorTip, findLabColor(it), findTestColor(it.labArray)
)
}
} </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (e: Exception) {
Log.e(TAG, </span>"setBytesFunctionValue Exception $e"<span style="color: rgba(0, 0, 0, 1)">)
}
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> fun findColor(bgHue: Float): ColorTipBean {
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (tableList.isEmpty()) {
Log.w(TAG, </span>"findColor hsvList is null"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> ColorTipBean(Color.WHITE)
}
var result </span>= tableList
var minDiff </span>= abs(result.hue() -<span style="color: rgba(0, 0, 0, 1)"> bgHue)
</span><span style="color: rgba(0, 0, 255, 1)">for</span> (i in 0<span style="color: rgba(0, 0, 0, 1)"> until tableList.size) {
val currentDiff </span>= abs(tableList.hue() -<span style="color: rgba(0, 0, 0, 1)"> bgHue)
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (currentDiff <<span style="color: rgba(0, 0, 0, 1)"> minDiff) {
minDiff </span>=<span style="color: rgba(0, 0, 0, 1)"> currentDiff
result </span>=<span style="color: rgba(0, 0, 0, 1)"> tableList
}
}
log(</span>"findColor bgHue=$bgHue,minDiff=$minDiff,result=$result"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> ColorTipBean(result.color)
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> fun findLabColor(bean: ColorTableBean): Int {
var color </span>=<span style="color: rgba(0, 0, 0, 1)"> Color.WHITE
var minDiff </span>=<span style="color: rgba(0, 0, 0, 1)"> Double.MAX_VALUE
tableList.forEachIndexed { index, it </span>-><span style="color: rgba(0, 0, 0, 1)">
val distance </span>=<span style="color: rgba(0, 0, 0, 1)"> ColorEabLabUtils.deltaE76(it.labArray, bean.labArray)
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (distance <<span style="color: rgba(0, 0, 0, 1)"> minDiff) {
minDiff </span>=<span style="color: rgba(0, 0, 0, 1)"> distance
color </span>=<span style="color: rgba(0, 0, 0, 1)"> it.color
}
}
log(</span>"findLabColor minDiff=$minDiff,color=[${bean.color},$color]"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (minDiff < 26) color <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> findColor(bean.hue()).colorTip
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> fun findTestColor(list: DoubleArray): Int {
var color </span>=<span style="color: rgba(0, 0, 0, 1)"> Color.WHITE
var minDiff </span>=<span style="color: rgba(0, 0, 0, 1)"> Double.MAX_VALUE
tableList.forEachIndexed { index, it </span>-><span style="color: rgba(0, 0, 0, 1)">
val distance </span>=<span style="color: rgba(0, 0, 0, 1)"> ColorEabLabUtils.deltaE76(it.labArray, list)
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (distance <<span style="color: rgba(0, 0, 0, 1)"> minDiff) {
minDiff </span>=<span style="color: rgba(0, 0, 0, 1)"> distance
color </span>=<span style="color: rgba(0, 0, 0, 1)"> it.color
}
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> color
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> fun getColors(bean: ColorTableBean): ByteArray {
val result </span>= mutableListOf<ColorTipBean><span style="color: rgba(0, 0, 0, 1)">()
val colorBean </span>=<span style="color: rgba(0, 0, 0, 1)"> ColorTipBean(findLabColor(bean))
result.add(colorBean)
result.add(colorBean)
result.add(colorBean)
val json </span>=<span style="color: rgba(0, 0, 0, 1)"> GsonUtils.toJson(ColorLightBean(result).list)
log(</span>"setBytesFunctionValue json=$json"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> json.toByteArray()
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> fun generate(newMap: Bitmap): ColorTableBean {
val dominantColor </span>=<span style="color: rgba(0, 0, 0, 1)"> ColorEabLabUtils.getPerceptuallyDominantColor(newMap)
val hsvArray </span>= FloatArray(3<span style="color: rgba(0, 0, 0, 1)">)
Color.colorToHSV(dominantColor, hsvArray)
val labArray </span>=<span style="color: rgba(0, 0, 0, 1)"> ColorEabLabUtils.rGBToLab(dominantColor)
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> ColorTableBean(hsvArray, labArray, dominantColor)
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span> fun log(str: String) =<span style="color: rgba(0, 0, 0, 1)"> Log.d(TAG, str)
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p> </p><br><br>
来源:https://www.cnblogs.com/LiuZhen/p/19157538
頁:
[1]