hue查找:整体偏差不会很大,但是对于亮度较高存在误差,精准度不够

lab查找:整体一般,但是精准度较好,不过算法复杂,增加耗时

hue色相查找存在误差,在有限的256色中,匹配的规则需要调整

这里使用lab算法提高精准度

RGB转Lab

fun rGBToLab(r: Int, g: Int, b: Int): DoubleArray {
        // 处理负值(如-2563864)
        val labR = if (r < 0) 0f else r / 255f
        val labG = if (g < 0) 0f else g / 255f
        val labB = if (b < 0) 0f else b / 255f
        // 线性化处理(sRGB转换)
        val rLin = if (labR <= 0.04045) labR / 12.92 else ((labR + 0.055) / 1.055).pow(2.4)
        val gLin = if (labG <= 0.04045) labG / 12.92 else ((labG + 0.055) / 1.055).pow(2.4)
        val bLin = if (labB <= 0.04045) labB / 12.92 else ((labB + 0.055) / 1.055).pow(2.4)

        // XYZ转换(D65标准光源)
        val x = 0.4124564 * rLin + 0.3575761 * gLin + 0.1804375 * bLin
        val y = 0.2126729 * rLin + 0.7151522 * gLin + 0.0721750 * bLin
        val z = 0.0193339 * rLin + 0.1191920 * gLin + 0.9503041 * bLin

        // Lab转换
        val xn = 0.95047f
        val yn = 1.0f
        val zn = 1.08883f
        val fX = x / xn
        val fY = y / yn
        val fZ = z / zn

        val l = 116f * fY.pow(1 / 3.0) - 16
        val a = 500f * (fX.pow(1 / 3.0) - fY.pow(1 / 3.0))
        val b = 200f * (fY.pow(1 / 3.0) - fZ.pow(1 / 3.0))

        return doubleArrayOf(l, a, b)
    }
View Code

对rgb进行转换,这里是简化版,如果需要精确要求极高,需要完善

处理流程跟方案一一样,使用集合缓存

接着使用 ΔE76色差公式 查找色值(另外还有ΔEab欧式距离算法,CIEDE2000色差公式,CIE76)

fun deltaE76(lab1: DoubleArray, lab2: DoubleArray): Double {
        val (l1, a1, b1) = lab1
        val (l2, a2, b2) = lab2

        // 计算中间变量
        val c1 = sqrt(a1.pow(2) + b1.pow(2))
        val c2 = sqrt(a2.pow(2) + b2.pow(2))
        val cAvg = (c1 + c2) / 2

        val g = 0.5 * (1 - sqrt(cAvg.pow(7) / (cAvg.pow(7) + 25f.pow(7))))
        val a1p = a1 * (1 + g)
        val a2p = a2 * (1 + g)

        val c1p = sqrt(a1p.pow(2) + b1.pow(2))
        val c2p = sqrt(a2p.pow(2) + b2.pow(2))
        val h1p = Math.toDegrees(kotlin.math.atan2(b1, a1p)).let { if (it < 0) it + 360 else it }
        val h2p = Math.toDegrees(kotlin.math.atan2(b2, a2p)).let { if (it < 0) it + 360 else it }

        // 色差计算
        val deltaLp = l2 - l1
        val deltaCp = c2p - c1p
        val deltaHp = when {
            c1p * c2p == 0.0 -> 0.0
            abs(h2p - h1p) <= 180 -> h2p - h1p
            h2p <= h1p -> h2p - h1p + 360
            else -> h2p - h1p - 360
        }
        val deltaHpS = 2 * sqrt(c1p * c2p) * sin(Math.toRadians(deltaHp) / 2)

        // 加权计算
        val lAvg = (l1 + l2) / 2
        val cpAvg = (c1p + c2p) / 2
        val hpAvg = when {
            c1p * c2p == 0.0 -> h1p + h2p
            abs(h1p - h2p) > 180 -> (h1p + h2p + 360) / 2
            else -> (h1p + h2p) / 2
        }.let { if (it >= 360) it - 360 else it }

        val t = 1 - 0.17 * cos(Math.toRadians(hpAvg - 30)) +
                0.24 * cos(Math.toRadians(2 * hpAvg)) +
                0.32 * cos(Math.toRadians(3 * hpAvg + 6)) -
                0.20 * cos(Math.toRadians(4 * hpAvg - 63))

        val sl = 1 + (0.015 * (lAvg - 50).pow(2)) / sqrt(20 + (lAvg - 50).pow(2))
        val sc = 1 + 0.045 * cpAvg
        val sh = 1 + 0.015 * cpAvg * t

        val deltaTheta = 30 * exp(-((hpAvg - 275) / 25).pow(2))
        val rc = 2 * sqrt(cpAvg.pow(7) / (cpAvg.pow(7) + 25f.pow(7)))
        val rt = -rc * sin(2 * Math.toRadians(deltaTheta))

        return sqrt(
            (deltaLp / sl).pow(2) +
                    (deltaCp / sc).pow(2) +
                    (deltaHpS / sh).pow(2) +
                    rt * (deltaCp / sc) * (deltaHpS / sh)
        )
    }
View Code
var minIndex = 0
        var minDistance = Double.MAX_VALUE
        var minLab = doubleArrayOf()
        tableLabPalette.forEachIndexed { index, lab ->
            val distance = ColorEabLabUtils.deltaE76(lab, list)
            if (distance < minDistance) {
                minDistance = distance
                minLab = lab
                minIndex = index
            }
        }
View Code

image

从上面图中结果可以明显看到,大部分图片中,hue原方案精准度对比lab查找,是有差距的

但是缺点也明显,在某些图片上表现不好,可以看到原方案hue更精准

image

经过大量调整算法,优化算法,测试下来发现,如果在不通过底层显示屏直接转换色值情况下,都存在误差

而刚好这两种查找结果有一定互补作用,那是否可以取各自的优势,结合结果,达到一个平衡

private fun findLabColor(list: DoubleArray, hue: Float): Int {
        // 寻找最近颜色(简化版)
        var minIndex = 0
        var minDistance = Double.MAX_VALUE
        var minLab = doubleArrayOf()
        tableLabPalette.forEachIndexed { index, lab ->
            val distance = ColorEabLabUtils.deltaE76(lab, list)
            if (distance < minDistance) {
                minDistance = distance
                minLab = lab
                minIndex = index
            }
        }
        return if (minDistance < 26) {
            tableColorList[minIndex]
        } else {
            findColor(hue).colorTip
        }
    }
