计算补色的JS函数?

71

有没有人能够脱口而出地提供一个用Javascript计算十六进制值的互补颜色的解决方案?

网络上有很多颜色选择套件和调色板生成器,但我还没有看到任何一个可以使用JS动态计算颜色的。

非常感谢提供详细提示或片段。

7个回答

68

使用http://design.geckotribe.com/colorwheel/ 进行解析

    // Complement
    temprgb={ r: 0, g: 0xff, b: 0xff }; // Cyan
    temphsv=RGB2HSV(temprgb);
    temphsv.hue=HueShift(temphsv.hue,180.0);
    temprgb=HSV2RGB(temphsv);
    console.log(temprgb); // Complement is red (0xff, 0, 0)
    
    function RGB2HSV(rgb) {
     hsv = new Object();
     max=max3(rgb.r,rgb.g,rgb.b);
     dif=max-min3(rgb.r,rgb.g,rgb.b);
     hsv.saturation=(max==0.0)?0:(100*dif/max);
     if (hsv.saturation==0) hsv.hue=0;
      else if (rgb.r==max) hsv.hue=60.0*(rgb.g-rgb.b)/dif;
     else if (rgb.g==max) hsv.hue=120.0+60.0*(rgb.b-rgb.r)/dif;
     else if (rgb.b==max) hsv.hue=240.0+60.0*(rgb.r-rgb.g)/dif;
     if (hsv.hue<0.0) hsv.hue+=360.0;
     hsv.value=Math.round(max*100/255);
     hsv.hue=Math.round(hsv.hue);
     hsv.saturation=Math.round(hsv.saturation);
     return hsv;
    }
    
    // RGB2HSV and HSV2RGB are based on Color Match Remix [http://color.twysted.net/]
    // which is based on or copied from ColorMatch 5K [http://colormatch.dk/]
    function HSV2RGB(hsv) {
     var rgb=new Object();
     if (hsv.saturation==0) {
      rgb.r=rgb.g=rgb.b=Math.round(hsv.value*2.55);
     } else {
      hsv.hue/=60;
      hsv.saturation/=100;
      hsv.value/=100;
      i=Math.floor(hsv.hue);
      f=hsv.hue-i;
      p=hsv.value*(1-hsv.saturation);
      q=hsv.value*(1-hsv.saturation*f);
      t=hsv.value*(1-hsv.saturation*(1-f));
      switch(i) {
      case 0: rgb.r=hsv.value; rgb.g=t; rgb.b=p; break;
      case 1: rgb.r=q; rgb.g=hsv.value; rgb.b=p; break;
      case 2: rgb.r=p; rgb.g=hsv.value; rgb.b=t; break;
      case 3: rgb.r=p; rgb.g=q; rgb.b=hsv.value; break;
      case 4: rgb.r=t; rgb.g=p; rgb.b=hsv.value; break;
      default: rgb.r=hsv.value; rgb.g=p; rgb.b=q;
      }
      rgb.r=Math.round(rgb.r*255);
      rgb.g=Math.round(rgb.g*255);
      rgb.b=Math.round(rgb.b*255);
     }
     return rgb;
    }

    //Adding HueShift via Jacob (see comments)
    function HueShift(h,s) { 
        h+=s; while (h>=360.0) h-=360.0; while (h<0.0) h+=360.0; return h; 
    }
    
    //min max via Hairgami_Master (see comments)
    function min3(a,b,c) { 
        return (a<b)?((a<c)?a:c):((b<c)?b:c); 
    } 
    function max3(a,b,c) { 
        return (a>b)?((a>c)?a:c):((b>c)?b:c); 
    }


7
神奇的HueShift(...)函数是这样的:function HueShift(h,s) { h+=s; while (h>=360.0) h-=360.0; while (h<0.0) h+=360.0; return h; }。该函数用于改变颜色的色相,并返回新的色相值。其中,参数h代表原始的色相值,参数s代表需要偏移的角度值。函数内部运用了循环语句,使得新的色相值在0到360之间。 - Jacob
您还需要max3和min3函数: - Hairgami_Master
4
函数min3(a,b,c)返回a、b和c中的最小值,函数max3(a,b,c)返回a、b和c中的最大值。代码如下:function min3(a,b,c) { return (a<b)?((a<c)?a:c):((b<c)?b:c); } function max3(a,b,c) { return (a>b)?((a>c)?a:c):((b>c)?b:c); } - Hairgami_Master
这个thisrgb值应该是多少? - Gino
3
虽然这个程序确实做了一些很棒的事情,但是有大量的代码缺失。“HueShift”?“看注释”你说?什么注释?由于不完整,这个答案变得无用。 - WebWanderer
显示剩余5条评论

