DEV Community

Valentino Stoll
Valentino Stoll

Posted on • Originally published at blog.codenamev.com on

2 2

How to Make Colors With Ruby and Bitwise Operations

How to Make Colors With Ruby and Bitwise Operations

In our last lesson, we utilized the badass (P)ulse (W)idth (M)odulation tech to breathe life into a simple LED. Here, we're going to take this to new levels and apply it to multiple channels.

Unfortunately, the PWM hardware included with the Raspberry B+ is only capable of communicating over a single channel. Since we have 3 channels we need to talk to (for each of our three colors), we'll have to go soft and crank this up with software: softPwm.

First: How the RGB LED works

The RGB LED I'm working with has four pins. Three of these pins take input that control the brightness level for (R)ed, (G)reen, and (B)lue. This allows us to take advantage of the additive color system to create all kinds of colors by supplying different proportions of these three types of light (e.g. fully bright red and fully bright blue will appear purple).

Our Problem: How can we easily store color values in RGB format as a percentage of luminance?

Hexadecimal To The Rescue!

I know what you're thinking:

How to Make Colors With Ruby and Bitwise Operations

Don't lose me just yet. There's some hidden gold here! The magic of bitwise operations.

The binary 11111111 is equal to 255. Luckily for us, this binary representation maps perfectly to Hexadecimal, as FF == 255!

11111111 00000000 00000000

RED GREEN BLUE

What this means is that we can use Hexadecimal literals in Ruby, and as a result start to see some serious gains. First off, Hexadecimal numbers make a lot of sense to us as colors already. These are the Ruby equivalent of the CSS versions you might be familiar with:

Color Hexadecimal Representation in Ruby
Red 0xFF0000
Green 0x00FF00
Blue 0x0000FF
Purple 0xFF00FF

The real value here comes with the calculations we can make. Using hexadecimal literals makes some things easier. We can extract each red, green, or blue value from any given hexadecimal color by AND'ing the bits of the maxed out version of our target.

Let's say we have the color 0xFF1133 . This has a binary form of:

11111111 00010001 00110011

We can extract the value of just the blue color 0x0000FF by using the bitwise AND (&) operator to cancel out all non-blue bits. So:

0xFF1133 & 0x0000FF == 0x000033

This is because all the 0 bits in our pure blue color 0x0000FF override any bits set in any of the other color positions, and any 0 bits in our 0xFF0033 color override any 1 bits in our pure blue.

  11111111 00010001 00110011
& 00000000 00000000 11111111
               ⇣
  00000000 00000000 00110011

This works great for our blue value, but for the others we have to take this one step further. If we were to perform this same bitwise AND on the pure green color 0x00FF00 then we will get what we want, except with a lot of trailing zeros.

  11111111 00010001 00110011
& 00000000 11111111 00000000
               ⇣
  00000000 00010001 00000000

In order to target that middle section of the resulting bitwise operation, we need to shift the bits to the right to get rid of the extra zeros. To do this, we can use the bitwise right shift operator >> and push them eight places to the right.

00000000 00010001 00000000 >> 8
             ⇣
00000000 00000000 00010001

Bingo! Unity. With this in mind, we can translate it to Ruby and apply these lightning fast calculations to extract percentages of each RGB color value from a hexadecimal numeric:

# Given the folowing color
color = 0x6495ED
percentage_blue = (color & 0x0000FF) * 100 / 255 # => 92%
percentage_green = ((color & 0x00FF00) >> 8) * 100 / 255 # => 58%
percentage_red = ((color & 0xFF0000) >> 16) * 100 / 255 # => 39%

All Together Now

Since the duty cycle value is essential the percentage that the signal will be ON, we can just extract the RGB color percentages from a hexadecimal color value and set these to the duty cycle of each RGB pin of the LED.

Ok, ok, all this binary mumbo jumbo and all we get is a bunch of flashing colors!? Let's turn the dial all the way up to eleven and add a simple twist: have a button trigger a random color.

BOOM! We have made a game! Guess what color the LED will be when you press the button?

How to Make Colors With Ruby and Bitwise Operations

