lib/faker/default/color.rb



# frozen_string_literal: true

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
      #
      # @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 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

      ##
      # Produces the name of a color.
      #
      # @return [String]
      #
      # @example
      #   Faker::Color.color_name #=> "yellow"
      #
      # @faker.version 1.6.2
      def color_name
        fetch('color.name')
      end

      # @private
      def single_rgb_color
        sample((0..255).to_a)
      end

      ##
      # Produces an array of integers representing an RGB color.
      #
      # @return [Array(Integer, Integer, Integer)]
      #
      # @example
      #   Faker::Color.rgb_color #=> [54, 233, 67]
      #
      # @faker.version 1.5.0
      def rgb_color
        Array.new(3) { single_rgb_color }
      end

      ##
      # 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 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

      ##
      # Produces an array of floats representing an HSLA color.
      # The array is in the form of `[hue, saturation, lightness, alpha]`.
      #
      # @return [Array(Float, Float, Float, Float)]
      #
      # @example
      #   Faker::Color.hsla_color #=> [154.77, 0.36, 0.9, 0.2]
      #
      # @faker.version 1.5.0
      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)
        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