View Code

在lab精度不够时,可能结果偏差较大,但是hue偏差不会很大,hue只是精度不够,所以这里兼容两种方案

在lab误差较大时,直接采用hue方案计算结果,这样直接弥补了自身的缺点,比如图一中误差小,采用lab方案,提高了精准度,而在图二精度不够时,采用hue,避免误差偏差较大

image

最终方案是融合方案,不过精度任然存在误差,但本身转换过程结果只有256色,所以不可能达到完美,相比原图取色结果精度是较高

import android.graphics.Bitmap
import android.graphics.Color
import androidx.palette.graphics.Palette
import androidx.palette.graphics.Palette.Swatch
import java.lang.Math.toDegrees
import java.lang.Math.toRadians
import kotlin.math.abs
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.exp
import kotlin.math.pow
import kotlin.math.sin
import kotlin.math.sqrt

object ColorEabLabUtils {

    fun getPerceptuallyDominantColor(bitmap: Bitmap): Int {
        val palette = Palette.from(bitmap).maximumColorCount(24).clearFilters().generate()
        val swatches = palette.swatches
        if (swatches.isEmpty()) return Color.WHITE

        var bestSwatch: Swatch? = null
        var maxScore = 0f

        for (swatch in swatches) {
            val hsl = swatch.getHsl()
            val saturation = hsl[1] // 饱和度 (0-1)
            val luminance = hsl[2] // 亮度 (0-1)
            val population = swatch.population

            // 评分公式:人口占比 * 饱和度 * 亮度因子
            // 亮度因子确保避免过暗或过亮的颜色(0.1-0.9为理想范围)
            val luminanceFactor = 1f - abs(luminance - 0.5f) * 1.8f
            val score = population * saturation * luminanceFactor

            if (score > maxScore) {
                maxScore = score
                bestSwatch = swatch
            }
        }

        return bestSwatch?.rgb ?: palette.getDominantColor(Color.WHITE)
    }

    // RGB转Lab *********
    fun rGBToLab(r: Int, g: Int, b: Int): DoubleArray {
        // 处理负值(如-2563864)
        val labR = if (r < 0) 0f else r / 255f
        val labG = if (g < 0) 0f else g / 255f
        val labB = if (b < 0) 0f else b / 255f
        // 线性化处理(sRGB转换)
        val rLin = if (labR <= 0.04045) labR / 12.92 else ((labR + 0.055) / 1.055).pow(2.4)
        val gLin = if (labG <= 0.04045) labG / 12.92 else ((labG + 0.055) / 1.055).pow(2.4)
        val bLin = if (labB <= 0.04045) labB / 12.92 else ((labB + 0.055) / 1.055).pow(2.4)

        // XYZ转换(D65标准光源)
        val x = 0.4124564 * rLin + 0.3575761 * gLin + 0.1804375 * bLin
        val y = 0.2126729 * rLin + 0.7151522 * gLin + 0.0721750 * bLin
        val z = 0.0193339 * rLin + 0.1191920 * gLin + 0.9503041 * bLin

        // Lab转换
        val xn = 0.95047f
        val yn = 1.0f
        val zn = 1.08883f
        val fX = x / xn
        val fY = y / yn
        val fZ = z / zn

        val l = 116f * fY.pow(1 / 3.0) - 16
        val a = 500f * (fX.pow(1 / 3.0) - fY.pow(1 / 3.0))
        val b = 200f * (fY.pow(1 / 3.0) - fZ.pow(1 / 3.0))

        return doubleArrayOf(l, a, b)
    }

    // ΔEab算法
    fun deltaEab(lab1: DoubleArray, lab2: DoubleArray): Double {
        val dL = lab1[0] - lab2[0]
        val da = lab1[1] - lab2[1]
        val db = lab1[2] - lab2[2]
        return sqrt(dL.pow(2) + da.pow(2) + db.pow(2))
    }

    // 改进的RGB转Lab(包含伽马校正和D65白点)
    fun rgbToLab(r: Int, g: Int, b: Int): DoubleArray {
        // 处理负值(如-2563864)
        val labR = if (r < 0) 0f else r / 255f
        val labG = if (g < 0) 0f else g / 255f
        val labB = if (b < 0) 0f else b / 255f

        // sRGB伽马校正
        val rLin = if (labR <= 0.04045) labR / 12.92 else ((labR + 0.055) / 1.055).pow(2.4)
        val gLin = if (labG <= 0.04045) labG / 12.92 else ((labG + 0.055) / 1.055).pow(2.4)
        val bLin = if (labB <= 0.04045) labB / 12.92 else ((labB + 0.055) / 1.055).pow(2.4)

        // D65标准光源XYZ转换
        val x = 0.4124564 * rLin + 0.3575761 * gLin + 0.1804375 * bLin
        val y = 0.2126729 * rLin + 0.7151522 * gLin + 0.0721750 * bLin
        val z = 0.0193339 * rLin + 0.1191920 * gLin + 0.9503041 * bLin

        // Lab转换(包含阈值处理)
        val xn = 0.95047
        val yn = 1.0
        val zn = 1.08883
        val fX = x / xn
        val fY = y / yn
        val fZ = z / zn

        val l = if (fY > 0.008856) 116 * fY.pow(1 / 3.0) - 16 else 903.3 * fY
        val a = 500 * (fX.pow(1 / 3.0) - fY.pow(1 / 3.0))
        val b = 200 * (fY.pow(1 / 3.0) - fZ.pow(1 / 3.0))

        return doubleArrayOf(l, a, b)
    }

