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

Issue-2464: Add color luminosity options #2566

Merged
merged 11 commits into from
Sep 26, 2022
83 changes: 77 additions & 6 deletions lib/faker/default/color.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,34 @@
module Faker
class Color < Base
class << self
LIGHTNESS_LOOKUP = {
light: 0.8,
dark: 0.2
}.freeze
##
# Produces a hex color code.
# Clients are able to specify the hue, saturation, or lightness of the required color.
# Alternatively a client can simply specify that they need a light or dark color.
#
# @param args [Hash, Symbol] Allows the client to specify what color should be return
Zeragamba marked this conversation as resolved.
Show resolved Hide resolved
#
# @return [String]
#
# @example
# Faker::Color.hex_color #=> "#31a785"
# @example
# Faker::Color.hex_color(hue: 118, saturation: 1, lightness: 0.53) #=> "#048700"
# @example
# Faker::Color.hex_color(:light) #=> "#FFEE99"
# @example
# Faker::Color.hex_color(:dark) #=> "#665500"
#
# @faker.version 1.5.0
def hex_color
format('#%06x', (rand * 0xffffff))
# @faker.version next
def hex_color(args = nil)
hsl_hash = {}
hsl_hash = { lightness: LIGHTNESS_LOOKUP[args] } if %i[dark light].include?(args)
hsl_hash = args if args.is_a?(Hash)
hsl_to_hex(hsl_color(**hsl_hash))
end

##
Expand Down Expand Up @@ -51,14 +68,28 @@ def rgb_color
# Produces an array of floats representing an HSL color.
# The array is in the form of `[hue, saturation, lightness]`.
#
# @param hue [FLoat] Optional value to use for hue
# @param saturation [Float] Optional value to use for saturation
# @param lightness [Float] Optional value to use for lightness
# @return [Array(Float, Float, Float)]
#
# @example
# Faker::Color.hsl_color #=> [69.87, 0.66, 0.3]
# @example
# Faker::Color.hsl_color(hue: 70, saturation: 0.5, lightness: 0.8) #=> [70, 0.5, 0.8]
# @example
# Faker::Color.hsl_color(hue: 70) #=> [70, 0.66, 0.6]
# @example
# Faker::Color.hsl_color(saturation: 0.2) #=> [54, 0.2, 0.3]
# @example
# Faker::Color.hsl_color(lightness: 0.6) #=> [69.87, 0.66, 0.6]
#
# @faker.version 1.5.0
def hsl_color
[sample((0..360).to_a), rand.round(2), rand.round(2)]
# @faker.version next
def hsl_color(hue: nil, saturation: nil, lightness: nil)
valid_hue = hue || sample((0..360).to_a)
valid_saturation = saturation&.clamp(0, 1) || rand.round(2)
valid_lightness = lightness&.clamp(0, 1) || rand.round(2)
[valid_hue, valid_saturation, valid_lightness]
end

##
Expand All @@ -74,6 +105,46 @@ def hsl_color
def hsla_color
hsl_color << rand.round(1)
end

private

##
# Produces a hex code representation of an HSL color
#
# @param a_hsl_color [Array(Float, Float, Float)] The array that represents the HSL color
#
# @return [String]
#
# @example
# hsl_to_hex([50, 100,80]) #=> #FFEE99
#
# @see https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB
# @see https://github.com/jpmckinney/color-generator/blob/master/lib/color-generator.rb
#
def hsl_to_hex(a_hsl_color)
fbuys marked this conversation as resolved.
Show resolved Hide resolved
h, s, l = a_hsl_color
c = (1 - (2 * l - 1).abs) * s
h_prime = h / 60
x = c * (1 - (h_prime % 2 - 1).abs)
m = l - 0.5 * c

rgb = case h_prime.to_i
when 0 # 0 <= H' < 1
[c, x, 0]
when 1 # 1 <= H' < 2
[x, c, 0]
when 2 # 2 <= H' < 3
[0, c, x]
when 3 # 3 <= H' < 4
[0, x, c]
when 4 # 4 <= H' < 5
[x, 0, c]
else # 5 <= H' < 6
[c, 0, x]
end.map { |value| ((value + m) * 255).round }

format('#%02x%02x%02x', rgb[0], rgb[1], rgb[2])
end
end
end
end
52 changes: 52 additions & 0 deletions test/faker/default/test_faker_color.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,33 @@ def test_hex_color
assert_match(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/, @tester.hex_color)
end

# @see https://www.rapidtables.com/convert/color/rgb-to-hsl.html
def helper_hex_lightness(hex_color)
Zeragamba marked this conversation as resolved.
Show resolved Hide resolved
result = hex_color.scan(/([A-Fa-f0-9]{2})/).flatten.map { |x| x.hex / 255.0 }
(result.max + result.min) / 2
end

def test_hex_color_light
assert_in_delta(0.8, helper_hex_lightness(@tester.hex_color(:light)))
end

def test_hex_color_dark
assert_in_delta(0.2, helper_hex_lightness(@tester.hex_color(:dark)))
end

def test_hex_color_with_hash_is_passed_to_hsl_color
mock_hsl_color = lambda do |args|
assert_equal(100, args[:hue])
assert_in_delta(0.2, args[:saturation])
assert_in_delta(0.8, args[:lightness])
[args[:hue], args[:saturation], args[:lightness]]
end
@tester.stub :hsl_color, mock_hsl_color do
result = @tester.hex_color(hue: 100, saturation: 0.2, lightness: 0.8)
assert_match(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/, result)
end
end

def test_single_rgb_color
assert @tester.single_rgb_color.between?(0, 255)
end
Expand All @@ -38,6 +65,31 @@ def test_hsl_color
assert @result[2].between?(0.0, 1.0)
end

def test_hsl_color_with_a_speficied_hue
@result = @tester.hsl_color(hue: 5)
assert_in_delta(5, @result[0])
end

def test_hsl_color_with_a_speficied_saturation
@result = @tester.hsl_color(saturation: 0.35)
assert_in_delta(0.35, @result[1])
end

def test_hsl_color_with_a_speficied_but_invalid_saturation
@result = @tester.hsl_color(saturation: 3.05)
assert_in_delta(1, @result[1])
end

def test_hsl_color_with_a_speficied_lightness
@result = @tester.hsl_color(lightness: 0.5)
assert_in_delta(0.5, @result[2])
end

def test_hsl_color_with_a_speficied_but_invalid_lightness
@result = @tester.hsl_color(lightness: -2.5)
assert_in_delta(0, @result[2])
end

def test_hsla_color
@result = @tester.hsla_color
assert_equal(4, @result.length)
Expand Down