Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lighten and darken should be absolute #53

Open
finnp opened this issue Feb 16, 2015 · 18 comments
Open

lighten and darken should be absolute #53

finnp opened this issue Feb 16, 2015 · 18 comments

Comments

@finnp
Copy link

finnp commented Feb 16, 2015

Hey,

in LESS and SASS the darken nand lighten function increase by an absolute amount. Example here: https://github.com/less/less.js/blob/fb5280f16f124e5062634a22be2f3c99e650d0a4/lib/less/functions/color.js#L163

This is a bit confusing because in color.js e.g. `color('#000000').lighten(1)' is still black.

I am not sure though if it would be better to change the function or create a new one like lightenAbsolute.

Best,
Finn

@finnp finnp changed the title lighten and darken be absolute lighten and darken should be absolute Feb 16, 2015
@MoOx
Copy link
Collaborator

MoOx commented Feb 19, 2015

I am not sure less and sass are even doing the same computation (from what I remember, I already saw issue about difference in less and sass).
That being said, this names are confusing, we should change that to whiteness, blackness, lightness.

@rickyvetter
Copy link

I think the names that exist are pretty solid. color.lightness(<value>) gives a way to update absolutely and color.lighten(<value>) gives a way to update relative to the current value.

The names are difficult because they are so similar. But they accurately describe what they do.

@MoOx
Copy link
Collaborator

MoOx commented Jun 2, 2015

css color function allow to use absolute percentage (10%) or relative ones (+/-10%).

@Qix-
Copy link
Owner

Qix- commented Oct 22, 2018

Right now, it's a multiplicative function. For backwards compatibility, I don't see that changing.

However, I would be open to something like .lightenBy() and .darkenBy() which would take offset values (addition). The same thing as what OP proposes as .lightenAbsolute(), just with a different name. I don't see it as an "absolute" operation as that just mean you're setting the lightness to a certain absolute value.

However, I'd even go so far as to suggest switching .lighten() and .lightenBy() since you would normally say Lighten by 10% rather than Lighten by 0.2. But that means a major release instead of a minor release, which means requiring a migration.

@diegohaz
Copy link

diegohaz commented Apr 30, 2019

In case someone is looking for it, I'm using these functions in my project:

function lightenBy(color, ratio) {
  const lightness = color.lightness();
  return color.lightness(lightness + (100 - lightness) * ratio);
}

function darkenBy(color, ratio) {
  const lightness = color.lightness();
  return color.lightness(lightness - lightness * ratio);
}

lightenBy(Color("black"), 0.5);

@Qix-
Copy link
Owner

Qix- commented Apr 30, 2019

Thanks! I think it's less of a concern for implemention and more of getting input about the API. I haven't seen a massive push for this in any definitive direction yet. Would love to hear more input.

@moritzhabegger
Copy link

moritzhabegger commented May 13, 2019

I was also confused by this implementation.

console.log(Color('#c880b6').lighten(0.5).hex()) => #FAF2F8
console.log(Color('#c880b6').lighten(0.6).hex()) => #FFFFFF

The function of @diegohaz did exactly what I was looking for

console.log(lightenBy(Color('#c880b6'), 0.5).hex()) => #E3C0DA

@Qix-
Copy link
Owner

Qix- commented May 13, 2019

If someone wants to submit a PR for @diegohaz's implementations and call them lightenAbs() and darkenAbs() I would accept/release it posthaste.

@shinonomeiro
Copy link

shinonomeiro commented Jul 10, 2020

For those like me who are wondering why this library's lighten doesn't behave like the SASS one:

@diegohaz 's answer didn't do it for me. From SASS' documentation, their lighten function increases the lightness (L) of the color's HSL by a fixed amount, e.g. #414141 lightened by 60% should yield #dadada, but this library's lighten yields

> new Color('#414141').lighten(0.6).hex()
'#686868'

instead, which is computed in the following way:

> new Color('#414141').lightness()
25.49019607843137
> new Color('#414141').lightness(25.49019607843137 + (25.49019607843137 * 0.6)).hex()
'#686868'

while SASS computes it this way:

> new Color('#414141').lightness(25.49019607843137 + 60).hex()
'#DADADA'

as shown in online color generators such as http://scg.ar-ch.org/.

Slight changes to diegohaz's helper functions (thanks by the way!):

function lightenBy(color, amount) {
  const lightness = color.lightness();
  return color.lightness(lightness + amount);
}

function darkenBy(color, amount) {
  const lightness = color.lightness();
  return color.lightness(lightness - amount);
}

lightenBy(Color("black"), 50);

@elsurudo
Copy link

Just as another data point, as a first-time user the current functionality was a surprise to me, and the helpers in this thread give the behaviour that I would have expected.

@Fuzzypeg
Copy link

Fuzzypeg commented May 25, 2021

I've been confused and frustrated by these functions as well, but I'm not trying to replicate LESS or SASS. I'll explain my use case and experience in case it's useful.

I've been trying to use lighten to create lighter versions of branding colours configured by the user, for styling css background-colour to ensure sufficient contrast with foreground text. Obviously, I don't control what branding colours will be supplied by the user, so I don't know whether lightening by 50% will max out to white or still be drowning in the murky depths of near-black. This makes it useless for my purpose.

Furthermore, it turns out that 100% lightening is the greatest possible by design. (You can go higher, but not in a useful way, since values of 101 or greater are treated as simple factors, rather than percentages. Thus 101 means 10100%. So it's possible to lighten by 100% or 10100%, but nothing in between!) Given the 100% limit, that would imply that 100% should give the maximum lightening possible, i.e. full white. But for many input colours this is far from the case. E.g., I lighten the colour #0A9DD9 by 100%, and I get #C9EDFC, which is a bit lighter, but still a long way from white. Another way of looking this is, given the multiplicative approach used by the lighten function, I should legitimately be allowed to lighten by > 100% -- but I cannot. To me, then the function seems clearly broken.

For my purposes, multiplicative (or even absolute additive) approaches are not very useful, since a piece of code isn't going to know how by how much it is appropriate to multiply (or add) unless it knows what the shade of the colour is to begin with. Diegohaz's functions, on the other hand, reliably gives sensible results, and it also has a nice intuitive analogy: lighten by 60% is the same as mixing with 60% white and darken by 60% is the same as mixing with 60% black. This suggests some possible names: whiten and blacken.

Whatever the case, lighten and darken are broken as far as I'm concerned, and people should be discouraged against using them.

@Fuzzypeg
Copy link

On second thoughts, Diegohaz's functions are not analogous to adding white or black, since they preserve saturation. Adding white to a very dark colour would give something close to grey, e.g. #000005 + 50% white = #7F7F82, not #0505FF, as Diegohaz's lightenBy would give. To me, the original lighten would have been better named brighten (and should have permitted > 100% brightening) analogous to brightening a photograph or brightening a monitor display, and Diegohaz's variant would ideally be named lighten. I don't know the best naming then, but I still feel Diegohaz's lightenBy function is the most useful for my purposes.

Also on second thoughts, It looks like darken does exactly the same as Diegohaz's version, which actually makes sense as a counterpart both to brighten and to lighten. It nicely fits both paradigms. So that's not broken.

(Sorry for multiple edits/posts. As well as writing software, I'm an artist trained in photography and studio lighting, and a frequent user of image editing tools, so that may partly explain my pedantry!)

@Qix-
Copy link
Owner

Qix- commented May 27, 2021

Again, I've said it a plethora of times on this project: The API creep is insanely high in color already and no good propositions on how to manage color operations across models has been brought forth.

Remember that increasing/decreasing "lightness", "brightness", "whiteness", whatever you want to call it, is confusing because it can mean different things to different people with different goals and different understandings of colors using different color models altogether.

The opposite of "dull" (low saturation) is often "bright" (which could mean higher saturation), but "bright" also means white in some cases, or a lighter tint, etc.

This is a hard naming problem. As-is, the methods provided now are not broken, so please do not claim they are. They simply do not do what you want them to do - just as a toaster not freezing your bread does not make the toaster "broken".

@Fuzzypeg
Copy link

Fuzzypeg commented May 27, 2021 via email

@Qix-
Copy link
Owner

Qix- commented May 28, 2021

What is preventing you from passing higher values to lighten or whiten?

@Fuzzypeg
Copy link

Fuzzypeg commented May 29, 2021 via email

@elauser
Copy link

elauser commented Sep 15, 2022

While it's true that technically multiplying 0 by a ratio returns 0, that functionality would be needed.
I'd go for pragmaticism and not purity.

To solve this in my code, and to lighten black I had to reverse the color, darken it, then reverse it again.
Feels pretty retarded to do but it works for me.

Maybe that code snippet helps anyone else having this problem.

function getShades (color: string) {
  return {
    50: Color(color).negate().darken(0.9).negate().hex(),
    100: Color(color).negate().darken(0.8).negate().hex(),
    150: Color(color).negate().darken(0.7).negate().hex(),
    200: Color(color).negate().darken(0.6).negate().hex(),
    250: Color(color).negate().darken(0.5).negate().hex(),
    300: Color(color).negate().darken(0.4).negate().hex(),
    350: Color(color).negate().darken(0.3).negate().hex(),
    400: Color(color).negate().darken(0.2).negate().hex(),
    450: Color(color).negate().darken(0.1).negate().hex(),
    500: color,
    550: Color(color).darken(0.1).hex(),
    600: Color(color).darken(0.2).hex(),
    650: Color(color).darken(0.3).hex(),
    700: Color(color).darken(0.4).hex(),
    750: Color(color).darken(0.5).hex(),
    800: Color(color).darken(0.6).hex(),
    850: Color(color).darken(0.7).hex(),
    900: Color(color).darken(0.8).hex(),
    950: Color(color).darken(0.9).hex(),
  }
}

@PierBover
Copy link

PierBover commented Oct 29, 2024

I'm sorry to say the methods provided by color are not very helpful even if they are technically correct (eg manipulating the L or W values).

lighten() and whiten() are not absolute so you get inconsistent results. With lightness() you set an absolute L value but different hues have different luminosities so you cannot get a consistent luminosity result either. Yellow won't produce the same result as blue.

The API that would work (from a practical standpoint) is a function that would return a color with a specific WCAG luminosity or luminance (not L or W value). So something like color.luminance(0.5);

In the meantime, the quick solution that works for my use case is to mix the color with white or black:

const brighter = color.mix(Color('white'), 0.3).hex();
const darker = color.mix(Color('black'), 0.3).hex();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests