Programaticamente Clarear ou Escurecer uma cor hexadecimal (ou rgb e misturar cores)

504

Aqui está uma função em que eu estava trabalhando para clarear ou escurecer programaticamente uma cor hexadecimal por uma quantidade específica. Basta passar uma string como "3F6D2A"para a cor ( col) e um número inteiro base10 ( amt) para a quantidade clarear ou escurecer. Para escurecer, passe um número negativo (ou seja,-20 ).

A razão de eu fazer isso foi por causa de todas as soluções que encontrei, até agora, pareciam complicar demais o problema. E tive a sensação de que isso poderia ser feito com apenas algumas linhas de código. Informe-me se encontrar algum problema ou se tiver algum ajuste a ser feito para acelerar isso.

function LightenDarkenColor(col, amt) {
  col = parseInt(col, 16);
  return (((col & 0x0000FF) + amt) | ((((col >> 8) & 0x00FF) + amt) << 8) | (((col >> 16) + amt) << 16)).toString(16);
}


// TEST
console.log( LightenDarkenColor("3F6D2A",40) );

Para desenvolvimento, use aqui uma versão mais fácil de ler:

function LightenDarkenColor(col, amt) {
  var num = parseInt(col, 16);
  var r = (num >> 16) + amt;
  var b = ((num >> 8) & 0x00FF) + amt;
  var g = (num & 0x0000FF) + amt;
  var newColor = g | (b << 8) | (r << 16);
  return newColor.toString(16);
}


// TEST
console.log(LightenDarkenColor("3F6D2A", -40));

E, finalmente, uma versão para lidar com cores que podem (ou não) ter o "#" no começo. Além disso, ajuste para valores de cores incorretos:

function LightenDarkenColor(col,amt) {
    var usePound = false;
    if ( col[0] == "#" ) {
        col = col.slice(1);
        usePound = true;
    }

    var num = parseInt(col,16);

    var r = (num >> 16) + amt;

    if ( r > 255 ) r = 255;
    else if  (r < 0) r = 0;

    var b = ((num >> 8) & 0x00FF) + amt;

    if ( b > 255 ) b = 255;
    else if  (b < 0) b = 0;

    var g = (num & 0x0000FF) + amt;

    if ( g > 255 ) g = 255;
    else if  ( g < 0 ) g = 0;

    return (usePound?"#":"") + (g | (b << 8) | (r << 16)).toString(16);
}

OK, agora não são apenas algumas linhas, mas parece muito mais simples e se você não estiver usando o "#" e não precisar verificar cores fora do intervalo, são apenas algumas linhas.

Se não estiver usando o "#", basta adicioná-lo no código como:

var myColor = "3F6D2A";
myColor = LightenDarkenColor(myColor,10);
thePlaceTheColorIsUsed = ("#" + myColor);

Acho que minha pergunta principal é: estou correto aqui? Isso não abrange algumas situações (normais)?

Pimp Trizkit
fonte
1
Se você não obtiver resultados esperados ao modificar cores, sugiro olhar para o espaço de cores do LAB, mais próximo da visão humana. Muitos idiomas têm bibliotecas para conversão. Na minha experiência, especialmente tons de laranja podem ser problemáticos ao escurecer ou clarear.
Henrik
Muito bom ponto. No entanto, o principal objetivo desta pergunta era encontrar, em primeiro lugar, a fórmula de menor tempo de execução e menor tamanho ... e, em segundo lugar, sua precisão. Por isso, por que não lidei com a conversão para HSL ou o que quer que seja. Aqui velocidade e tamanho são mais importantes. Mas, como você pode ver na minha versão 2 da fórmula. Usar o LERP para sombrear resultará em laranjas agradáveis ​​em toda a faixa de cores. Dê uma olhada na tabela de cores abaixo e deixe-me saber se esse intervalo de cores não é muito próximo da precisão real.
Pimp Trizkit
Fiquei um pouco confuso com a estrutura aqui, mas você está certo, os níveis de laranja para shadeColor1 parecem estar muito bons.
Henrik
Lol, você quer dizer shadeColor2. Eu acho que a estrutura que você está falando é o layout geral da resposta em si? Alguma dica para deixar mais claro?
Pimp Trizkit
3
Há apenas um problema na função com # acima é que ela não cria os zeros iniciais se o código hexadecimal final começar com zeros. Por exemplo, se o código hexadecimal for # 00a6b7, ele será exibido como # a6b7, que não funcionará se estiver sendo usado como um css. Você pode corrigir isso substituindo a linha de retorno por: var string = "000000" + (g | (b << 8) | (r << 16)). ToString (16); return (usePound? "#": "") + string.substr (string.length-6);
Rafael Levy

Respostas:

877

Bem, essa resposta se tornou sua própria fera. Muitas versões novas estavam ficando muito estúpidas. Muito obrigado a todos os muitos contribuidores desta resposta. Mas, para manter as coisas simples para as massas. Arquivei todas as versões / histórico da evolução desta resposta no meu github . E começou tudo de novo no StackOverflow aqui com a versão mais recente. Um agradecimento especial a Mike 'Pomax' Kamermans por esta versão. Ele me deu a nova matemática.


Esta função ( pSBC) terá uma cor da web HEX ou RGB.pSBCpode sombrear ou escurecer ou misturar com uma segunda cor e também pode passar direto, mas converter de Hex para RGB (Hex2RGB) ou RGB para Hex (RGB2Hex). Tudo sem você saber que formato de cor você está usando.

Isso roda muito rápido, provavelmente o mais rápido, especialmente considerando seus muitos recursos. Foi um longo período de preparação. Veja toda a história no meu github . Se você deseja a maneira absolutamente menor e mais rápida possível de sombrear ou misturar, consulte as Micro Funções abaixo e use um dos demônios de velocidade de 2 linhas. Eles são ótimos para animações intensas, mas esta versão aqui é rápida o suficiente para a maioria das animações.

