Aclara u oscurece programáticamente un color hexadecimal (o rgb, y combina colores)

504

Aquí hay una función en la que estaba trabajando para aclarar u oscurecer mediante programación un color hexadecimal en una cantidad específica. Simplemente pase una cadena como "3F6D2A"para el color ( col) y un entero base10 ( amt) para que la cantidad se aclare u oscurezca. Para oscurecer, pase un número negativo (es decir -20).

La razón por la que hice esto fue por todas las soluciones que encontré, hasta ahora, parecían complicar demasiado el problema. Y tuve la sensación de que podría hacerse con solo un par de líneas de código. Avíseme si encuentra algún problema o si tiene que hacer ajustes para acelerarlo.

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 uso de desarrollo, aquí hay una versión más fácil de leer:

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));

Y finalmente, una versión para manejar colores que pueden (o no) tener el "#" al principio. Más ajuste para valores de color incorrectos:

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, ahora no son solo un par de líneas, sino que parece mucho más simple y si no está usando el "#" y no necesita verificar si los colores están fuera de rango, son solo un par de líneas.

Si no usa el "#", puede agregarlo en un código como:

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

Supongo que mi pregunta principal es, ¿estoy en lo correcto aquí? ¿Esto no abarca algunas situaciones (normales)?

Pimp Trizkit
fuente
1
Si no obtiene los resultados esperados al modificar los colores, le sugiero que busque en el espacio de color LAB, que está más cerca de la visión humana. Muchos idiomas tienen bibliotecas para la conversión. En mi experiencia, especialmente los tonos de naranja pueden ser problemáticos al oscurecer o aligerar.
Henrik
Muy buen punto. Sin embargo, el objetivo principal de esta pregunta era encontrar, en primer lugar, el tiempo de ejecución más rápido y la fórmula de menor tamaño ... y en segundo lugar, su precisión. Por lo tanto, por qué no me ocupé de convertir a HSL o lo que sea. Aquí la velocidad y el tamaño son más importantes. Pero, como puedes ver con mi versión 2 de la fórmula. Usar LERP para dar sombra dará como resultado naranjas agradables en todo el rango de sombra. Eche un vistazo a la tabla de colores a continuación y avíseme si ese rango de tonos no es muy cercano a la precisión real.
Pimp Trizkit
Me confundí un poco con la estructura aquí, pero tienes razón, los niveles de naranja para shadeColor1 parecen ser muy buenos.
Henrik
Lol, te refieres a shadowColor2. Supongo que la estructura de la que estás hablando es el diseño general de la respuesta en sí. ¿Alguna pista para aclarar más?
Pimp Trizkit
3
Solo hay un problema en la función con # anterior es que no crea los ceros iniciales si el código hexadecimal final comienza con ceros. Por ejemplo, si el código hexadecimal es # 00a6b7, lo generará como # a6b7, que no funcionará si se usa como CSS. Puede corregir eso reemplazando la línea de retorno por esto: var string = "000000" + (g | (b << 8) | (r << 16)). ToString (16); return (usePound? "#": "") + string.substr (string.length-6);
Rafael Levy

Respuestas:

877

Bueno, esta respuesta se ha convertido en su propia bestia. Muchas versiones nuevas, se estaba volviendo estúpido por mucho tiempo. Muchas gracias a todos los grandes contribuyentes a esta respuesta. Pero, para mantenerlo simple para las masas. Archivé todas las versiones / historia de la evolución de esta respuesta en mi github . Y comencé de nuevo en StackOverflow aquí con la versión más nueva. Un agradecimiento especial para Mike 'Pomax' Kamermans por esta versión. Me dio las nuevas matemáticas.


Esta función ( pSBC) tomará un color web HEX o RGB. pSBCpuede sombrearlo más oscuro o más claro, o mezclarlo con un segundo color, y también puede pasarlo directamente pero convertir de Hex a RGB (Hex2RGB) o de RGB a Hex (RGB2Hex). Todo sin siquiera saber qué formato de color está utilizando.

Esto funciona muy rápido, probablemente el más rápido, especialmente teniendo en cuenta sus muchas características. Fue mucho tiempo en la fabricación. Ver toda la historia en mi github . Si desea la forma más pequeña y rápida posible de sombrear o mezclar, vea las Micro Funciones a continuación y use uno de los demonios de velocidad de 2 líneas. Son excelentes para animaciones intensas, pero esta versión aquí es lo suficientemente rápida para la mayoría de las animaciones.

