眼看着你们堕落 發表於 2025-10-16 15:28:00

氛围灯动态屏保取色方案一

<p>氛围灯并不支持所有的颜色,只能支持256色,所以在取到图片颜色后需要根据结果颜色去跟氛围灯所支持的256色对比,取最接近的结果色,然后同步到氛围灯显示</p>
<h4>取色流程</h4>
<p>取色需要用到原生 Palette.from(bitmap).generate() 方法,通过量化算法分析位图的像素颜色分布,提取最具代表性的颜色组合,也有异步获取方法,下面方法都处于子线程,所以这里直接使用同步方法</p>
<p>查看&nbsp;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色进行缓存处理,先新建&nbsp;<strong>color_rgb_256</strong>.json&nbsp;文件,将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&lt;HsvColor&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      log(</span>"saveHsvColor"<span style="color: rgba(0, 0, 0, 1)">)
      val hsvList </span>= mutableListOf&lt;HsvColor&gt;<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&lt;MutableList&lt;HsvColor&gt;&gt;<span style="color: rgba(0, 0, 0, 1)">() {}.type
                Gson().fromJson</span>&lt;MutableList&lt;HsvColor&gt;&gt;(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,取&nbsp;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 &lt;<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>想要达到联动效果,需要确保取色结果的准确度,原生方案使用&nbsp;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 方法直接取的&nbsp;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 则根据色块&nbsp;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&lt;Swatch&gt; swatches, List&lt;Target&gt;<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&lt;&gt;<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 &lt; 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() &gt;<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>假设氛围灯需要多个取色,可以直接从&nbsp;mSwatches 颜色集合中按 population&nbsp;排序获取</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 &gt;<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 &gt; 0.3 || whiteRatio &gt; 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&lt;HsvColor&gt;<span style="color: rgba(0, 0, 0, 1)">()
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> val hueList = CopyOnWriteArrayList&lt;FloatArray&gt;<span style="color: rgba(0, 0, 0, 1)">()
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> val test1List = CopyOnWriteArrayList&lt;FloatArray&gt;<span style="color: rgba(0, 0, 0, 1)">()
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> val test2List = CopyOnWriteArrayList&lt;FloatArray&gt;<span style="color: rgba(0, 0, 0, 1)">()
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> val test3List = CopyOnWriteArrayList&lt;FloatArray&gt;<span style="color: rgba(0, 0, 0, 1)">()
    var test1Listener: ((Int, Int, Int) </span>-&gt; 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>-&gt; 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&lt;MutableList&lt;HsvColor&gt;&gt;<span style="color: rgba(0, 0, 0, 1)">() {}.type
                Gson().fromJson</span>&lt;MutableList&lt;HsvColor&gt;&gt;(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&lt;HsvColor&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      log(</span>"saveHsvColor"<span style="color: rgba(0, 0, 0, 1)">)
      val hsvList </span>= mutableListOf&lt;HsvColor&gt;<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 &lt; 0 || index &gt;=<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 &lt;<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>&lt;String&gt;<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&lt;ColorTipBean&gt;<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 &gt;<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 &gt; 0.3 || whiteRatio &gt; 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>&lt; 0.15f -&gt; 0.2f<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 太暗的惩罚</span>
            luminance &gt; 0.85f -&gt; 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> -&gt; 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 &gt;= 330f || hue &lt;= 30f) -&gt; 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 -&gt; 0.9f
            <span style="color: rgba(0, 0, 255, 1)">else</span> -&gt; 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 &gt;<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>&lt;ColorTipBean&gt;<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>&nbsp;</p><br><br>
来源:https://www.cnblogs.com/LiuZhen/p/19145784
頁: [1]
查看完整版本: 氛围灯动态屏保取色方案一