根据十六进制颜色获取最接近的颜色名称

10

我尝试根据给定的十六进制值获取最匹配的颜色名称。例如,如果我们有十六进制颜色#f00,我们需要获取颜色名称red

'#ff0000' => 'red'
'#000000' => 'black'
'#ffff00' => 'yellow'

我目前使用的是Levenshtein距离算法来获取最接近的颜色名称,目前效果良好,但有时不如预期。

例如:

'#0769ad' => 'chocolate'
'#00aaee' => 'mediumspringgreen'

有什么建议可以让结果更接近吗?

以下是我用来获取最接近颜色的方法:

Array.closest = (function () {

    // http://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#JavaScript
    function levDist(s, t) {
        if (!s.length) return t.length;
        if (!t.length) return s.length;

        return Math.min(
            levDist(s.substring(1), t) + 1,
            levDist(t.substring(1), s) + 1,
            levDist(s.substring(1), t.substring(1)) + (s[0] !== t[0] ? 1 : 0)
        );
    }

    return function (arr, str) {
        // https://dev59.com/Gmct5IYBdhLWcg3wqfL9#comment16113902_11919065
        return arr.sort(function (a, b) {
            return levDist(a, str) - levDist(b, str);
        });
    };

}());

http://jsfiddle.net/ARTsinn/JUZVd/2/

另一个问题是性能!看起来有一个非常大的问题使得这个程序运行缓慢(是算法的问题吗?)。


1
对于更相似的颜色,最好使用HSL颜色。 - Sirko
1
如果在排序之前预先计算距离,那么您可以使排序步骤快得多。 - Pointy
我也不确定为什么你不使用简单的笛卡尔距离计算。 (实际上,我想我会转换为角度坐标空间,并在HSL或HSV三元组中进行距离计算。) - Pointy
1
我会通过计算两种颜色在每个 R、G 和 B 通道上的差异平均值来计算它们之间的差异。例如,(3,4,5) 和 (10,14,18) 的平均差异为 10。 - dandavis
dandavis所描述的方法是我在答案中使用的相同方法。 - Andrew Clark
显示剩余4条评论
1个回答

12

Levenshtein距离在这里并不是很合适,因为它会逐个字符地比较相等。你需要分别检查每种颜色,并且你希望798000更接近。

以下内容似乎更接近您所需的内容,只需对您的代码进行最小更改:

Array.closest = (function () {
    function dist(s, t) {
        if (!s.length || !t.length) return 0;
        return dist(s.slice(2), t.slice(2)) +
            Math.abs(parseInt(s.slice(0, 2), 16) - parseInt(t.slice(0, 2), 16));
    }

    return function (arr, str) {
        return arr.sort(function (a, b) {
            return dist(a, str) - dist(b, str);
        });
    };
}());

请注意,仅当st都是6个字符的颜色十六进制代码时,才能获得合理的结果。
您的代码效率低下,因为您不需要对整个数组进行排序以获取最接近的颜色。相反,您应该只需循环遍历数组并跟踪最短距离。
例如:
Array.closest = (function () {
    function dist(s, t) {
        if (!s.length || !t.length) return 0;
        return dist(s.slice(2), t.slice(2)) +
            Math.abs(parseInt(s.slice(0, 2), 16) - parseInt(t.slice(0, 2), 16));
    }

    return function (arr, str) {
        var min = 0xffffff;
        var best, current, i;
        for (i = 0; i < arr.length; i++) {
            current = dist(arr[i], str)
            if (current < min) {
                min = current
                best = arr[i];
            }
        }
        return best;
    };
}());

请注意,在此更改后,Array.closest()将返回单个值而不是数组,因此您需要在代码中进一步删除[0]

哇,太棒了!到目前为止,谢谢:- 当s和t都是6个字符的颜色十六进制代码时,结果很合理 没问题,我将3位十六进制颜色转换为6位。 :) - yckart
顺便提一下:Pointy在这里提到,预先计算距离再排序可以使速度更快?! - yckart
1
需要记住的一件事是,G通道实际上比R和B通道更重要。此外,并非每个级别步骤在0-255值的每个级别都相同,例如:7-2是巨大的变化,但132-127相对较小。通过将感知校正(NTSC)灰度级差与上面计算的平均RGB差异进行平均来筛选屏幕。我认为它应该占25%,因此将其添加到RGB中并除以4而不是3。如果你想知道,NTSC灰度计算公式为((0.29r)+(0.59g)+(0.12*b)) / 3... - dandavis
@dandavis 听起来很不错,但我不知道如何实现。无论如何,还是谢谢你的建议 :) - yckart

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接