redgreenblue 0.6.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,22 @@
1
+ class RGB
2
+
3
+ # Returns the color as a 24-bit integer in the range 0..16777215.
4
+ def to_i
5
+ ( r << 16 ) + ( g << 8 ) + b
6
+ end
7
+
8
+ # Creates a new RGB object from a 24-bit integer in the range 0..16777215.
9
+ def self.at(number)
10
+ n = number.to_i
11
+ if (0..16777215) === n
12
+ rgb(
13
+ ( n & 0xff0000 ) >> 16,
14
+ ( n & 0x00ff00 ) >> 8,
15
+ ( n & 0x0000ff )
16
+ )
17
+ else
18
+ raise ArgumentError, "Argument '#{number}' not in range 0..16777215"
19
+ end
20
+ end
21
+
22
+ end
@@ -1,11 +1,53 @@
1
1
  class RGB
2
2
 
3
+ # Creates a white RGB object.
3
4
  def self.white
4
- new([1,1,1])
5
+ new(1,1,1)
5
6
  end
6
7
 
8
+ # Creates a black RGB object.
7
9
  def self.black
8
- new([0,0,0])
10
+ new(0,0,0)
11
+ end
12
+
13
+ # Creates a grey RGB object. Defaults to lightness 0.5, a middle grey. Black equals grey(0), white equals grey(1).
14
+ #
15
+ # ::gray is an alias for ::grey.
16
+ def self.grey(lightness=0.5)
17
+ new(lightness, lightness, lightness)
18
+ end
19
+
20
+ # Alias gray for grey.
21
+ self.singleton_class.send(:alias_method, :gray, :grey)
22
+
23
+ # Creates a pure red RGB object.
24
+ def self.red
25
+ new(1,0,0)
26
+ end
27
+
28
+ # Creates a pure green RGB object.
29
+ def self.green
30
+ new(0,1,0)
31
+ end
32
+
33
+ # Creates a pure blue RGB object.
34
+ def self.blue
35
+ new(0,0,1)
36
+ end
37
+
38
+ # Creates a yellow RGB object.
39
+ def self.yellow
40
+ new(1,1,0)
41
+ end
42
+
43
+ # Creates a cyan RGB object.
44
+ def self.cyan
45
+ new(0,1,1)
46
+ end
47
+
48
+ # Creates a magenta RGB object.
49
+ def self.magenta
50
+ new(1,0,1)
9
51
  end
10
52
 
11
53
  end
@@ -0,0 +1,9 @@
1
+ class RGB
2
+
3
+ private
4
+
5
+ def zip_add(a,b)
6
+ a.zip(b).map { |ab| ( ab[0] || 0 ) + ab[1] }
7
+ end
8
+
9
+ end
@@ -1,60 +1,39 @@
1
1
  class RGB
2
2
 
3
- # Mix
4
-
5
- # Mix with second RGB color.
6
- # p denotes portion of the mixed-in color to be used.
7
- # p=0 delivers pure original color (no change),
8
- # p=1 delivers pure mixed-in color.
9
-
10
- def mix!(color,p=0.5)
11
- self.values = mix_values(color.values, p)
12
- self
13
- end
14
-
15
- def mix(color,p=0.5)
16
- RGB.new mix_values(color.values, p)
17
- end
18
-
19
- # Invert
20
-
3
+ # Inverts the object's color.
21
4
  def invert!
22
- self.values = values.map { |v| 1-v }
5
+ self.values = values.map { |v| 1 - v }
23
6
  self
24
7
  end
25
8
 
9
+ # Creates a new RGB object with the inverted color of this RGB object.
26
10
  def invert
27
11
  dup.invert!
28
12
  end
29
13
 
30
- # Mix with white
31
-
32
- def whiten!(p)
33
- mix!(RGB.white, p)
14
+ # Returns true when this is an achromatic color: red, green, and blue have equal values.
15
+ # Otherwise false.
16
+ def achromatic?
17
+ values.min == values.max
34
18
  end
35
19
 
36
- def whiten(p)
37
- mix(RGB.white, p)
20
+ # Returns an array of RGB objects for all possible ways in which the red, green, and blue values of this object can be exchanged.
21
+ #
22
+ # Example: RGB.red.permutation returns [ RGB.red, RGB.green, RGB.blue ].
23
+ # See also: shuffle.
24
+ def permutation
25
+ values.permutation.to_a.uniq.map { |v| RGB.new v }
38
26
  end