Esta função usa mistura de log ou mistura linear. No entanto, NÃO converte em HSL para clarear ou escurecer adequadamente uma cor. Portanto, os resultados dessa função serão diferentes das funções muito maiores e mais lentas que usam HSL.

jsFiddle com pSBC

github> Wiki do pSBC

Recursos:

  • Detecta automaticamente e aceita cores hexadecimais padrão na forma de seqüências de caracteres. Por exemplo: "#AA6622"ou "#bb551144".
  • Detecta automaticamente e aceita cores RGB padrão na forma de strings. Por exemplo: "rgb(123,45,76)"ou"rgba(45,15,74,0.45)" .
  • Tons de cores para branco ou preto por porcentagem.
  • Combina cores por porcentagem.
  • Conversão Hex2RGB e RGB2Hex ao mesmo tempo, ou solo.
  • Aceita códigos de cores HEX de 3 dígitos (ou 4 dígitos com alfa), no formato #RGB (ou #RGBA). Vai expandi-los. Por exemplo: "#C41"torna - se"#CC4411" .
  • Aceita e (Linear) combina canais alfa. Se a c0cor (de) ou c1(para) tiver um canal alfa, a cor retornada terá um canal alfa. Se ambas as cores tiverem um canal alfa, a cor retornada será uma mistura linear dos dois canais alfa usando a porcentagem fornecida (como se fosse um canal de cores normal). Se apenas uma das duas cores tiver um canal alfa, esse alfa será passado apenas para a cor retornada. Isso permite misturar / sombrear uma cor transparente, mantendo o nível de transparência. Ou, se os níveis de transparência também se misturarem, verifique se ambas as cores possuem alfas. Ao sombrear, passará o canal alfa diretamente. Se você deseja sombreamento básico que também sombreie o canal alfa, use rgb(0,0,0,1)ou rgb(255,255,255,1)comoc1(a) cor (ou seus equivalentes hexadecimais). Para cores RGB, o canal alfa da cor retornada será arredondado para três casas decimais.
  • As conversões RGB2Hex e Hex2RGB estão implícitas ao usar o blending. Independentemente da c0cor (de); a cor retornada sempre estará no formato de cor c1(para), se houver. Se não houver c1(para) cor, passe 'c'como a c1cor e ela sombreará e converterá a c0cor que for. Se apenas a conversão for desejada, passe 0como porcentagem ( p) também. Se a c1cor for omitida ou não string-passada, ela não será convertida.
  • Uma função secundária é adicionada ao global também. pSBCrpode receber uma cor Hex ou RGB e retorna um objeto que contém essas informações de cores. Está no formato: {r: XXX, g: XXX, b: XXX, a: X.XXX}. Onde .r, .ge .btem intervalo de 0 a 255. E quando não há alpha: .aé -1. Caso contrário: .atem um intervalo de 0,000 a 1.000.
  • Para saída RGB, ele gera rgba()sobre rgb()quando uma cor com um canal alfa foi passado em c0(de) e / ou c1(a).
  • A verificação de erros menores foi adicionada. Não é perfeito. Ele ainda pode travar ou criar bobagens. Mas vai pegar algumas coisas. Basicamente, se a estrutura estiver errada de alguma forma ou se a porcentagem não for um número ou estiver fora do escopo, ela retornará null. Um exemplo:, pSBC(0.5,"salt") == nullonde, como pensa, #salté uma cor válida. Exclua as quatro linhas que terminam com return null;para remover esse recurso e torná-lo mais rápido e menor.
  • Usa mistura de log. Passe truepara l(o quarto parâmetro) para usar a mistura linear.

Código:

// Version 4.0
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)
}

Uso:

// Setup:

let color1 = "rgb(20,60,200)";
let color2 = "rgba(20,60,200,0.67423)";
let color3 = "#67DAF0";
let color4 = "#5567DAF0";
let color5 = "#F3A";
let color6 = "#F3A9";
let color7 = "rgb(200,60,20)";
let color8 = "rgba(200,60,20,0.98631)";

// Tests:

/*** Log Blending ***/
// Shade (Lighten or Darken)
pSBC ( 0.42, color1 ); // rgb(20,60,200) + [42% Lighter] => rgb(166,171,225)
pSBC ( -0.4, color5 ); // #F3A + [40% Darker] => #c62884
pSBC ( 0.42, color8 ); // rgba(200,60,20,0.98631) + [42% Lighter] => rgba(225,171,166,0.98631)

// Shade with Conversion (use "c" as your "to" color)
pSBC ( 0.42, color2, "c" ); // rgba(20,60,200,0.67423) + [42% Lighter] + [Convert] => #a6abe1ac

// RGB2Hex & Hex2RGB Conversion Only (set percentage to zero)
pSBC ( 0, color6, "c" ); // #F3A9 + [Convert] => rgba(255,51,170,0.6)

// Blending
pSBC ( -0.5, color2, color8 ); // rgba(20,60,200,0.67423) + rgba(200,60,20,0.98631) + [50% Blend] => rgba(142,60,142,0.83)
pSBC ( 0.7, color2, color7 ); // rgba(20,60,200,0.67423) + rgb(200,60,20) + [70% Blend] => rgba(168,60,111,0.67423)
pSBC ( 0.25, color3, color7 ); // #67DAF0 + rgb(200,60,20) + [25% Blend] => rgb(134,191,208)
pSBC ( 0.75, color7, color3 ); // rgb(200,60,20) + #67DAF0 + [75% Blend] => #86bfd0