38

我发现按位取反的效果很好,而且速度很快。

var color = 0x320ae3;
var complement = 0xffffff ^ color;

我不确定它是否是“混合在一起形成70%的灰色”意义上的完美补充,但在电影的颜色定时方面,70%的灰色是“纯白色”。我想到了通过对纯白色进行RGB十六进制异或运算可能是一个不错的初步近似值。你也可以尝试更暗的灰色来看看效果如何。

再次强调,这只是一个快速的近似值,我不能保证它完全准确。

请查看https://github.com/alfl/textful/blob/master/app.js#L38获取我的实现。


10
除非你希望结果始终为6个字符长,否则这种方法效果很好。我建议使用('000000' + (('0xffffff' ^ '0x320ae3').toString(16))).slice(-6);来实现。 - professormeowingtons
Buzzzz:(错误)0x7F7F7F的相反颜色是什么?0x808080!这不是非常“相反”。使用0x808080进行XOR运算会更好(颜色距离始终为一半),但仍然不是“最佳”的。 HSL方法可以获得最佳结果。 - geowar
如果你将灰色从灰色中异或,你会得到另一种灰色。称其为方法的极限:D - Alex Flanagan
解决方案是仅翻转每个组件的最高有效位,而不是全部翻转:color ^ 0x808080。灰色变成几乎白色或几乎黑色,与灰色形成足够对比。 - undefined

36

这里的其他功能都不能直接使用,所以我写了这个。

它接受一个十六进制值,将其转换为HSL,将色相向180度移动,然后再转换回十六进制。

/* hexToComplimentary : Converts hex value to HSL, shifts
 * hue by 180 degrees and then converts hex, giving complimentary color
 * as a hex value
 * @param  [String] hex : hex value  
 * @return [String] : complimentary color as hex value
 */
function hexToComplimentary(hex){

    // Convert hex to rgb
    // Credit to Denis https://dev59.com/7G035IYBdhLWcg3wGMDa#36253499
    var rgb = 'rgb(' + (hex = hex.replace('#', '')).match(new RegExp('(.{' + hex.length/3 + '})', 'g')).map(function(l) { return parseInt(hex.length%2 ? l+l : l, 16); }).join(',') + ')';

    // Get array of RGB values
    rgb = rgb.replace(/[^\d,]/g, '').split(',');

    var r = rgb[0], g = rgb[1], b = rgb[2];

    // Convert RGB to HSL
    // Adapted from answer by 0x000f https://dev59.com/CXE95IYBdhLWcg3wV8e_#34946092
    r /= 255.0;
    g /= 255.0;
    b /= 255.0;
    var max = Math.max(r, g, b);
    var min = Math.min(r, g, b);
    var h, s, l = (max + min) / 2.0;

    if(max == min) {
        h = s = 0;  //achromatic
    } else {
        var d = max - min;
        s = (l > 0.5 ? d / (2.0 - max - min) : d / (max + min));

        if(max == r && g >= b) {
            h = 1.0472 * (g - b) / d ;
        } else if(max == r && g < b) {
            h = 1.0472 * (g - b) / d + 6.2832;
        } else if(max == g) {
            h = 1.0472 * (b - r) / d + 2.0944;
        } else if(max == b) {
            h = 1.0472 * (r - g) / d + 4.1888;
        }
    }

    h = h / 6.2832 * 360.0 + 0;

    // Shift hue to opposite side of wheel and convert to [0-1] value
    h+= 180;
    if (h > 360) { h -= 360; }
    h /= 360;

    // Convert h s and l values into r g and b values
    // Adapted from answer by Mohsen https://dev59.com/CXE95IYBdhLWcg3wV8e_#9493060
    if(s === 0){
        r = g = b = l; // achromatic
    } else {
        var hue2rgb = function hue2rgb(p, q, t){
            if(t < 0) t += 1;
            if(t > 1) t -= 1;
            if(t < 1/6) return p + (q - p) * 6 * t;
            if(t < 1/2) return q;
            if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
            return p;
        };

        var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
        var p = 2 * l - q;

        r = hue2rgb(p, q, h + 1/3);
        g = hue2rgb(p, q, h);
        b = hue2rgb(p, q, h - 1/3);
    }

    r = Math.round(r * 255);
    g = Math.round(g * 255); 
    b = Math.round(b * 255);

    // Convert r b and g values to hex
    rgb = b | (g << 8) | (r << 16); 
    return "#" + (0x1000000 | rgb).toString(16).substring(1);
}  

