给定一个随机十六进制颜色列表,根据"相似度"对它们进行排序。

时间:2014-04-09 21:06:00

标签: javascript sorting colors grouping approximation

例如,这个十六进制值列表:

{
"colors" : [{"hex"   : "#fe4670"},
            {"hex"   : "#5641bc"},
            {"hex"   : "#d53fc3"},
            {"hex"   : "#6b5e09"},
            {"hex"   : "#4dd685"},
            {"hex"   : "#88d63f"},
            {"hex"   : "#eb93f3"},
            {"hex"   : "#f44847"},
            {"hex"   : "#32d159"},
            {"hex"   : "#6e9bde"},
            {"hex"   : "#c3ec64"},
            {"hex"   : "#81cce5"},
            {"hex"   : "#7233b6"},
            {"hex"   : "#bb90c3"},
            {"hex"   : "#728fde"},
            {"hex"   : "#7ef46a"},
            {"hex"   : "#f7cfff"},
            {"hex"   : "#c8b708"},
            {"hex"   : "#b45a35"},
            {"hex"   : "#589279"},
            {"hex"   : "#51f1e1"},
            {"hex"   : "#b1d770"},
            {"hex"   : "#db463d"},
            {"hex"   : "#5b02a2"},
            {"hex"   : "#909440"},
            {"hex"   : "#6f53fe"},
            {"hex"   : "#4c29bd"},
            {"hex"   : "#3b24f8"},
            {"hex"   : "#465271"},
            {"hex"   : "#6243"},
            {"hex"   : "#dbcc4"},
            {"hex"   : "#187c6"},            
            {"hex"   : "#1085e2"},
            {"hex"   : "#b521e9"},
            {"hex"   : "#4bd36d"},             
            {"hex"   : "#11bc34"},
            {"hex"   : "#455c47"},
            {"hex"   : "#a71bbf"},
            {"hex"   : "#988fc2"},
            {"hex"   : "#226cfe"}]
}

理想情况下,它应该分组"绿色","布鲁斯","紫色"等。

我还没有找到一个好方法来分组。将颜色转换为HSV然后按Hue排序,然后Sat然后Val工作得很好,但有一些例外突出:

image

我读过的另一种方法是将它们转换为LAB色彩空间,然后计算DeltaE。我在这方面取得了不同程度的成功:

image

(这是根据一种颜色对整个列表进行排序)。我想根据每种颜色对每种颜色的距离进行排序。

1 个答案:

答案 0 :(得分:1)

以下示例选择第一种颜色作为比较颜色。它首先将颜色添加到新数组中,然后迭代其余颜色,比较寻找最相似颜色的颜色。

对于它迭代的每种颜色,它从第一种颜色中减去第二种颜色中的红色,然后是绿色,然后是蓝色。然后它找到绝对值(没有负数)。之后,它将这些值加在一起并除以3。这个数字是两种颜色之间的平均差异。

一旦找到最接近的颜色,它就会选择该颜色作为新的比较颜色,将其从原始颜色数组中删除,然后将其推入已排序的数组中。这样做直到没有剩下的颜色。

当提供更大的数据集时,它肯定需要一些工作,但这是我昨晚所有的时间。我将继续努力,直到我有更好的东西。

const sort = data => {
    data = Object.assign([], data);
    const sorted = [data.shift()];

    while(data.length) {
        const [a] = sorted, c = { d: Infinity };

        for(let [i, b] of Object.entries(data)) {
            const average = Math.floor((
                Math.abs(a.r - b.r) +
                Math.abs(a.g - b.g) +
                Math.abs(a.b - b.b)
            ) / 3);

            if(average < c.d) {
                Object.assign(c, { d: average, i: i });
            }
        }
        
        sorted.unshift(data.splice(c.i, 1)[0]);
    }
    
    return sorted.reverse();
};

const test = (title, data) => {
    document.body.insertAdjacentHTML('beforeend', `<h2>${title}</h2>`);

    for(let c of data) {
        document.body.insertAdjacentHTML('beforeend', `<swatch style="background: rgb(${c.r},${c.g},${c.b})"></swatch>`);
    }
    
    return test;
}