Esta función utiliza la combinación de registros o la combinación lineal. Sin embargo, NO se convierte a HSL para aclarar u oscurecer adecuadamente un color. Por lo tanto, los resultados de esta función diferirán de las funciones mucho más grandes y lentas que usan HSL.

jsFiddle con pSBC

github> pSBC Wiki

caracteristicas:

  • Detecta automáticamente y acepta colores Hex estándar en forma de cadenas. Por ejemplo: "#AA6622"o "#bb551144".
  • Detecta automáticamente y acepta colores RGB estándar en forma de cadenas. Por ejemplo: "rgb(123,45,76)"o "rgba(45,15,74,0.45)".
  • Sombrea los colores a blanco o negro por porcentaje.
  • Mezcla colores por porcentaje.
  • Hace la conversión Hex2RGB y RGB2Hex al mismo tiempo, o solo.
  • Acepta códigos de color HEX de 3 dígitos (o 4 dígitos con alfa), en la forma #RGB (o #RGBA). Los ampliará. Por ejemplo: se "#C41"convierte "#CC4411".
  • Acepta y (Lineal) combina canales alfa. Si el c0color (desde) o el color c1(hasta) tiene un canal alfa, entonces el color devuelto tendrá un canal alfa. Si ambos colores tienen un canal alfa, entonces el color devuelto será una mezcla lineal de los dos canales alfa usando el porcentaje dado (como si fuera un canal de color normal). Si solo uno de los dos colores tiene un canal alfa, este alfa solo se pasará al color devuelto. Esto permite mezclar / sombrear un color transparente mientras se mantiene el nivel de transparencia. O, si los niveles de transparencia también se combinan, asegúrese de que ambos colores tengan alfa. Al sombrear, pasará el canal alfa directamente. Si desea un sombreado básico que también sombree el canal alfa, use rgb(0,0,0,1)o rgb(255,255,255,1)como suc1(a) color (o sus equivalentes hexadecimales). Para los colores RGB, el canal alfa del color devuelto se redondeará a 3 decimales.
  • Las conversiones RGB2Hex y Hex2RGB están implícitas cuando se usa la combinación. Independientemente del c0color (de); el color devuelto siempre estará en el formato de color c1(a), si existe. Si no hay c1color (a), entonces pase 'c'como c1color y sombreará y convertirá el c0color que sea. Si solo se desea la conversión, entonces pase 0como porcentaje ( p) también. Si c1se omite el color o stringse pasa un no , no se convertirá.
  • También se agrega una función secundaria a la global. pSBCrse le puede pasar un color Hex o RGB y devuelve un objeto que contiene esta información de color. Está en la forma: {r: XXX, g: XXX, b: XXX, a: X.XXX}. Donde .r, .gy .btienen un rango de 0 a 255. Y cuando no hay alfa: .aes -1. De lo contrario: .atiene un rango de 0,000 a 1,000.
  • Para la salida RGB, se emite cuando se rgba()pasa rgb()un color con un canal alfa a c0(desde) y / o c1(hasta).
  • Se ha agregado la comprobación de errores menores. No es perfecto Todavía puede bloquearse o crear jibberish. Pero atrapará algunas cosas. Básicamente, si la estructura es incorrecta de alguna manera o si el porcentaje no es un número o está fuera de alcance, volverá null. Un ejemplo: pSBC(0.5,"salt") == nulldonde, como cree, #saltes un color válido. Elimine las cuatro líneas que terminan con return null;para eliminar esta característica y hacerla más rápida y más pequeña.
  • Utiliza la combinación de registros. Pase truepor l(el 4º parámetro) para usar Linear Blending.

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}

La siguiente imagen ayudará a mostrar la diferencia en los dos métodos de fusión:


Micro funciones

Si realmente quieres velocidad y tamaño, tendrás que usar RGB, no HEX. RGB es más directo y simple, HEX escribe demasiado lento y viene en muchos sabores para un simple dos líneas (es decir, podría ser un código HEX de 3, 4, 6 u 8 dígitos). También deberá sacrificar algunas funciones, sin verificación de errores, sin HEX2RGB ni RGB2HEX. Además, deberá elegir una función específica (según el nombre de la función a continuación) para las matemáticas de combinación de colores, y si desea sombrear o combinar. Estas funciones admiten canales alfa. Y cuando ambos colores de entrada tienen alfa, se combinarán linealmente. Si solo uno de los dos colores tiene un alfa, lo pasará directamente al color resultante. A continuación hay dos funciones de línea que son increíblemente rápidas y pequeñas:

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:")");
}

