0
votes

I'm trying to create some jQuery function that changes object bgcolor to lighter or darker (you set parameter as difference of tone of color). And as I think it should work, but it cracks.

$.fn.changeBg = function(difference){




    var hexToRgb = function(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;
    }

    var rgbToHex = function(r, g, b) {
        return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
    }

    //returns color n-tones lighter or darker (for negavitve values) from given.
    var changeColor = function(hex,tones){
        var rgb = hexToRgb(hex);
        rgb.r+=tones;
        rgb.g+=tones;
        rgb.b+=tones;

        rgb.r=rgb.r>255?255:rgb.r;
        rgb.r=rgb.r<0?0:rgb.r;

        rgb.g=rgb.g>255?255:rgb.g;
        rgb.g=rgb.g<0?0:rgb.g;

        rgb.b=rgb.b>255?255:rgb.b;
        rgb.b=rgb.b<0?0:rgb.b;

        var hex = rgbToHex( rgb.r , rgb.g , rgb.b );
        return hex;
    }

    var bgColor = $(this).css('background-color');
    var secColor = changeColor(bgColor,difference);
    $(this).css('background-color',secColor);

}

Fiddle - any idea what is wrong with that?

3
The javascript console has the answer ;) You may want to add console.log(hex, result); just after you're calling .exec().Andreas
Be nice to whoever will use this and pick a color space where there is a dedicate channel for the luminance, HSV, HSL, HSB, etc.mmgp

3 Answers

2
votes

bgColor isn't necessarily in #RRGBB format, regardless of the original CSS. It could be any of:

  • #RRGGBB
  • #RGB
  • rgb(R, G, B)
  • rgba(R, G, B, A)
  • name

And you would have to parse each of them.

It would be much easier if you could have the original colour in your JavaScript source somewhere in the right format.

2
votes

Well, my answer below doesn't use JQuery for anything. But it should work just fine as a JQuery function either way. As Niet the Dark Absol mentioned in his answer, the function below can parse the various types of color code, except the written name (IE. except the word blue). Unfortunately, this uses Linear interpolation and/or Log interpolation but not HSL. So its not 100% accurate, but pretty close. For this small inaccuracy we gain a lot of speed and a smaller size.

Programmatically Lighten or Darken a hex color (or rgb, and blend colors)

const pSBC=(p,c0,c1,l)=>{
    let r,g,b,P,f,t,h,i=parseInt,m=Math.round,a=typeof(c1)=="string";
    if(typeof(p)!="number"||p<-1||p>1||typeof(c0)!="string"||(c0[0]!='r'&&c0[0]!='#')||(c1&&!a))return null;
    if(!this.pSBCr)this.pSBCr=(d)=>{
        let n=d.length,x={};
        if(n>9){
            [r,g,b,a]=d=d.split(","),n=d.length;
            if(n<3||n>4)return null;
            x.r=i(r[3]=="a"?r.slice(5):r.slice(4)),x.g=i(g),x.b=i(b),x.a=a?parseFloat(a):-1
        }else{
            if(n==8||n==6||n<4)return null;
            if(n<6)d="#"+d[1]+d[1]+d[2]+d[2]+d[3]+d[3]+(n>4?d[4]+d[4]:"");
            d=i(d.slice(1),16);
            if(n==9||n==5)x.r=d>>24&255,x.g=d>>16&255,x.b=d>>8&255,x.a=m((d&255)/0.255)/1000;
            else x.r=d>>16,x.g=d>>8&255,x.b=d&255,x.a=-1
        }return x};
    h=c0.length>9,h=a?c1.length>9?true:c1=="c"?!h:false:h,f=this.pSBCr(c0),P=p<0,t=c1&&c1!="c"?this.pSBCr(c1):P?{r:0,g:0,b:0,a:-1}:{r:255,g:255,b:255,a:-1},p=P?p*-1:p,P=1-p;
    if(!f||!t)return null;
    if(l)r=m(P*f.r+p*t.r),g=m(P*f.g+p*t.g),b=m(P*f.b+p*t.b);
    else r=m((P*f.r**2+p*t.r**2)**0.5),g=m((P*f.g**2+p*t.g**2)**0.5),b=m((P*f.b**2+p*t.b**2)**0.5);
    a=f.a,t=t.a,f=a>=0||t>=0,a=f?a<0?t:t<0?a:a*P+t*p:0;
    if(h)return"rgb"+(f?"a(":"(")+r+","+g+","+b+(f?","+m(a*1000)/1000:"")+")";
    else return"#"+(4294967296+r*16777216+g*65536+b*256+(f?m(a*255):0)).toString(16).slice(1,f?undefined:-2)
}

