faker icon indicating copy to clipboard operation
faker copied to clipboard

Issue-2464: Add color luminosity options

Open fbuys opened this issue 3 years ago • 2 comments

Summary

Closes: https://github.com/faker-ruby/faker/issues/2464

We added 2 new config options.

  1. Ability to specify light or dark hex color.
  2. Ability to specify lightness when generating an HSL color.

The change to the HSL color generator enables us to easily generate a light / dark color since the lightness (In HSL) directly affect the color's luminosity.

We added a private method (not directly tested) that converts and HSL value into a HEX color value. The conversion algorithm was heavily inspired by:

  • https://github.com/jpmckinney/color-generator/blob/e4ffecf799ec475878ed34533f3d6dc7b4ed0362/lib/color-generator.rb#L97-L115
  • https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB

The original issue suggested we implement is as follows:

  • Faker::Color.hex_color(luminosity: 'dark')
  • Faker::Color.hex_color(luminosity: 'light')

Instead I implemented with a slightly less verbose syntax, but I am open to changing it.

  • Faker::Color.hex_color(:dark) -> generates a color with lightness set to 20%
  • Faker::Color.hex_color(:light)-> generates a color with lightness set to 80%

A side effect of this change is that you can now also (optionally) provide a specific lightness when generating a HSL value

  • Faker::Color.hsl_color(lightness: 0.3) will generate a color with lightness set at 30%

Other Information

If you would like to manually verify that the generated color is light/dark.

  1. Output the generated value with something like this: puts @tester.hex_color(:light)
  2. Then insert the generated hex value here: https://colordesigner.io/convert/hextohsl
  3. Confirm that the color is light (with a lightness of 80% / 0.8)
  4. Similar process can be followed for `:dark: colors.

fbuys avatar Sep 19 '22 23:09 fbuys

This isn't the prettiest code in the world but it's a code snippet I use to determine if a color should get white or black text based on whether or not the background is dark or light:

  def contrast_color(hex)
    yiq_threshold = 128

    # TODO: This doesn't account for 3 character hex codes.
    red, green, blue = hex.scan(/../).map{ |c| c.to_i(16) }

    yiq = ((red * 299) + (green * 587) + (blue * 114)) / 1000
    yiq > yiq_threshold ? "#000" : "#fff"
  end

It's a well known algorithm https://24ways.org/2010/calculating-color-contrast/ , here's a JS demo: http://jsfiddle.net/decx/RRt3q/. It could be useful to help test this PR because a dark color should always produce contrasted white text.

nickjj avatar Sep 20 '22 23:09 nickjj

yiq_threshold = 128

    # TODO: This doesn't account for 3 character hex codes.
    red, green, blue = hex.scan(/../).map{ |c| c.to_i(16) }

    yiq = ((red * 299) + (green * 587) + (blue * 114)) / 1000
    yiq > yiq_threshold ? "#000" : "#fff"

The interesting thing is that not all light colors contrast well with black for example. This is a light color (#b6e2b6) but it contrasts better with white.

So perhaps the question here is if we want a color that contrasts with white or black? This color does have a lightness of 80%, so it is a light color by that definition.

fbuys avatar Sep 22 '22 12:09 fbuys

Text size does play a role here. What size were you testing it on?

If you use a contrast checker such as https://webaim.org/resources/contrastchecker/, having black text with a #b6e2b6 background produces a 14.54:1 ratio where as white text is 1.44:1.

Personally on both my monitors (IPS displays) the black text is much easier to read. White text fails the contrast test checker.

nickjj avatar Sep 23 '22 12:09 nickjj

This is just a thought but what do you think about introducing a way to let users customize the lightness?

For example, let's say I want to generate dark colors but 0.2 is too dark and I'd like to use 0.3. At the moment we can't do that.

Something like being able to do hex_color(lumonisity: 0.3) could be useful. For quality of life enhancements you could allow hex_color(:dark) or hex_color(:light) and the function can internally convert those to 0.8 and 0.2 so that the implementation only ever has to deal with a value between 1.0 and 0?

nickjj avatar Sep 23 '22 12:09 nickjj

Hey @Zeragamba and @nickjj I have pushed some updates to address your feedback. Please let me know what you think or if you have concerns with the implementation. Thank you!

fbuys avatar Sep 26 '22 14:09 fbuys

Based on the documented example usage this looks great, thanks for making it so customizable. This will let folks pick any type of variety category they prefer.

There's also neat options that we can do now like: Faker::Color.hex_color(lightness: [0.5, 0.6, 0.7].sample)

nickjj avatar Sep 26 '22 17:09 nickjj

Tests be passin, I be sayin LGTM, and we be mergin.

Thanks for the feature!

Zeragamba avatar Sep 26 '22 17:09 Zeragamba