¿Quieres más información? Lea el artículo completo en github .

PT

(Ps. Si alguien tiene las matemáticas para otro método de combinación, por favor comparta).

Pimp Trizkit
fuente
8
Una versión PHP para quienes lo necesitan: gist.github.com/chaoszcat/5325115#file-gistfile1-php
Lionel Chan el
28
Solía TinyColor -tinycolor.darken(color,amount);
FWrnr
44
Gran publicación ... :) ... acaba de crear la extensión Swift: gist.github.com/matejukmar/1da47f7a950d1ba68a95
Matej Ukmar
2
Aquí está la versión PHP para la versión actualizada de 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
Lo siento, aparentemente perdí ese punto. Quizás hay dos razones. La primera y obvia es que uso Math.Round y no puedes usar números decimales para colorear con precisión (los colores no tienen decimales en hexadecimal). Por ejemplo, si el canal Rojo es 8, agregue 10%obtendrá a 8.8qué rondas 9. Luego 9.09%quítatelo 9y lo conseguirás 8.1819. Lo que se redondea a 8eso es un mal ejemplo. Pero todavía ilustra que usted está tomando 9.09%de 9no 8.8. Por lo tanto, puede haber algunos números allí que no redondeen exactamente lo mismo que mi ejemplo aquí.
Pimp Trizkit
122

Hice una solución que me funciona muy bien:

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;
}

Ejemplo aligerar:

shadeColor("#63C6FF",40);

Ejemplo Oscurecer:

shadeColor("#63C6FF",-40);
Pablo
fuente
44
Bien, me gusta el porcentaje! +1 Tho, podría hacer R = ((R<255)?R:255).toString(16);entonces R = R.length==1 ? "0"+R : Rpor la velocidad. ¿Y no estoy seguro del punto de toUpperCase?
Pimp Trizkit
Es innecesario Solo agrego eso para una impresión bonita durante la prueba. Lo editaré.
Pablo
Muy agradable. Sin embargo, ¿debería ser 100% más claro no volverse completamente blanco y 100% oscuro siempre negro, sin importar el color? parece que -100 hace que cualquier color sea negro, pero 100 (positivo) no lo hace completamente blanco.
Kevin M
44
no funciona con colores sólidos como # ff0000, # 00ff00, # 0000ff
Hitori
Para que funcione con color negro, acabo de hacer este truco 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

Aquí hay un forro súper simple basado en la respuesta 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));
}

Ejemplos:

adjust('#ffffff', -20) => "#ebebeb"
adjust('000000', 20) => "#141414"
supersan
fuente
77
"super simple".
Andrew
5

Esto es lo que usé en función de su función. Prefiero usar pasos sobre el porcentaje porque es más intuitivo para mí.

Por ejemplo, el 20% de un valor azul de 200 es muy diferente al 20% de un valor azul de 40.

De todos modos, aquí está mi modificación, gracias por su función 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
fuente
Encontré esto mucho más útil que la respuesta principal porque la respuesta principal estaba haciendo que mis colores fueran muy intensos en lugar de ser más oscuros. Saludos Eric
Gusano
4

Probé su función y hubo un pequeño error: si algún valor final 'r' es solo de 1 dígito, el resultado aparece como: 'a0a0a' cuando el valor correcto es '0a0a0a', por ejemplo. Simplemente lo arreglé rápidamente agregando esto en lugar de su 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;

Tal vez no sea tan agradable, pero hace el trabajo. Gran función, por cierto. Justo lo que necesitaba. :)

Ácido fresco
fuente
1
¡Gracias por la depuración y el cumplido! Lástima que no sea una respuesta a si existe o no una forma más rápida, que es mi pregunta principal. Como posiblemente uno que usa todas las conversiones hexadecimales y ninguna base. Aunque, supongo que me dijiste si tenía el código correcto (+1). Desafortunadamente, la solución agregó considerablemente más sobrecarga (ahora su llamada a String 6 veces), y un poco menos de BESO. Tal vez sería más rápido verificar si el número de base10 es 15 o menos, antes de la conversión de base16. ¡Pero me gusta!
Pimp Trizkit
4

¿Has pensado en una conversión rgb> hsl? entonces solo mueva la Luminosidad hacia arriba y hacia abajo? así es como yo iría.

