simplecolor 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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