39
27
 
40
- # Mix with black
41
-
42
- def blacken!(p)
43
- mix!(RGB.black, p)
28
+ # Returns an array of three RGB objects, for the red, green, and blue components of this object.
29
+ def components
30
+ [ RGB.new(red,0,0), RGB.new(0,green,0), RGB.new(0,0,blue) ]
44
31
  end
45
32
 
46
- def blacken(p)
47
- mix(RGB.black, p)
33
+ # Creates a new RGB object from three RGB objects representing the red, green, and blue components.
34
+ def self.assemble(*a)
35
+ v = a.flatten
36
+ RGB.new(v[0].red, v[1].green, v[2].blue)
48
37
  end
49
38
 
50
- private
51
-
52
- def mix_values(v, p)
53
- raise(ArgumentError, "Portion '#{p}' not in range (0..1)") unless (0..1).include? p
54
- [
55
- ( red * (1-p) ) + ( v[0] * p ),
56
- ( green * (1-p) ) + ( v[1] * p ),
57
- ( blue * (1-p) ) + ( v[2] * p )
58
- ]
59
- end
60
39
  end
@@ -0,0 +1,65 @@
1
+ class RGB
2
+
3
+ # Changes the object's color by mixing it with a portion of another RGB color.
4
+ def mix!(another,portion=0.5)
5
+ self.values = mix_values(another.values, portion)
6
+ self
7
+ end
8
+
9
+ # Creates a new RGB object by mixing this object's color with a portion of another RGB color.
10
+ def mix(another,portion=0.5)
11
+ RGB.new mix_values(another.values, portion)
12
+ end
13
+
14
+ # Changes the object's color by mixing it with a portion of white.
15
+ def whiten!(portion=0.5)
16
+ mix!(RGB.white, portion)
17
+ end
18
+
19
+ # Creates one or more new RGB objects by mixing this object's color with a portion of white.
20
+ def whiten(portion=0.5, *portions)
21
+ if (portion.class != Array) and portions.none?
22
+ mix(RGB.white, portion)
23
+ else
24
+ ( [portion].flatten + portions ).map { |p| mix(RGB.white, p) }
25
+ end
26
+ end
27
+
28
+ # Changes the object's color by mixing it with a portion of black.
29
+ def blacken!(portion=0.5)
30
+ mix!(RGB.black, portion)
31
+ end
32
+
33
+ # Creates one or more new RGB objects by mixing this object's color with a portion of black.
34
+ def blacken(portion=0.5, *portions)
35
+ if (portion.class != Array) and portions.none?
36
+ mix(RGB.black, portion)
37
+ else
38
+ ( [portion].flatten + portions ).map { |p| mix(RGB.black, p) }
39
+ end
40
+ end
41
+
42
+ # Returns a set of colors between this color and another. That other color is included.
43
+ #
44
+ # The resulting colors are spaced evenly in the RGB color space using a straightforward calculation.
45
+ # You will likely experience these colors as not exactly evenly spaced.
46
+ def steps(another,step_count=1,include_begin=false)
47
+ # origin (self, optional)
48
+ ( include_begin ? [self.dup] : [] ) +
49
+ # ...plus intermediate colors
50
+ (1..step_count-1).map { |c| mix(another, c.to_f/step_count) } +
51
+ # ...plus destination color
52
+ [another.dup]
53
+ end
54
+
55
+ private
56
+
57
+ def mix_values(some_values, portion)
58
+ raise(ArgumentError, "Portion '#{portion}' not in range (0..1)") unless (0..1).include? portion
59
+ [
60
+ ( red * (1 - portion) ) + ( some_values[0] * portion ),
61
+ ( green * (1 - portion) ) + ( some_values[1] * portion ),
62
+ ( blue * (1 - portion) ) + ( some_values[2] * portion )
63
+ ]
64
+ end
65
+ end
@@ -0,0 +1,54 @@
1
+ # Optional support for Philips Hue lights.
2
+ #
3
+ # Conforms to Bridge API 1.35 for Philips Hue, published 20-Nov-2019.
4
+
5
+ # Automatically load core RGB class before loading options.
6
+ require 'redgreenblue'
7
+
8
+ class RGB
9
+
10
+ # Only available when optional support for Philips Hue lights is loaded.
11
+ #
12
+ # Returns a hash with the arguments required by the Philips Hue API,
13
+ # to set a light to this RGB object's hue, saturation and brightness.
14
+ #
15
+ # Formatted as JSON, this hash can be sent to a bridge to set a light's state.
16
+ #
17
+ # See (regrettably requires registration):
18
+ # - https://developers.meethue.com/develop/hue-api/
19
+ #
20
+ # @example Load
21
+ # require 'redgreenblue/opt/philipshue'
22
+ # @example Use
23
+ # RGB.magenta.to_philips_hue_api_hsb_arguments
24
+ # => {"on"=>true, "bri"=>254, "hue"=>54613, "sat"=>254}
25
+ # @return [Hash] API arguments
26
+ def to_philips_hue_api_hsb_arguments(black_off=true)
27
+ my_hsb = hsb
28
+
29
+ # Black means 'off'
30
+ if black_off and ( my_hsb[2] == 0 )
31
+ { 'on' => false }
32
+
33
+ else
34
+ {
35
+
36
+ # On/Off state of the light
37
+ 'on' => true,
38
+
39
+ # Brightness 1..254
40
+ # Note: a brightness of 1 will not switch the light off
41
+ 'bri' => ( my_hsb[2] * 253 + 1 ).round,
42
+
43
+ # Hue 0..65535
44
+ 'hue' => (( my_hsb[0] || 0 ) * 65535 / 360 ).round,
45
+
46
+ # Saturation 0..254
47
+ 'sat' => ( my_hsb[1] * 254 ).round
48
+
49
+ }
50
+
51
+ end
52
+ end
53
+
54
+ end
@@ -1,5 +1,7 @@
1
1
  class RGB