Un vistazo rápido a algunos algoritmos me consiguió los siguientes sitios.

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 el enlace anterior ya no es válido. Puede ver git hub para la fuente de la página o la esencia

Alternativamente, otra pregunta de StackOverflow podría ser un buen lugar para buscar.


Aunque esta no es la opción correcta para el OP, lo siguiente es una aproximación del código que estaba sugiriendo originalmente. (Suponiendo que tiene funciones de conversión 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;
}

Esto supone:

  1. Tienes funciones hslToRgby rgbToHsl.
  2. El parámetro colorValuees una cadena en la forma #RRGGBB

Aunque si estamos discutiendo css hay una sintaxis para especificar hsl / hsla para IE9 / Chrome / Firefox.

James Khoury
fuente
Interesante, pero ¿no tendría que convertir de hexadecimal a rgb a hsl? Parece que es más complicado. Tal vez me estoy perdiendo algo. Pero, estoy buscando una forma de KISS para hacerlo, tan rápido como sea posible (tiempo de ejecución). Lo ideal es que si pudiera hacerlo todo en hexadecimal sería lo más rápido. Pero, la solución que he desarrollado aquí implica ir a rgb para poder agregar una cantidad incremental.
Pimp Trizkit
Sí, supongo que sería más lento, más complicado y si no usa la conversión de rgb a hsl en ningún otro lugar, entonces probablemente no sería la solución más simplista. Sin embargo, sería más preciso que agregar valores rgb, aunque yo tampoco soy una persona de color. Todo depende de cuán preciso quieras ser, supongo.
James Khoury
¿Cuál es la pérdida de precisión que mencionas? ¿Supongo que quiere decir que todos los colores [web] no son accesibles con rgb o algo así?
Pimp Trizkit
Como dije, no sé mucho sobre el color: wiki Color Theory
James Khoury
@Pimp Trizkit: es menos preciso porque (y esta es solo mi teoría ... no soy un experto en color) estás cambiando la misma cantidad en cada canal, sin importar cuánto de ese color haya para empezar. Creo que eso reduciría la saturación porque estás acercando los canales entre sí (en porcentaje). Por supuesto, si se desborda / desborda, eso es inevitable de todos modos.
Matthew Crumley
2

Versión C # ... tenga en cuenta que estoy obteniendo cadenas de color en este formato # FF12AE34, y necesito cortar el #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);
    }
usuario1618171
fuente
55
Nunca usé C # antes, pero parece que las últimas tres declaraciones de variables son extrañas. Una inty dos varspara el mismo tipo de datos.
Pimp Trizkit
44
La palabra clave var en C # significa dejar que el compilador deduzca el tipo en tiempo de compilación. Entonces, en el ejemplo anterior, int y var definen una variable del mismo tipo: int. Esto es útil si tiene un nombre de tipo largo o si desea hacer referencia a un tipo anónimo. Es extraño porque user1618171 ha mezclado dos estilos de declaración de variables, probablemente un error tipográfico.
Daniel James Bryars
2

Quería cambiar un color a un nivel de brillo específico , sin importar el brillo del color anterior, aquí hay una función JS simple que parece funcionar bien, aunque estoy seguro de que podría ser más corta

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
fuente
Coolio! Supongo que ptiene rango 0-100? Ni siquiera sé cómo definir correctamente el brillo en RGB, eso es algo de HSL. Por ejemplo, ¿es #FF00FFmás brillante que #FF0000? Si es así, eso implicaría que el magenta es dos veces más brillante que el rojo. Por lo tanto, se utiliza la prueba de rojo puro. Pase en rojo puro #FF0000, ajuste al 50% de brillo, y aquí lo tenemos #FF4040, ¿es eso correcto? Supuse que para hacer que el rojo fuera del 50% de brillo, nos estaríamos volviendo más oscuros, ya que ya es completamente brillante ... como en un #800000150% de brillo #FF8080. ¿Es el rosa un rojo más brillante? o el rojo ya es completamente brillante?
Pimp Trizkit
Tienes razón: ¡debería haber mencionado que p debe estar en el rango 1-100!
Torbjörn Josefsson
# FF00FF tiene 255 como valor en el canal rojo, 0 en el canal verde y 255 en el canal azul. Cuanto mayores sean los valores combinados en los canales, mayor será el brillo del color. El número p indica que queremos que el nuevo color sea 50% tan brillante como el color original. No estoy 100% seguro de que # FF4040 sea la respuesta correcta a "50% lo más brillante posible Rojo". Producir sombras más oscuras (con, en este caso, un valor más bajo en el canal rojo) requeriría modificación
Torbjörn Josefsson
Sí, solo estaba señalando la ambigüedad al hablar sobre el brillo en RGB. Si se convierte a HSL, el Lcanal es literalmente brillo. Mi problema [mental personal] aquí es que, para mí, #FF0000es completamente brillante. Y #FF4040es más claro pero no más brillante ... para mí, más claro significa más cerca del blanco, como el rosa. Y el brillo es cuánto tiene, y se pone completamente rojo, tan rojo, es completamente brillante. Por lo tanto, #FF0000no se puede hacer más brillante ... sino más bien ... más ligero ... tal vez solo soy un bicho raro, ¡jaja! Realmente no sé la teoría del color, así que realmente estoy hablando de mi ...
Pimp Trizkit
Pero sé que cuando cambio el brillo de mi monitor, los rojos no se vuelven rosados ​​... para mí. Entonces, aquí es probablemente donde comencé mi lógica.
Pimp Trizkit
1

