redgreenblue 0.6.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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