redgreenblue 0.9.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,14 @@
1
+ class RGB
2
+
3
+ # Returns the color's hue (0..360), whiteness (0..1), and blackness (0..1), as defined by the HWB color model.
4
+ #
5
+ # For achromatic colors, hue is nil.
6
+ #
7
+ # Based on:
8
+ # - http://alvyray.com/Papers/CG/HWB_JGTv208.pdf (PDF)
9
+ # - https://en.wikipedia.org/wiki/HWB_color_model
10
+ def hwb
11
+ [ hsv_h, cwk[1,2] ].flatten
12
+ end
13
+
14
+ end
@@ -3,11 +3,11 @@ class RGB
3
3
  private
4
4
 
5
5
  def _inspect_default(prefix='RGB')
6
- "#{prefix} ##{hex} (red=%1.5f green=%1.5f blue=%1.5f)" % [red, green, blue]
6
+ "#{prefix} #{_inspect_hex} (red=%1.5f green=%1.5f blue=%1.5f)" % [red, green, blue] + ( name ? ' ' + name : '' )
7
7
  end
8
8
 
9
9
  def _inspect_hex
10
- "##{hex}"
10
+ (self == snap ? '#' : '~') + hex
11
11
  end
12
12
 
13
13
  def _inspect_swatch
@@ -15,13 +15,17 @@ class RGB
15
15
  end
16
16
 
17
17
  def _inspect_short
18
- _inspect_swatch + " ##{hex}"
18
+ "#{_inspect_swatch} #{_inspect_hex}"
19
19
  end
20
20
 
21
21
  def _inspect_simple
22
22
  _inspect_default _inspect_swatch
23
23
  end
24
24
 
25
+ def _inspect_name
26
+ _inspect_swatch + ( name ? ' ' + name : '' )
27
+ end
28
+
25
29
  public
26
30
 
27
31
  # Returns a programmer-friendly representation of the object.
@@ -38,7 +42,15 @@ class RGB
38
42
 
39
43
  # Returns the base inspect style, dependent on the COLORTERM environment variable.
40
44
  def self.base_style
41
- ENV['COLORTERM'] == 'truecolor' ? 'simple' : 'default'
45
+ if styles.include? ENV['REDGREENBLUE_STYLE']
46
+ ENV['REDGREENBLUE_STYLE']
47
+ else
48
+ if ENV['COLORTERM'] == 'truecolor'
49
+ 'simple'
50
+ else
51
+ 'default'
52
+ end
53
+ end
42
54
  end
43
55
 
44
56
  # Returns the current inspect style.
@@ -1,53 +1,73 @@
1
1
  class RGB
2
2
 
3
- # Creates a white RGB object.
4
- def self.white
5
- new(1,1,1)
6
- end
3
+ #----------------------------------------------------------------------#
4
+ # Class Methods #
5
+ #----------------------------------------------------------------------#
7
6
 
8
- # Creates a black RGB object.
9
- def self.black
10
- new(0,0,0)
11
- end
7
+ class << self
12
8
 
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
9
+ # Creates a white RGB object.
10
+ def white
11
+ new(1,1,1)
12
+ end
19
13
 
20
- # Alias gray for grey.
21
- self.singleton_class.send(:alias_method, :gray, :grey)
14
+ # Creates a black RGB object.
15
+ def black
16
+ new(0,0,0)
17
+ end
22
18
 
23
- # Creates a pure red RGB object.
24
- def self.red
25
- new(1,0,0)
26
- end
19
+ # Creates a grey RGB object. Defaults to lightness 0.5, a middle grey. Black equals grey(0), white equals grey(1).
20
+ #
21
+ # ::gray is an alias for ::grey.
22
+ def grey(lightness=0.5)
23
+ new(lightness, lightness, lightness)
24
+ end
27
25
 
28
- # Creates a pure green RGB object.
29
- def self.green
30
- new(0,1,0)
31
- end
26
+ # Alias gray for grey.
27
+ alias gray grey
32
28
 
33
- # Creates a pure blue RGB object.
34
- def self.blue
35
- new(0,0,1)
36
- end
29
+ # Creates a pure red RGB object.
30
+ def red
31
+ new(1,0,0)
32
+ end
37
33
 