El siguiente método le permitirá aclarar u oscurecer el valor de exposición de una cadena de color 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 el último valor del parámetro en GetHexFromRGB (), pase un valor doble en algún lugar entre -1 y 1 (-1 es negro, 0 no cambia, 1 es blanco):

// 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
fuente
0

¿Cómo simplificar el color de sombra en 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
fuente
Esta es una versión php de la respuesta de Pablo. Desafortunadamente, es más largo y más lento que la solución final y no aclara los colores con precisión. Los oscurece con precisión aunque. Pruebe con rojo puro (# FF0000), un aligeramiento del 25% debe ser (# FF4040). Vea el final de mi respuesta para la versión PHP de Kevin M de la solución final v2.
Pimp Trizkit
0

Hice un puerto de la excelente biblioteca xcolor para eliminar su dependencia de jQuery. Hay un montón de funciones que incluyen colores de iluminación y oscurecimiento.

Realmente, la conversión de hexadecimal a RGB es una función completamente separada de los colores claros u oscuros. Mantenga las cosas secas por favor. En cualquier caso, una vez que tenga un color RGB, puede agregar la diferencia entre el nivel de luz que desea y el nivel de luz que tiene a cada uno de los 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 obtener más información sobre la fuente.

BT
fuente
Todavía no he analizado el código en lo que respecta a mi OP (velocidad / tamaño / precisión). Pero en la primera lectura hay algunos comentarios que hacer: 1) Estoy de acuerdo en que la conversión de hexadecimal a RGB se puede ver como una función completamente separada. SI mi problema estaba destinado a resolverse con una función seca, lo cual no era un requisito. La intención aquí era tener una respuesta (ver mi Versión 2) que fuera súper rápida y súper pequeña (¡2 líneas!) Y una que aclarara y oscureciera un color hexadecimal ... específicamente ... como un autónomo autónomo función. Para que, en su uso final, sea una simple llamada de función única. <Cont.>
Pimp Trizkit
2) Y el caso con la Versión 3, por demanda popular, es la intención de tener una función universal autónoma completamente independiente, lo más rápida y lo más pequeña posible, que pueda tomar a ciegas un color hexadecimal o RGB y en todos sus variaciones Por lo tanto, se necesita una conversión de hexadecimal a RGB. <cont.>
Pimp Trizkit
3) Tras un simple análisis del código. Parece que funcionaría mucho más lento y obviamente es mucho más grande que mi Versión 2 (que es la respuesta real a mi OP; la versión 3 fue para las masas). Para ser justos, debería comparar este código con mi RGB Versión 2, que no realiza una conversión y parece responder a su punto sobre la sequedad. Y sinceramente hablando, su puerto no es mucho más fácil de entender que mi trazador de líneas 2 para hexadecimal. Entonces, aunque es más seco, en realidad no es mucho más simple, si es que lo hay. (la sequedad no ayudó mucho para la capacidad de comprensión) <cont.>
Pimp Trizkit
4) Mi RGB Versión 2 es una función de línea sin conversión 2 si así lo desea. Mi solución particular para mi OP original quería hexadecimal. Es por eso que hay dos tipos diferentes de Versión 2. Pero usted menciona el punto sobre la sequedad y las conversiones hexadecimales, por lo que ahora nos estamos centrando realmente en la versión 3. La Versión 3 llegó mucho más tarde; solo después de que la versión 2 fuera popular. <cont.>
Pimp Trizkit
5) Si bien estaré de acuerdo en que la sequedad generalmente ayuda a la universalidad. Y en la mayoría de los casos, para la capacidad de comprensión. Desafortunadamente, tiene un costo en este ejemplo. Estos costos son que es mucho más grande y aparentemente mucho más lento y aparentemente usa más memoria tanto en la pila (con su naturaleza recursiva) como en global (2 funciones; en comparación con v2).
Pimp Trizkit
0