    // 改进的ΔE76色差公式(包含亮度权重)  *********
    fun deltaE76(lab1: DoubleArray, lab2: DoubleArray): Double {
        val (l1, a1, b1) = lab1
        val (l2, a2, b2) = lab2

        // 计算中间变量
        val c1 = sqrt(a1.pow(2) + b1.pow(2))
        val c2 = sqrt(a2.pow(2) + b2.pow(2))
        val cAvg = (c1 + c2) / 2

        val g = 0.5 * (1 - sqrt(cAvg.pow(7) / (cAvg.pow(7) + 25f.pow(7))))
        val a1p = a1 * (1 + g)
        val a2p = a2 * (1 + g)

        val c1p = sqrt(a1p.pow(2) + b1.pow(2))
        val c2p = sqrt(a2p.pow(2) + b2.pow(2))
        val h1p = Math.toDegrees(kotlin.math.atan2(b1, a1p)).let { if (it < 0) it + 360 else it }
        val h2p = Math.toDegrees(kotlin.math.atan2(b2, a2p)).let { if (it < 0) it + 360 else it }

        // 色差计算
        val deltaLp = l2 - l1
        val deltaCp = c2p - c1p
        val deltaHp = when {
            c1p * c2p == 0.0 -> 0.0
            abs(h2p - h1p) <= 180 -> h2p - h1p
            h2p <= h1p -> h2p - h1p + 360
            else -> h2p - h1p - 360
        }
        val deltaHpS = 2 * sqrt(c1p * c2p) * sin(Math.toRadians(deltaHp) / 2)

        // 加权计算
        val lAvg = (l1 + l2) / 2
        val cpAvg = (c1p + c2p) / 2
        val hpAvg = when {
            c1p * c2p == 0.0 -> h1p + h2p
            abs(h1p - h2p) > 180 -> (h1p + h2p + 360) / 2
            else -> (h1p + h2p) / 2
        }.let { if (it >= 360) it - 360 else it }

        val t = 1 - 0.17 * cos(Math.toRadians(hpAvg - 30)) +
                0.24 * cos(Math.toRadians(2 * hpAvg)) +
                0.32 * cos(Math.toRadians(3 * hpAvg + 6)) -
                0.20 * cos(Math.toRadians(4 * hpAvg - 63))

        val sl = 1 + (0.015 * (lAvg - 50).pow(2)) / sqrt(20 + (lAvg - 50).pow(2))
        val sc = 1 + 0.045 * cpAvg
        val sh = 1 + 0.015 * cpAvg * t

        val deltaTheta = 30 * exp(-((hpAvg - 275) / 25).pow(2))
        val rc = 2 * sqrt(cpAvg.pow(7) / (cpAvg.pow(7) + 25f.pow(7)))
        val rt = -rc * sin(2 * Math.toRadians(deltaTheta))

        return sqrt(
            (deltaLp / sl).pow(2) +
                    (deltaCp / sc).pow(2) +
                    (deltaHpS / sh).pow(2) +
                    rt * (deltaCp / sc) * (deltaHpS / sh)
        )
    }


    /**
     * CIEDE2000色差公式实现
     * 更符合人眼感知的颜色差异计算
     * @param lab1 第一个颜色的Lab值
     * @param lab2 第二个颜色的Lab值
     * @return Double 色差值(ΔE越小颜色越接近)
     */
    fun deltaE2000(lab1: DoubleArray, lab2: DoubleArray): Double {
        val (l1, a1, b1) = lab1
        val (l2, a2, b2) = lab2

        // 步骤1:计算色度
        val c1 = sqrt(a1.pow(2) + b1.pow(2))
        val c2 = sqrt(a2.pow(2) + b2.pow(2))
        val cAvg = (c1 + c2) / 2.0

        // 步骤2:计算补偿因子
        val g = 0.5 * (1.0 - sqrt(cAvg.pow(7) / (cAvg.pow(7) + 25.0.pow(7))))

        val a1Prime = a1 * (1.0 + g)
        val a2Prime = a2 * (1.0 + g)

        val c1Prime = sqrt(a1Prime.pow(2) + b1.pow(2))
        val c2Prime = sqrt(a2Prime.pow(2) + b2.pow(2))
        val cBarPrime = (c1Prime + c2Prime) / 2.0

        // 步骤3:计算色调角
        val h1Prime = toDegrees(atan2(b1, a1Prime)).let { if (it < 0) it + 360.0 else it }
        val h2Prime = toDegrees(atan2(b2, a2Prime)).let { if (it < 0) it + 360.0 else it }

        // 步骤4:计算基本差异
        val deltaLPrime = l2 - l1
        val deltaCPrime = c2Prime - c1Prime

        // 步骤5:计算色调差
        var deltaHPrime = 0.0
        if (c1Prime * c2Prime != 0.0) {
            val deltaHPrimeRaw = when {
                abs(h2Prime - h1Prime) <= 180.0 -> h2Prime - h1Prime
                h2Prime <= h1Prime -> h2Prime - h1Prime + 360.0
                else -> h2Prime - h1Prime - 360.0
            }
            deltaHPrime = 2.0 * sqrt(c1Prime * c2Prime) * sin(toRadians(deltaHPrimeRaw) / 2.0)
        }

        // 步骤6:计算加权平均值
        val lBar = (l1 + l2) / 2.0
        val cBar = (c1 + c2) / 2.0

        // 步骤7:计算色调平均值
        val hBarPrime = when {
            abs(h1Prime - h2Prime) > 180.0 -> (h1Prime + h2Prime + 360.0) / 2.0
            else -> (h1Prime + h2Prime) / 2.0
        }

        // 步骤8:计算补偿项
        val t = 1.0 - 0.17 * cos(toRadians(hBarPrime - 30.0)) +
                0.24 * cos(toRadians(2.0 * hBarPrime)) +
                0.32 * cos(toRadians(3.0 * hBarPrime + 6.0)) -
                0.20 * cos(toRadians(4.0 * hBarPrime - 63.0))

        // 步骤9:计算旋转项
        val deltaTheta = 30.0 * exp(-((hBarPrime - 275.0) / 25.0).pow(2))

        val rc = 2.0 * sqrt(cBarPrime.pow(7) / (cBarPrime.pow(7) + 25.0.pow(7)))
        val rt = -rc * sin(2.0 * toRadians(deltaTheta))

        // 步骤10:计算权重因子
        val sl = 1.0 + (0.015 * (lBar - 50.0).pow(2) / sqrt(20.0 + (lBar - 50.0).pow(2)))
        val sc = 1.0 + 0.045 * cBarPrime
        val sh = 1.0 + 0.015 * cBarPrime * t

        // 步骤11:最终色差计算
        val term1 = (deltaLPrime / sl).pow(2)
        val term2 = (deltaCPrime / sc).pow(2)
        val term3 = (deltaHPrime / sh).pow(2)
        val term4 = rt * (deltaCPrime / sc) * (deltaHPrime / sh)

        return sqrt(term1 + term2 + term3 + term4)
    }