Where p is a float percentage from -1.0 to 1.0 and from and to are respectively the starting color and the ending color. And l defaults to false which is Log interpolation, if you want Linear interpolation pass true in as l.

This function can lighten, darken, blend and convert colors in these formats:

  • #RGB
  • #RRGGBB
  • #RGBA
  • #RRGGBBAA
  • rgb(R,G,B)
  • rgb(R,G,B,A)

Hex2RGB/RGB2Hex conversions are implicit. Check the link for more info and for other versions of this function. The main version has some Error Checking. Also there are versions without the Hex2RGB/RGB2Hex conversion; they are a bit smaller.

To use it the way that I think you want, do this:

var color1 = "rgb(114,93,20)";
var c2 = pSBC(0.3,color1); // rgb(114,93,20) + [30% Lighter] => rgb(156,142,91)
var c3 = pSBC(-0.13,"#F3A"); // #F3A + [13% Darker]  => #de2c94

See link for more usage options. There are many.

PT

0
votes

I had a similar issue, which is why I was looking for a quick solution myself. Albeit, these solutions didn't meet my needs, so I did this:

const lighten = (color, lighten)=>{
  const c = color.replace("#", "");
  let rgb = [parseInt(c.substr(0, 2), 16), parseInt(c.substr(2, 2), 16), parseInt(c.substr(4, 2), 16)];
  let returnstatement = "#";
  rgb.forEach((color)=>{returnstatement+=Math.round(((255-color)*(1-Math.pow(Math.E, -lighten))+color)).toString(16)});
  return returnstatement;
}

it takes a hex color code, and a lightening parameter. The color can't exceed #ffffff because of the negative exponential term, but sufficiently high values will get you there (where e^-lighten < 1/255, this is guaranteed).

negative values would cause it to darken, but the exponential safeguard doesn't work in that direction, they lead to a catastrophic failure rather quickly.

(from here on out, the code is not yet tested)

const darken = (color, darken)=>{
      const c = color.replace("#", "");
      let rgb = [parseInt(c.substr(0, 2), 16), parseInt(c.substr(2, 2), 16), parseInt(c.substr(4, 2), 16)];
      let returnstatement = "#";
      rgb.forEach((color)=>{returnstatement+=Math.round(color/(1-Math.pow(Math.E, -darken))).toString(16)});
      return returnstatement;
    }

if you combine them like this

const lightdark = (color, lparam)=>{
 if(lparam>0){
return lighten(color, lparam);
}
return darken(color, lparam);
}

you will avoid the exponential catastrophe in both directions

finally, concerning colors, if you already are getting rgb, you might want tocheck if the color data is in an array, in which case you could

const parseColor = (color) => {
  let rgb;
  if (!Array.isArray(color)){
    if(color.includes("#")||color.includes("0x")){
      const c = color.replace("#", "").replace("0x", "");
      rgb = [parseInt(c.substr(0, 2), 16), parseInt(c.substr(2, 2), 16), parseInt(c.substr(4, 2), 16)];
    }else{
      rgb=color.split(",").map((item)=>parseInt(item.trim()));
    }
  }
  return rgb;
}

this code could be made more efficient overall, for sure. If you want to catch the possibility of it arriving as an object, like

color={r:0, b:0, g:0}

however the keys of the color object are named,

const rgb = Object.keys(color).map((key)=>color[key])

should make this case right, (just insert it in the parser with another else) since I haven't heard of any html colors being coded as cym