2
2
 
3
+ # On Mac OS, shows the color picker to choose a color for the RGB object.
4
+ # Not available on other platforms.
3
5
  def pick
4
6
  result = RGB.mac_choose(rrggbb)
5
7
  if result
@@ -7,10 +9,12 @@ class RGB
7
9
  end
8
10
  end
9
11
 
10
- # factory method
11
-
12
- def self.pick
13
- result = RGB.mac_choose(RGB.new.rrggbb)
12
+ # On Mac OS, shows the color picker and creates an RGB object with the chosen color.
13
+ # Not available on other platforms.
14
+ #
15
+ # If no default color is specified, the picker defaults to a middle grey.
16
+ def self.pick(default_color=RGB.new)
17
+ result = RGB.mac_choose(default_color.rrggbb)
14
18
  if result
15
19
  RGB.rrggbb result
16
20
  else
@@ -20,15 +24,25 @@ class RGB
20
24
 
21
25
  private
22
26
 
23
- # Use Applescript to call color picker on Mac.
24
- # - requires a 48-bit RGB triplet [rr, gg, bb] for default choice.
25
- #
26
- # - Returns nil when <cancel> is clicked or <esc> key hit.
27
- # - Otherwise returns 48-bit RGB triplet [rr, gg, bb].
27
+ # Uses Applescript to call the color picker on Mac OS.
28
+ # - requires a 48-bit RGB triplet [rr, gg, bb] for default choice.
29
+ #
30
+ # - Returns nil when <cancel> is clicked or <esc> key hit.
31
+ # - Otherwise returns 48-bit RGB triplet [rr, gg, bb].
32
+ #
33
+ # Applescript command documented here:
34
+ # Standard Additions -> User Interaction -> choose color
28
35
  def self.mac_choose(color)
29
36
 
37
+ app = case ENV['TERM_PROGRAM']
38
+ when /iTerm\.app/
39
+ 'iTerm'
40
+ else
41
+ 'Terminal'
42
+ end
43
+
30
44
  script = <<~ENDSCRIPT
31
- tell application "Terminal"
45
+ tell application "#{app}"
32
46
  try