    // 优化的伽马校正函数
    fun preciseGammaExpand(c: Double) = if (c <= 0.04045) {
        c / 12.92
    } else {
        ((c + 0.055) / 1.055).pow(2.4)
    }

    // 优化的Lab转换函数
    fun preciseF(t: Double) = if (t > 0.008856) {
        t.pow(1.0 / 3.0)
    } else {
        (7.787 * t) + (16.0 / 116.0)
    }

    /**
     * 优化的RGB转Lab转换方法
     * 使用更精确的浮点数计算和标准化处理
     */
    fun rgbToLabOptimized(r: Int, g: Int, b: Int): DoubleArray {
        // 使用更精确的归一化
        val rNormalized = r / 255.0
        val gNormalized = g / 255.0
        val bNormalized = b / 255.0

        // 应用伽马校正
        val rLinear = preciseGammaExpand(rNormalized)
        val gLinear = preciseGammaExpand(gNormalized)
        val bLinear = preciseGammaExpand(bNormalized)

        // 使用D65标准光源的精确XYZ转换矩阵
        val x = 0.4124564 * rLinear + 0.3575761 * gLinear + 0.1804375 * bLinear
        val y = 0.2126729 * rLinear + 0.7151522 * gLinear + 0.0721750 * bLinear
        val z = 0.0193339 * rLinear + 0.1191920 * gLinear + 0.9503041 * bLinear

        // 使用CIE标准参考白点
        val refX = 0.95047
        val refY = 1.00000
        val refZ = 1.08883

        // 计算相对值
        val xRelative = x / refX
        val yRelative = y / refY
        val zRelative = z / refZ

        val fx = preciseF(xRelative)
        val fy = preciseF(yRelative)
        val fz = preciseF(zRelative)

        // 计算Lab值
        val l = (116.0 * fy) - 16.0
        val a = 500.0 * (fx - fy)
        val bLab = 200.0 * (fy - fz)

        return doubleArrayOf(l, a, bLab)
    }