1
这个很棒。你写这个花了多长时间?我已经寻找能做到这个的东西有一段时间了,也遇到了几个“开箱即用”的解决方案,但都不起作用。 - AlexanderGriffin
5
大约花了一个小时,我只是将一些stackoverflow的答案拼凑在一起(代码中注明了出处)。 - Edward
@Edd:你好,我试了你的算法,将以下颜色#00BCD4作为基色应该得到互补色#d41900,但实际上给出的是#d41800。能帮我看看吗? - Paul
非常好!对于任何想要更小的差异的人,将 h+= 180; 更改为 h+= 40; - Rasmus
生成与此网络工具相符的值。https://www.canva.com/colors/color-wheel - pistol-pete

9

不用重新发明轮子,我找到了一个与颜色相关的库。

Tiny Color

使用它实现其他答案的方法如下:

color1 = tinycolor2('#f00').spin(180).toHexString(); // Hue Shift
color2 = tinycolor2("#f00").complement().toHexString(); // bitwise

3

十六进制和RGB互补色 这是获取互补的十六进制颜色值最正确和高效的方式。

function complementryHexColor(hex){
    let r = hex.length == 4 ? parseInt(hex[1] + hex[1], 16) : parseInt(hex.slice(1, 3), 16);
    let g = hex.length == 4 ? parseInt(hex[2] + hex[2], 16) : parseInt(hex.slice(3, 5), 16);
    let b = hex.length == 4 ? parseInt(hex[3] + hex[3], 16) : parseInt(hex.slice(5), 16);
  
    [r, g, b] = complementryRGBColor(r, g, b);
    return '#' + (r < 16 ? '0' + r.toString(16) : r.toString(16)) + (g < 16 ? '0' + g.toString(16) : g.toString(16)) + (b < 16 ? '0' + b.toString(16) : b.toString(16));
}

function complementryRGBColor(r, g, b) {
    if (Math.max(r, g, b) == Math.min(r, g, b)) {
        return [255 - r, 255 - g, 255 - b];

    } else {
        r /= 255, g /= 255, b /= 255;
        var max = Math.max(r, g, b), min = Math.min(r, g, b);
        var h, s, l = (max + min) / 2;
        var d = max - min;
        s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
    
        switch (max) {
            case r: h = (g - b) / d + (g < b ? 6 : 0); break;
            case g: h = (b - r) / d + 2; break;
            case b: h = (r - g) / d + 4; break;
        }
    
        h = Math.round((h*60) + 180) % 360;
        h /= 360;
        
        function hue2rgb(p, q, t) {
            if (t < 0) t += 1;
            if (t > 1) t -= 1;
            if (t < 1/6) return p + (q - p) * 6 * t;
            if (t < 1/2) return q;
            if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
            return p;
        }
    
        var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
        var p = 2 * l - q;
    
        r = hue2rgb(p, q, h + 1/3);
        g = hue2rgb(p, q, h);
        b = hue2rgb(p, q, h - 1/3);

        return [Math.round(r*255), Math.round(g*255), Math.round(b*255)];
    }
}

2

RGB互补色

function componentToHex(c) {
    var hex = c.toString(16);
    return hex.length == 1 ? "0" + hex : hex;
}


function hexToRgb(hex) {
  var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  return result ? {
    r: parseInt(result[1], 16),
    g: parseInt(result[2], 16),
    b: parseInt(result[3], 16)
  } : null;
}