38
- # Creates a yellow RGB object.
39
- def self.yellow
40
- new(1,1,0)
41
- end
34
+ # Creates a pure green RGB object.
35
+ def green
36
+ new(0,1,0)
37
+ end
42
38
 
43
- # Creates a cyan RGB object.
44
- def self.cyan
45
- new(0,1,1)
46
- end
39
+ # Creates a pure blue RGB object.
40
+ def blue
41
+ new(0,0,1)
42
+ end
43
+
44
+ # Creates a yellow RGB object.
45
+ def yellow
46
+ new(1,1,0)
47
+ end
48
+
49
+ # Creates a cyan RGB object.
50
+ def cyan
51
+ new(0,1,1)
52
+ end
53
+
54
+ # Creates a magenta RGB object.
55
+ def magenta
56
+ new(1,0,1)
57
+ end
58
+
59
+ # Returns the 8 corners of the RGB cube.
60
+ def corners
61
+ [ black, red, yellow, green, cyan, blue, magenta, white ]
62
+ end
63
+
64
+ # Returns the centre of the RGB cube.
65
+ def centre
66
+ grey
67
+ end
68
+
69
+ alias center centre
47
70
 
48
- # Creates a magenta RGB object.
49
- def self.magenta
50
- new(1,0,1)
51
71
  end
52
72
 
53
73
  end
@@ -0,0 +1,8 @@
1
+ class RGB
2
+
3
+ # Returns the color in the format used by AppleScript (a 48-bit RGB triplet).
4
+ def applescript
5
+ "{%i, %i, %i}" % rrggbb
6
+ end
7
+
8
+ end
@@ -0,0 +1,21 @@
1
+ class RGB
2
+
3
+ # Matches this color to a set of colors by calculating their euclidean distance.
4
+ #
5
+ # Returns the given set of colors with their distance from this color, sorted by distance (nearest color first).
6
+ # @example What is nearer: red, grey or white?
7
+ # RGB.hex('f9c').match_distance [RGB.red, RGB.grey, RGB.white]
8
+ def match_distance(others)
9
+ others.map { |c| [ c, distance(c) ] }.sort_by { |a| a[1] }
10
+ end
11
+
12
+ # Matches this color to a set of colors using the CIE 1976 delta E formula.
13
+ #
14
+ # Returns the given set of colors with their difference from this color, sorted by difference (nearest color first).
15
+ # @example Find the 3 nearest CSS named colors
16
+ # RGB.hex('f9c').match_de76(RGB.css).first(3)
17
+ def match_de76(others)
18
+ others.map { |c| [ c, de76(c) ] }.sort_by { |a| a[1] }
19
+ end
20
+
21
+ 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
@@ -11,6 +11,12 @@ class RGB
11
11
  dup.invert!
12
12
  end
13
13
 
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
18
+ end
19
+
14
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.
15
21
  #
16
22
  # Example: RGB.red.permutation returns [ RGB.red, RGB.green, RGB.blue ].
@@ -30,4 +36,17 @@ class RGB
30
36
  RGB.new(v[0].red, v[1].green, v[2].blue)
31
37
  end
32
38
 
39
+ # Returns the euclidean distance between this color and another color.
40
+ #
41
+ # When you imagine a color as a point in a 3-dimensional space,
42
+ # the dimensions being red, green, and blue,
43
+ # this is the distance between two colors.
44
+ def distance(another)
45
+ (
46
+ ( (another.red - red ) ** 2) +
47
+ ( (another.green - green) ** 2) +
48
+ ( (another.blue - blue ) ** 2)
49
+ ) ** (1/2.0)
50
+ end
51
+
33
52
  end
@@ -16,9 +16,13 @@ class RGB
16
16
  mix!(RGB.white, portion)
17
17
  end
18
18
 
19
- # Creates a new RGB object by mixing this object's color with a portion of white.
20
- def whiten(portion=0.5)
21
- mix(RGB.white, portion)
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
22
26
  end
23
27
 
24
28
  # Changes the object's color by mixing it with a portion of black.
@@ -26,9 +30,13 @@ class RGB
26
30
  mix!(RGB.black, portion)
27
31
  end
28
32
 
29
- # Creates a new RGB object by mixing this object's color with a portion of black.
30
- def blacken(portion=0.5)
31
- mix(RGB.black, portion)
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
32
40
  end
33
41
 
34
42
  # Returns a set of colors between this color and another. That other color is included.