    /**
     * 完整的CIEDE2000色差公式实现
     * 包含所有补偿项和旋转项
     */
    fun deltaE2000Complete(lab1: DoubleArray, lab2: DoubleArray): Double {
        val (l1, a1, b1) = lab1
        val (l2, a2, b2) = lab2

        // 步骤1:计算色度值
        val c1 = sqrt(a1.pow(2) + b1.pow(2))
        val c2 = sqrt(a2.pow(2) + b2.pow(2))

        // 步骤2:计算平均值
        val lBar = (l1 + l2) / 2.0
        val cBar = (c1 + c2) / 2.0

        // 步骤3:计算补偿因子
        val g = 0.5 * (1.0 - sqrt(cBar.pow(7) / (cBar.pow(7) + 25.0.pow(7))))

        // 步骤4:计算调整后的a'值
        val a1Prime = a1 * (1.0 + g)
        val a2Prime = a2 * (1.0 + g)

        // 步骤5:计算调整后的色度值
        val c1Prime = sqrt(a1Prime.pow(2) + b1.pow(2))
        val c2Prime = sqrt(a2Prime.pow(2) + b2.pow(2))
        val cBarPrime = (c1Prime + c2Prime) / 2.0

        // 步骤6:计算色调角
        val h1Prime = toDegrees(atan2(b1, a1Prime)).let {
            if (it < 0) it + 360.0 else it
        }
        val h2Prime = toDegrees(atan2(b2, a2Prime)).let {
            if (it < 0) it + 360.0 else it
        }

        // 步骤7:计算基本差异
        val deltaLPrime = l2 - l1
        val deltaCPrime = c2Prime - c1Prime

        // 步骤8:计算色调差
        var deltaHPrime = 0.0
        if (c1Prime != 0.0 && c2Prime != 0.0) {
            val deltaHPrimeRaw = when {
                abs(h2Prime - h1Prime) <= 180.0 -> h2Prime - h1Prime
                h2Prime <= h1Prime -> h2Prime - h1Prime + 360.0
                else -> h2Prime - h1Prime - 360.0
            }
            deltaHPrime = 2.0 * sqrt(c1Prime * c2Prime) * sin(toRadians(deltaHPrimeRaw) / 2.0)
        }

        // 步骤9:计算色调平均值
        val hBarPrime = when {
            abs(h1Prime - h2Prime) > 180.0 -> (h1Prime + h2Prime + 360.0) / 2.0
            else -> (h1Prime + h2Prime) / 2.0
        }

        // 步骤10:计算补偿项
        val t = 1.0 - 0.17 * cos(toRadians(hBarPrime - 30.0)) +
                0.24 * cos(toRadians(2.0 * hBarPrime)) +
                0.32 * cos(toRadians(3.0 * hBarPrime + 6.0)) -
                0.20 * cos(toRadians(4.0 * hBarPrime - 63.0))

        // 步骤11:计算旋转项
        val deltaTheta = 30.0 * exp(-((hBarPrime - 275.0) / 25.0).pow(2))

        val rc = 2.0 * sqrt(cBarPrime.pow(7) / (cBarPrime.pow(7) + 25.0.pow(7)))
        val rt = -rc * sin(2.0 * toRadians(deltaTheta))

        // 步骤12:计算权重因子
        val sl = 1.0 + (0.015 * (lBar - 50.0).pow(2) / sqrt(20.0 + (lBar - 50.0).pow(2)))
        val sc = 1.0 + 0.045 * cBarPrime
        val sh = 1.0 + 0.015 * cBarPrime * t

        // 步骤13:最终色差计算
        val term1 = (deltaLPrime / sl).pow(2)
        val term2 = (deltaCPrime / sc).pow(2)
        val term3 = (deltaHPrime / sh).pow(2)
        val term4 = rt * (deltaCPrime / sc) * (deltaHPrime / sh)

        return sqrt(term1 + term2 + term3 + term4)
    }

}
View Code
{
  "list": [
    {
      "r": 0,
      "g": 84,
      "b": 255
    },
    {
      "r": 18,
      "g": 92,
      "b": 255
    },
    {
      "r": 65,
      "g": 82,
      "b": 255
    },
    {
      "r": 90,
      "g": 81,
      "b": 255
    },
    {
      "r": 110,
      "g": 80,
      "b": 255
    },
    {
      "r": 127,
      "g": 78,
      "b": 255
    },
    {
      "r": 142,
      "g": 77,
      "b": 255
    },
    {
      "r": 157,
      "g": 76,
      "b": 255
    },
    {
      "r": 170,
      "g": 74,
      "b": 255
    },
    {
      "r": 197,
      "g": 70,
      "b": 255
    },
    {
      "r": 209,
      "g": 68,
      "b": 255
    },
    {
      "r": 237,
      "g": 62,
      "b": 255
    },
    {
      "r": 255,
      "g": 51,
      "b": 244
    },
    {
      "r": 255,
      "g": 35,
      "b": 219
    },
    {
      "r": 255,
      "g": 11,
      "b": 196
    },
    {
      "r": 255,
      "g": 0,
      "b": 176
    },
    {
      "r": 255,
      "g": 48,
      "b": 158
    },
    {
      "r": 255,
      "g": 19,
      "b": 142
    },
    {
      "r": 255,
      "g": 15,
      "b": 122
    },
    {
      "r": 255,
      "g": 0,
      "b": 99
    },
    {
      "r": 255,
      "g": 6,
      "b": 75
    },
    {
      "r": 255,
      "g": 9,
      "b": 55
    },
    {
      "r": 255,
      "g": 6,
      "b": 0
    },
    {
      "r": 255,
      "g": 28,
      "b": 0
    },
    {
      "r": 255,
      "g": 62,
      "b": 0
    },
    {
      "r": 255,
      "g": 85,
      "b": 0
    },
    {
      "r": 255,
      "g": 105,
      "b": 0
    },
    {
      "r": 255,
      "g": 125,
      "b": 0
    },
    {
      "r": 255,
      "g": 155,
      "b": 5
    },
    {
      "r": 255,
      "g": 171,
      "b": 41
    },
    {
      "r": 255,
      "g": 180,
      "b": 15
    },
    {
      "r": 255,
      "g": 192,
      "b": 19
    },
    {
      "r": 255,
      "g": 206,
      "b": 45
    },
    {
      "r": 255,
      "g": 218,
      "b": 27
    },
    {
      "r": 255,
      "g": 231,
      "b": 30
    },
    {
      "r": 255,
      "g": 244,
      "b": 34
    },
    {
      "r": 251,
      "g": 255,
      "b": 37
    },
    {
      "r": 235,
      "g": 255,
      "b": 38
    },
    {
      "r": 219,
      "g": 255,
      "b": 39
    },
    {
      "r": 203,
      "g": 255,
      "b": 40
    },
    {
      "r": 185,
      "g": 255,
      "b": 41
    },
    {
      "r": 165,
      "g": 255,
      "b": 42
    },
    {
      "r": 142,
      "g": 255,
      "b": 42
    },
    {
      "r": 109,
      "g": 255,
      "b": 29
    },
    {
      "r": 69,
      "g": 255,
      "b": 44
    },
    {
      "r": 24,
      "g": 255,
      "b": 46
    },
    {
      "r": 22,
      "g": 255,
      "b": 32
    },
    {
      "r": 0,
      "g": 255,
      "b": 102
    },
    {
      "r": 0,
      "g": 255,
      "b": 136
    },
    {
      "r": 6,
      "g": 209,
      "b": 128
    },
    {
      "r": 0,
      "g": 255,
      "b": 189
    },
    {
      "r": 0,
      "g": 255,
      "b": 212
    },
    {
      "r": 0,
      "g": 255,
      "b": 234
    },
    {
      "r": 0,
      "g": 254,
      "b": 255
    },
    {
      "r": 0,
      "g": 234,
      "b": 255
    },
    {
      "r": 0,
      "g": 200,
      "b": 255
    },
    {
      "r": 0,
      "g": 186,
      "b": 255
    },
    {
      "r": 0,
      "g": 173,
      "b": 255
    },
    {
      "r": 72,
      "g": 164,
      "b": 237
    },
    {
      "r": 2,
      "g": 146,
      "b": 255
    },
    {
      "r": 0,
      "g": 138,
      "b": 255
    },
    {
      "r": 0,
      "g": 127,
      "b": 255
    },
    {
      "r": 0,
      "g": 106,
      "b": 255
    },
    {
      "r": 255,
      "g": 255,
      "b": 255
    },
    {
      "r": 78,
      "g": 85,
      "b": 255
    },
    {
      "r": 100,
      "g": 150,
      "b": 255
    },
    {
      "r": 98,
      "g": 204,
      "b": 255
    },
    {
      "r": 103,
      "g": 172,
      "b": 156
    },
    {
      "r": 84,
      "g": 255,
      "b": 167
    },
    {
      "r": 58,
      "g": 255,
      "b": 112
    },
    {
      "r": 52,
      "g": 255,
      "b": 88
    },
    {
      "r": 149,
      "g": 95,
      "b": 255
    },
    {
      "r": 162,
      "g": 152,
      "b": 255
    },
    {
      "r": 171,
      "g": 228,
      "b": 255
    },
    {
      "r": 159,
      "g": 255,
      "b": 201
    },
    {
      "r": 130,
      "g": 255,
      "b": 142
    },
    {
      "r": 108,
      "g": 255,
      "b": 99
    },
    {
      "r": 97,
      "g": 255,
      "b": 74
    },
    {
      "r": 79,
      "g": 255,
      "b": 41
    },
    {
      "r": 236,
      "g": 101,
      "b": 254
    },
    {
      "r": 254,
      "g": 169,
      "b": 255
    },
    {
      "r": 225,
      "g": 255,
      "b": 170
    },
    {
      "r": 184,
      "g": 255,
      "b": 155
    },
    {
      "r": 155,
      "g": 255,
      "b": 75
    },
    {
      "r": 133,
      "g": 255,
      "b": 45
    },
    {
      "r": 255,
      "g": 86,
      "b": 196
    },
    {
      "r": 255,
      "g": 130,
      "b": 171
    },
    {
      "r": 255,
      "g": 175,
      "b": 146
    },
    {
      "r": 255,
      "g": 221,
      "b": 119
    },
    {
      "r": 235,
      "g": 255,
      "b": 95
    },
    {
      "r": 203,
      "g": 255,
      "b": 51
    },
    {
      "r": 255,
      "g": 68,
      "b": 138
    },
    {
      "r": 255,
      "g": 104,
      "b": 117
    },
    {
      "r": 255,
      "g": 140,
      "b": 95
    },
    {
      "r": 255,
      "g": 187,
      "b": 85
    },
    {
      "r": 255,
      "g": 217,
      "b": 49
    },
    {
      "r": 255,
      "g": 25,
      "b": 115
    },
    {
      "r": 255,
      "g": 40,
      "b": 96
    },
    {
      "r": 255,
      "g": 85,
      "b": 78
    },
    {
      "r": 255,
      "g": 116,
      "b": 59
    },
    {
      "r": 255,
      "g": 148,
      "b": 40
    },
    {
      "r": 255,
      "g": 45,
      "b": 62
    },
    {
      "r": 255,
      "g": 81,
      "b": 54
    },
    {
      "r": 255,
      "g": 98,
      "b": 33
    },
    {
      "r": 255,
      "g": 22,
      "b": 36
    },
    {
      "r": 255,
      "g": 49,
      "b": 35
    },
    {
      "r": 255,
      "g": 32,
      "b": 23
    },
    {
      "r": 35,
      "g": 55,
      "b": 255
    },
    {
      "r": 35,
      "g": 73,
      "b": 255
    },
    {
      "r": 35,
      "g": 94,
      "b": 255
    },
    {
      "r": 35,
      "g": 116,
      "b": 255
    },
    {
      "r": 65,
      "g": 145,
      "b": 255
    },
    {
      "r": 35,
      "g": 182,
      "b": 255
    },
    {
      "r": 35,
      "g": 214,
      "b": 255
    },
    {
      "r": 2,
      "g": 251,
      "b": 255
    },
    {
      "r": 0,
      "g": 255,
      "b": 223
    },
    {
      "r": 0,
      "g": 255,
      "b": 203
    },
    {
      "r": 22,
      "g": 255,
      "b": 168
    },
    {
      "r": 54,
      "g": 255,
      "b": 144
    },
    {
      "r": 58,
      "g": 55,
      "b": 255
    },
    {
      "r": 59,
      "g": 86,
      "b": 255
    },
    {
      "r": 61,
      "g": 120,
      "b": 255
    },
    {
      "r": 64,
      "g": 175,
      "b": 255
    },
    {
      "r": 66,
      "g": 225,
      "b": 255
    },
    {
      "r": 65,
      "g": 255,
      "b": 246
    },
    {
      "r": 83,
      "g": 255,
      "b": 211
    },
    {
      "r": 51,
      "g": 255,
      "b": 181
    },
    {
      "r": 46,
      "g": 255,
      "b": 156
    },
    {
      "r": 41,
      "g": 255,
      "b": 134
    },
    {
      "r": 37,
      "g": 255,
      "b": 115
    },
    {
      "r": 83,
      "g": 56,
      "b": 255
    },
    {
      "r": 84,
      "g": 67,
      "b": 255
    },
    {
      "r": 85,
      "g": 77,
      "b": 255
    },
    {
      "r": 86,
      "g": 104,
      "b": 255
    },
    {
      "r": 89,
      "g": 113,
      "b": 255
    },
    {
      "r": 90,
      "g": 126,
      "b": 255
    },
    {
      "r": 94,
      "g": 170,
      "b": 255
    },
    {
      "r": 102,
      "g": 242,
      "b": 255
    },
    {
      "r": 84,
      "g": 255,
      "b": 193
    },
    {
      "r": 68,
      "g": 255,
      "b": 140
    },
    {
      "r": 56,
      "g": 255,
      "b": 101
    },
    {
      "r": 110,
      "g": 58,
      "b": 255
    },
    {
      "r": 112,
      "g": 68,
      "b": 255
    },
    {
      "r": 116,
      "g": 91,
      "b": 255
    },
    {
      "r": 120,
      "g": 117,
      "b": 255
    },
    {
      "r": 124,
      "g": 145,
      "b": 255
    },
    {
      "r": 130,
      "g": 176,
      "b": 255
    },
    {
      "r": 149,
      "g": 222,
      "b": 255
    },
    {
      "r": 142,
      "g": 253,
      "b": 255
    },
    {
      "r": 127,
      "g": 255,
      "b": 216
    },
    {
      "r": 114,
      "g": 255,
      "b": 183
    },
    {
      "r": 103,
      "g": 255,
      "b": 156
    },
    {
      "r": 94,
      "g": 255,
      "b": 132
    },
    {
      "r": 86,
      "g": 255,
      "b": 111
    },
    {
      "r": 78,
      "g": 255,
      "b": 93
    },
    {
      "r": 72,
      "g": 255,
      "b": 77
    },
    {
      "r": 87,
      "g": 255,
      "b": 71
    },
    {
      "r": 62,
      "g": 255,
      "b": 51
    },
    {
      "r": 143,
      "g": 70,
      "b": 255
    },
    {
      "r": 140,
      "g": 112,
      "b": 255
    },
    {
      "r": 170,
      "g": 186,
      "b": 255
    },
    {
      "r": 178,
      "g": 255,
      "b": 240
    },
    {
      "r": 143,
      "g": 255,
      "b": 169
    },
    {
      "r": 118,
      "g": 255,
      "b": 119
    },
    {
      "r": 100,
      "g": 255,
      "b": 82
    },
    {
      "r": 85,
      "g": 255,
      "b": 53
    },
    {
      "r": 74,
      "g": 255,
      "b": 30
    },
    {
      "r": 178,
      "g": 72,
      "b": 255
    },
    {
      "r": 192,
      "g": 105,
      "b": 255
    },
    {
      "r": 195,
      "g": 127,
      "b": 255
    },
    {
      "r": 205,
      "g": 160,
      "b": 255
    },
    {
      "r": 216,
      "g": 198,
      "b": 255
    },
    {
      "r": 191,
      "g": 255,
      "b": 186
    },
    {
      "r": 173,
      "g": 255,
      "b": 155
    },
    {
      "r": 162,
      "g": 255,
      "b": 125
    },
    {
      "r": 143,
      "g": 255,
      "b": 106
    },
    {
      "r": 130,
      "g": 255,
      "b": 91
    },
    {
      "r": 121,
      "g": 255,
      "b": 71
    },
    {
      "r": 112,
      "g": 255,
      "b": 56
    },
    {
      "r": 104,
      "g": 255,
      "b": 43
    },
    {
      "r": 93,
      "g": 255,
      "b": 37
    },
    {
      "r": 217,
      "g": 74,
      "b": 255
    },
    {
      "r": 242,
      "g": 140,
      "b": 255
    },
    {
      "r": 255,
      "g": 199,
      "b": 240
    },
    {
      "r": 203,
      "g": 255,
      "b": 140
    },
    {
      "r": 169,
      "g": 255,
      "b": 94
    },
    {
      "r": 143,
      "g": 255,
      "b": 59
    },
    {
      "r": 124,
      "g": 255,
      "b": 33
    },
    {
      "r": 255,
      "g": 84,
      "b": 255
    },
    {
      "r": 255,
      "g": 112,
      "b": 255
    },
    {
      "r": 255,
      "g": 123,
      "b": 222
    },
    {
      "r": 255,
      "g": 148,
      "b": 208
    },
    {
      "r": 255,
      "g": 166,
      "b": 205
    },
    {
      "r": 255,
      "g": 194,
      "b": 204
    },
    {
      "r": 255,
      "g": 224,
      "b": 166
    },
    {
      "r": 255,
      "g": 251,
      "b": 151
    },
    {
      "r": 233,
      "g": 255,
      "b": 125
    },
    {
      "r": 210,
      "g": 255,
      "b": 91
    },
    {
      "r": 194,
      "g": 255,
      "b": 81
    },
    {
      "r": 179,
      "g": 255,
      "b": 63
    },
    {
      "r": 154,
      "g": 255,
      "b": 32
    },
    {
      "r": 158,
      "g": 255,
      "b": 37
    },
    {
      "r": 255,
      "g": 65,
      "b": 208
    },
    {
      "r": 255,
      "g": 108,
      "b": 184
    },
    {
      "r": 255,
      "g": 152,
      "b": 159
    },
    {
      "r": 255,
      "g": 198,
      "b": 133
    },
    {
      "r": 255,
      "g": 253,
      "b": 95
    },
    {
      "r": 220,
      "g": 255,
      "b": 67
    },
    {
      "r": 188,
      "g": 255,
      "b": 36
    },
    {
      "r": 255,
      "g": 57,
      "b": 176
    },
    {
      "r": 255,
      "g": 76,
      "b": 164
    },
    {
      "r": 255,
      "g": 96,
      "b": 153
    },
    {
      "r": 255,
      "g": 125,
      "b": 150
    },
    {
      "r": 255,
      "g": 136,
      "b": 130
    },
    {
      "r": 255,
      "g": 166,
      "b": 126
    },
    {
      "r": 255,
      "g": 176,
      "b": 106
    },
    {
      "r": 255,
      "g": 197,
      "b": 94
    },
    {
      "r": 255,
      "g": 223,
      "b": 88
    },
    {
      "r": 255,
      "g": 240,
      "b": 69
    },
    {
      "r": 248,
      "g": 255,
      "b": 56
    },
    {
      "r": 228,
      "g": 255,
      "b": 38
    },
    {
      "r": 255,
      "g": 36,
      "b": 167
    },
    {
      "r": 255,
      "g": 88,
      "b": 134
    },
    {
      "r": 255,
      "g": 122,
      "b": 106
    },
    {
      "r": 255,
      "g": 160,
      "b": 77
    },
    {
      "r": 255,
      "g": 207,
      "b": 86
    },
    {
      "r": 255,
      "g": 237,
      "b": 37
    },
    {
      "r": 255,
      "g": 41,
      "b": 143
    },
    {
      "r": 255,
      "g": 61,
      "b": 116
    },
    {
      "r": 255,
      "g": 84,
      "b": 111
    },
    {
      "r": 255,
      "g": 94,
      "b": 96
    },
    {
      "r": 255,
      "g": 110,
      "b": 86
    },
    {
      "r": 255,
      "g": 127,
      "b": 76
    },
    {
      "r": 255,
      "g": 144,
      "b": 65
    },
    {
      "r": 255,
      "g": 162,
      "b": 55
    },
    {
      "r": 255,
      "g": 179,
      "b": 44
    },
    {
      "r": 255,
      "g": 197,
      "b": 33
    },
    {
      "r": 255,
      "g": 70,
      "b": 88
    },
    {
      "r": 254,
      "g": 107,
      "b": 64
    },
    {
      "r": 255,
      "g": 132,
      "b": 50
    },
    {
      "r": 255,
      "g": 164,
      "b": 30
    },
    {
      "r": 255,
      "g": 22,
      "b": 98
    },
    {
      "r": 255,
      "g": 50,
      "b": 80
    },
    {
      "r": 254,
      "g": 89,
      "b": 64
    },
    {
      "r": 255,
      "g": 92,
      "b": 54
    },
    {
      "r": 255,
      "g": 121,
      "b": 36
    },
    {
      "r": 255,
      "g": 138,
      "b": 37
    },
    {
      "r": 255,
      "g": 111,
      "b": 25
    },
    {
      "r": 255,
      "g": 41,
      "b": 54
    },
    {
      "r": 255,
      "g": 53,
      "b": 46
    },
    {
      "r": 255,
      "g": 78,
      "b": 30
    },
    {
      "r": 255,
      "g": 61,
      "b": 28
    },
    {
      "r": 255,
      "g": 39,
      "b": 37
    },
    {
      "r": 255,
      "g": 57,
      "b": 18
    },
    {
      "r": 255,
      "g": 29,
      "b": 15
    },
    {
      "r": 255,
      "g": 14,
      "b": 8
    }
  ]
}
color_rgb_256.json
import android.graphics.Bitmap
import android.graphics.Color
import android.util.Log
import com.blankj.utilcode.util.GsonUtils
import com.blankj.utilcode.util.ScreenUtils
import com.blankj.utilcode.util.Utils
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import java.util.concurrent.CopyOnWriteArrayList
import kotlin.math.abs