/*** Linear Blending ***/
// Shade (Lighten or Darken)
pSBC ( 0.42, color1, false, true ); // rgb(20,60,200) + [42% Lighter] => rgb(119,142,223)
pSBC ( -0.4, color5, false, true ); // #F3A + [40% Darker] => #991f66
pSBC ( 0.42, color8, false, true ); // rgba(200,60,20,0.98631) + [42% Lighter] => rgba(223,142,119,0.98631)

// Shade with Conversion (use "c" as your "to" color)
pSBC ( 0.42, color2, "c", true ); // rgba(20,60,200,0.67423) + [42% Lighter] + [Convert] => #778edfac

// RGB2Hex & Hex2RGB Conversion Only (set percentage to zero)
pSBC ( 0, color6, "c", true ); // #F3A9 + [Convert] => rgba(255,51,170,0.6)

// Blending
pSBC ( -0.5, color2, color8, true ); // rgba(20,60,200,0.67423) + rgba(200,60,20,0.98631) + [50% Blend] => rgba(110,60,110,0.83)
pSBC ( 0.7, color2, color7, true ); // rgba(20,60,200,0.67423) + rgb(200,60,20) + [70% Blend] => rgba(146,60,74,0.67423)
pSBC ( 0.25, color3, color7, true ); // #67DAF0 + rgb(200,60,20) + [25% Blend] => rgb(127,179,185)
pSBC ( 0.75, color7, color3, true ); // rgb(200,60,20) + #67DAF0 + [75% Blend] => #7fb3b9

/*** Other Stuff ***/
// Error Checking
pSBC ( 0.42, "#FFBAA" ); // #FFBAA + [42% Lighter] => null  (Invalid Input Color)
pSBC ( 42, color1, color5 ); // rgb(20,60,200) + #F3A + [4200% Blend] => null  (Invalid Percentage Range)
pSBC ( 0.42, {} ); // [object Object] + [42% Lighter] => null  (Strings Only for Color)
pSBC ( "42", color1 ); // rgb(20,60,200) + ["42"] => null  (Numbers Only for Percentage)
pSBC ( 0.42, "salt" ); // salt + [42% Lighter] => null  (A Little Salt is No Good...)

// Error Check Fails (Some Errors are not Caught)
pSBC ( 0.42, "#salt" ); // #salt + [42% Lighter] => #a5a5a500  (...and a Pound of Salt is Jibberish)

// Ripping
pSBCr ( color4 ); // #5567DAF0 + [Rip] => [object Object] => {'r':85,'g':103,'b':218,'a':0.941}

A figura abaixo ajudará a mostrar a diferença nos dois métodos de mesclagem:


Micro Funções

Se você realmente deseja velocidade e tamanho, precisará usar RGB e não HEX. O RGB é mais direto e simples, o HEX escreve muito devagar e apresenta muitos sabores para um simples de duas linhas (ou seja, poderia ser um código HEX de 3, 4, 6 ou 8 dígitos). Você também precisará sacrificar alguns recursos, sem verificação de erros, sem HEX2RGB nem RGB2HEX. Além disso, você precisará escolher uma função específica (com base no nome da função abaixo) para a matemática da mistura de cores e se desejar sombreamento ou mistura. Essas funções suportam canais alfa. E quando ambas as cores de entrada tiverem alfas, elas serão combinadas linearmente. Se apenas uma das duas cores tiver um alfa, ele passará direto para a cor resultante. Abaixo estão duas funções de liner incrivelmente rápidas e pequenas:

const RGB_Linear_Blend=(p,c0,c1)=>{
    var i=parseInt,r=Math.round,P=1-p,[a,b,c,d]=c0.split(","),[e,f,g,h]=c1.split(","),x=d||h,j=x?","+(!d?h:!h?d:r((parseFloat(d)*P+parseFloat(h)*p)*1000)/1000+")"):")";
    return"rgb"+(x?"a(":"(")+r(i(a[3]=="a"?a.slice(5):a.slice(4))*P+i(e[3]=="a"?e.slice(5):e.slice(4))*p)+","+r(i(b)*P+i(f)*p)+","+r(i(c)*P+i(g)*p)+j;
}

const RGB_Linear_Shade=(p,c)=>{
    var i=parseInt,r=Math.round,[a,b,c,d]=c.split(","),P=p<0,t=P?0:255*p,P=P?1+p:1-p;
    return"rgb"+(d?"a(":"(")+r(i(a[3]=="a"?a.slice(5):a.slice(4))*P+t)+","+r(i(b)*P+t)+","+r(i(c)*P+t)+(d?","+d:")");
}

const RGB_Log_Blend=(p,c0,c1)=>{
    var i=parseInt,r=Math.round,P=1-p,[a,b,c,d]=c0.split(","),[e,f,g,h]=c1.split(","),x=d||h,j=x?","+(!d?h:!h?d:r((parseFloat(d)*P+parseFloat(h)*p)*1000)/1000+")"):")";
    return"rgb"+(x?"a(":"(")+r((P*i(a[3]=="a"?a.slice(5):a.slice(4))**2+p*i(e[3]=="a"?e.slice(5):e.slice(4))**2)**0.5)+","+r((P*i(b)**2+p*i(f)**2)**0.5)+","+r((P*i(c)**2+p*i(g)**2)**0.5)+j;
}

const RGB_Log_Shade=(p,c)=>{
    var i=parseInt,r=Math.round,[a,b,c,d]=c.split(","),P=p<0,t=P?0:p*255**2,P=P?1+p:1-p;
    return"rgb"+(d?"a(":"(")+r((P*i(a[3]=="a"?a.slice(5):a.slice(4))**2+t)**0.5)+","+r((P*i(b)**2+t)**0.5)+","+r((P*i(c)**2+t)**0.5)+(d?","+d:")");
}

Quer mais informações? Leia o artigo completo no github .

PT

(Ps Se alguém tiver a matemática para outro método de mistura, compartilhe.)

