class Sass::Script::Value::Color

A SassScript object representing a CSS color.

A color may be represented internally as RGBA, HSLA, or both. It's originally represented as whatever its input is; if it's created with RGB values, it's represented as RGBA, and if it's created with HSL values, it's represented as HSLA. Once a property is accessed that requires the other representation – for example, {#red} for an HSL color – that component is calculated and cached.

The alpha channel of a color is independent of its RGB or HSL representation. It's always stored, as 1 if nothing else is specified. If only the alpha channel is modified using {#with}, the cached RGB and HSL values are retained.

Constants

ALTERNATE_COLOR_NAMES
COLOR_NAMES

A hash from color names to `[red, green, blue]` value arrays.

COLOR_NAMES_REVERSE

A hash from `[red, green, blue, alpha]` value arrays to color names.

Public Class Methods

from_hex(hex_string, alpha = nil) click to toggle source

Create a new color from a valid CSS hex string.

The leading hash is optional.

@return [Color]

# File lib/sass/script/value/color.rb, line 268
def self.from_hex(hex_string, alpha = nil)
  unless hex_string =~ /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i ||
         hex_string =~ /^#?([0-9a-f])([0-9a-f])([0-9a-f])$/i
    raise ArgumentError.new("#{hex_string.inspect} is not a valid hex color.")
  end
  red   = $1.ljust(2, $1).to_i(16)
  green = $2.ljust(2, $2).to_i(16)
  blue  = $3.ljust(2, $3).to_i(16)
  attrs = {:red => red, :green => green, :blue => blue}
  attrs[:alpha] = alpha if alpha
  new(attrs)
end
int_to_rgba(color) click to toggle source

@private

Convert a ruby integer to a rgba components @param color [Fixnum] @return [Array<Fixnum>] Array of 4 numbers representing r,g,b and alpha

# File lib/sass/script/value/color.rb, line 22
def self.int_to_rgba(color)
  rgba = (0..3).map {|n| color >> (n << 3) & 0xff}.reverse
  rgba[-1] = rgba[-1] / 255.0
  rgba
end
new(attrs, allow_both_rgb_and_hsl = false) click to toggle source

Constructs an RGB or HSL color object, optionally with an alpha channel.

The RGB values must be between 0 and 255. The saturation and lightness values must be between 0 and 100. The alpha value must be between 0 and 1.

@raise [Sass::SyntaxError] if any color value isn't in the specified range

@overload initialize(attrs)

The attributes are specified as a hash.
This hash must contain either `:hue`, `:saturation`, and `:value` keys,
or `:red`, `:green`, and `:blue` keys.
It cannot contain both HSL and RGB keys.
It may also optionally contain an `:alpha` key.

@param attrs [{Symbol => Numeric}] A hash of color attributes to values
@raise [ArgumentError] if not enough attributes are specified,
  or both RGB and HSL attributes are specified

@overload initialize(rgba)

The attributes are specified as an array.
This overload only supports RGB or RGBA colors.

@param rgba [Array<Numeric>] A three- or four-element array
  of the red, green, blue, and optionally alpha values (respectively)
  of the color
@raise [ArgumentError] if not enough attributes are specified
Calls superclass method Sass::Script::Value::Base.new
# File lib/sass/script/value/color.rb, line 218
def initialize(attrs, allow_both_rgb_and_hsl = false)
  super(nil)

  if attrs.is_a?(Array)
    unless (3..4).include?(attrs.size)
      raise ArgumentError.new("Color.new(array) expects a three- or four-element array")
    end

    red, green, blue = attrs[0...3].map {|c| c.to_i}
    @attrs = {:red => red, :green => green, :blue => blue}
    @attrs[:alpha] = attrs[3] ? attrs[3].to_f : 1
  else
    attrs = attrs.reject {|k, v| v.nil?}
    hsl = [:hue, :saturation, :lightness] & attrs.keys
    rgb = [:red, :green, :blue] & attrs.keys
    if !allow_both_rgb_and_hsl && !hsl.empty? && !rgb.empty?
      raise ArgumentError.new("Color.new(hash) may not have both HSL and RGB keys specified")
    elsif hsl.empty? && rgb.empty?
      raise ArgumentError.new("Color.new(hash) must have either HSL or RGB keys specified")
    elsif !hsl.empty? && hsl.size != 3
      raise ArgumentError.new("Color.new(hash) must have all three HSL values specified")
    elsif !rgb.empty? && rgb.size != 3
      raise ArgumentError.new("Color.new(hash) must have all three RGB values specified")
    end

    @attrs = attrs
    @attrs[:hue] = 360 if @attrs[:hue]
    @attrs[:alpha] ||= 1
  end

  [:red, :green, :blue].each do |k|
    next if @attrs[k].nil?
    @attrs[k] = @attrs[k].to_i
    Sass::Util.check_range("#{k.to_s.capitalize} value", 0..255, @attrs[k])
  end

  [:saturation, :lightness].each do |k|
    next if @attrs[k].nil?
    value = Number.new(@attrs[k], ['%']) # Get correct unit for error messages
    @attrs[k] = Sass::Util.check_range("#{k.to_s.capitalize}", 0..100, value, '%')
  end

  @attrs[:alpha] = Sass::Util.check_range("Alpha channel", 0..1, @attrs[:alpha])