const data = [
    {"hex": "#fe4670"},{"hex": "#5641bc"},{"hex": "#d53fc3"},{"hex": "#6b5e09"},
    {"hex": "#4dd685"},{"hex": "#88d63f"},{"hex": "#eb93f3"},{"hex": "#f44847"},
    {"hex": "#32d159"},{"hex": "#6e9bde"},{"hex": "#c3ec64"},{"hex": "#81cce5"},
    {"hex": "#7233b6"},{"hex": "#bb90c3"},{"hex": "#728fde"},{"hex": "#7ef46a"},
    {"hex": "#f7cfff"},{"hex": "#c8b708"},{"hex": "#b45a35"},{"hex": "#589279"},
    {"hex": "#51f1e1"},{"hex": "#b1d770"},{"hex": "#db463d"},{"hex": "#5b02a2"},
    {"hex": "#909440"},{"hex": "#6f53fe"},{"hex": "#4c29bd"},{"hex": "#3b24f8"},
    {"hex": "#465271"},{"hex": "#6243"},  {"hex": "#dbcc4"}, {"hex": "#187c6"},
    {"hex": "#1085e2"},{"hex": "#b521e9"},{"hex": "#4bd36d"},{"hex": "#11bc34"},
    {"hex": "#455c47"},{"hex": "#a71bbf"},{"hex": "#988fc2"},{"hex": "#226cfe"}
].reduce((m, e) => (m.push(Object.assign(e, {
    r: parseInt(e.hex.substring(1, 3), 16) || 0,
    g: parseInt(e.hex.substring(3, 5), 16) || 0,
    b: parseInt(e.hex.substring(5, 7), 16) || 0
})), m), []);

const bigdata = (() => {
    const data = [];

    const rand = () => Math.floor(Math.random() * 256);

    for(let i = 0; i < 1000; ++i) {
        data.push({r: rand(), g: rand(), b: rand()});
    }
    
    return data;
})();

test('Unsorted', data)('Sorted', sort(data))('A Larger Dataset', sort(bigdata));
swatch { display: inline-block;  border: 1px solid;  margin-left: 1px; margin-top: 1px; width: 20px; height: 20px; }
h2 { margin: 0; font-family: Verdana, Tahoma, "Sans Serif"}

以下代码段大致相同,只是它搜索已排序的数组以查找该数组中最接近的匹配项,然后将未排序数组中的颜色插入其最接近的匹配项旁边。

它似乎没有像渐变样本那样好,但似乎确实将颜色组合在一起更好。

const sort = data => {
    data = Object.assign([], data);
    const sorted = [data.shift()];

    while(data.length) {
        const a = data.shift(), c = { d: Infinity };

        for(let [i, b] of Object.entries(sorted)) {
            const average = Math.floor((
                Math.abs(a.r - b.r) +
                Math.abs(a.g - b.g) +
                Math.abs(a.b - b.b)
            ) / 3);

            if(average < c.d) {
                Object.assign(c, { d: average, i: i });
            }
        }
        
        sorted.splice(c.i, 0, a);
    }
    
    return sorted.reverse();
};

const test = (title, data) => {
    document.body.insertAdjacentHTML('beforeend', `<h2>${title}</h2>`);

    for(let c of data) {
        document.body.insertAdjacentHTML('beforeend', `<swatch style="background: rgb(${c.r},${c.g},${c.b})"></swatch>`);
    }
    
    return test;
}

const data = [
    {"hex": "#fe4670"},{"hex": "#5641bc"},{"hex": "#d53fc3"},{"hex": "#6b5e09"},
    {"hex": "#4dd685"},{"hex": "#88d63f"},{"hex": "#eb93f3"},{"hex": "#f44847"},
    {"hex": "#32d159"},{"hex": "#6e9bde"},{"hex": "#c3ec64"},{"hex": "#81cce5"},
    {"hex": "#7233b6"},{"hex": "#bb90c3"},{"hex": "#728fde"},{"hex": "#7ef46a"},
    {"hex": "#f7cfff"},{"hex": "#c8b708"},{"hex": "#b45a35"},{"hex": "#589279"},
    {"hex": "#51f1e1"},{"hex": "#b1d770"},{"hex": "#db463d"},{"hex": "#5b02a2"},
    {"hex": "#909440"},{"hex": "#6f53fe"},{"hex": "#4c29bd"},{"hex": "#3b24f8"},
    {"hex": "#465271"},{"hex": "#6243"},  {"hex": "#dbcc4"}, {"hex": "#187c6"},
    {"hex": "#1085e2"},{"hex": "#b521e9"},{"hex": "#4bd36d"},{"hex": "#11bc34"},
    {"hex": "#455c47"},{"hex": "#a71bbf"},{"hex": "#988fc2"},{"hex": "#226cfe"}
].reduce((m, e) => (m.push(Object.assign(e, {
    r: parseInt(e.hex.substring(1, 3), 16) || 0,
    g: parseInt(e.hex.substring(3, 5), 16) || 0,
    b: parseInt(e.hex.substring(5, 7), 16) || 0
})), m), []);

const bigdata = (() => {
    const data = [];

    const rand = () => Math.floor(Math.random() * 256);

    for(let i = 0; i < 1000; ++i) {
        data.push({r: rand(), g: rand(), b: rand()});
    }
    
    return data;
})();

test('Unsorted', data)('Sorted', sort(data))('A Larger Dataset', sort(bigdata));
swatch { display: inline-block;  border: 1px solid;  margin-left: 1px; margin-top: 1px; width: 20px; height: 20px; }
h2 { margin: 0; font-family: Verdana, Tahoma, "Sans Serif"}