Hace tiempo que quería poder producir tintes / sombras de colores, aquí está mi solución de 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);
    }
};
usuario2655360
fuente
Algunos ejemplos de uso ayudarían. Y tal vez alguna explicación de por qué esta versión sobre las otras. Esta versión parece correr considerablemente más lento. Y es mucho más largo. Y no parece sombrear con precisión. Parece que estás usando LERP, o algo similar ... lo cual es bueno. Desafortunadamente, es solo de un canal, entonces ese mismo valor se usa en todos los canales. Esto no es correcto, para obtener una mayor precisión, debe LERP cada canal individualmente. Como hace mi respuesta a esta pregunta. Además, es más pequeño y más rápido y comprueba si hay errores y maneja rgb, y hace conversiones, podría seguir
Pimp Trizkit
Un ejemplo de uso: varyHue ("6e124c", 77) donde el primer argumento es el color en hexadecimal y el segundo el cambio porcentual. Un cambio de porcentaje positivo sombrea (oscurece) mientras que un valor negativo tiñe (aclara) el resultado. Escribí la rutina como mi primer intento solo unas pocas horas antes de encontrar esta página y publicarla simplemente como una cuestión de interés. No sabía que tenía que mejorar su esfuerzo o que necesitaba su aprobación antes de hacerlo. Es completamente mi propio trabajo sin referencia a nadie más. No he oído hablar de LERP. Lo comprobaré, gracias por la sugerencia.
user2655360
Jeje, bueno, ¡por supuesto que no tienes que hacer nada! ¡Y todos les agradecemos por sus esfuerzos! Mis primeras preocupaciones principales fueron las enumeradas primero. Tratando de ayudarlo con su respuesta para que pueda obtener votos. (muestre los usos y la explicación de cómo funciona, etc.) El otro material es obviamente un análisis rápido para ayudar a promover el conocimiento de todos . Lo siento, si parecía un poco agresivo. Pero otra sugerencia es hacer que acepte los #colores hexadecimales. Lo siento si parecía .. "aprobación" ... Lo vi como una revisión por pares. Si no desea que alguien analice su código u ofrezca comentarios, le pido disculpas.
Pimp Trizkit
0

Su enfoque está bien :) Simplifico un poco su versión más corta (para control de saturación, vea aquí )

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

Y versión con comprobación de rangos de color y #

Kamil Kiełczewski
fuente
0

Hice un paquete simple también. Utilicé la convexidad de IR ^ 3, por supuesto, los valores RGB están en IN ^ 3, que no es convexo, por lo que esto no es realmente perfecto

Para oscurecer usé lo siguiente

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

Y para aligerar

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

Aquí esta el paquete https://github.com/MarchWorks/colortone

Manifestación https://colortone.now.sh/

con la forma en que estoy haciendo las cosas si pasas una proporción de -1 terminarás en negro, blanco si la proporción es 1. Pasando 0 ya que la proporción no cambiará el color

evgeni fotia
fuente
0

Lo necesitaba en C #, puede ayudar a los desarrolladores de .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
fuente
Convirtió la función "no funciona" que no trata con ceros a la izquierda, y puede ir por encima de FF en las sumas, = modificar el componente de color anterior ...
B. Ir
Cuando probé la función, no funcionó debido a valores no hexadecimales (16 = 0xF) y (8 = 0x8) y da un color en 8 posiciones, pero ahora funciona muy bien
Nassim
¿Intentaste aumentar desde FF? (digamos de 0xFFFFFF + 0x000004): su código pasa a ese valor máximo (digamos 0x1000003), en lugar de no aumentar, y especialmente configurar los 2 componentes de color superiores a 00, que es el cambio más grande que podrían hacer ...
B. Ve
tienes razón, gracias por el comentario, excepto una entrada de límites alrededor de fff y 000 funcionará bien.
Nassim