Pimp Trizkit
fonte
8
Uma versão PHP para aqueles que dela necessitam: gist.github.com/chaoszcat/5325115#file-gistfile1-php
Lionel Chan
28
Eu costumava TinyColor -tinycolor.darken(color,amount);
FWrnr
4
Grande post ... :) ... acabou de criar extensão Swift dele: gist.github.com/matejukmar/1da47f7a950d1ba68a95
Matej Ukmar
2
Aqui é a versão PHP para a versão atualizada shadeColor2: function shadeColor2($color, $percent) { $color = str_replace("#", "", $color); $t=$percent<0?0:255; $p=$percent<0?$percent*-1:$percent; $RGB = str_split($color, 2); $R=hexdec($RGB[0]); $G=hexdec($RGB[1]); $B=hexdec($RGB[2]); return '#'.substr(dechex(0x1000000+(round(($t-$R)*$p)+$R)*0x10000+(round(($t-$G)*$p)+$G)*0x100+(round(($t-$B)*$p)+$B)),1); }
Kevin M
2
Desculpe, aparentemente perdi esse ponto. Talvez haja duas razões. A primeira e óbvia é que eu uso Math.Round e você não pode usar números decimais para cores precisas (as cores não têm decimais em hexadecimal). Por exemplo, se o canal Vermelho estiver 8, adicione 10%você para 8.8qual round 9. Em seguida, tomar 9.09%longe 9e você começa 8.1819. O que faz com 8que seja um mau exemplo. Mas ainda ilustra que está a tomar 9.09%de 9e não 8.8. Portanto, pode haver alguns números que não sejam exatamente iguais aos do meu exemplo aqui.
Pimp Trizkit
122

Eu fiz uma solução que funciona muito bem para mim:

function shadeColor(color, percent) {

    var R = parseInt(color.substring(1,3),16);
    var G = parseInt(color.substring(3,5),16);
    var B = parseInt(color.substring(5,7),16);

    R = parseInt(R * (100 + percent) / 100);
    G = parseInt(G * (100 + percent) / 100);
    B = parseInt(B * (100 + percent) / 100);

    R = (R<255)?R:255;  
    G = (G<255)?G:255;  
    B = (B<255)?B:255;  

    var RR = ((R.toString(16).length==1)?"0"+R.toString(16):R.toString(16));
    var GG = ((G.toString(16).length==1)?"0"+G.toString(16):G.toString(16));
    var BB = ((B.toString(16).length==1)?"0"+B.toString(16):B.toString(16));

    return "#"+RR+GG+BB;
}

Exemplo Lighten:

shadeColor("#63C6FF",40);

Exemplo Escurecer:

shadeColor("#63C6FF",-40);
Pablo
fonte
4
Bom, eu gosto da porcentagem! +1 Tho, eu poderia fazer R = ((R<255)?R:255).toString(16);, em seguida, R = R.length==1 ? "0"+R : Rpara a velocidade. E eu não tenho certeza do ponto do toUpperCase?
Pimp Trizkit
Isso é desnecessário. Eu apenas adiciono isso para impressão bonita durante o teste. Eu vou editar isso.
Pablo
Muito agradável. No entanto, 100% mais claro não deve ficar totalmente branco e 100% escuro sempre preto, independentemente da cor? parece que -100 torna qualquer cor preta, mas 100 (positivo) não a torna totalmente branca.
Kevin M
4
não funciona com cores sólidas como # ff0000, # 00ff00, # 0000ff
Hitori
Para fazê-lo funcionar com a cor preta eu só fiz este hack var R = parseInt(color.substring(1, 3), 16) var G = parseInt(color.substring(3, 5), 16) var B = parseInt(color.substring(5, 7), 16) if (R == 0) R = 32; if (G == 0) G = 32; if (B == 0) B = 32;
Irfan Raza
21

Aqui está um forro super simples, baseado na resposta de Eric

function adjust(color, amount) {
    return '#' + color.replace(/^#/, '').replace(/../g, color => ('0'+Math.min(255, Math.max(0, parseInt(color, 16) + amount)).toString(16)).substr(-2));
}

Exemplos:

adjust('#ffffff', -20) => "#ebebeb"
adjust('000000', 20) => "#141414"
supersan
fonte
7
"super simples".
Andrew
5

Isto é o que eu usei com base em sua função. Prefiro usar etapas acima da porcentagem, porque é mais intuitivo para mim.

Por exemplo, 20% de um valor de 200 azul é muito diferente de 20% de um valor de 40 azul.

De qualquer forma, aqui está a minha modificação, obrigado pela sua função original.

function adjustBrightness(col, amt) {

    var usePound = false;

    if (col[0] == "#") {
        col = col.slice(1);
        usePound = true;
    }

    var R = parseInt(col.substring(0,2),16);
    var G = parseInt(col.substring(2,4),16);
    var B = parseInt(col.substring(4,6),16);

    // to make the colour less bright than the input
    // change the following three "+" symbols to "-"
    R = R + amt;
    G = G + amt;
    B = B + amt;

    if (R > 255) R = 255;
    else if (R < 0) R = 0;

    if (G > 255) G = 255;
    else if (G < 0) G = 0;

    if (B > 255) B = 255;
    else if (B < 0) B = 0;

    var RR = ((R.toString(16).length==1)?"0"+R.toString(16):R.toString(16));
    var GG = ((G.toString(16).length==1)?"0"+G.toString(16):G.toString(16));
    var BB = ((B.toString(16).length==1)?"0"+B.toString(16):B.toString(16));

    return (usePound?"#":"") + RR + GG + BB;

}
Eric Sloan
fonte
Achei isso muito mais útil que a resposta principal, porque a resposta principal estava tornando minhas cores muito intensas, em vez de apenas mais escuras. Cheers Eric
Worm
4

Eu tentei sua função e houve um pequeno bug: se algum valor final de 'r' tiver apenas 1 dígito, o resultado será semelhante a: 'a0a0a' quando o valor correto for '0a0a0a', por exemplo. Eu apenas o corrigi rapidamente adicionando isso em vez do seu retorno:

var rStr = (r.toString(16).length < 2)?'0'+r.toString(16):r.toString(16);
var gStr = (g.toString(16).length < 2)?'0'+g.toString(16):g.toString(16);
var bStr = (b.toString(16).length < 2)?'0'+b.toString(16):b.toString(16);

return (usePound?"#":"") + rStr + gStr + bStr;

Talvez não seja tão legal, mas faça o trabalho. Ótima função, entre. Apenas o que eu precisava. :)

