redgreenblue 0.6.0 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/redgreenblue.rb +17 -13
- data/lib/redgreenblue/24bit.rb +38 -5
- data/lib/redgreenblue/48bit.rb +12 -5
- data/lib/redgreenblue/base.rb +58 -12
- data/lib/redgreenblue/bgr24bit.rb +5 -5
- data/lib/redgreenblue/cie.rb +45 -0
- data/lib/redgreenblue/gamma.rb +44 -0
- data/lib/redgreenblue/gif.rb +3 -2
- data/lib/redgreenblue/gpl.rb +30 -0
- data/lib/redgreenblue/hex.rb +46 -15
- data/lib/redgreenblue/hsb.rb +61 -0
- data/lib/redgreenblue/hsl.rb +96 -0
- data/lib/redgreenblue/hsv.rb +96 -0
- data/lib/redgreenblue/hsx_shared.rb +94 -0
- data/lib/redgreenblue/inspect.rb +69 -0
- data/lib/redgreenblue/int.rb +22 -0
- data/lib/redgreenblue/lazy.rb +44 -2
- data/lib/redgreenblue/math.rb +9 -0
- data/lib/redgreenblue/misc.rb +20 -41
- data/lib/redgreenblue/mix.rb +65 -0
- data/lib/redgreenblue/opt/philipshue.rb +54 -0
- data/lib/redgreenblue/os/mac.rb +24 -10
- data/lib/redgreenblue/ostwald.rb +45 -0
- data/lib/redgreenblue/random.rb +11 -3
- data/lib/redgreenblue/rgb565.rb +9 -10
- data/lib/redgreenblue/terminal.rb +19 -0
- data/lib/redgreenblue/version.rb +9 -1
- data/lib/redgreenblue/web.rb +10 -0
- metadata +23 -6
- data/lib/redgreenblue/nice.rb +0 -7
@@ -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
|
data/lib/redgreenblue/lazy.rb
CHANGED
@@ -1,11 +1,53 @@
|
|
1
1
|
class RGB
|
2
2
|
|
3
|
+
# Creates a white RGB object.
|
3
4
|
def self.white
|
4
|
-
new(
|
5
|
+
new(1,1,1)
|
5
6
|
end
|
6
7
|
|
8
|
+
# Creates a black RGB object.
|
7
9
|
def self.black
|
8
|
-
new(
|
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
|
data/lib/redgreenblue/misc.rb
CHANGED
@@ -1,60 +1,39 @@
|
|
1
1
|
class RGB
|
2
2
|
|
3
|
-
#
|
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
|
-
#
|
31
|
-
|
32
|
-
def
|
33
|
-
|
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
|
-
|
37
|
-
|
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
|
-
#
|
41
|
-
|
42
|
-
|
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
|
-
|
47
|
-
|
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
|
data/lib/redgreenblue/os/mac.rb
CHANGED
@@ -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
|
-
#
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
#
|
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 "
|
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
|
data/lib/redgreenblue/random.rb
CHANGED
@@ -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.
|
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.
|
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(
|
22
|
+
new(Kernel::rand, Kernel::rand, Kernel::rand)
|
15
23
|
end
|
16
24
|
|
17
25
|
end
|
data/lib/redgreenblue/rgb565.rb
CHANGED
@@ -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
|
-
#
|
8
|
-
|
9
|
-
|
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
|
-
|
16
|
-
|
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 =
|
21
|
+
c.rgb565 = rgb565_string
|
23
22
|
c
|
24
23
|
end
|
25
24
|
|