frank
frank

Reputation: 31

How do I do math with colors like SASS but in Ruby?

In SASS I can do:

!pink = #ff43a7
!darker_pink = !pink - #333333

I'd like to the same in Ruby.

Upvotes: 3

Views: 1375

Answers (5)

Groveriffic
Groveriffic

Reputation: 1421

I just came across something where I wanted to distribute colors for a set across different hues. The SASS source didn't help much because I didn't see a way to get RGB from HSV.

The color gem had what I needed.

I wrote this helper:

def region_color(index, size)
  h = (index.to_f / (size - 1).to_f)
  Color::HSL.from_fraction(h, 0.95, 0.3).html
end

Upvotes: 0

chriseppstein
chriseppstein

Reputation: 10623

The basic approach of adding/subtracting colors in Sass is nonsense and only really works when using a gray adjustment. That's why in Sass 3 we now have full support of operations in the HSL domain which maps closely to the way people think about colors.

Since Sass is written in Ruby, you can at least read our code to see what's going on.

Here's the Color class, and the functions that operate on them.

It really is non-trivial code. Why not just use Sass?

Upvotes: 2

maček
maček

Reputation: 77778

Hex can be represented in Ruby by prefixing your value with 0x:

pink = 0xff43a7
darker_pink = pink - 0x333333

color helper

def color(hex)
  "#%06x" % hex
end

Usage in ERb template

.container {
  color: <%= color pink %>;
  border: 1px solid <%= color darker_pink %>;
}

Output

.container {
  color: #ff43a7;
  border: 1px solid #cc1074;
}

Upvotes: 4

Nathan Long
Nathan Long

Reputation: 125882

Use the Sass module

If you already have the Sass library, you can instantiate and work with its objects.

For instance:

red  = Sass::Script::Color.new([255, 0, 0])
gray = Sass::Script::Color.new([128, 128, 128])
red.lightness < gray.lightness # => true

There must be a built-in way to turn hexadecimal strings like #00FF00 into Color objects, but not seeing one, I wrote this function:

# @param color_string - hex string, like '#22FF22'. MUST be 6 characters,
# because I don't feel like dealing with the use-case for 3. :)
def color_from_hex_string(color_string)
  # Drop the leading '#', if any
  color_string = color_string[1..-1] if color_string.start_with?('#')
  raise ArgumentError.new('Hex string must be 6 characters') unless color_string.length == 6

  # Turn into array of 2-digit decimal numbers. 
  # Eg, '808080' becomes [128, 128, 128]; '#ff0000' becomes [255, 0, 0]
  rgb_array = color_string.split('').each_slice(2).map do |slice|
    slice.join('').to_i(16).to_s(10)
  end

  # Use that to build a new Color object
  color = Sass::Script::Color.new(rgb_array)

  # Set this option so it won't complain (?)
  color.options = {:style => :compressed}

  return color
end

Upvotes: 2

Steve Cotner
Steve Cotner

Reputation: 505

To refine @macek's answer, following @drawnownward's and @lpsquiggle's wishes:

You can make two helpers, like so:

  def color(color)
    "#%06x" % color
  end

  def darker_color(color)
    x = color.match(/0x(..)(..)(..)/)
    r = x[1].sub(/[0-3]/, '5')
    g = x[2].sub(/[0-3]/, '5')
    b = x[3].sub(/[0-3]/, '5')
    rgb = "0x#{r}#{g}#{b}"
    "#%06x" % (rgb.hex - 0x444444)
  end

The advantage: if you've defined a color hex with low values (between 0 and 3, here), these will be bumped up before the subtraction, so that they end up as 0 afterward, instead of wrapping around and becoming c, d, e, or f (which would give you a color you didn't expect). It only does this for the first value in each #rrggbb pair, so #313131 becomes #0d0d0d, which isn't technically correct, but it's much better than #fdfdfd, so it seems like a good enough compromise, since you'll want to keep those second values in other cases.

In your Erb template, then, you would write this:

.container {
  color: <%= color pink %>;
  border: 1px solid <%= darker_color pink %>;
}

Instead of:

.container {
  color: <%= color pink %>;
  border: 1px solid <%= color darker_pink %>;
}

Hope that helps someone.

Upvotes: 0

Related Questions