Ácido fresco
fonte
1
Obrigado pela depuração e elogio! Pena que não é uma resposta para saber se existe ou não uma maneira mais rápida, que é a minha pergunta principal. Como possivelmente um usando todas as conversões hexadecimais e sem base. Tho, acho que você me disse se eu tinha o código correto (+1). Infelizmente, a correção adicionou uma sobrecarga consideravelmente maior (agora você está ligando para String 6 vezes) e um pouco menos de beijo. Talvez seja mais rápido verificar se o número da base10 é 15 ou menos antes da conversão da base16. Mas eu gosto!
Pimp Trizkit
4

você pensou em uma conversão rgb> hsl? então basta mover a luminosidade para cima e para baixo? é assim que eu iria.

Uma rápida olhada em alguns algoritmos me deu os seguintes sites.

PHP: http://serennu.com/colour/rgbtohsl.php

Javascript: http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript

EDITAR o link acima não é mais válido. Você pode visualizar o git hub para a origem da página ou o gist

Como alternativa, outra pergunta sobre StackOverflow pode ser um bom lugar para procurar.


Mesmo que essa não seja a escolha certa para o OP, a seguir é uma aproximação do código que eu estava sugerindo originalmente. (Supondo que você tenha funções de conversão rgb / hsl)

var SHADE_SHIFT_AMOUNT = 0.1; 

function lightenShade(colorValue)
{
    if(colorValue && colorValue.length >= 6)
    {
        var redValue = parseInt(colorValue.slice(-6,-4), 16);
        var greenValue = parseInt(colorValue.slice(-4,-2), 16);
        var blueValue = parseInt(colorValue.slice(-2), 16);

        var hsl = rgbToHsl(redValue, greenValue, blueValue);
        hsl[2]= Math.min(hsl[2] + SHADE_SHIFT_AMOUNT, 1);
        var rgb = hslToRgb(hsl[0], hsl[1], hsl[2]);
        return "#" + rgb[0].toString(16) + rgb[1].toString(16) + rgb[2].toString(16);
    }
    return null;
}

function darkenShade(colorValue)
{
    if(colorValue && colorValue.length >= 6)
    {
        var redValue = parseInt(colorValue.slice(-6,-4), 16);
        var greenValue = parseInt(colorValue.slice(-4,-2), 16);
        var blueValue = parseInt(colorValue.slice(-2), 16);

        var hsl = rgbToHsl(redValue, greenValue, blueValue);
        hsl[2]= Math.max(hsl[2] - SHADE_SHIFT_AMOUNT, 0);
        var rgb = hslToRgb(hsl[0], hsl[1], hsl[2]);
        return "#" + rgb[0].toString(16) + rgb[1].toString(16) + rgb[2].toString(16);
    }
    return null;
}

Isso pressupõe:

  1. Você tem funções hslToRgbergbToHsl .
  2. O parâmetro colorValueé uma sequência no formato #RRGGBB

Embora, se estamos discutindo o css, exista uma sintaxe para especificar o hsl / hsla para o IE9 / Chrome / Firefox.

James Khoury
fonte
Interessante, mas não precisaria converter de hexadecimal para rgb em hsl? Parece que é mais complicado. Talvez esteja faltando alguma coisa. Mas, estou procurando uma maneira do KISS de fazê-lo, o mais rápido possível (tempo de execução). Eu me sinto idealmente, se eu pudesse fazer tudo em hexadecimal, seria o mais rápido. Mas, a solução que desenvolvi aqui envolve usar o rgb para poder adicionar uma quantidade incremental.
Pimp Trizkit
Sim, eu suponho que seria mais lento, mais complicado e se você não usar rgb para conversão de hsl em outro lugar, provavelmente não seria a solução mais simplista. Seria, no entanto, mais preciso do que aumentar os valores de rgb, embora eu também não seja uma pessoa muito colorida. Tudo depende da precisão que você quer ser, eu acho.
James Khoury
Qual é a perda de precisão que você mencionou? Eu suponho que você quer dizer que todas as cores [web] não são acessíveis com rgb ou algo assim?
Pimp Trizkit
Como eu disse eu não sei muito sobre a cor: wiki Teoria da Cor
James Khoury
@Pimp Trizkit: É menos preciso porque (e essa é apenas a minha teoria ... eu não sou especialista em cores) você está mudando cada canal na mesma quantidade, não importa quanto dessa cor existia para começar. Eu acho que isso resultaria na diminuição da saturação, porque você aproxima os canais um do outro (em porcentagem). Obviamente, se você transbordar / transbordar, isso é inevitável.
Matthew Crumley #
2

