simplecolor 0.2.0 → 0.3.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,228 @@
1
+ module SimpleColor
2
+ ColorerError=Class.new(SimpleColorError)
3
+ WrongColor=Class.new(ColorerError)
4
+ WrongParameter=Class.new(ColorerError)
5
+
6
+ # The Colorer module handle all color outputs
7
+ module Colorer
8
+ extend self
9
+ def colors
10
+ COLORS
11
+ end
12
+ # A color name can be:
13
+ # - an array of rgb data (truecolor)
14
+ # (start with :on to specify background)
15
+ # - a String:
16
+ # rgb:10-20-30 (foreground truecolor)
17
+ # on_rgb:10-20-30 (background truecolor)
18
+ # t:rgb... (don't fallback to lower color mode)
19
+ # (on_)rgb256:r:g:b (force 256 color mode)
20
+ # (on_)rgb256:grey3 (256 grey scale)
21
+ # (on_)rgb256:5 (direct color code)
22
+ # (t:)(on_)#AABBCC (hex code, truecolor)
23
+ # (t:)(on_)#ABC (reduced hex code, truecolor)
24
+ # (t:)(on_)name (X11 color name, truecolor)
25
+ # A color attribute can be:
26
+ # - a symbol (looked in at COLORS)
27
+ # - an integer (direct color code)
28
+ # - a color escape sequence
29
+ # - a String
30
+ def color_attributes(*args, mode: :text, color_mode: :truecolor, abbreviations: {}, rgb_class: RGB, **rgb_parse_opts)
31
+ return "" if mode==:disabled or mode==false #early abort
32
+ abbreviations={} if abbreviations.nil?
33
+ colors=self.colors
34
+ accu=[]
35
+ buffer=""
36
+ flush=lambda {r=accu.join(";"); accu=[]; r.empty? || r="\e["+r+"m"; buffer<<r} #Note: "\e"="\x1b"
37
+
38
+ parse_rgb=lambda do |s|
39
+ symbol=s.is_a?(Symbol)
40
+ truecol=/(?<truecol>(?:truecolor|true|t):)?/
41
+ on=/(?<on>on_)?/
42
+ s.match(/\A#{truecol}#{on}(?<rest>.*)\z/) do |m|
43
+ tcol=m[:truecol]; on=m[:on]; string=m[:rest]
44
+ lcolormode = tcol ? :truecolor : color_mode
45
+ string=string.to_sym if symbol
46
+ accu << rgb_class.parse(string, **rgb_parse_opts).ansi(background: !!on, convert: lcolormode)
47
+ end
48
+ end
49
+
50
+ parse=lambda do |*cols, abbrevs: abbreviations|
51
+ cols.each do |col|
52
+ if (scol=abbrevs[col])
53
+ # Array are special, in a non abbreviation they mean an rgb mode but for abbreviations it just combine several color attributes
54
+ parse.call(*scol, abbrevs: {}) #we erase abbreviations so :red = :red do not get an infinite loop
55
+ next
56
+ end
57
+ case col
58
+ when Proc
59
+ scol=col.call(buffer, accu)
60
+ parse.call(*scol)
61
+ when Symbol
62
+ if colors.key?(col) #ANSI effects
63
+ accu << colors[col]
64
+ else
65
+ parse_rgb.call(col)
66
+ end
67
+ when Integer #direct ansi code
68
+ accu << col.to_s
69
+ when Array
70
+ background=false
71
+ if col.first == :on
72
+ background=true; col.shift
73
+ end
74
+ accu << rgb_class.new(col).ansi(convert: color_mode, background: background)
75
+ when rgb_class
76
+ accu << col.ansi(convert: color_mode)
77
+ when COLOR_REGEXP
78
+ flush.call
79
+ buffer<<col
80
+ when String
81
+ parse_rgb.call(col)
82
+ when nil # skip
83
+ else
84
+ raise WrongColor.new(col)
85
+ end
86
+ end
87
+ end
88
+
89
+ parse.call(*args)
90
+ flush.call
91
+
92
+ case mode
93
+ when :shell
94
+ "%{"+buffer+"%}"
95
+ when :disabled
96
+ "" # already handled above
97
+ when :text, :enabled, true
98
+ buffer
99
+ else
100
+ raise WrongParameter.new(mode)
101
+ end
102
+ end
103
+
104
+ def regexp(type=:color, mode: :text, **_rest)
105
+ case type
106
+ when :color
107
+ if mode == :shell
108
+ m=regexp(:ansi, mode: mode)
109
+ /#{m}+/
110
+ else
111
+ COLOR_REGEXP
112
+ end
113
+ when :match
114
+ if mode == :shell
115
+ m=regexp(:ansi, mode: mode)
116
+ /#{m}*/
117
+ else
118
+ COLORMATCH_REGEXP
119
+ end
120
+ when :ansi
121
+ if mode == :shell
122
+ /%{#{ANSICOLOR_REGEXP}%}/
123
+ else
124
+ ANSICOLOR_REGEXP
125
+ end
126
+ when :clear
127
+ if mode == :shell
128
+ /%{#{CLEAR_REGEXP}%}/
129
+ else
130
+ CLEAR_REGEXP
131
+ end
132
+ end
133
+ end
134
+
135
+ # Returns a colored version of the string (modified in place),
136
+ # according to attributes
137
+ def colorer(s, *attributes, global_color: :after, local_color: :before, **kwds)
138
+ if s.nil?
139
+ color_attributes(*attributes,**kwds)
140
+ elsif s.empty?
141
+ # We don't color an empty string; use nil to get color attributes
142
+ s
143
+ else
144
+ match_reg=regexp(:match, **kwds)
145
+ color_reg=regexp(:color, **kwds)
146
+ clear_reg=regexp(:clear, **kwds)
147
+ colors=color_attributes(*attributes,**kwds)
148
+ clear=color_attributes(:clear,**kwds)
149
+
150
+ split=SimpleColor.color_strings(s, color_regexp: color_reg)
151
+ sp, i=split.each_with_index.find do |sp, i|
152
+ i>=1 && sp.match(/#{color_reg}/)
153
+ end
154
+ global=true unless sp and (i<split.length-1 || !sp.match(/#{clear_reg}$/))
155
+
156
+ if global
157
+ case global_color
158
+ when :keep
159
+ matched = s.match(match_reg)
160
+ s.insert(0, colors) unless matched.end(0)>0
161
+ when :before
162
+ s.insert(0, colors)
163
+ when :after
164
+ # we need to insert the ANSI sequences after existing ones so that
165
+ # the new colors have precedence
166
+ matched = s.match(match_reg) #since this has a '*' it matches at the beginning
167
+ s.insert(matched.end(0), colors)
168
+ end
169
+ else
170
+ pos=0
171
+ split.each_with_index do |sp,i|
172
+ if sp.match(/#{clear_reg}$/)
173
+ #don't reinsert ourselves at end
174
+ unless i==split.length-1
175
+ pos+=sp.length
176
+ s.insert(pos, colors)
177
+ pos+=colors.length
178
+ end
179
+ elsif sp.match(/#{color_reg}/)
180
+ case local_color
181
+ when :keep
182
+ if i!=0
183
+ # we need to clear ourselves
184
+ s.insert(pos, clear)
185
+ pos+=clear.length
186
+ end
187
+ pos+=sp.length
188
+ when :before
189
+ #we already inserted ourselves except if we are on the
190
+ #first block
191
+ if i==0
192
+ s.insert(pos, colors)
193
+ pos+=colors.length
194
+ end
195
+ pos+=sp.length
196
+ when :after
197
+ pos+=sp.length
198
+ s.insert(pos, colors)
199
+ pos+=colors.length
200
+ end
201
+ else
202
+ if i==0
203
+ s.insert(pos, colors)
204
+ pos+=colors.length
205
+ end
206
+ pos+=sp.length
207
+ end
208
+ end
209
+ end
210
+ s.concat(clear) unless s =~ /#{clear_reg}$/ or colors.empty?
211
+ s
212
+ end
213
+ end
214
+
215
+ # Returns an uncolored version of the string, that is all
216
+ # ANSI-sequences are stripped from the string.
217
+ # @see: colorer
218
+ def uncolorer(s, **kwds)
219
+ s.to_str.gsub!(regexp(:color, **kwds), '') || s.to_str
220
+ rescue ArgumentError #rescue from "invalid byte sequence in UTF-8"
221
+ s.to_str
222
+ end
223
+
224
+ def colored?(s, **kwds)
225
+ !! (s =~ regexp(:color, **kwds))
226
+ end
227
+ end
228
+ end
@@ -1,109 +1,71 @@
1
1
  module SimpleColor
2
+ module Colorer
2
3
 
3
- # Regular expression to scan if there is a clear ANSI effect
4
- CLEAR_REGEXP = /\e\[0m/
5
- # Regular expression that is used to scan for ANSI-sequences
6
- ANSICOLOR_REGEXP = /\e\[(?:[\d;]*)m/
7
- COLOR_REGEXP = /#{ANSICOLOR_REGEXP}+/
8
- COLORMATCH_REGEXP = /#{ANSICOLOR_REGEXP}*/
4
+ # Regular expression to scan if there is a clear ANSI effect
5
+ CLEAR = "\e\[0m"
6
+ CLEAR_REGEXP = /\e\[0m/
7
+ # Regular expression that is used to scan for ANSI-sequences
8
+ ANSICOLOR_REGEXP = /\e\[(?:[\d;]*)m/
9
+ COLOR_REGEXP = /#{ANSICOLOR_REGEXP}+/
10
+ COLORMATCH_REGEXP = /#{ANSICOLOR_REGEXP}*/
9
11
 
10
- #Stolen from the paint gem.
11
- #See alosa http://en.wikipedia.org/wiki/ANSI_escape_code
12
+ #Originally stolen from the paint gem.
13
+ #See also http://en.wikipedia.org/wiki/ANSI_escape_code
12
14
 
13
- # Basic colors (often, the color differs when using the bright effect)
14
- # Final color will be 30 + value for foreground and 40 + value for background
15
- # 90+value for intense foreground, 100+value for intense background
16
- ANSI_COLORS = {
17
- :black => 0,
18
- :red => 1,
19
- :green => 2,
20
- :yellow => 3,
21
- :blue => 4,
22
- :magenta => 5,
23
- :cyan => 6,
24
- :white => 7,
25
- :default => 9,
26
- }
27
-
28
- ANSI_EFFECTS = {
29
- :reset => 0, :nothing => 0, # usually supported
30
- :clear => 0, :normal => 0, # usually supported
31
- :bright => 1, :bold => 1, # usually supported
32
- :faint => 2,
33
- :italic => 3,
34
- :underline => 4, # usually supported
35
- :blink => 5, :slow_blink => 5,
36
- :rapid_blink => 6,
37
- :inverse => 7, :swap => 7, # usually supported
38
- :conceal => 8, :hide => 9,
39
- :default_font => 10,
40
- :font0 => 10, :font1 => 11, :font2 => 12, :font3 => 13, :font4 => 14,
41
- :font5 => 15, :font6 => 16, :font7 => 17, :font8 => 18, :font9 => 19,
42
- :fraktur => 20,
43
- :bright_off => 21, :bold_off => 21, :double_underline => 21,
44
- :clean => 22,
45
- :italic_off => 23, :fraktur_off => 23,
46
- :underline_off => 24,
47
- :blink_off => 25,
48
- :inverse_off => 26, :positive => 26,
49
- :conceal_off => 27, :show => 27, :reveal => 27,
50
- :crossed_off => 29, :crossed_out_off => 29,
51
- :frame => 51,
52
- :encircle => 52,
53
- :overline => 53,
54
- :frame_off => 54, :encircle_off => 54,
55
- :overline_off => 55,
56
- }
15
+ # Basic colors (often, the color differs when using the bright effect)
16
+ # Final color will be 30 + value for foreground and 40 + value for background
17
+ # 90+value for intense foreground, 100+value for intense background
18
+ ANSI_COLORS = {
19
+ :black => 0,
20
+ :red => 1,
21
+ :green => 2,
22
+ :yellow => 3,
23
+ :blue => 4,
24
+ :magenta => 5,
25
+ :cyan => 6,
26
+ :white => 7,
27
+ :default => 9,
28
+ }
57
29
 
58
- ANSI_COLORS_FOREGROUND = {
59
- :black => 30,
60
- :red => 31,
61
- :green => 32,
62
- :yellow => 33,
63
- :blue => 34,
64
- :magenta => 35,
65
- :cyan => 36,
66
- :white => 37,
67
- :default => 39,
68
- }
30
+ ANSI_COLORS_FOREGROUND = Hash[ANSI_COLORS.map {|k,v| [k, 30+v]}]
31
+ ANSI_COLORS_BACKGROUND = Hash[ANSI_COLORS.map {|k,v| [:"on_#{k}", 40+v]}]
32
+ # aixterm (not standard)
33
+ ANSI_COLORS_INTENSE_FOREGROUND = Hash[ANSI_COLORS.map {|k,v| [:"intense_#{k}", 90+v]}]
34
+ ANSI_COLORS_INTENSE_BACKGROUND = Hash[ANSI_COLORS.map {|k,v| [:"on_intense_#{k}", 100+v]}]
69
35
 
70
- ANSI_COLORS_BACKGROUND = {
71
- :on_black => 40,
72
- :on_red => 41,
73
- :on_green => 42,
74
- :on_yellow => 43,
75
- :on_blue => 44,
76
- :on_magenta => 45,
77
- :on_cyan => 46,
78
- :on_white => 47,
79
- :on_default => 49,
80
- }
81
-
82
- ANSI_COLORS_INTENSE_FOREGROUND = {
83
- :intense_black => 90,
84
- :intense_red => 91,
85
- :intense_green => 92,
86
- :intense_yellow => 93,
87
- :intense_blue => 94,
88
- :intense_magenta => 95,
89
- :intense_cyan => 96,
90
- :intense_white => 97,
91
- :intense_default => 99,
92
- }
93
-
94
- ANSI_COLORS_INTENSE_BACKGROUND = {
95
- :on_intense_black => 100,
96
- :on_intense_red => 101,
97
- :on_intense_green => 102,
98
- :on_intense_yellow => 103,
99
- :on_intense_blue => 104,
100
- :on_intense_magenta => 105,
101
- :on_intense_cyan => 106,
102
- :on_intense_white => 107,
103
- :on_intense_default => 109,
104
- }
105
-
106
- #attributes that can be specified to the color method
107
- COLORS = [ANSI_EFFECTS,ANSI_COLORS_FOREGROUND, ANSI_COLORS_BACKGROUND, ANSI_COLORS_INTENSE_FOREGROUND, ANSI_COLORS_INTENSE_BACKGROUND].inject({}){ |a,b| a.merge(b) }
36
+ ANSI_EFFECTS = {
37
+ :reset => 0, :nothing => 0, # usually supported
38
+ :clear => 0, :normal => 0, # usually supported
39
+ :bright => 1, :bold => 1, # usually supported
40
+ :faint => 2,
41
+ :italic => 3,
42
+ :underline => 4, # usually supported
43
+ :blink => 5, :slow_blink => 5,
44
+ :rapid_blink => 6,
45
+ :inverse => 7, :reverse => 7, :swap => 7, # usually supported
46
+ :conceal => 8, :hide => 8,
47
+ :crossed => 9, :crossed_out => 9,
48
+ :default_font => 10,
49
+ :font0 => 10, :font1 => 11, :font2 => 12, :font3 => 13, :font4 => 14,
50
+ :font5 => 15, :font6 => 16, :font7 => 17, :font8 => 18, :font9 => 19,
51
+ :fraktur => 20,
52
+ :bright_off => 21, :bold_off => 21, :double_underline => 21,
53
+ :clean => 22, :regular => 22, #neither bold or faint
54
+ :italic_off => 23, :fraktur_off => 23,
55
+ :underline_off => 24,
56
+ :blink_off => 25,
57
+ :inverse_off => 26, :positive => 26,
58
+ :conceal_off => 27, :show => 27, :reveal => 27,
59
+ :crossed_off => 29, :crossed_out_off => 29,
60
+ :frame => 51, :framed => 51,
61
+ :encircle => 52, :encircled => 52,
62
+ :overline => 53, :overlined => 53,
63
+ :frame_off => 54, :encircle_off => 54,
64
+ :framed_off => 54, :encircled_off => 54,
65
+ :overline_off => 55, :overlined_off => 55,
66
+ }
108
67
 
68
+ #attributes that can be specified to the color method
69
+ COLORS = [ANSI_EFFECTS,ANSI_COLORS_FOREGROUND, ANSI_COLORS_BACKGROUND, ANSI_COLORS_INTENSE_FOREGROUND, ANSI_COLORS_INTENSE_BACKGROUND].inject({}){ |a,b| a.merge(b) }
70
+ end
109
71
  end
@@ -0,0 +1,2 @@
1
+ require 'simplecolor'
2
+ SimpleColor.mix_in_string
@@ -0,0 +1,318 @@
1
+ require 'json'
2
+ require "zlib"
3
+ require 'simplecolor/rgb_colors'
4
+
5
+ # rgb color conversion
6
+ # taken from the paint gem, all copyright belong to its author
7
+
8
+ module SimpleColor
9
+ RGBError=Class.new(SimpleColorError)
10
+ WrongRGBColor=Class.new(RGBError)
11
+ WrongRGBParameter=Class.new(RGBError)
12
+
13
+ class RGB
14
+
15
+ module Parsers
16
+ # Creates RGB color from a HTML-like color definition string
17
+ def rgb_hex(string)
18
+ case string.size
19
+ when 6
20
+ string.each_char.each_slice(2).map{ |hex_color| hex_color.join.to_i(16) }
21
+ when 3
22
+ string.each_char.map{ |hex_color_half| (hex_color_half*2).to_i(16) }
23
+ else
24
+ raise WrongRGBColor.new(string)
25
+ end
26
+ end
27
+
28
+ def parse(col, color_names: proc {|c| find_color(c)})
29
+ case col
30
+ when self
31
+ return col
32
+ when Proc
33
+ scol=col.call(self)
34
+ return parse(scol, color_names: color_names)
35
+ when Symbol
36
+ if ANSI_COLORS_16.key?(col)
37
+ return self.new(col, mode: 16)
38
+ end
39
+ when Array
40
+ return self.new(col, mode: true)
41
+ when String
42
+ if (m=col.match(/\A(?:rgb)?256[+:-]?(?<grey>gr[ae]y)?(?<red>\d+)(?:[+:-](?<green>\d+)[+:-](?<blue>\d+))?\z/))
43
+ grey=m[:grey]; red=m[:red]&.to_i; green=m[:green]&.to_i; blue=m[:blue]&.to_i
44
+ if grey
45
+ return self.new(GREY256+red, mode: 256)
46
+ elsif green and blue
47
+ return self.new([red, green, blue], mode: 256)
48
+ else
49
+ raise WrongRGBColor.new(col) if green
50
+ return self.new(red, mode: 256)
51
+ end
52
+ elsif (m=col.match(/\A(?:rgb[+:-]?)?(?<red>\d+)[+:-](?<green>\d+)[+:-](?<blue>\d+)\z/))
53
+ red=m[:red]&.to_i; green=m[:green]&.to_i; blue=m[:blue]&.to_i
54
+ return self.new([red, green, blue], mode: :truecolor)
55
+ elsif (m=col.match(/\A#?(?<hex_color>[[:xdigit:]]{3}{1,2})\z/)) # 3 or 6 hex chars
56
+ return self.new(rgb_hex(m[:hex_color]), mode: :truecolor)
57
+ end
58
+ end
59
+ if color_names && (c=color_names[col])
60
+ return self.parse(c, color_names: nil)
61
+ else
62
+ # fallback to parsing col.to_s?
63
+ raise WrongRGBColor.new(col)
64
+ end
65
+ end
66
+ end
67
+ extend Parsers
68
+
69
+ module Utils
70
+ def rgb_random
71
+ RGB.new((1..3).map { Random.rand(256) })
72
+ end
73
+
74
+ #c=16 + 36 × r + 6 × g + b
75
+ def color256_to_rgb(c)
76
+ bgr=(c-16).digits(6)
77
+ bgr+=[0]*(3-rgb.length)
78
+ bgr.reverse
79
+ end
80
+
81
+ def rgb_values(c)
82
+ case c
83
+ when self
84
+ c.to_truecolor.color
85
+ else
86
+ self.parse(c).to_truecolor.color
87
+ end
88
+ end
89
+ end
90
+ extend Utils
91
+
92
+ module RGB16
93
+ def rgb_colors16
94
+ RGB_COLORS_ANSI_16
95
+ end
96
+ def rgb_colors8
97
+ RGB_COLORS_ANSI
98
+ end
99
+ end
100
+ extend RGB16
101
+ extend RGB16
102
+
103
+ attr_accessor :color, :mode, :background
104
+
105
+ private def color_mode(mode)
106
+ case mode
107
+ when true, :truecolor, TRUE_COLOR
108
+ mode=:truecolor
109
+ end
110
+ case mode
111
+ when 8, 16, 256, :truecolor
112
+ yield mode if block_given?
113
+ return mode
114
+ else
115
+ raise WrongRGBParameter.new(mode)
116
+ end
117
+ end
118
+
119
+ def initialize(*rgb, mode: :truecolor, background: false)
120
+ raise WrongRGBColor.new(rgb) if rgb.empty?
121
+ rgb=rgb.first if rgb.length==1
122
+ raise WrongRGBColor.new(rgb) if rgb.nil?
123
+
124
+ @init=rgb
125
+ @color=rgb #should be an array for truecolor, a number otherwise
126
+ @mode=color_mode(mode)
127
+ @background=!!background
128
+
129
+ case @mode
130
+ when :truecolor
131
+ unless @color&.size == 3 && @color&.all?{ |n| n.is_a? Numeric }
132
+ raise WrongRGBColor.new(rgb)
133
+ end
134
+ raise WrongRGBColor.new(rgb) unless rgb.all? do |c|
135
+ (0..255).include?(c)
136
+ end
137
+ when 256 #for 256 colors we are more lenient
138
+ case rgb
139
+ when Array
140
+ unless @color&.size == 3 && @color&.all?{ |n| n.is_a? Numeric }
141
+ raise WrongRGBColor.new(rgb)
142
+ end
143
+ raise WrongRGBColor.new(rgb) unless rgb.all? do |c|
144
+ (0..5).include?(c)
145
+ end
146
+ red, green, blue=rgb
147
+ @color=16 + 36 * red.to_i + 6 * green.to_i + blue.to_i
148
+ when String, Symbol #for grey mode
149
+ if (m=rgb.to_s.match(/\Agr[ae]y(\d+)\z/))
150
+ @color=GREY256+m[1].to_i
151
+ else
152
+ raise WrongRGBColor.new(rgb)
153
+ end
154
+ else
155
+ raise WrongRGBColor.new(rgb) unless @color.is_a?(Numeric)
156
+ end
157
+ when 8,16
158
+ @color=ANSI_COLORS_16[rgb] if ANSI_COLORS_16.key?(rgb)
159
+ raise WrongRGBColor.new(rgb) unless @color.is_a?(Numeric)
160
+ end
161
+ end
162
+
163
+ def ==(other)
164
+ [:color, :mode, :background].all? do |sym|
165
+ self.public_send(sym) == other.public_send(sym)
166
+ end
167
+ end
168
+
169
+ def truecolor?
170
+ @mode == :truecolor
171
+ end
172
+
173
+ def nbcolors
174
+ return TRUE_COLOR if @mode == :truecolor
175
+ return @mode
176
+ end
177
+
178
+ def to_hex
179
+ r,g,b=rgb
180
+ hexa = ->(c) {h=c.to_s(16).upcase; h.length == 1 ? "0#{h}" : h}
181
+ "\##{hexa[r]}#{hexa[g]}#{hexa[b]}"
182
+ end
183
+
184
+ def rgb
185
+ to_truecolor.color
186
+ end
187
+
188
+ # For RGB 256 colors,
189
+ # Foreground = "\e[38;5;#{fg}m", Background = "\e[48;5;#{bg}m"
190
+ # where the color code is
191
+ # 0- 7: standard colors (as in ESC [ 30–37 m)
192
+ # 8- 15: high intensity colors (as in ESC [ 90–97 m)
193
+ # 16-231: 6 × 6 × 6 cube (216 colors): 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5)
194
+ # 232-255: grayscale from black to white in 24 steps
195
+
196
+ #For true colors:
197
+ # ESC[ 38;2;<r>;<g>;<b> m Select RGB foreground color
198
+ # ESC[ 48;2;<r>;<g>;<b> m Select RGB background color
199
+ def ansi(background: @background, convert: nil)
200
+ return self.convert(convert, only_down: true).ansi(background: background, convert: nil) if convert
201
+ case @mode
202
+ when 8, 16
203
+ if @color < 8
204
+ "#{background ? 4 : 3}#{@color}"
205
+ else
206
+ # Note that on_intense_* is not supported by terminals
207
+ # it is usually better to convert to 256 colors palette which is
208
+ # better supported
209
+ "#{background ? 10 : 9}#{@color-8}"
210
+ end
211
+ when 256
212
+ "#{background ? 48 : 38};5;#{@color}"
213
+ when :truecolor
214
+ red, green, blue=@color
215
+ "#{background ? 48 : 38};2;#{red};#{green};#{blue}"
216
+ end
217
+ end
218
+
219
+ def convert(mode, only_down: false)
220
+ case color_mode(mode)
221
+ when 8
222
+ return to_8
223
+ when 16
224
+ return to_16 unless only_down and nbcolors < 16
225
+ when 256
226
+ return to_256 unless only_down and nbcolors < 256
227
+ when :truecolor
228
+ return to_truecolor unless only_down and nbcolors < TRUE_COLOR
229
+ end
230
+ self
231
+ end
232
+
233
+ def to_truecolor
234
+ case @mode
235
+ when 8, 16
236
+ name=ANSI_COLORS_16.key(@color)
237
+ rgb=self.class.rgb_colors16[name]
238
+ self.class.new(rgb)
239
+ when 256
240
+ if @color < 16
241
+ to_16.to_truecolor
242
+ elsif @color < GREY256
243
+ red, green, blue=self.class.color256_to_rgb(@color)
244
+ self.class.new([red, green, blue].map {|c| (c * 256.0/7.0).round})
245
+ else
246
+ grey=@color-GREY256
247
+ self.class.new([(grey*256.0/24.0).round]*3)
248
+ end
249
+ else
250
+ self
251
+ end
252
+ end
253
+
254
+ def to_256
255
+ case @mode
256
+ when 256
257
+ return self
258
+ when 8, 16
259
+ return self.class.new(@color, mode: 256)
260
+ else
261
+ red,green,blue=@color
262
+
263
+ gray_possible = true
264
+ sep = 42.5
265
+
266
+ while gray_possible
267
+ if red < sep || green < sep || blue < sep
268
+ gray = red < sep && green < sep && blue < sep
269
+ gray_possible = false
270
+ end
271
+ sep += 42.5
272
+ end
273
+
274
+ col=if gray
275
+ GREY256 + ((red.to_f + green.to_f + blue.to_f)/33).round
276
+ else # rgb
277
+ [16, *[red, green, blue].zip([36, 6, 1]).map{ |color, mod|
278
+ (6 * (color.to_f / 256)).to_i * mod
279
+ }].inject(:+)
280
+ end
281
+ self.class.new(col, mode: 256)
282
+
283
+ end
284
+ end
285
+
286
+ def to_8
287
+ rgb8=self.class.rgb_colors8
288
+ color_pool = rgb8.values
289
+ closest=rgb_to_pool(color_pool)
290
+ name=rgb8.key(closest)
291
+ self.class.new(ANSI_COLORS_16[name], mode: 8)
292
+ end
293
+
294
+ def to_16
295
+ rgb16=self.class.rgb_colors16
296
+ color_pool = rgb16.values
297
+ closest=rgb_to_pool(color_pool)
298
+ name=rgb16.key(closest)
299
+ self.class.new(ANSI_COLORS_16[name], mode: 16)
300
+ end
301
+
302
+ def rgb_color_distance(rgb2)
303
+ if truecolor?
304
+ @color.zip(self.class.rgb_values(rgb2)).inject(0){ |acc, (cur1, cur2)| acc + (cur1 - cur2)**2 }
305
+ else
306
+ to_truecolor.rgb_color_distance(rgb2)
307
+ end
308
+ end
309
+
310
+ def rgb_to_pool(color_pool)
311
+ if truecolor?
312
+ color_pool.min_by{ |col| rgb_color_distance(col) }
313
+ else
314
+ to_truecolor.rgb_to_pool(color_pool)
315
+ end
316
+ end
317
+ end
318
+ end