Skip to content

Commit 3b60417

Browse files
committed
add Color module
1 parent cfb2767 commit 3b60417

File tree

2 files changed

+264
-0
lines changed

2 files changed

+264
-0
lines changed

autoload/vital/__vital__/Color.vim

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
" RGB/HSL conversion library
2+
3+
let s:save_cpo = &cpo
4+
set cpo&vim
5+
6+
7+
let s:Color = {}
8+
9+
function! s:Color.as_rgb_str() abort
10+
let [r, g, b] = self.as_rgb()
11+
let [r, g, b] = map([r, g, b], 'float2nr(round(v:val))')
12+
return printf('rgb(%d,%d,%d)', r, g, b)
13+
endfunction
14+
15+
function! s:Color.as_hsl_str() abort
16+
let [h, s, l] = self.as_hsl()
17+
let [h, s, l] = map([h, s, l], 'float2nr(round(v:val))')
18+
return printf('hsl(%d,%d%%,%d%%)', h, s, l)
19+
endfunction
20+
21+
function! s:Color.eq(color) abort
22+
return self.as_rgb() ==# a:color.as_rgb()
23+
endfunction
24+
25+
function! s:Color.diff(color) abort
26+
let [r1, g1, b1] = self.as_rgb()
27+
let [r2, g2, b2] = a:color.as_rgb()
28+
return abs(r1 - r2) + abs(g1 - g2) + abs(b1 - b2)
29+
endfunction
30+
31+
let s:RGB = deepcopy(s:Color)
32+
33+
function! s:RGB.as_rgb_hex() abort
34+
let [r, g, b] = map([self._r, self._g, self._b], 'float2nr(round(v:val))')
35+
return '#' . s:_nr2hex(r) . s:_nr2hex(g) . s:_nr2hex(b)
36+
endfunction
37+
38+
function! s:RGB.as_rgb() abort
39+
return [self._r, self._g, self._b]
40+
endfunction
41+
42+
function! s:RGB.as_hsl() abort
43+
return s:_rgb2hsl(self._r, self._g, self._b)
44+
endfunction
45+
46+
let s:HSL = deepcopy(s:Color)
47+
48+
function! s:HSL.as_rgb_hex() abort
49+
let [r, g, b] = s:_hsl2rgb(self._h, self._s, self._l)
50+
let [r, g, b] = map([r, g, b], 'float2nr(round(v:val))')
51+
return '#' . s:_nr2hex(r) . s:_nr2hex(g) . s:_nr2hex(b)
52+
endfunction
53+
54+
function! s:HSL.as_rgb() abort
55+
return s:_hsl2rgb(self._h, self._s, self._l)
56+
endfunction
57+
58+
function! s:HSL.as_hsl() abort
59+
return [self._h, self._s, self._l]
60+
endfunction
61+
62+
63+
" Constructors
64+
65+
let s:RGB_HEX_RE = '\v^#(\x{3}(\x{3})?)$'
66+
let s:RGB_RE = '\v^rgb\((\d+),\s*(\d+),\s*(\d+)\)$'
67+
let s:HSL_RE = '\v^hsl\((\d+),\s*(\d+)\%,\s*(\d+)\%\)$'
68+
function! s:parse(str) abort
69+
if type(a:str) !=# type('')
70+
throw 'vital: Color: parse(): invalid format: ' . a:str
71+
endif
72+
let m = matchlist(a:str, s:RGB_HEX_RE)
73+
if !empty(m)
74+
if strlen(m[1]) ==# 3
75+
let [r, g, b] = [str2float('0x' . m[1][0] . m[1][0]), str2float('0x' . m[1][1] . m[1][1]), str2float('0x' . m[1][2] . m[1][2])]
76+
else
77+
let [r, g, b] = [str2float('0x' . m[1][0:1]), str2float('0x' . m[1][2:3]), str2float('0x' . m[1][4:5])]
78+
endif
79+
return s:rgb(r, g, b)
80+
endif
81+
let m = matchlist(a:str, s:RGB_RE)
82+
if !empty(m)
83+
let [r, g, b] = [str2float(m[1]), str2float(m[2]), str2float(m[3])]
84+
return s:rgb(r, g, b)
85+
endif
86+
let m = matchlist(a:str, s:HSL_RE)
87+
if !empty(m)
88+
let [h, s, l] = [str2float(m[1]), str2float(m[2]), str2float(m[3])]
89+
return s:hsl(h, s, l)
90+
endif
91+
throw 'vital: Color: parse(): invalid format: ' . a:str
92+
endfunction
93+
94+
function! s:rgb(r, g, b) abort
95+
if !s:_check_rgb_range(a:r, a:g, a:b)
96+
throw printf('vital: Color: rgb(): invalid value: r = %s, g = %s, b = %s',
97+
\ string(a:r), string(a:g), string(a:b))
98+
endif
99+
return extend(deepcopy(s:RGB), {'_r': a:r, '_g': a:g, '_b': a:b})
100+
endfunction
101+
102+
function! s:_check_rgb_range(r, g, b) abort
103+
for n in [a:r, a:g, a:b]
104+
if type(n) !=# type(0) && type(n) !=# type(0.0)
105+
\ || 0 ># n || n ># 255
106+
return 0
107+
endif
108+
endfor
109+
return 1
110+
endfunction
111+
112+
function! s:hsl(h, s, l) abort
113+
if !s:_check_hsl_range(a:h, a:s, a:l)
114+
throw printf('vital: Color: hsl(): invalid value: h = %s, s = %s, l = %s',
115+
\ string(a:h), string(a:s), string(a:l))
116+
endif
117+
return extend(deepcopy(s:HSL), {'_h': a:h, '_s': a:s, '_l': a:l})
118+
endfunction
119+
120+
function! s:_check_hsl_range(h, s, l) abort
121+
for n in [a:h, a:s, a:l]
122+
if type(n) !=# type(0) && type(n) !=# type(0.0)
123+
return 0
124+
endif
125+
endfor
126+
if 0 ># a:h || a:h ># 360
127+
return 0
128+
endif
129+
if 0 ># a:s || a:s ># 100
130+
return 0
131+
endif
132+
if 0 ># a:l || a:l ># 100
133+
return 0
134+
endif
135+
return 1
136+
endfunction
137+
138+
139+
" Numeric Conversion Functions
140+
141+
" NOTE: min()/max() can't take Float numbers
142+
function! s:_rgb2hsl(r, g, b) abort
143+
let [min, _, max] = sort([a:r, a:g, a:b], 'f')
144+
if min ==# max
145+
return [0, 0, 100.0 * min / 255.0]
146+
elseif max ==# a:r
147+
let h = 60.0 * (a:g - a:b) / (max - min)
148+
elseif max ==# a:g
149+
let h = 60.0 * (a:b - a:r) / (max - min) + 120.0
150+
else
151+
let h = 60.0 * (a:r - a:g) / (max - min) + 240.0
152+
endif
153+
if h < 0
154+
let h += 360
155+
endif
156+
let l = 100.0 * (max + min) / 2.0 / 255.0
157+
if l <=# 50.0
158+
let s = 100.0 * (max - min) / (max + min)
159+
else
160+
let s = 100.0 * (max - min) / (510.0 - max - min)
161+
endif
162+
return [h, s, l]
163+
endfunction
164+
165+
function! s:_hsl2rgb(h, s, l) abort
166+
if a:l <# 50.0
167+
let l2 = a:l
168+
else
169+
let l2 = 100.0 - a:l
170+
endif
171+
let max = 2.55 * (a:l + l2 * (a:s / 100.0))
172+
let min = 2.55 * (a:l - l2 * (a:s / 100.0))
173+
if a:h <# 60.0
174+
let r = max
175+
let g = (a:h / 60.0) * (max - min) + min
176+
let b = min
177+
elseif a:h <# 120.0
178+
let r = ((120.0 - a:h) / 60.0) * (max - min) + min
179+
let g = max
180+
let b = min
181+
elseif a:h <# 180.0
182+
let r = min
183+
let g = max
184+
let b = ((a:h - 120.0) / 60.0) * (max - min) + min
185+
elseif a:h <# 240.0
186+
let r = min
187+
let g = ((240.0 - a:h) / 60.0) * (max - min) + min
188+
let b = max
189+
elseif a:h <# 300.0
190+
let r = ((a:h - 240.0) / 60.0) * (max - min) + min
191+
let g = min
192+
let b = max
193+
else
194+
let r = max
195+
let g = min
196+
let b = ((360.0 - a:h) / 60.0) * (max - min) + min
197+
endif
198+
return [r, g, b]
199+
endfunction
200+
201+
let s:HEX_TABLE = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F']
202+
function! s:_nr2hex(n) abort
203+
return s:HEX_TABLE[a:n / 16] . s:HEX_TABLE[a:n % 16]
204+
endfunction
205+
206+
207+
let &cpo = s:save_cpo
208+
unlet s:save_cpo
209+
" vim:set et ts=2 sts=2 sw=2 tw=0:

test/Color.vim

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
scriptencoding utf-8
2+
3+
let s:suite = themis#suite('Color')
4+
let s:assert = themis#helper('assert')
5+
6+
function! s:suite.before()
7+
let s:C = vital#vital#new().import('Color')
8+
endfunction
9+
10+
function! s:suite.rgb()
11+
call s:assert.equals(s:C.parse('#abcdef').as_rgb_hex(), '#ABCDEF')
12+
call s:assert.equals(s:C.parse('#abcdef').as_rgb_str(), 'rgb('.0xAB.','.0xCD.','.0xEF.')')
13+
call s:assert.equals(s:C.parse('#012').as_rgb_hex(), '#001122')
14+
call s:assert.equals(s:C.parse('#012').as_rgb_str(), 'rgb('.0x00.','.0x11.','.0x22.')')
15+
call s:assert.equals(s:C.parse('rgb(123, 56, 78)').as_rgb_hex(), '#7B384E')
16+
call s:assert.equals(s:C.parse('rgb(123, 56, 78)').as_rgb_str(), 'rgb('.0x7B.','.0x38.','.0x4E.')')
17+
call s:assert.equals(s:C.rgb(255, 255, 255).as_rgb_hex(), '#FFFFFF')
18+
call s:assert.equals(s:C.rgb(255, 255, 255).as_rgb_str(), 'rgb('.0xFF.','.0xFF.','.0xFF.')')
19+
call s:assert.equals(s:C.rgb(255, 0, 0).as_rgb_hex(), '#FF0000')
20+
call s:assert.equals(s:C.rgb(255, 0, 0).as_rgb_str(), 'rgb('.0xFF.','.0x00.','.0x00.')')
21+
call s:assert.equals(s:C.rgb(0, 0, 0).as_rgb_hex(), '#000000')
22+
call s:assert.equals(s:C.rgb(0, 0, 0).as_rgb_str(), 'rgb('.0x00.','.0x00.','.0x00.')')
23+
endfunction
24+
25+
function! s:suite.hsl()
26+
call s:assert.equals(s:C.parse('hsl(210,68%,80%)').as_hsl_str(), 'hsl(210,68%,80%)')
27+
call s:assert.equals(s:C.parse('hsl(210,100%,7%)').as_hsl_str(), 'hsl(210,100%,7%)')
28+
call s:assert.equals(s:C.parse('hsl(340,37%,35%)').as_hsl_str(), 'hsl(340,37%,35%)')
29+
call s:assert.equals(s:C.hsl(0, 0, 100).as_hsl_str(), 'hsl(0,0%,100%)')
30+
call s:assert.equals(s:C.hsl(0, 100, 50).as_hsl_str(), 'hsl(0,100%,50%)')
31+
call s:assert.equals(s:C.hsl(0, 0, 0).as_hsl_str(), 'hsl(0,0%,0%)')
32+
endfunction
33+
34+
function! s:suite.eq() abort
35+
for [l, r] in [
36+
\ [s:C.parse('#abcdef'), s:C.parse('rgb('.0xAB.', '.0xCD.', '.0xEF.')')],
37+
\ [s:C.parse('#deadbe'), s:C.parse('rgb('.0xDE.','.0xAD.','.0xBE.')')],
38+
\ [s:C.hsl(210, 100, 7), s:C.parse('hsl(210,100%,7%)')],
39+
\ [s:C.hsl(123, 45, 67), s:C.parse('hsl(123, 45%, 67%)')],
40+
\]
41+
call s:assert.true(l.eq(r), l.as_rgb_hex() . ' eq ' . r.as_rgb_hex())
42+
call s:assert.true(r.eq(l), r.as_rgb_hex() . ' eq ' . l.as_rgb_hex())
43+
endfor
44+
endfunction
45+
46+
function! s:suite.diff() abort
47+
for [l, r] in [
48+
\ [s:C.parse('#abcdef'), s:C.parse('hsl(210, 68%, 80%)')],
49+
\ [s:C.parse('#012'), s:C.parse('hsl(210, 100%, 7%)')],
50+
\ [s:C.parse('#deadbe'), s:C.parse('rgb('.0xDE.','.0xAD.','.0xBE.')')],
51+
\]
52+
call s:assert.compare(l.diff(r), '<', 5, l.as_rgb_hex() . ' diff ' . r.as_rgb_hex())
53+
call s:assert.compare(r.diff(l), '<', 5, r.as_rgb_hex() . ' diff ' . l.as_rgb_hex())
54+
endfor
55+
endfunction

0 commit comments

Comments
 (0)