氛围灯动态屏保取色方案一
<p>氛围灯并不支持所有的颜色,只能支持256色,所以在取到图片颜色后需要根据结果颜色去跟氛围灯所支持的256色对比,取最接近的结果色,然后同步到氛围灯显示</p><h4>取色流程</h4>
<p>取色需要用到原生 Palette.from(bitmap).generate() 方法,通过量化算法分析位图的像素颜色分布,提取最具代表性的颜色组合,也有异步获取方法,下面方法都处于子线程,所以这里直接使用同步方法</p>
<p>查看 androidx.palette.graphics.Palette 源码可以得知,该方法默认提取16种颜色样本</p>
<p><img src="https://img2024.cnblogs.com/blog/583064/202510/583064-20251016142207533-1863067305.png"></p>
<p>需要确保取色精准度,16可能错过次要但视觉显著的颜色,过高又会导致耗时,所以这里使用24</p>
<p>针对原图还需要缩放处理,但是不宜过度,否则对准确度会有影响,这里对2560分辨率的图片缩小三分之一处理</p>
<div class="cnblogs_code"><img src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_15a20326-c575-4430-8fd9-151c999bca8f" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_15a20326-c575-4430-8fd9-151c999bca8f" class="cnblogs_code_hide">
<pre><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, 0, 0, 1)">
Glide.with(Utils.getApp())
.asBitmap()
.load(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> File(path))
.override(width, height)
.centerCrop()
.skipMemoryCache(</span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.submit(width, height)
.get();</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>对氛围灯的256色进行缓存处理,先新建 <strong>color_rgb_256</strong>.json 文件,将rgb色值保存,用于后续转换对比</p>
<p><img alt="image" width="223" height="224" loading="lazy" src="https://img2024.cnblogs.com/blog/583064/202510/583064-20251016144020263-1440713970.png"></p>
<p>初始化时解析成hsv缓存到本地集合中</p>
<div class="cnblogs_code"><img src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_c53a3954-1333-4736-8984-db32011f318f" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_c53a3954-1333-4736-8984-db32011f318f" class="cnblogs_code_hide">
<pre> <span style="color: rgba(0, 0, 255, 1)">private</span> fun saveHsvColor(): MutableList<HsvColor><span style="color: rgba(0, 0, 0, 1)"> {
log(</span>"saveHsvColor"<span style="color: rgba(0, 0, 0, 1)">)
val hsvList </span>= mutableListOf<HsvColor><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)
val hsvColors </span>= FloatArray(3<span style="color: rgba(0, 0, 0, 1)">)
</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)
Color.colorToHSV(myColor, hsvColors)
hsvList.add(HsvColor(hsvColors[</span>0], hsvColors, hsvColors))
}
}
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></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>此文件颜色不会变,所以不用重复操作,判断首次转换就行</p>
<div class="cnblogs_code"><img id="code_img_closed_f2110559-8c58-472f-af08-95a0aeac29ba" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_f2110559-8c58-472f-af08-95a0aeac29ba" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_f2110559-8c58-472f-af08-95a0aeac29ba" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> fun initHsvColor() {
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (hsvTableList.isEmpty()) {
runCatching {
val json </span>=<span style="color: rgba(0, 0, 0, 1)"> SharedPreferencesUtils.getRGB256HsvColor(Utils.getApp())
val listType </span>= object : TypeToken<MutableList<HsvColor>><span style="color: rgba(0, 0, 0, 1)">() {}.type
Gson().fromJson</span><MutableList<HsvColor>>(json, listType)?<span style="color: rgba(0, 0, 0, 1)">.let {
hsvTableList.addAll(it)
log(</span>"initHsvColor xml list size=${hsvTableList.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)"> (hsvTableList.isEmpty()) {
saveHsvColor().let {
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (it.isNotEmpty()) {
hsvTableList.addAll(it)
}
}
log(</span>"initHsvColor json list size=${hsvTableList.size}"<span style="color: rgba(0, 0, 0, 1)">)
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>耗时操作需要放在子线程</p>
<div class="cnblogs_code"><img id="code_img_closed_c3370a14-cd58-4c4d-bb8e-8e5d657299c2" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_c3370a14-cd58-4c4d-bb8e-8e5d657299c2" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_c3370a14-cd58-4c4d-bb8e-8e5d657299c2" class="cnblogs_code_hide">
<pre><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) {
hsvTableList.clear()
initHsvColor()
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>后面对图片进行取色,见下面方案</p>
<p>取色后,跟256色进行就近查找,所以需要转换成hsv,取 hue 进行对比</p>
<div class="cnblogs_code"><img id="code_img_closed_bb0ddb00-736a-4189-b450-c75bba04bb67" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_bb0ddb00-736a-4189-b450-c75bba04bb67" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_bb0ddb00-736a-4189-b450-c75bba04bb67" class="cnblogs_code_hide">
<pre><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)"> (hsvTableList.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>= hsvTableList
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 hsvTableList.size) {
val currentDiff </span>= abs(hsvTableList.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)"> hsvTableList
}
}
log(</span>"findColor bgHue=$bgHue,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(
Color.HSVToColor(floatArrayOf(result.hue, result.saturation, result.value))
)
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>拿到结果后,通过信号下设到氛围灯显示</p>
<h3>准确度</h3>
<p>想要达到联动效果,需要确保取色结果的准确度,原生方案使用 getDominantColor 直接获取主色,但是大部分结果差异较大,下面提供了几种方案对比</p>
<h4>方案一:</h4>
<p>通过原生提供的方法直接获取图片主色</p>
<div class="cnblogs_code"><img id="code_img_closed_3dffb8a4-b933-425b-a0be-be9ed65f1ef9" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_3dffb8a4-b933-425b-a0be-be9ed65f1ef9" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_3dffb8a4-b933-425b-a0be-be9ed65f1ef9" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 0, 1)">Palette.from(newMap).generate().apply {
val dominantColor </span>=<span style="color: rgba(0, 0, 0, 1)"> getDominantColor(Color.WHITE)
val hsvColorArray </span>= FloatArray(3<span style="color: rgba(0, 0, 0, 1)">)
val hsv </span>=<span style="color: rgba(0, 0, 0, 1)"> colorToHSV(dominantColor, hsvColorArray)
Log.d(TAG, </span>"dominantColor $dominantColor hsv $hsv"<span style="color: rgba(0, 0, 0, 1)">)
result.fill(hsv)
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>getDominantColor 方法直接取的 mDominantSwatch.getRgb</p>
<div class="cnblogs_code"><img id="code_img_closed_c2a1a4e8-764f-46d7-be90-198d4f455c4d" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_c2a1a4e8-764f-46d7-be90-198d4f455c4d" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_c2a1a4e8-764f-46d7-be90-198d4f455c4d" class="cnblogs_code_hide">
<pre> <span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
* Returns the color of the dominant swatch from the palette, as an RGB packed int.
*
* </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> defaultColor value to return if the swatch isn't available
* </span><span style="color: rgba(128, 128, 128, 1)">@see</span><span style="color: rgba(0, 128, 0, 1)"> #getDominantSwatch()
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
@ColorInt
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span> getDominantColor(@ColorInt <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> defaultColor) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> mDominantSwatch != <span style="color: rgba(0, 0, 255, 1)">null</span> ?<span style="color: rgba(0, 0, 0, 1)"> mDominantSwatch.getRgb() : defaultColor;
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>而 mDominantSwatch 则根据色块 population 排序的结果</p>
<div class="cnblogs_code"><img id="code_img_closed_3e510bb7-6cc6-428e-9e33-693ca2a01527" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_3e510bb7-6cc6-428e-9e33-693ca2a01527" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_3e510bb7-6cc6-428e-9e33-693ca2a01527" class="cnblogs_code_hide">
<pre> Palette(List<Swatch> swatches, List<Target><span style="color: rgba(0, 0, 0, 1)"> targets) {
mSwatches </span>=<span style="color: rgba(0, 0, 0, 1)"> swatches;
mTargets </span>=<span style="color: rgba(0, 0, 0, 1)"> targets;
mUsedColors </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> SparseBooleanArray();
mSelectedSwatches </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> ArrayMap<><span style="color: rgba(0, 0, 0, 1)">();
mDominantSwatch </span>=<span style="color: rgba(0, 0, 0, 1)"> findDominantSwatch();
}
@Nullable
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> Swatch findDominantSwatch() {
</span><span style="color: rgba(0, 0, 255, 1)">int</span> maxPop =<span style="color: rgba(0, 0, 0, 1)"> Integer.MIN_VALUE;
Swatch maxSwatch </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = 0, count = mSwatches.size(); i < count; i++<span style="color: rgba(0, 0, 0, 1)">) {
Swatch swatch </span>=<span style="color: rgba(0, 0, 0, 1)"> mSwatches.get(i);
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (swatch.getPopulation() ><span style="color: rgba(0, 0, 0, 1)"> maxPop) {
maxSwatch </span>=<span style="color: rgba(0, 0, 0, 1)"> swatch;
maxPop </span>=<span style="color: rgba(0, 0, 0, 1)"> swatch.getPopulation();
}
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> maxSwatch;
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>假设氛围灯需要多个取色,可以直接从 mSwatches 颜色集合中按 population 排序获取</p>
<p><img alt="image" width="377" height="198" loading="lazy" src="https://img2024.cnblogs.com/blog/583064/202510/583064-20251016141056588-972744233.png"></p>
<p>Swatch 代表的颜色在图片中的权重占比(多个小红点可能被聚类到同一个红色 Swatch)</p>
<p>经自测验证,改方案准确度不够,偏差较大,特别是在氛围灯所支持的256色中,查找出的相近结果出入较大,整体准确度不够</p>
<p>因为实际环境中无法看到氛围灯(车机上效果),所以在左上角显示测试结果,方便查看</p>
<p><img src="https://img2024.cnblogs.com/blog/583064/202510/583064-20251016150025255-182630436.png"></p>
<p>图片中,左上角测试区域,中间上面是图片主色,下面是通过主色映射的氛围灯颜色,很显然跟图片差异较大</p>
<h4>方案二:</h4>
<p>在原生基础上使用饱和度跟亮度参与计算,避免过暗或过亮的颜色</p>
<div class="cnblogs_code"><img src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_64baff5c-ae02-4899-8be8-d0490fefe5f5" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_64baff5c-ae02-4899-8be8-d0490fefe5f5" 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>该方案将纯黑白色过滤(实际图片中纯黑白色占比很少,但是很印象色块,容易出现误差),同时避免了过亮的颜色,更突出我们肉眼看到的颜色</p>
<h4>其它方案:</h4>
<p>1、在方案二的基础上,加入色相,改进计算公式</p>
<p>2、调整图片,缩小区域,针对中心区域进行取色</p>
<p>3、自定义过滤器,针对业务情况单独处理某些图片</p>
<p>比如,可以针对纯黑白占比大于30%的进行过滤,否则不过滤</p>
<div class="cnblogs_code"><img id="code_img_closed_e8fcd774-d24d-4d2f-ac2e-8ccab14089b1" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_e8fcd774-d24d-4d2f-ac2e-8ccab14089b1" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_e8fcd774-d24d-4d2f-ac2e-8ccab14089b1" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> fun isClear(bitmap: Bitmap): Boolean {
val totalPixels </span>= bitmap.width *<span style="color: rgba(0, 0, 0, 1)"> bitmap.height
var blackCount </span>= 0.0<span style="color: rgba(0, 0, 0, 1)">
var whiteCount </span>= 0.0
<span style="color: rgba(0, 0, 255, 1)">for</span> (x in 0<span style="color: rgba(0, 0, 0, 1)"> until bitmap.width) {
</span><span style="color: rgba(0, 0, 255, 1)">for</span> (y in 0<span style="color: rgba(0, 0, 0, 1)"> until bitmap.height) {
val pixel </span>=<span style="color: rgba(0, 0, 0, 1)"> bitmap
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (pixel ==<span style="color: rgba(0, 0, 0, 1)"> Color.BLACK) {
blackCount</span>++<span style="color: rgba(0, 0, 0, 1)">
}
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (pixel ==<span style="color: rgba(0, 0, 0, 1)"> Color.WHITE) {
whiteCount</span>++<span style="color: rgba(0, 0, 0, 1)">
}
}
}
val blackRatio </span>= blackCount /<span style="color: rgba(0, 0, 0, 1)"> totalPixels
val whiteRatio </span>= whiteCount /<span style="color: rgba(0, 0, 0, 1)"> totalPixels
val isClear </span>= blackRatio > 0.3 || whiteRatio > 0.3<span style="color: rgba(0, 0, 0, 1)">
Log.d(TAG, </span>"isClear=$isClear totalPixels=$totalPixels,blackCount=$blackCount, blackRatio=${String.format("%.2f", blackRatio)},whiteRatio=${String.format("%.2f", whiteRatio)}"<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)"> isClear
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>但需要慎重,会提高计算耗时</p>
<p><img alt="image" loading="lazy" src="https://img2024.cnblogs.com/blog/583064/202510/583064-20251016151606229-937694496.png"></p>
<p><img src="https://img2024.cnblogs.com/blog/583064/202510/583064-20251016151644417-751285326.png"></p>
<p><img src="https://img2024.cnblogs.com/blog/583064/202510/583064-20251016151706917-686998766.png"></p>
<p>左上角,上面的方格代表直接从图片中读取的色值,下面的方格是映射后的色值,最左边的是方案二,中间的是方案一,右边的是替补方案</p>
<p>结论图片不多展示,经过大量图片验证,准确度最高的是方案二</p>
<div class="cnblogs_code"><img id="code_img_closed_fbc1df53-69d0-45c4-aecd-b5044ff547c3" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_fbc1df53-69d0-45c4-aecd-b5044ff547c3" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_fbc1df53-69d0-45c4-aecd-b5044ff547c3" 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)"> 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)"> 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.annotations.SerializedName
</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.Collections
</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
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> kotlin.math.sqrt
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> androidx.core.graphics.get
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, 0, 255, 1)">private</span> val hsvTableList = mutableListOf<HsvColor><span style="color: rgba(0, 0, 0, 1)">()
</span><span style="color: rgba(0, 0, 255, 1)">private</span> val hueList = CopyOnWriteArrayList<FloatArray><span style="color: rgba(0, 0, 0, 1)">()
</span><span style="color: rgba(0, 0, 255, 1)">private</span> val test1List = CopyOnWriteArrayList<FloatArray><span style="color: rgba(0, 0, 0, 1)">()
</span><span style="color: rgba(0, 0, 255, 1)">private</span> val test2List = CopyOnWriteArrayList<FloatArray><span style="color: rgba(0, 0, 0, 1)">()
</span><span style="color: rgba(0, 0, 255, 1)">private</span> val test3List = CopyOnWriteArrayList<FloatArray><span style="color: rgba(0, 0, 0, 1)">()
var test1Listener: ((Int, Int, 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) {
hsvTableList.clear()
initHsvColor()
}
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> fun initHsvColor() {
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (hsvTableList.isEmpty()) {
runCatching {
val json </span>=<span style="color: rgba(0, 0, 0, 1)"> SharedPreferencesUtils.getRGB256HsvColor(Utils.getApp())
val listType </span>= object : TypeToken<MutableList<HsvColor>><span style="color: rgba(0, 0, 0, 1)">() {}.type
Gson().fromJson</span><MutableList<HsvColor>>(json, listType)?<span style="color: rgba(0, 0, 0, 1)">.let {
hsvTableList.addAll(it)
log(</span>"initHsvColor xml list size=${hsvTableList.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)"> (hsvTableList.isEmpty()) {
saveHsvColor().let {
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (it.isNotEmpty()) {
hsvTableList.addAll(it)
}
}
log(</span>"initHsvColor json list size=${hsvTableList.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<HsvColor><span style="color: rgba(0, 0, 0, 1)"> {
log(</span>"saveHsvColor"<span style="color: rgba(0, 0, 0, 1)">)
val hsvList </span>= mutableListOf<HsvColor><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)
val hsvColors </span>= FloatArray(3<span style="color: rgba(0, 0, 0, 1)">)
</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)
Color.colorToHSV(myColor, hsvColors)
hsvList.add(HsvColor(hsvColors[</span>0], hsvColors, hsvColors))
}
}
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)"> (hueList.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)"> hueList.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, 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)"> (hsvTableList.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>= hsvTableList
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 hsvTableList.size) {
val currentDiff </span>= abs(hsvTableList.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)"> hsvTableList
}
}
log(</span>"findColor bgHue=$bgHue,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(
Color.HSVToColor(floatArrayOf(result.hue, result.saturation, result.value))
)
}
</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)">)
hueList.clear()
test1List.clear()
test2List.clear()
test3List.clear()
</span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> ((index, picture) in pictures.withIndex()) {
runCatching {
val bitmap </span>=<span style="color: rgba(0, 0, 0, 1)"> GlideCacheUtils.loadImageAsBitmap(picture, mWidth, mHeight)
testGenerate(bitmap)
val result </span>=<span style="color: rgba(0, 0, 0, 1)"> generate(bitmap)
hueList.add(result)
log(</span>"loadData add index=$index,colors=${GsonUtils.toJson(result)}"<span style="color: rgba(0, 0, 0, 1)">)
}.getOrElse {
Log.e(TAG, </span>"loadData exception ${it.message}"<span style="color: rgba(0, 0, 0, 1)">)
}
}
log(</span>"loadData hueList size=${hueList.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)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
AdapterCarManager.iCarFunction.setFunctionValue(functionId, zone, value)
} </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>"setFunctionValue 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 setBytesFunctionValue(index: Int) {
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
test1Listener</span>?<span style="color: rgba(0, 0, 0, 1)">.invoke(
Color.HSVToColor(test1List),
Color.HSVToColor(test2List),
Color.HSVToColor(test3List),
)
test2Listener</span>?<span style="color: rgba(0, 0, 0, 1)">.invoke(
findColor(test1List[</span>0<span style="color: rgba(0, 0, 0, 1)">]).colorTip,
findColor(test2List[</span>0<span style="color: rgba(0, 0, 0, 1)">]).colorTip,
findColor(test3List[</span>0<span style="color: rgba(0, 0, 0, 1)">]).colorTip,
)
} </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 getColors(list: FloatArray): ByteArray {
val result </span>= mutableListOf<ColorTipBean><span style="color: rgba(0, 0, 0, 1)">()
list.forEach {
result.add(findColor(it))
}
val json </span>=<span style="color: rgba(0, 0, 0, 1)"> GsonUtils.toJson(LightColorBean(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): FloatArray {
val result </span>= FloatArray(3<span style="color: rgba(0, 0, 0, 1)">)
Log.w(TAG, </span>"------generate start"<span style="color: rgba(0, 0, 0, 1)">)
val dominantColor </span>=<span style="color: rgba(0, 0, 0, 1)"> getPerceptuallyDominantColor(newMap)
val hsvColorArray </span>= FloatArray(3<span style="color: rgba(0, 0, 0, 1)">)
val hsv </span>=<span style="color: rgba(0, 0, 0, 1)"> colorToHSV(dominantColor, hsvColorArray)
result.fill(hsv)
Log.d(TAG, </span>"dominantColor $dominantColor, hsv ${GsonUtils.toJson(hsvColorArray)}"<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)"> result
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> fun testGenerate(newMap: Bitmap) {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 评分公式</span>
val dominantColor1 =<span style="color: rgba(0, 0, 0, 1)"> getPerceptuallyDominantColor(newMap)
val hsvColorArray1 </span>= FloatArray(3<span style="color: rgba(0, 0, 0, 1)">)
colorToHSV(dominantColor1, hsvColorArray1)
test1List.add(hsvColorArray1)
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 主色</span>
Palette.from(newMap).maximumColorCount(24<span style="color: rgba(0, 0, 0, 1)">).clearFilters().generate().apply {
val hsvColorArray2 </span>= FloatArray(3<span style="color: rgba(0, 0, 0, 1)">)
val dominantColor2 </span>=<span style="color: rgba(0, 0, 0, 1)"> getDominantColor(Color.WHITE)
colorToHSV(dominantColor2, hsvColorArray2)
test2List.add(hsvColorArray2)
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 评分优化公式</span>
val dominantColor3 =<span style="color: rgba(0, 0, 0, 1)"> getPerceptuallyDominantColor1(newMap)
val hsvColorArray3 </span>= FloatArray(3<span style="color: rgba(0, 0, 0, 1)">)
colorToHSV(dominantColor3, hsvColorArray3)
test3List.add(hsvColorArray3)
}
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, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> fun isClear(bitmap: Bitmap): Boolean {
val totalPixels </span>= bitmap.width *<span style="color: rgba(0, 0, 0, 1)"> bitmap.height
var blackCount </span>= 0.0<span style="color: rgba(0, 0, 0, 1)">
var whiteCount </span>= 0.0
<span style="color: rgba(0, 0, 255, 1)">for</span> (x in 0<span style="color: rgba(0, 0, 0, 1)"> until bitmap.width) {
</span><span style="color: rgba(0, 0, 255, 1)">for</span> (y in 0<span style="color: rgba(0, 0, 0, 1)"> until bitmap.height) {
val pixel </span>=<span style="color: rgba(0, 0, 0, 1)"> bitmap
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (pixel ==<span style="color: rgba(0, 0, 0, 1)"> Color.BLACK) {
blackCount</span>++<span style="color: rgba(0, 0, 0, 1)">
}
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (pixel ==<span style="color: rgba(0, 0, 0, 1)"> Color.WHITE) {
whiteCount</span>++<span style="color: rgba(0, 0, 0, 1)">
}
}
}
val blackRatio </span>= blackCount /<span style="color: rgba(0, 0, 0, 1)"> totalPixels
val whiteRatio </span>= whiteCount /<span style="color: rgba(0, 0, 0, 1)"> totalPixels
val isClear </span>= blackRatio > 0.3 || whiteRatio > 0.3<span style="color: rgba(0, 0, 0, 1)">
Log.d(TAG, </span>"isClear=$isClear totalPixels=$totalPixels,blackCount=$blackCount, blackRatio=${String.format("%.2f", blackRatio)},whiteRatio=${String.format("%.2f", whiteRatio)}"<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)"> isClear
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> fun calculateSwatchScore(
hue: Float,
saturation: Float,
luminance: Float,
population: Float
): Float {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 1. 人口权重 (标准化)</span>
val populationWeight = population /<span style="color: rgba(0, 0, 0, 1)"> 1000000f
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 2. 饱和度权重 - 适度重视但不过度</span>
val saturationWeight = sqrt(saturation) <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)"> 3. 亮度权重 - 偏好中等亮度范围</span>
val luminanceWeight =<span style="color: rgba(0, 0, 0, 1)"> when {
luminance </span>< 0.15f -> 0.2f<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 太暗的惩罚</span>
luminance > 0.85f -> 0.3f<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)">else</span> -> 1.0f - abs(luminance - 0.5f) * 1.5f<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. 色相权重 - 可选:降低过于鲜艳的红色/蓝色的优势</span>
val hueWeight =<span style="color: rgba(0, 0, 0, 1)"> when {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 红色范围 (330-30度)</span>
(hue >= 330f || hue <= 30f) -> 0.8f
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 蓝色范围 (210-270度)</span>
hue in 210f..270f -> 0.9f
<span style="color: rgba(0, 0, 255, 1)">else</span> -> 1.0f<span style="color: rgba(0, 0, 0, 1)">
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span> populationWeight * saturationWeight * luminanceWeight *<span style="color: rgba(0, 0, 0, 1)"> hueWeight
}
fun getPerceptuallyDominantColor1(bitmap: Bitmap): Int {
val palette </span>=<span style="color: rgba(0, 0, 0, 1)"> Palette.from(bitmap)
.maximumColorCount(</span>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.hsl
val hue </span>= hsl <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 色相 (0-360)</span>
val saturation = 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.toFloat()
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 改进的评分公式</span>
val score =<span style="color: rgba(0, 0, 0, 1)"> calculateSwatchScore(hue, saturation, luminance, population)
</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, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> fun colorToHSV(rgb: Int, hsvColorArray: FloatArray): Float {
Color.colorToHSV(rgb, hsvColorArray)
</span><span style="color: rgba(0, 0, 255, 1)">return</span> hsvColorArray
}
</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)
data </span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> LightColorBean(
val list: List</span><ColorTipBean><span style="color: rgba(0, 0, 0, 1)">
)
data </span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> ColorTipBean(
@SerializedName(</span>"ColorTip"<span style="color: rgba(0, 0, 0, 1)">)
var colorTip: Int,
)
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p> </p><br><br>
来源:https://www.cnblogs.com/LiuZhen/p/19145784
頁:
[1]