Versão C # ... observe que estou recebendo seqüências de cores neste formato # FF12AE34 e preciso cortar o #FF.

    private string GetSmartShadeColorByBase(string s, float percent)
    {
        if (string.IsNullOrEmpty(s))
            return "";
        var r = s.Substring(3, 2);
        int rInt = int.Parse(r, NumberStyles.HexNumber);
        var g = s.Substring(5, 2);
        int gInt = int.Parse(g, NumberStyles.HexNumber);
        var b = s.Substring(7, 2);
        int bInt = int.Parse(b, NumberStyles.HexNumber);

        var t = percent < 0 ? 0 : 255;
        var p = percent < 0 ? percent*-1 : percent;

        int newR = Convert.ToInt32(Math.Round((t - rInt) * p) + rInt);
        var newG = Convert.ToInt32(Math.Round((t - gInt) * p) + gInt);
        var newB = Convert.ToInt32(Math.Round((t - bInt) * p) + bInt);

        return String.Format("#{0:X2}{1:X2}{2:X2}", newR, newG, newB);
    }
user1618171
fonte
5
Nunca usei C # antes, mas parece que as três últimas declarações de variáveis ​​são estranhas. Um inte dois varspara o mesmo tipo de dados.
Pimp Trizkit
4
A palavra-chave var em C # significa permitir que o compilador inferir o tipo em tempo de compilação. Portanto, no exemplo acima, int e var definem uma variável do mesmo tipo - int. Isso é útil se você tiver um nome de tipo longo ou se desejar fazer referência a um tipo anônimo. É estranho porque user1618171 misturou dois estilos de declaração de variáveis ​​- provavelmente um erro de digitação.
Daniel James Bryars
2

Eu queria mudar uma cor para um nível de brilho específico - independentemente do brilho da cor antes - aqui está uma função JS simples que parece funcionar bem, embora eu tenha certeza de que poderia ser menor

function setLightPercentage(col: any, p: number) {
    const R = parseInt(col.substring(1, 3), 16);
    const G = parseInt(col.substring(3, 5), 16);
    const B = parseInt(col.substring(5, 7), 16);
    const curr_total_dark = (255 * 3) - (R + G + B);

    // calculate how much of the current darkness comes from the different channels
    const RR = ((255 - R) / curr_total_dark);
    const GR = ((255 - G) / curr_total_dark);
    const BR = ((255 - B) / curr_total_dark);

    // calculate how much darkness there should be in the new color
    const new_total_dark = ((255 - 255 * (p / 100)) * 3);

    // make the new channels contain the same % of available dark as the old ones did
    const NR = 255 - Math.round(RR * new_total_dark);
    const NG = 255 - Math.round(GR * new_total_dark);
    const NB = 255 - Math.round(BR * new_total_dark);

    const RO = ((NR.toString(16).length === 1) ? "0" + NR.toString(16) : NR.toString(16));
    const GO = ((NG.toString(16).length === 1) ? "0" + NG.toString(16) : NG.toString(16));
    const BO = ((NB.toString(16).length === 1) ? "0" + NB.toString(16) : NB.toString(16));

    return "#" + RO + GO + BO;}
Torbjörn Josefsson
fonte
Coolio! Presumo que ptenha alcance 0-100? Eu nem sei como definir corretamente o brilho em RGB, isso é uma coisa HSL. Por exemplo, é #FF00FFmais brilhante que #FF0000? Nesse caso, isso significaria que magenta é duas vezes mais brilhante que vermelho. Portanto, o teste de vermelho puro é usado. Passe em vermelho puro #FF0000, com um brilho de 50%, e aqui chegamos #FF4040, não é? Eu imaginaria que o brilho seria 50% vermelho, estaríamos ficando mais escuros, visto que já é totalmente brilhante ... como #800000seria o brilho ou 150% #FF8080. Rosa é um vermelho mais brilhante? ou o vermelho já está totalmente claro?
Pimp Trizkit
Você está certo - eu deveria ter mencionado que p deve estar no intervalo de 1 a 100!
Torbjörn Josefsson
# FF00FF possui 255 como valor no canal vermelho, 0 no canal verde e 255 no canal azul. Quanto mais altos os valores combinados nos canais, maior o brilho da cor. O número p afirma que queremos que a nova cor seja 50% mais brilhante que a cor original. Não estou 100% certo que # FF4040 é a resposta correta para "50% o mais brilhante possível", vermelho. A produção de tons mais escuros (com, neste caso, menor valor no canal vermelho) iria exigir a modificação
Torbjörn Josefsson
Sim, eu estava apenas apontando a ambiguidade em falar sobre brilho em RGB. Se convertido para HSL, o Lcanal é literalmente brilho. Minha questão [mental pessoal] aqui é que, para mim, #FF0000é totalmente brilhante. E #FF4040é mais leve, mas não mais brilhante ... para mim, mais leve significa mais perto do branco, como o rosa. E brilho é o quanto ele tem, e seu vermelho está cheio, tão vermelho, está cheio de brilho. Portanto, #FF0000não pode ser mais brilhante .. mas sim .. mais leve ... talvez eu seja apenas uma aberração, lol !! Eu realmente não sei teoria da cor, sooo, eu realmente estou apenas falando a minha um ...
Pimp Trizkit
Mas sei que quando altero o brilho do meu monitor, os vermelhos não ficam rosados ​​... para mim. Então é provavelmente aqui que iniciei minha lógica.
Pimp Trizkit
1

O método a seguir permitirá clarear ou escurecer o valor da exposição de uma sequência de cores Hexadecimal (Hex):

private static string GetHexFromRGB(byte r, byte g, byte b, double exposure)
{
    exposure = Math.Max(Math.Min(exposure, 1.0), -1.0);
    if (exposure >= 0)
    {
        return "#"
            + ((byte)(r + ((byte.MaxValue - r) * exposure))).ToString("X2")
            + ((byte)(g + ((byte.MaxValue - g) * exposure))).ToString("X2")
            + ((byte)(b + ((byte.MaxValue - b) * exposure))).ToString("X2");
    }
    else
    {
        return "#"
            + ((byte)(r + (r * exposure))).ToString("X2")
            + ((byte)(g + (g * exposure))).ToString("X2")
            + ((byte)(b + (b * exposure))).ToString("X2");
    }

}