function rgbComplimentary(r,g,b){

    var hex = "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
    var rgb = 'rgb(' + (hex = hex.replace('#', '')).match(new RegExp('(.{' + hex.length/3 + '})', 'g')).map(function(l) { return parseInt(hex.length%2 ? l+l : l, 16); }).join(',') + ')';

    // Get array of RGB values
    rgb = rgb.replace(/[^\d,]/g, '').split(',');

    var r = rgb[0]/255.0, g = rgb[1]/255.0, b = rgb[2]/255.0;

    var max = Math.max(r, g, b);
    var min = Math.min(r, g, b);
    var h, s, l = (max + min) / 2.0;

    if(max == min) {
        h = s = 0;  //achromatic
    } else {
        var d = max - min;
        s = (l > 0.5 ? d / (2.0 - max - min) : d / (max + min));

        if(max == r && g >= b) {
            h = 1.0472 * (g - b) / d ;
        } else if(max == r && g < b) {
            h = 1.0472 * (g - b) / d + 6.2832;
        } else if(max == g) {
            h = 1.0472 * (b - r) / d + 2.0944;
        } else if(max == b) {
            h = 1.0472 * (r - g) / d + 4.1888;
        }
    }

    h = h / 6.2832 * 360.0 + 0;

    // Shift hue to opposite side of wheel and convert to [0-1] value
    h+= 180;
    if (h > 360) { h -= 360; }
    h /= 360;

    // Convert h s and l values into r g and b values
    // Adapted from answer by Mohsen https://dev59.com/CXE95IYBdhLWcg3wV8e_#9493060
    if(s === 0){
        r = g = b = l; // achromatic
    } else {
        var hue2rgb = function hue2rgb(p, q, t){
            if(t < 0) t += 1;
            if(t > 1) t -= 1;
            if(t < 1/6) return p + (q - p) * 6 * t;
            if(t < 1/2) return q;
            if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
            return p;
        };

        var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
        var p = 2 * l - q;

        r = hue2rgb(p, q, h + 1/3);
        g = hue2rgb(p, q, h);
        b = hue2rgb(p, q, h - 1/3);
    }

    r = Math.round(r * 255);
    g = Math.round(g * 255); 
    b = Math.round(b * 255);

    // Convert r b and g values to hex
    rgb = b | (g << 8) | (r << 16); 
    return hexToRgb("#" + (0x1000000 | rgb).toString(16).substring(1));

}


console.log(rgbComplimentary(242, 211, 215));

HEX Complimentary

function hexComplimentary(hex){

    var rgb = 'rgb(' + (hex = hex.replace('#', '')).match(new RegExp('(.{' + hex.length/3 + '})', 'g')).map(function(l) { return parseInt(hex.length%2 ? l+l : l, 16); }).join(',') + ')';

    // Get array of RGB values
    rgb = rgb.replace(/[^\d,]/g, '').split(',');

    var r = rgb[0]/255.0, g = rgb[1]/255.0, b = rgb[2]/255.0;

    var max = Math.max(r, g, b);
    var min = Math.min(r, g, b);
    var h, s, l = (max + min) / 2.0;

    if(max == min) {
        h = s = 0;  //achromatic
    } else {
        var d = max - min;
        s = (l > 0.5 ? d / (2.0 - max - min) : d / (max + min));

        if(max == r && g >= b) {
            h = 1.0472 * (g - b) / d ;
        } else if(max == r && g < b) {
            h = 1.0472 * (g - b) / d + 6.2832;
        } else if(max == g) {
            h = 1.0472 * (b - r) / d + 2.0944;
        } else if(max == b) {
            h = 1.0472 * (r - g) / d + 4.1888;
        }
    }

    h = h / 6.2832 * 360.0 + 0;

    // Shift hue to opposite side of wheel and convert to [0-1] value
    h+= 180;
    if (h > 360) { h -= 360; }
    h /= 360;

    if(s === 0){
        r = g = b = l; // achromatic
    } else {
        var hue2rgb = function hue2rgb(p, q, t){
            if(t < 0) t += 1;
            if(t > 1) t -= 1;
            if(t < 1/6) return p + (q - p) * 6 * t;
            if(t < 1/2) return q;
            if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
            return p;
        };

        var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
        var p = 2 * l - q;

        r = hue2rgb(p, q, h + 1/3);
        g = hue2rgb(p, q, h);
        b = hue2rgb(p, q, h - 1/3);
    }

    r = Math.round(r * 255);
    g = Math.round(g * 255); 
    b = Math.round(b * 255);

    // Convert r b and g values to hex
    rgb = b | (g << 8) | (r << 16); 
    return "#" + (0x1000000 | rgb).toString(16).substring(1);
}


console.log(hexComplimentary("#ff5a5a"));

来源:更新的https://dev59.com/PXI-5IYBdhLWcg3wy7-n#37657940答案


嗨,我尝试了你的算法,使用以下颜色#00BCD4应该给我相应的互补颜色是#d41900,但实际上它给出的是#d41800。你能帮我一下吗? - Paul

0
有一个比来回转换RGB和HSV/HSL(尽管结果相同)更简单的方法来获得互补色:
function complement(r, g, b) {
    const m = Math.min(r, g, b) + Math.max(r, g, b);
    return [m - r, m - g, m - b];
}

一路从十六进制来:
function hexComplement(rgb) {
    rgb = Number.parseInt(rgb.substring(1), 16);
    const [r, g, b] = complement(rgb >> 16, (rgb >> 8) & 0xff, rgb & 0xff);
    return '#' + ((r << 16) | (g << 8) | b).toString(16).padStart(6, '0');
}

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