33
47
  return choose color default color { #{color[0]}, #{color[1]}, #{color[2]} }
34
48
  on error
@@ -0,0 +1,45 @@
1
+ class RGB
2
+
3
+ # Returns a new RGB object with this color's Ostwald full-color,
4
+ # or nil for achromatic colors (white, greys, and black).
5
+ #
6
+ # The resulting color contains no white or black,
7
+ # i.e. it has at least one RGB component set to 0, and at least one set to maximum.
8
+ #
9
+ # This is identical (barring very small rounding errors)
10
+ # to setting HSL-saturation to 1, and -lightness to 0.5
11
+ #
12
+ # Based on:
13
+ # - Color for the Sciences, pp. 575–591
14
+ # - https://lirias.kuleuven.be/retrieve/306124 (PDF)
15
+ def ostwald_color
16
+ white_portion = values.min
17
+ color_portion = values.max - white_portion
18
+
19
+ if color_portion == 0
20
+ nil
21
+ else
22
+ RGB.new( values.map { |v| ( ( v - white_portion ) / color_portion ).round(6) } )
23
+ end
24
+ end
25
+
26
+ # Returns the portions of Ostwald full-color, white, and black, which constitute this color.
27
+ #
28
+ # The sum of these three numbers equals 1.
29
+ #
30
+ # #cwk is an alias for #ostwald_cwk.
31
+ #
32
+ # Based on:
33
+ # - Color for the Sciences, pp. 575–591
34
+ # - https://lirias.kuleuven.be/retrieve/306124 (PDF)
35
+ def ostwald_cwk
36
+ [
37
+ color_portion = values.max - values.min,
38
+ white_portion = values.min,
39
+ 1 - color_portion - white_portion
40
+ ]
41
+ end
42
+
43
+ alias cwk ostwald_cwk
44
+
45
+ end
@@ -1,17 +1,25 @@
1
1
  class RGB
2
2
 
3
+ # Shuffles the object's red, green, and blue values.
3
4
  def shuffle!
4
- self.red, self.green, self.blue = [red, green, blue].shuffle
5
+ self.values = values.shuffle
5
6
  self
6
7
  end
7
8
 
9
+ # Creates a new RGB object with this object's red, green, and blue values shuffled.
10
+ def shuffle
11
+ RGB.new values.shuffle
12
+ end
13
+
14
+ # Assigns random values to red, green, and blue.
8
15
  def randomize!
9
- self.red, self.green, self.blue = Kernel::rand, Kernel::rand, Kernel::rand
16
+ self.values = Kernel::rand, Kernel::rand, Kernel::rand
10
17
  self
11
18
  end
12
19
 
20
+ # Creates a new RGB object with random red, green, and blue values.
13
21
  def self.rand
14
- new([Kernel::rand, Kernel::rand, Kernel::rand])
22
+ new(Kernel::rand, Kernel::rand, Kernel::rand)
15
23
  end
16
24
 
17
25
  end
@@ -1,25 +1,24 @@
1
1
  class RGB
2
2
 
3
+ # Returns the color in 16-bit RGB565 format.
3
4
  def rgb565
4
5
  [((r >> 3) << 11) + ((g >> 2) << 5) + (b >> 3)].pack 'S<'
5
6
  end
6
7
 
7
- # https://stackoverflow.com/questions/2442576/
8
- def rgb565=(s)
9
- v = ( s.unpack "S<" )[0]
8
+ # Sets the color from 16-bit RGB565 data.
9
+ # With help from:
10
+ # - https://stackoverflow.com/questions/2442576/
11
+ def rgb565=(rgb565_string)
12
+ v = ( rgb565_string.unpack "S<" )[0]
10
13
  self.r = ( ( v & 0xf800 ) >> 11 ) << 3
11
14
  self.g = ( ( v & 0x07e0 ) >> 5 ) << 2
12
15
  self.b = ( ( v & 0x001f ) ) << 3
13
16
  end
14
17
 
15
- def rgb565_binary
16
- rgb565.bytes.reverse.map { |b| "%08b" % b }.join
17
- end
18
-
19
- # factory method
20
- def self.rgb565(s)
18
+ # Creates a new RGB color from 16-bit RGB565 data.
19
+ def self.rgb565(rgb565_string)
21
20
  c = self.new
22
- c.rgb565 = s
21
+ c.rgb565 = rgb565_string
23
22
  c
24
23
  end
25
24