end

Public Instance Methods

alpha() click to toggle source

The alpha channel (opacity) of the color. This is 1 unless otherwise defined.

@return [Fixnum]

# File lib/sass/script/value/color.rb, line 333
def alpha
  @attrs[:alpha].to_f
end
alpha?() click to toggle source

Returns whether this color object is translucent; that is, whether the alpha channel is non-1.

@return [Boolean]

# File lib/sass/script/value/color.rb, line 341
def alpha?
  alpha < 1
end
blue() click to toggle source

The blue component of the color.

@return [Fixnum]

# File lib/sass/script/value/color.rb, line 300
def blue
  hsl_to_rgb!
  @attrs[:blue]
end
div(other) click to toggle source

The SassScript `/` operation. Its functionality depends on the type of its argument:

{Number} : Divides each of the RGB color channels by the number.

{Color} : Divides each of this color's RGB color channels by the other color's.

{Value} : See {Value::Base#div}.

@param other [Value] The right-hand side of the operator @return [Color] The resulting color @raise [Sass::SyntaxError] if `other` is a number with units

Calls superclass method Sass::Script::Value::Base#div
# File lib/sass/script/value/color.rb, line 515
def div(other)
  if other.is_a?(Sass::Script::Value::Number) ||
      other.is_a?(Sass::Script::Value::Color)
    piecewise(other, :/)
  else
    super
  end
end
eq(other) click to toggle source

The SassScript `==` operation. **Note that this returns a {Sass::Script::Value::Bool} object, not a Ruby boolean**.

@param other [Value] The right-hand side of the operator @return [Bool] True if this value is the same as the other,

false otherwise
# File lib/sass/script/value/color.rb, line 384
def eq(other)
  Sass::Script::Value::Bool.new(
    other.is_a?(Color) && rgb == other.rgb && alpha == other.alpha)
end
green() click to toggle source

The green component of the color.

@return [Fixnum]

# File lib/sass/script/value/color.rb, line 292
def green
  hsl_to_rgb!
  @attrs[:green]
end
hash() click to toggle source
# File lib/sass/script/value/color.rb, line 389
def hash
  [rgb, alpha].hash
end
hsl() click to toggle source

Returns the hue, saturation, and lightness components of the color.

@return [Array<Fixnum>] A frozen three-element array of the

hue, saturation, and lightness values (respectively) of the color
# File lib/sass/script/value/color.rb, line 365
def hsl
  [hue, saturation, lightness].freeze
end
hsla() click to toggle source

Returns the hue, saturation, lightness, and alpha components of the color.

@return [Array<Fixnum>] A frozen four-element array of the hue,

saturation, lightness, and alpha values (respectively) of the color
# File lib/sass/script/value/color.rb, line 373
def hsla
  [hue, saturation, lightness].freeze
end
hue() click to toggle source

The hue component of the color.

@return [Numeric]

# File lib/sass/script/value/color.rb, line 308
def hue
  rgb_to_hsl!
  @attrs[:hue]
end
inspect() click to toggle source

Returns a string representation of the color.

@return [String] The hex value

# File lib/sass/script/value/color.rb, line 560
def inspect
  alpha? ? rgba_str : hex_str
end
lightness() click to toggle source

The lightness component of the color.

@return [Numeric]

# File lib/sass/script/value/color.rb, line 324
def lightness
  rgb_to_hsl!
  @attrs[:lightness]
end
minus(other) click to toggle source

The SassScript `-` operation. Its functionality depends on the type of its argument:

{Number} : Subtracts the number from each of the RGB color channels.

{Color} : Subtracts each of the other color's RGB color channels from this color's.

{Value} : See {Value::Base#minus}.

@param other [Value] The right-hand side of the operator @return [Color] The resulting color @raise [Sass::SyntaxError] if `other` is a number with units

Calls superclass method Sass::Script::Value::Base#minus
# File lib/sass/script/value/color.rb, line 472
def minus(other)
  if other.is_a?(Sass::Script::Value::Number) || other.is_a?(Sass::Script::Value::Color)
    piecewise(other, :-)
  else
    super
  end
end
mod(other) click to toggle source

The SassScript `%` operation. Its functionality depends on the type of its argument:

{Number} : Takes each of the RGB color channels module the number.

{Color} : Takes each of this color's RGB color channels modulo the other color's.

@param other [Number, Color] The right-hand side of the operator @return [Color] The resulting color @raise [Sass::SyntaxError] if `other` is a number with units

# File lib/sass/script/value/color.rb, line 536
def mod(other)
  if other.is_a?(Sass::Script::Value::Number) ||
      other.is_a?(Sass::Script::Value::Color)
    piecewise(other, :%)
  else
    raise NoMethodError.new(nil, :mod)
  end
end
plus(other) click to toggle source

The SassScript `+` operation. Its functionality depends on the type of its argument:

{Number} : Adds the number to each of the RGB color channels.

{Color} : Adds each of the RGB color channels together.

{Value} : See {Value::Base#plus}.

@param other [Value] The right-hand side of the operator @return [Color] The resulting color @raise [Sass::SyntaxError] if `other` is a number with units

Calls superclass method Sass::Script::Value::Base#plus
# File lib/sass/script/value/color.rb, line 449
def plus(other)
  if other.is_a?(Sass::Script::Value::Number) || other.is_a?(Sass::Script::Value::Color)
    piecewise(other, :+)
  else
    super
  end
end
red() click to toggle source

The red component of the color.

@return [Fixnum]

# File lib/sass/script/value/color.rb, line 284
def red
  hsl_to_rgb!
  @attrs[:red]
end
rgb() click to toggle source

Returns the red, green, and blue components of the color.

@return [Array<Fixnum>] A frozen three-element array of the red, green, and blue

values (respectively) of the color
# File lib/sass/script/value/color.rb, line 349
def rgb
  [red, green, blue].freeze
end
rgba() click to toggle source

Returns the red, green, blue, and alpha components of the color.

@return [Array<Fixnum>] A frozen four-element array of the red, green,

blue, and alpha values (respectively) of the color
# File lib/sass/script/value/color.rb, line 357
def rgba
  [red, green, blue, alpha].freeze
end
saturation() click to toggle source

The saturation component of the color.

@return [Numeric]

# File lib/sass/script/value/color.rb, line 316
def saturation
  rgb_to_hsl!
  @attrs[:saturation]
end
times(other) click to toggle source

The SassScript `*` operation. Its functionality depends on the type of its argument:

{Number} : Multiplies the number by each of the RGB color channels.

{Color} : Multiplies each of the RGB color channels together.

@param other [Number, Color] The right-hand side of the operator @return [Color] The resulting color @raise [Sass::SyntaxError] if `other` is a number with units

# File lib/sass/script/value/color.rb, line 492
def times(other)
  if other.is_a?(Sass::Script::Value::Number) || other.is_a?(Sass::Script::Value::Color)
    piecewise(other, :*)
  else
    raise NoMethodError.new(nil, :times)
  end
end
to_s(opts = {}) click to toggle source

Returns a string representation of the color. This is usually the color's hex value, but if the color has a name that's used instead.

@return [String] The string representation

# File lib/sass/script/value/color.rb, line 550
def to_s(opts = {})
  return smallest if options[:style] == :compressed
  return COLOR_NAMES_REVERSE[rgba] if COLOR_NAMES_REVERSE[rgba]
  alpha? ? rgba_str : hex_str
end
Also aliased as: to_sass
to_sass(opts = {})
Alias for: to_s
with(attrs) click to toggle source

Returns a copy of this color with one or more channels changed. RGB or HSL colors may be changed, but not both at once.

For example:

Color.new([10, 20, 30]).with(:blue => 40)
  #=> rgb(10, 40, 30)
Color.new([126, 126, 126]).with(:red => 0, :green => 255)
  #=> rgb(0, 255, 126)
Color.new([255, 0, 127]).with(:saturation => 60)
  #=> rgb(204, 51, 127)
Color.new([1, 2, 3]).with(:alpha => 0.4)
  #=> rgba(1, 2, 3, 0.4)

@param attrs [{Symbol => Numeric}]

A map of channel names (`:red`, `:green`, `:blue`,
`:hue`, `:saturation`, `:lightness`, or `:alpha`) to values

@return [Color] The new Color object @raise [ArgumentError] if both RGB and HSL keys are specified

# File lib/sass/script/value/color.rb, line 412
def with(attrs)
  attrs = attrs.reject {|k, v| v.nil?}
  hsl = !([:hue, :saturation, :lightness] & attrs.keys).empty?
  rgb = !([:red, :green, :blue] & attrs.keys).empty?
  if hsl && rgb
    raise ArgumentError.new("Cannot specify HSL and RGB values for a color at the same time")
  end

  if hsl
    [:hue, :saturation, :lightness].each {|k| attrs[k] ||= send(k)}
  elsif rgb
    [:red, :green, :blue].each {|k| attrs[k] ||= send(k)}
  else
    # If we're just changing the alpha channel,
    # keep all the HSL/RGB stuff we've calculated
    attrs = @attrs.merge(attrs)
  end
  attrs[:alpha] ||= alpha

  Color.new(attrs, :allow_both_rgb_and_hsl)
end

Private Instance Methods

hex_str() click to toggle source
# File lib/sass/script/value/color.rb, line 578
def hex_str
  red, green, blue = rgb.map {|num| num.to_s(16).rjust(2, '0')}
  "##{red}#{green}#{blue}"
end
hsl_to_rgb!() click to toggle source
# File lib/sass/script/value/color.rb, line 603
def hsl_to_rgb!
  return if @attrs[:red] && @attrs[:blue] && @attrs[:green]

  h = @attrs[:hue] / 360.0
  s = @attrs[:saturation] / 100.0
  l = @attrs[:lightness] / 100.0

  # Algorithm from the CSS3 spec: http://www.w3.org/TR/css3-color/#hsl-color.
  m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s
  m1 = l * 2 - m2
  @attrs[:red], @attrs[:green], @attrs[:blue] = [
    hue_to_rgb(m1, m2, h + 1.0 / 3),
    hue_to_rgb(m1, m2, h),
    hue_to_rgb(m1, m2, h - 1.0 / 3)
  ].map {|c| (c * 0xff).round}
end
hue_to_rgb(m1, m2, h) click to toggle source
# File lib/sass/script/value/color.rb, line 620
def hue_to_rgb(m1, m2, h)
  h += 1 if h < 0
  h -= 1 if h > 1
  return m1 + (m2 - m1) * h * 6 if h * 6 < 1
  return m2 if h * 2 < 1
  return m1 + (m2 - m1) * (2.0 / 3 - h) * 6 if h * 3 < 2
  m1
end
piecewise(other, operation) click to toggle source
# File lib/sass/script/value/color.rb, line 583
def piecewise(other, operation)
  other_num = other.is_a? Number
  if other_num && !other.unitless?
    raise Sass::SyntaxError.new(
      "Cannot add a number with units (#{other}) to a color (#{self}).")
  end

  result = []
  (0...3).each do |i|
    res = rgb[i].send(operation, other_num ? other.value : other.rgb[i])
    result[i] = [[res, 255].min, 0].max
  end

  if !other_num && other.alpha != alpha
    raise Sass::SyntaxError.new("Alpha channels must be equal: #{self} #{operation} #{other}")
  end

  with(:red => result[0], :green => result[1], :blue => result[2])
end
rgb_to_hsl!() click to toggle source
# File lib/sass/script/value/color.rb, line 629
def rgb_to_hsl!
  return if @attrs[:hue] && @attrs[:saturation] && @attrs[:lightness]
  r, g, b = [:red, :green, :blue].map {|k| @attrs[k] / 255.0}

  # Algorithm from http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV
  max = [r, g, b].max
  min = [r, g, b].min
  d = max - min

  h =
    case max
    when min; 0
    when r; 60 * (g - b) / d
    when g; 60 * (b - r) / d + 120
    when b; 60 * (r - g) / d + 240
    end

  l = (max + min) / 2.0

  s =
    if max == min
      0
    elsif l < 0.5
      d / (2 * l)
    else
      d / (2 - 2 * l)
    end

  @attrs[:hue] = h % 360
  @attrs[:saturation] = s * 100
  @attrs[:lightness] = l * 100
end
rgba_str() click to toggle source
# File lib/sass/script/value/color.rb, line 573
def rgba_str
  split = options[:style] == :compressed ? ',' : ', '
  "rgba(#{rgb.join(split)}#{split}#{Number.round(alpha)})"
end
smallest() click to toggle source
# File lib/sass/script/value/color.rb, line 566
def smallest
  small_explicit_str = alpha? ? rgba_str : hex_str.gsub(/^#(.)\1(.)\2(.)\3$/, '#\1\2\3')
  return small_explicit_str unless (color = COLOR_NAMES_REVERSE[rgba]) &&
    color.size <= small_explicit_str.size
  color
end