object AmbientLightColorPickManager {

    private const val TAG = "AmbientLightColorPickManager"

    private var scope = MainScope()
    private val mWidth = ScreenUtils.getScreenWidth() / 2
    private val mHeight = ScreenUtils.getScreenHeight() / 2

    // 256色
    private val tableList = mutableListOf<ColorTableBean>()

    // 原图色
    private val originalList = CopyOnWriteArrayList<ColorTableBean>()

    var test1Listener: ((Int) -> Unit)? = null
    var test2Listener: ((Int, Int, Int) -> Unit)? = null

    @JvmStatic
    fun init() {
        log("$TAG init")
        scope.launch(Dispatchers.IO) {
            initHsvColor()
        }
    }

    private fun initHsvColor() {
        tableList.clear()
        runCatching {
            val json = SharedPreferencesUtils.getRGB256HsvColor(Utils.getApp())
            val listType = object : TypeToken<MutableList<ColorTableBean>>() {}.type
            Gson().fromJson<MutableList<ColorTableBean>>(json, listType)?.let {
                tableList.addAll(it)
                log("initHsvColor xml list size=${tableList.size}")
            }
        }.getOrElse {
            Log.e(TAG, "initHsvColor Exception ${it.message}")
        }
        if (tableList.isEmpty()) {
            saveHsvColor().let {
                if (it.isNotEmpty()) {
                    tableList.addAll(it)
                }
            }
            log("initHsvColor json list size=${tableList.size}")
        }
    }