#!/usr/bin/env ruby
require "rpi_gpio"
# Set up a color table in Hexadecimal
COLOR = [
0xFF0000, # red
0x00FF00, # green
0x0000FF, # blue
0xFFFF00, # yellow
0xFF00FF, # purple
0xFF8000 # orange
]
# Set PINS' channels with dictionary
PINS = {"Red" => 17, "Green" => 18, "Blue" => 27}
BUTTON_PIN_NUMBER = 22
GUESS_DURATION = 3
puts "========================================"
puts "| Guess the Color! |"
puts "| ------------------------------ |"
puts "| Red Pin connect to GPIO0 |"
puts "| Green Pin connect to GPIO1 |"
puts "| Blue Pin connect to GPIO2 |"
puts "| Button Pin connect to GPIO3 |"
puts "| |"
puts "| Make a RGB LED emits various color |"
puts "| |"
puts "| codenamev |"
puts "========================================\n"
puts "Program is running..."
puts "Please press Ctrl+C to end the program..."
puts "Press Enter to begin\n"
STDIN.gets
# Set the GPIO modes to BCM Numbering
RPi::GPIO.set_numbering :bcm
# Set all LedPin's mode to output, and initial level to High(3.3v)
PINS.values.each do |pin|
RPi::GPIO.setup pin, as: :output, initialize: :high
end
# Set button to input and pull up to high (3.3V)
RPi::GPIO.setup BUTTON_PIN_NUMBER, as: :input, pull: :up
class MultiColorLED
DEFAULT_FREQUENCY = 2_000 # in Hz
PWMPin = Struct.new(:number, :pwm)
class LEDColor < Struct.new(:hex_color)
MAX_RGB_VALUE = 255
RED = 0xFF0000
GREEN = 0x00FF00
BLUE = 0x0000FF
# Extracts the first two values of the hexadecimal
def red
((hex_color & RED) >> 16) / MAX_RGB_VALUE * 100
end
# Extracts the second two values of the hexadecimal
def green
((hex_color & GREEN) >> 8) / MAX_RGB_VALUE * 100
end
# Extracts the last two values of the hexadecimal
def blue
(hex_color & BLUE) / MAX_RGB_VALUE * 100
end
end
attr_reader :red_pin, :green_pin, :blue_pin
def initialize(red_pin:, green_pin:, blue_pin:)
@red_pin = PWMPin.new(red_pin, RPi::GPIO::PWM.new(red_pin, DEFAULT_FREQUENCY))
@green_pin = PWMPin.new(green_pin, RPi::GPIO::PWM.new(green_pin, DEFAULT_FREQUENCY))
@blue_pin = PWMPin.new(blue_pin, RPi::GPIO::PWM.new(blue_pin, DEFAULT_FREQUENCY))
reset
start
end
def reset
red_pin.pwm.duty_cycle = 0
green_pin.pwm.duty_cycle = 0
blue_pin.pwm.duty_cycle = 0
end
def start
red_pin.pwm.start(0)
green_pin.pwm.start(0)
blue_pin.pwm.start(0)
end
def stop
# Stop all pwm channel
red_pin.pwm.stop
green_pin.pwm.stop
blue_pin.pwm.stop
# Turn off all LEDs
RPi::GPIO.set_high red_pin.number
RPi::GPIO.set_high green_pin.number
RPi::GPIO.set_high blue_pin.number
end
# Sets each color's luminance
# Duty Cylce (D) is the percentage of one period in which the signal is active.
# Period (P) is the time it takes the signal to complete an on-off cycle.
# Active Time (T) is the time the signal is active.
#
# D = T / P * 100%
#
# A 60% Duty Cycle means the signal is on 60% of the time and off 40% of the
# time. The "on time" for this cycle could be a fraction of a second, a day, or a
# week. It all depends on the length of the Period.
def setColor(color_as_hex)
led_color = LEDColor.new(color_as_hex)
# Change the colors
red_pin.pwm.duty_cycle = led_color.red
green_pin.pwm.duty_cycle = led_color.green
blue_pin.pwm.duty_cycle = led_color.blue
puts "[Color Change] rgb(#{red_pin.pwm.duty_cycle}, #{green_pin.pwm.duty_cycle}, #{blue_pin.pwm.duty_cycle})"
end
end
# Set all led as pwm channel, and frequency to 2KHz
@rgb_led = MultiColorLED.new(
red_pin: PINS["Red"],
blue_pin: PINS["Green"],
green_pin: PINS["Blue"]
)
at_exit do
@rgb_led.stop
@rgb_led.reset
# Release resource
RPi::GPIO.clean_up
end
begin
loop do
if RPi::GPIO.high? BUTTON_PIN_NUMBER
@rgb_led.reset
else
@rgb_led.setColor(COLOR.sample)
sleep GUESS_DURATION
end
end
rescue Interrupt, SignalException
exit
end
view raw color_guess.rb hosted with ❤ by GitHub

Special thank you to Calle Erlandsson's fantastic write-up on bitwise operations in Ruby.

Top comments (0)