Para o último valor do parâmetro em GetHexFromRGB (), passe um valor duplo em algum lugar entre -1 e 1 (-1 é preto, 0 é inalterado, 1 é branco):

// split color (#e04006) into three strings
var r = Convert.ToByte("e0", 16);
var g = Convert.ToByte("40", 16);
var b = Convert.ToByte("06", 16);

GetHexFromRGB(r, g, b, 0.25);  // Lighten by 25%;
Jason Williams
fonte
0

Como simples cor de sombra em PHP?

<?php
function shadeColor ($color='#cccccc', $percent=-25) {

  $color = Str_Replace("#",Null,$color);

  $r = Hexdec(Substr($color,0,2));
  $g = Hexdec(Substr($color,2,2));
  $b = Hexdec(Substr($color,4,2));

  $r = (Int)($r*(100+$percent)/100);
  $g = (Int)($g*(100+$percent)/100);
  $b = (Int)($b*(100+$percent)/100);

  $r = Trim(Dechex(($r<255)?$r:255));  
  $g = Trim(Dechex(($g<255)?$g:255));  
  $b = Trim(Dechex(($b<255)?$b:255));

  $r = ((Strlen($r)==1)?"0{$r}":$r);
  $g = ((Strlen($g)==1)?"0{$g}":$g);
  $b = ((Strlen($b)==1)?"0{$b}":$b);

  return (String)("#{$r}{$g}{$b}");
}

echo shadeColor(); // #999999
jsebestyan
fonte
Esta é uma versão php da resposta de Pablo. Infelizmente, é mais longo e mais lento que a solução final e não clareia as cores com precisão. Escurece-os com precisão. Teste com vermelho puro (# FF0000), um clarão de 25% deve ser (# FF4040). Confira o final da minha resposta para a versão PHP de Kevin M da solução final v2.
Pimp Trizkit
0

Eu criei uma porta da excelente biblioteca xcolor para remover sua dependência do jQuery. Existem várias funções, incluindo cores claras e escuras.

Realmente, converter hex para RGB é uma função completamente separada das cores claras ou escuras. Mantenha as coisas secas por favor. De qualquer forma, depois de ter uma cor RGB, você pode adicionar a diferença entre o nível de luz desejado e o nível de luz que você possui para cada um dos valores RGB:

var lightness = function(level) {
    if(level === undefined) {
        return Math.max(this.g,this.r,this.b)
    } else {
        var roundedLevel = Math.round(level) // fractions won't work here
        var levelChange = roundedLevel - this.lightness()

        var r = Math.max(0,this.r+levelChange)
        var g = Math.max(0,this.g+levelChange)
        var b = Math.max(0,this.b+levelChange)

        if(r > 0xff) r = 0xff
        if(g > 0xff) g = 0xff
        if(b > 0xff) b = 0xff

        return xolor({r: r, g: g, b: b})
    }
}

var lighter = function(amount) {
    return this.lightness(this.lightness()+amount)
}

Consulte https://github.com/fresheneesz/xolor para obter mais informações sobre a fonte.

BT
fonte
Ainda não analisei o código no que se refere ao meu OP (velocidade / tamanho / precisão). Mas, à primeira leitura, há alguns comentários a serem feitos: 1) Concordo que a conversão de hex para RGB pode ser vista como uma função completamente separada. Se meu problema pretendia ser resolvido com uma função seca, o que não era um requisito. A intenção aqui era ter uma resposta (veja minha versão 2) super rápida e super minúscula (2 linhas!) E uma que iluminasse e escurecesse uma cor hexadecimal ... especificamente ... como um autônomo independente função. Para que, em seu uso final, seja uma simples chamada de função. <Cont.>
Pimp Trizkit
2) E o caso da Versão 3, por demanda popular, é a intenção de ter uma função universal independente, o mais rápida e pequena possível, que possa assumir cegamente uma cor hexadecimal ou RGB e em todos os seus aspectos. variações. Portanto, é necessária uma conversão de hex para RGB. <cont.>
Pimp Trizkit
3) Após uma análise simples do código. Parece que seria muito mais lento e obviamente é muito maior que a minha versão 2 (que é a resposta real para o meu OP; a versão 3 era para as massas). Para ser justo, devo comparar esse código com a minha versão 2 do RGB, que não faz uma conversão e parece responder ao seu ponto de vista sobre a secura. E sinceramente, sua porta não é muito mais simples de entender do que o meu 2 liner para hex. Portanto, embora seja mais seco, na verdade não é muito, se é que é mais simples. (a secura não ajudou muito na compreensão) <cont.>
Pimp Trizkit
4) Minha versão 2 do RGB é uma função de 2 linhas sem conversão, se você desejar. Minha solução específica para o meu OP original queria hex. É por isso que existem dois tipos diferentes de versão 2. Mas você menciona o ponto sobre secura e conversões hexadecimais, agora estamos realmente focados na versão 3. A versão 3 veio muito mais tarde; somente após a versão 2 era popular. <cont.>
Pimp Trizkit
5) Embora eu concorde que a secura geralmente ajuda a universalidade. E na maioria dos casos, pela capacidade de compreensão. Infelizmente, está a um custo neste exemplo. Esses custos são que é muito maior e aparentemente muito mais lento e aparentemente usa mais memória na pilha (com sua natureza recursiva) e no global (2 funções; em comparação com a v2).
Pimp Trizkit
0

Há muito que eu queria poder produzir matizes / cores, aqui está minha solução JavaScript:

const varyHue = function (hueIn, pcIn) {
    const truncate = function (valIn) {
        if (valIn > 255) {
            valIn = 255;
        } else if (valIn < 0)  {
            valIn = 0;
        }
        return valIn;
    };

    let red   = parseInt(hueIn.substring(0, 2), 16);
    let green = parseInt(hueIn.substring(2, 4), 16);
    let blue  = parseInt(hueIn.substring(4, 6), 16);
    let pc    = parseInt(pcIn, 10);    //shade positive, tint negative
    let max   = 0;
    let dif   = 0;

    max = red;

    if (pc < 0) {    //tint: make lighter
        if (green < max) {
            max = green;
        }

        if (blue < max) {
            max = blue;
        }

        dif = parseInt(((Math.abs(pc) / 100) * (255 - max)), 10);

        return leftPad(((truncate(red + dif)).toString(16)), '0', 2)  + leftPad(((truncate(green + dif)).toString(16)), '0', 2) + leftPad(((truncate(blue + dif)).toString(16)), '0', 2);
    } else {    //shade: make darker
        if (green > max) {
            max = green;
        }

        if (blue > max) {
            max = blue;
        }

        dif = parseInt(((pc / 100) * max), 10);

        return leftPad(((truncate(red - dif)).toString(16)), '0', 2)  + leftPad(((truncate(green - dif)).toString(16)), '0', 2) + leftPad(((truncate(blue - dif)).toString(16)), '0', 2);
    }
};
user2655360
fonte
Alguns exemplos de uso ajudariam. E talvez alguma explicação sobre o porquê desta versão sobre as outras. Esta versão parece correr consideravelmente mais lenta. E é muito mais longo. E não parece sombrear com precisão. Parece que você está usando LERP, ou algo semelhante ... o que é bom. Infelizmente, é apenas de um canal, então esse mesmo valor é usado em todos os canais. Isso não está certo, para obter maior precisão, você deve LERP cada canal individualmente. Como minha resposta a esta pergunta faz. Além disso, é menor e mais rápido, e verifica se há erros e manipula rgb e faz conversões, eu poderia continuar #
Pimp Trizkit
Um exemplo de uso: variableHue ("6e124c", 77) em que o primeiro argumento é a cor em hexadecimal e o segundo a alteração percentual. Uma porcentagem positiva muda de tom (escurece), enquanto um valor negativo tinge (ilumina) o resultado. Escrevi a rotina como minha primeira tentativa apenas algumas horas antes de chegar a esta página e a publiquei simplesmente como uma questão de interesse. Eu não sabia que precisava melhorar seu esforço ou que precisava de sua aprovação antes de fazê-lo. É inteiramente meu próprio trabalho, sem referência a mais ninguém. Eu não ouvi falar do LERP, vou dar uma olhada, obrigado pela sugestão.
user2655360
Hehe, bem, é claro que você não precisa fazer nada! E todos agradecemos por seus esforços! Minhas primeiras preocupações principais foram as listadas primeiro. Tentando ajudá-lo com sua resposta para que ela possa receber votos. (mostre usos e explicações de como funciona, etc.) O outro material é obviamente uma análise rápida para ajudar a aprofundar o conhecimento de todos . Desculpe, se parecia um pouco agressivo. Mas outra sugestão é fazê-lo aceitar as #cores hexadecimais. Desculpe se parecia ... "aprovação" ... eu vi isso como revisão por pares. Se você não quer que alguém analise seu código ou ofereça feedback, peço desculpas.
Pimp Trizkit
0

Sua abordagem está correta :) Simplifiquei um pouco sua versão mais curta (para controle de saturação, veja aqui )

(col,amt)=> (+('0x'+col)+amt*0x010101).toString(16).padStart(6,0)

E versão com verificação de # e de cores

Kamil Kiełczewski
fonte
0

Também fiz um pacote simples. Eu usei a convexidade de IR ^ 3, é claro, os valores RGB estão em IN ^ 3, o que não é convexo, então isso não é realmente perfeito

Para escurecer, usei o seguinte

for (let i = 0; i < rgb.length; i++) {
   rgb[i] = Math.floor(rgb[i] - ratio * rgb[i]);
}

E para clarear

for (let i = 0; i < rgb.length; i++) {
   rgb[i] = Math.ceil(rgb[i] + ratio * (255 - rgb[i]));
}

Aqui está o pacote https://github.com/MarchWorks/colortone

Demo https://colortone.now.sh/

com a maneira como estou fazendo as coisas, se você passar uma proporção de -1, você terminará em preto, branco se a proporção for 1. Passando 0, pois a proporção não mudará de cor

evgeni fotia
fonte
0

Eu precisava dele em C #, pode ajudar desenvolvedores .net

public static string LightenDarkenColor(string color, int amount)
    {
        int colorHex = int.Parse(color, System.Globalization.NumberStyles.HexNumber);
        string output = (((colorHex & 0x0000FF) + amount) | ((((colorHex >> 0x8) & 0x00FF) + amount) << 0x8) | (((colorHex >> 0xF) + amount) << 0xF)).ToString("x6");
        return output;
    }
Nassim
fonte
Você converteu a função "não funciona" que não lida com zeros à esquerda e pode ir acima de FF nas somas, = modificar o componente de cor acima ...
B. Ir
Quando tentei a função, ela não funcionou por causa de valores não hexadecimais (16 = 0xF) e (8 = 0x8) e fornece uma cor em 8 posições, mas agora funciona muito bem
Nassim
você tentou aumentar de FF? (digamos 0xFFFFFF + 0x000004): seu código passa por cima desse valor máximo (digamos 0x1000003), em vez de não aumentar, e principalmente define os 2 componentes superiores da cor para 00, que é a maior alteração que eles poderiam fazer ...
B. Go
você está certo, obrigado pela observação, exceto por uma entrada de limites em torno de fff e 000 ela funcionará bem.
Nassim