    /** 将本地rgb色值转换成hsv保存到本地 */
    private fun saveHsvColor(): MutableList<ColorTableBean> {
        log("saveHsvColor")
        val hsvList = mutableListOf<ColorTableBean>()
        runCatching {
            val assetManager = Utils.getApp().assets
            val file = assetManager.open("color_rgb_256.json")
            val jsonStr = file.bufferedReader().readText()
            file.close()
            val bean = Gson().fromJson(jsonStr, AmbientLightList::class.java)
            for (i in 0 until bean.list.size) {
                bean.list[i].apply {
                    val myColor = Color.rgb(r, g, b)
                    val hsvColors = FloatArray(3)
                    Color.colorToHSV(myColor, hsvColors)
                    val lab = ColorEabLabUtils.rGBToLab(r, g, b)
                    val bean = ColorTableBean(hsvColors, lab, myColor)
                    hsvList.add(bean)
                }
            }
            val json = Gson().toJson(hsvList)
            log("saveHsvColor hsvListSize=${hsvList.size}")
            SharedPreferencesUtils.setRGB256HsvColor(Utils.getApp(), json)
        }.getOrElse {
            Log.e(TAG, "saveHsvColor Exception ${it.message}")
        }
        return hsvList
    }


    /** 设置氛围灯 */
    @JvmStatic
    fun setAmbientLight(displayId: Int, index: Int) {
        if (displayId != DisplayParameter.DISPLAY_CSD.displayId) return
        log("setAmbientLight displayId=$displayId")
        scope.launch(Dispatchers.IO) {
            if (originalList.isEmpty()) {
                Log.w(TAG, "setAmbientLight hueList is null")
                return@launch
            }
            if (index < 0 || index >= originalList.size) {
                Log.w(TAG, "setAmbientLight 索引异常")
                return@launch
            }
            // 氛围灯取色
            setBytesFunctionValue(index)
        }
    }

