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.
@@ -1,35 +1,66 @@
1
1
  class RGB
2
2
 
3
+ # Returns a 6-digit hexadecimal string representing the object's red, green, and blue components as 8-bit values.
4
+ #
5
+ # If shorthand is true, returns 3-digit shorthand if possible.
3
6
  def hex(shorthand=false)
4
7
  if shorthand
5
- RGB.hex_shorthand hexadecimal
8
+ RGB.hex_shorthand hex6
6
9
  else
7
- hexadecimal
10
+ hex6
8
11
  end
9
12
  end
10
13
 
11
- def hex=(s)
12
- if ( s =~ /^(#?)(\h\h)(\h\h)(\h\h)$/ ) # 6-digit hex
13
- self.rgb = [ $2.to_i(16), $3.to_i(16), $4.to_i(16) ]
14
- elsif ( s =~ /^(#?)(\h)(\h)(\h)$/ ) # 3-digit hex
15
- self.rgb = [ ($2*2).to_i(16), ($3*2).to_i(16), ($4*2).to_i(16) ]
14
+ # Sets red, green, and blue using a 6- or 3-digit hexadecimal string.
15
+ #
16
+ # The string may include a '#' prefix.
17
+ def hex=(hex_string)
18
+ if rgb = RGB.hex_to_rgb(hex_string)
19
+ self.rgb = rgb
20
+ else
21
+ raise ArgumentError, "'#{hex_string}' is not a usable hexadecimal string"
22
+ end
23
+ end
24
+
25
+
26
+ # Class methods
27
+
28
+
29
+ # Creates a new object from a 6- or 3-digit hexadecimal string representing red, green, and blue.
30
+ #
31
+ # The string may include a '#' prefix.
32
+ def self.hex(hex_string)
33
+ if rgb = RGB.hex_to_rgb(hex_string)
34
+ self.rgb rgb
35
+ else
36
+ raise ArgumentError, "'#{hex_string}' is not a usable hexadecimal string"
16
37
  end
17
38
  end
18
39
 
19
- # factory method
20
- def self.hex(s)
21
- c = self.new
22
- c.hex = s
23
- c
40
+ # Returns a 3-digit shorthand version of a 6-digit hexadecimal string.
41
+ #
42
+ # If a shorthand version is not possible, returns the original string.
43
+ def self.hex_shorthand(hex_string)
44
+ hex_string.sub( /^(#?)(\h)\2(\h)\3(\h)\4$/, '\1\2\3\4' )
24
45
  end
25
46
 
26
- def self.hex_shorthand(h)
27
- h.sub( /^(#?)(\h)\2(\h)\3(\h)\4$/, '\1\2\3\4' )
47
+ # Parses a 6- or 3-digit hexadecimal string.
48
+ # Returns three integers in the range 0..255, or nil if not successful.
49
+ #
50
+ # The string may include a '#' prefix.
51
+ def self.hex_to_rgb(hex_string)
52
+ if hex_string =~ /^(#?)(\h\h)(\h\h)(\h\h)$/
53
+ [$2, $3, $4].map { |h| h.to_i(16) }
54
+ elsif hex_string =~ /^(#?)(\h)(\h)(\h)$/
55
+ [$2, $3, $4].map { |h| (h*2).to_i(16) }
56
+ else
57
+ nil
58
+ end
28
59
  end
29
60
 
30
61
  private
31
62
 
32
- def hexadecimal
63
+ def hex6
33
64
  '%02x%02x%02x' % [ r, g, b ]
34
65
  end
35
66
 
@@ -0,0 +1,61 @@
1
+ require 'redgreenblue/hsv'
2
+
3
+ class RGB
4
+
5
+ ########################################################################
6
+ # Class methods #
7
+ ########################################################################
8
+
9
+ class << self
10
+
11
+ # Creates a new RGB object from HSB values: hue (0..360), saturation (0..1), and brightness (0..1).
12
+ alias hsb hsv
13
+
14
+ end
15
+
16
+ ########################################################################
17
+ # Instance methods #
18
+ ########################################################################
19
+
20
+ # Returns color as HSB:
21
+ # hue (0..360), saturation (0..1), brightness (0..1).
22
+ # When saturation is 0, hue is nil.
23
+ alias hsb hsv
24
+
25
+ # Returns the object's HSB-hue (0..360).
26
+ alias hsb_h hsv_h
27
+
28
+ # Returns the object's HSB-saturation (0..1).
29
+ alias hsb_s hsv_s
30
+
31
+ # Returns the object's HSB-brightness (0..1).
32
+ alias hsb_b hsv_v
33
+
34
+ # Sets red, green, and blue using HSB values: hue (0..360), saturation (0..1), and brightness (0..1).
35
+ alias hsb= hsv=
36
+
37
+ # Sets HSB-hue to a number of degrees (0..360) or nil.
38
+ #
39
+ # Adjusts red, green, and blue, leaving HSB-saturation and -brightness unchanged.
40
+ # When hue is nil, saturation will be 0.
41
+ alias hsb_h= hsv_h=
42
+
43
+ # Sets HSB-saturation to a number between 0 and 1.
44
+ #
45
+ # Adjusts red, green, and blue, leaving HSB-hue and -brightness unchanged.
46
+ # When saturation is 0, hue will be nil.
47
+ alias hsb_s= hsv_s=
48
+
49
+ # Sets HSB-brightness to a number between 0 and 1.
50
+ #
51
+ # Adjusts red, green, and blue, leaving HSB-hue and -saturation unchanged.
52
+ # When brightness is 0, hue will be nil, and saturation will be 0.
53
+ alias hsb_b= hsv_v=
54
+
55
+ # Sets red, green, and blue by rotating the object's HSB-hue a number of degrees.
56
+ alias hsb_rotate! hsv_rotate!
57
+
58
+ # Creates one or more new RGB objects by rotating this object's HSB-hue a number of degrees.
59
+ alias hsb_rotate hsv_rotate
60
+
61
+ end
@@ -0,0 +1,96 @@
1
+ require 'redgreenblue/hsx_shared'
2
+ require 'redgreenblue/math'
3
+
4
+ class RGB
5
+
6
+ ########################################################################
7
+ # Class methods #
8
+ ########################################################################
9
+
10
+ class << self
11
+
12
+ # Creates a new RGB object from HSL values: hue (0..360), saturation (0..1), and lightness (0..1).
13
+ def hsl(*a)
14
+ new hsl_to_values(*a)
15
+ end
16
+
17
+ # Calculates RGB values from HSL.
18
+ # Given hue (0..360), saturation (0..1), and lightness (0..1),
19
+ # returns red, green, and blue as three values between 0 and 1.
20
+ def hsl_to_values(*a)
21
+ hsm_to_values(:hsl, a)
22
+ end
23
+
24
+ end
25
+
26
+ ########################################################################
27
+ # Instance methods #
28
+ ########################################################################
29
+
30
+ # Returns color as HSL:
31
+ # hue (0..360), saturation (0..1), lightness (0..1).
32
+ # When saturation is 0, hue is nil.
33
+ def hsl
34
+ hsl_hsv_c[0]
35
+ end
36
+
37
+ # Returns the object's HSL-hue (0..360).
38
+ def hsl_h
39
+ hsl[0]
40
+ end
41
+
42
+ # Returns the object's HSL-saturation (0..1).
43
+ def hsl_s
44
+ hsl[1]
45
+ end
46
+
47
+ # Returns the object's HSL-lightness (0..1).
48
+ def hsl_l
49
+ hsl[2]
50
+ end
51
+
52
+ # Sets red, green, and blue using HSL values: hue (0..360), saturation (0..1), and lightness (0..1).
53
+ def hsl=(*a)
54
+ self.values = RGB.hsl_to_values(*a)
55
+ end
56
+
57
+ # Sets HSL-hue to a number of degrees (0..360) or nil.
58
+ #
59
+ # Adjusts red, green, and blue, leaving saturation and lightness unchanged.
60
+ # When hue is nil, saturation will be 0.
61
+ def hsl_h=(degrees)
62
+ self.hsl = hsl.fill(degrees,0,1)
63
+ end
64
+
65
+ # Sets HSL-saturation to a value between 0 and 1.
66
+ #
67
+ # Adjusts red, green, and blue, leaving hue and lightness unchanged.
68
+ # When saturation is 0, hue will be nil.
69
+ def hsl_s=(value)
70
+ self.hsl = hsl.fill(value ,1,1)
71
+ end
72
+
73
+ # Sets HSL-lightness to a value between 0 and 1.
74
+ #
75
+ # Adjusts red, green, and blue, leaving hue and saturation unchanged.
76
+ # When lightness is 0 or 1, hue will be nil, and saturation will be 0.
77
+ def hsl_l=(value)
78
+ self.hsl = hsl.fill(value ,2,1)
79
+ end
80
+
81
+ # Sets red, green, and blue by rotating the object's HSL-hue a number of degrees.
82
+ def hsl_rotate!(degrees)
83
+ self.hsl = zip_add(hsl, [degrees, 0, 0])
84
+ self
85
+ end
86
+
87
+ # Creates one or more new RGB objects by rotating this object's HSL-hue a number of degrees.
88
+ def hsl_rotate(a_degrees, *b_degrees)
89
+ if a_degrees.class != Array and b_degrees.none?
90
+ RGB.hsl zip_add(hsl, [a_degrees, 0, 0])
91
+ else
92
+ ( [a_degrees] + b_degrees ).flatten.map { |degrees| RGB.hsl zip_add(hsl, [degrees, 0, 0]) }
93
+ end
94
+ end
95
+
96
+ end
@@ -0,0 +1,96 @@
1
+ require 'redgreenblue/hsx_shared'
2
+ require 'redgreenblue/math'
3
+
4
+ class RGB
5
+
6
+ ########################################################################
7
+ # Class methods #
8
+ ########################################################################
9
+
10
+ class << self
11
+
12
+ # Creates a new RGB object from HSV values: hue (0..360), saturation (0..1), and value (0..1).
13
+ def hsv(*a)
14
+ new hsv_to_values(*a)
15
+ end
16
+
17
+ # Calculates RGB values from HSV.
18
+ # Given hue (0..360), saturation (0..1), and value (0..1),
19
+ # returns red, green, and blue as three values between 0 and 1.
20
+ def hsv_to_values(*a)
21
+ hsm_to_values(:hsv, a)
22
+ end
23
+
24
+ end
25
+
26
+ ########################################################################
27
+ # Instance methods #
28
+ ########################################################################
29
+
30
+ # Returns color as HSV:
31
+ # hue (0..360), saturation (0..1), value (0..1).
32
+ # When saturation is 0, hue is nil.
33
+ def hsv
34
+ hsl_hsv_c[1]
35
+ end
36
+
37
+ # Returns the object's HSV-hue (0..360).
38
+ def hsv_h
39
+ hsv[0]
40
+ end
41
+
42
+ # Returns the object's HSV-saturation (0..1).
43
+ def hsv_s
44
+ hsv[1]
45
+ end
46
+
47
+ # Returns the object's HSV-value (0..1).
48
+ def hsv_v
49
+ hsv[2]
50
+ end
51
+
52
+ # Sets red, green, and blue using HSV values: hue (0..360), saturation (0..1), and value (0..1).
53
+ def hsv=(*a)
54
+ self.values = RGB.hsv_to_values(*a)
55
+ end
56
+
57
+ # Sets HSV-hue to a number of degrees (0..360) or nil.
58
+ #
59
+ # Adjusts red, green, and blue, leaving HSV-saturation and -value unchanged.
60
+ # When hue is nil, saturation will be 0.
61
+ def hsv_h=(degrees)
62
+ self.hsv = hsv.fill(degrees,0,1)
63
+ end
64
+
65
+ # Sets HSV-saturation to a number between 0 and 1.
66
+ #
67
+ # Adjusts red, green, and blue, leaving HSV-hue and -value unchanged.
68
+ # When saturation is 0, hue will be nil.
69
+ def hsv_s=(value)
70
+ self.hsv = hsv.fill(value ,1,1)
71
+ end
72
+
73
+ # Sets HSV-value to a number between 0 and 1.
74
+ #
75
+ # Adjusts red, green, and blue, leaving HSV-hue and -saturation unchanged.
76
+ # When value is 0, hue will be nil, and saturation will be 0.
77
+ def hsv_v=(value)
78
+ self.hsv = hsv.fill(value ,2,1)
79
+ end
80
+
81
+ # Sets red, green, and blue by rotating the object's HSV-hue a number of degrees.
82
+ def hsv_rotate!(degrees)
83
+ self.hsv = zip_add(hsv, [degrees, 0, 0])
84
+ self
85
+ end
86
+
87
+ # Creates one or more new RGB objects by rotating this object's HSV-hue a number of degrees.
88
+ def hsv_rotate(a_degrees, *b_degrees)
89
+ if a_degrees.class != Array and b_degrees.none?
90
+ RGB.hsv zip_add(hsv, [a_degrees, 0, 0])
91
+ else
92
+ ( [a_degrees] + b_degrees ).flatten.map { |degrees| RGB.hsv zip_add(hsv, [degrees, 0, 0]) }
93
+ end
94
+ end
95
+
96
+ end
@@ -0,0 +1,94 @@
1
+ class RGB
2
+
3
+ ########################################################################
4
+ # Class methods #
5
+ ########################################################################
6
+
7
+ class << self
8
+
9
+ private
10
+
11
+ # Calculates RGB values from HSL or HSV.
12
+ # With help from:
13
+ # - https://en.wikipedia.org/wiki/HSL_and_HSV
14
+ def hsm_to_values(type, *a)
15
+ raise NotImplementedError unless [:hsl, :hsv].include? type
16
+
17
+ hue, saturation, magnitude = a.flatten
18
+
19
+ if ( saturation == 0 ) or ( ! hue )
20
+ return Array.new(3) { magnitude }
21
+ end
22
+
23
+ hue = hue.modulo 360
24
+
25
+ chroma = case type
26
+ when :hsl
27
+ ( 1 - ( 2 * magnitude - 1 ).abs ) * saturation
28
+ when :hsv
29
+ magnitude * saturation
30
+ end
31
+
32
+ values = [
33
+ chroma,
34
+ ( 1 - ( ( hue / 60.0 ).modulo(2) - 1 ).abs ) * chroma,
35
+ 0
36
+ ]
37
+
38
+ values = case type
39
+ when :hsl
40
+ values.map { |v| ( v + magnitude - chroma / 2.0 ).round(9) }
41
+ when :hsv
42
+ values.map { |v| ( v + magnitude - chroma ).round(9) }
43
+ end
44
+
45
+ # order values according to hue sextant
46
+ [ [0,1,2], [1,0,2], [2,0,1], [2,1,0], [1,2,0], [0,2,1] ][hue.div 60].map { |i| values[i]}
47
+ end
48
+
49
+ end
50
+
51
+ ########################################################################
52
+ # Instance methods #
53
+ ########################################################################
54
+
55
+ private
56
+
57
+ # Compute HSL, HSV, and chroma.
58
+ # With help from:
59
+ # - https://en.wikipedia.org/wiki/HSL_and_HSV
60
+ def hsl_hsv_c
61
+ sorted_hash = to_h
62
+ min, max = sorted_hash.values.values_at(2,0)
63
+
64
+ chroma = max - min
65
+
66
+ hue =
67
+ if chroma == 0
68
+ nil
69
+ else
70
+ ( case sorted_hash.keys.first
71
+ when :red
72
+ 60 * ( ( ( green - blue ) / chroma ).modulo 6 )
73
+ when :green
74
+ 60 * ( ( blue - red ) / chroma + 2 )
75
+ when :blue
76
+ 60 * ( ( red - green ) / chroma + 4 )
77
+ end
78
+ ).round(6)
79
+ end
80
+
81
+ lightness = ( min + max ) / 2.0
82
+
83
+ saturation_hsl =
84
+ chroma == 0 ? 0.0 : ( chroma / ( 1 - (2 * lightness - 1).abs ) ).round(9)
85
+
86
+ value = max
87
+
88
+ saturation_hsv =
89
+ value == 0 ? 0.0 : chroma / value
90
+
91
+ [ [hue, saturation_hsl, lightness], [hue, saturation_hsv, value], chroma ]
92
+ end
93
+
94
+ end
@@ -0,0 +1,69 @@
1
+ class RGB
2
+
3
+ private
4
+
5
+ def _inspect_default(prefix='RGB')
6
+ "#{prefix} ##{hex} (red=%1.5f green=%1.5f blue=%1.5f)" % [red, green, blue]
7
+ end
8
+
9
+ def _inspect_hex
10
+ "##{hex}"
11
+ end
12
+
13
+ def _inspect_swatch
14
+ terminal_background + " \e[0m"
15
+ end
16
+
17
+ def _inspect_short
18
+ _inspect_swatch + " ##{hex}"
19
+ end
20
+
21
+ def _inspect_simple
22
+ _inspect_default _inspect_swatch
23
+ end
24
+
25
+ public
26
+
27
+ # Returns a programmer-friendly representation of the object.
28
+ #
29
+ # You can choose among several inspect styles. See the styles, style, and style= class methods.
30
+ def inspect
31
+ send "_inspect_#{self.class.style}"
32
+ end
33
+
34
+ # Returns a string representation of the object.
35
+ def to_s
36
+ _inspect_default
37
+ end
38
+
39
+ # Returns the base inspect style, dependent on the COLORTERM environment variable.
40
+ def self.base_style
41
+ if styles.include? ENV['REDGREENBLUE_STYLE']
42
+ ENV['REDGREENBLUE_STYLE']
43
+ else
44
+ if ENV['COLORTERM'] == 'truecolor'
45
+ 'simple'
46
+ else
47
+ 'default'
48
+ end
49
+ end
50
+ end
51
+
52
+ # Returns the current inspect style.
53
+ def self.style
54
+ @@style ||= base_style
55
+ end
56
+
57
+ # Returns a list of all available inspect styles.
58
+ def self.styles
59
+ ( self.instance_methods + self.private_instance_methods ).grep( /^_inspect_(.*)/ ) { $1 }.sort
60
+ end
61
+
62
+ # Selects an inspect style.
63
+ #
64
+ # Only the first few characters of your preferred style are required.
65
+ def self.style=(s)
66
+ @@style = styles.grep( /^#{s.to_s.downcase}/ ).first || style
67
+ end
68
+
69
+ end