    @JvmStatic
    fun switchLight(isOn: Boolean) {
        log("switchLight isOn=$isOn")
    }

    /** 初始化资源 */
    @JvmStatic
    fun loadData(displayId: Int, pictures: List<String>) {
        if (displayId != DisplayParameter.DISPLAY_CSD.displayId) return
        log("loadData pictures size=${pictures.size} pictures $pictures")
        originalList.clear()
        for ((_, picture) in pictures.withIndex()) {
            runCatching {
                val bitmap = GlideCacheUtils.loadImageAsBitmap(picture, mWidth, mHeight)
                originalList.add(generate(bitmap))
            }.getOrElse {
                Log.e(TAG, "loadData exception ${it.message}")
            }
        }
        log("loadData hueList size=${originalList.size}")
    }

    private fun setFunctionValue(functionId: Int, value: Int, zone: Int) {
        
    }

    private fun setBytesFunctionValue(index: Int) {
        try {
            originalList[index].let {
                test1Listener?.invoke(it.color)
                test2Listener?.invoke(
                    findColor(it.hue()).colorTip, findLabColor(it), findTestColor(it.labArray)
                )
            }
        } catch (e: Exception) {
            Log.e(TAG, "setBytesFunctionValue Exception $e")
        }
    }

    private fun findColor(bgHue: Float): ColorTipBean {
        if (tableList.isEmpty()) {
            Log.w(TAG, "findColor hsvList is null")
            return ColorTipBean(Color.WHITE)
        }
        var result = tableList[0]
        var minDiff = abs(result.hue() - bgHue)
        for (i in 0 until tableList.size) {
            val currentDiff = abs(tableList[i].hue() - bgHue)
            if (currentDiff < minDiff) {
                minDiff = currentDiff
                result = tableList[i]
            }
        }
        log("findColor bgHue=$bgHue,minDiff=$minDiff,result=$result")
        return ColorTipBean(result.color)
    }

    private fun findLabColor(bean: ColorTableBean): Int {
        var color = Color.WHITE
        var minDiff = Double.MAX_VALUE
        tableList.forEachIndexed { index, it ->
            val distance = ColorEabLabUtils.deltaE76(it.labArray, bean.labArray)
            if (distance < minDiff) {
                minDiff = distance
                color = it.color
            }
        }
        log("findLabColor minDiff=$minDiff,color=[${bean.color},$color]")
        return if (minDiff < 26) color else findColor(bean.hue()).colorTip
    }

    private fun findTestColor(list: DoubleArray): Int {
        var color = Color.WHITE
        var minDiff = Double.MAX_VALUE
        tableList.forEachIndexed { index, it ->
            val distance = ColorEabLabUtils.deltaE76(it.labArray, list)
            if (distance < minDiff) {
                minDiff = distance
                color = it.color
            }
        }
        return color
    }

    private fun getColors(bean: ColorTableBean): ByteArray {
        val result = mutableListOf<ColorTipBean>()
        val colorBean = ColorTipBean(findLabColor(bean))
        result.add(colorBean)
        result.add(colorBean)
        result.add(colorBean)
        val json = GsonUtils.toJson(ColorLightBean(result).list)
        log("setBytesFunctionValue json=$json")
        return json.toByteArray()
    }

    private fun generate(newMap: Bitmap): ColorTableBean {
        val dominantColor = ColorEabLabUtils.getPerceptuallyDominantColor(newMap)
        val hsvArray = FloatArray(3)
        Color.colorToHSV(dominantColor, hsvArray)
        val labArray = ColorEabLabUtils.rGBToLab(dominantColor)
        return ColorTableBean(hsvArray, labArray, dominantColor)
    }

    private fun log(str: String) = Log.d(TAG, str)

}
View Code

 

posted on 2025-10-22 11:21  翻滚的咸鱼  阅读(12)  评论(0)    收藏  举报