├── .gitignore ├── LICENSE ├── README.md ├── autoload ├── colortuner.vim └── colortuner │ ├── conv.vim │ └── ui.vim ├── plugin └── colortuner.vim └── screenshots └── screencast1.gif /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | 4 | doc/tags 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Zefei Xuan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vim-colortuner 2 | 3 | Colortuner is a simple utility to allow you tuning your color scheme using 4 | sliders. It can make any color scheme look great with very little effort. 5 | 6 | # Screenshots 7 | 8 | ![image](https://raw.githubusercontent.com/zefei/vim-colortuner/master/screenshots/screencast1.gif) 9 | 10 | # Installation 11 | 12 | Use your favorite package manager to install: 13 | 14 | * [Pathogen](https://github.com/tpope/vim-pathogen) 15 | * `git clone https://github.com/zefei/vim-colortuner 16 | ~/.vim/bundle/vim-colortuner` 17 | * [Vundle](https://github.com/gmarik/Vundle.vim) 18 | * `Plugin 'zefei/vim-colortuner'` 19 | * [NeoBundle](https://github.com/Shougo/neobundle.vim) 20 | * `NeoBundle 'zefei/vim-colortuner'` 21 | 22 | # Usage 23 | 24 | Once installed, you can type `:Colortuner` to open the tuner panel, then just 25 | use normal movement keys to adjust settings. Tuner settings are persisted to 26 | disk on a per-colorscheme basis. 27 | 28 | # Configuration 29 | 30 | g:colortuner_preferred_schemes 31 | values: list of strings 32 | default: [] 33 | example: ['zenburn', 'solarized', 'cake16'] 34 | Colortuner rotates among these colorschemes when you adjust the 35 | colorscheme option in tuner panel. If empty list (default) is set, 36 | colortuner will rotate among all installed colorschemes. 37 | 38 | g:colortuner_vivid_mode 39 | values: 0 or 1 40 | default: 0 41 | Colortuner by default tries to avoid over-saturation when tuning 42 | brightness or contrast. This might result in under-saturated colors. You 43 | can set this option to 1 to reverse the effect. 44 | 45 | g:colortuner_filepath 46 | values: string 47 | default: '~/.vim-colortuner' 48 | This option sets the file path of tuner panel settings. 49 | 50 | g:colortuner_enabled 51 | values: 0 or 1 52 | default: 1 53 | Colortuner is enabled at start if this option is set. 54 | 55 | # FAQ 56 | 57 | Q: Does colortuner support vim in terminal? 58 | 59 | A: Currently no, since true color support is essential for tuning colors. 60 | However, Neovim has support for true color terminals, and colortuner supports 61 | Neovim in the terminal. But notice that at the moment very few terminals work 62 | well with Neovim true color mode. 63 | 64 | Q: How can I save tuned colorscheme? 65 | 66 | A: You can press 'y' in the colortuner window to yank all color values to 67 | current register, then 'p'ut it wherever you want. 68 | 69 | # License 70 | 71 | MIT License. 72 | -------------------------------------------------------------------------------- /autoload/colortuner.vim: -------------------------------------------------------------------------------- 1 | function! colortuner#init() 2 | let s:colors = {} 3 | let s:enabled = g:colortuner_enabled 4 | let s:schemes = g:colortuner_preferred_schemes 5 | call colortuner#load() 6 | 7 | augroup colortuner_colorscheme 8 | autocmd! 9 | autocmd ColorScheme,VimEnter * call colortuner#on_colorscheme() 10 | autocmd VimEnter * call colortuner#get_all_colorschemes() 11 | autocmd BufEnter __colortuner__ call colortuner#ui#setup() 12 | autocmd VimLeave * call colortuner#save() 13 | augroup END 14 | endfunction 15 | 16 | function! colortuner#load() 17 | let file = expand(g:colortuner_filepath) 18 | let settings = filereadable(file) ? readfile(file) : [] 19 | if len(settings) == 1 20 | execute 'let s:settings = '.settings[0] 21 | else 22 | let s:settings = {} 23 | endif 24 | endfunction 25 | 26 | function! colortuner#save() 27 | call writefile([string(s:settings)], expand(g:colortuner_filepath)) 28 | endfunction 29 | 30 | function! colortuner#on_colorscheme() 31 | let name = s:get_colorscheme() 32 | let s:colors[name] = s:get_colors() 33 | if !has_key(s:settings, name) 34 | call colortuner#reset() 35 | else 36 | call s:render() 37 | endif 38 | endfunction 39 | 40 | function! colortuner#get_all_colorschemes() 41 | if s:schemes != [] 42 | return 43 | endif 44 | 45 | for fname in split(globpath(&runtimepath, 'colors/*.vim'), '\n') 46 | let name = fnamemodify(fname, ':t:r') 47 | let s:schemes += [name] 48 | endfor 49 | endfunction 50 | 51 | function! s:get_colorscheme() 52 | redir => name 53 | silent colorscheme 54 | redir END 55 | return substitute(name,'\_s*\(.\{-}\)\_s*$','\1','') 56 | endfunction 57 | 58 | function! colortuner#reset() 59 | let name = s:get_colorscheme() 60 | let s:settings[name] = {'inverted': 0, 61 | \'brightness': 0, 'contrast': 0, 'saturation': 0, 'hue': 0} 62 | call s:render() 63 | endfunction 64 | 65 | function! colortuner#get_setting() 66 | let name = s:get_colorscheme() 67 | return {'enabled': s:enabled, 'name': name, 'setting': s:settings[name]} 68 | endfunction 69 | 70 | function! colortuner#set(attr, delta) 71 | let s = s:settings[s:get_colorscheme()] 72 | if a:attr == 'enabled' 73 | let s:enabled = !s:enabled 74 | elseif a:attr == 'inverted' 75 | let s.inverted = !s.inverted 76 | else 77 | let step = {'brightness': 1, 'contrast': 1, 'saturation': 1, 'hue': 5} 78 | let m = {'brightness': -50, 'contrast': -50, 'saturation': -50, 'hue': -180} 79 | let M = {'brightness': 50, 'contrast': 50, 'saturation': 50, 'hue': 180} 80 | let s[a:attr] = s:clamp(s[a:attr] + a:delta * step[a:attr], m[a:attr], M[a:attr]) 81 | endif 82 | call s:render() 83 | endfunction 84 | 85 | function! colortuner#rotate_colorscheme(delta) 86 | let name = s:get_colorscheme() 87 | let i = index(s:schemes, name) 88 | let n = len(s:schemes) 89 | let i = (i + a:delta) % n 90 | let i += i < 0 ? n : 0 91 | execute 'colorscheme '.s:schemes[i] 92 | endfunction 93 | 94 | function! colortuner#yank() 95 | let @" = s:current_colors 96 | endfunction 97 | 98 | function! s:render() 99 | let name = s:get_colorscheme() 100 | let colors = s:colors[name] 101 | let s:current_colors = '' 102 | 103 | for [group, value] in items(colors) 104 | if empty(value) 105 | continue 106 | endif 107 | let cmd = 'highlight '.group 108 | for [key, color] in items(value) 109 | let c = s:apply(color, s:settings[name]) 110 | let cmd = cmd.' gui'.key.'='.colortuner#conv#rgb2hex(c) 111 | endfor 112 | let s:current_colors = s:current_colors.cmd."\n" 113 | execute cmd 114 | endfor 115 | endfunction 116 | 117 | function! s:apply(color, setting) 118 | if !s:enabled 119 | return a:color 120 | endif 121 | 122 | let color = copy(a:color) 123 | 124 | let color.h = (float2nr(color.h * 360) + a:setting.hue + 360) % 360 / 360.0 125 | let color.s = s:clamp(color.s + a:setting.saturation / 100.0, 0.0, 1.0) 126 | 127 | let c = a:setting.contrast 128 | let f = (80.0 + c) / (80.0 - c) 129 | let b = a:setting.brightness 130 | 131 | " only invert lightness, not hue, this is better than negative color 132 | if a:setting.inverted 133 | let color.l = 1 - color.l 134 | endif 135 | 136 | " vivid mode, default to 0, decides tuning methods with the chart below 137 | " vivid? | contrast/brightness value | tuning method 138 | " false | > 0 | tune on lightness 139 | " false | < 0 | tune on rgb separately 140 | " true | > 0 | tune on rgb separately 141 | " true | < 0 | tune on lightness 142 | let vivid = g:colortuner_vivid_mode 143 | 144 | if c > 0 && !vivid || c < 0 && vivid 145 | let color.l = f * (color.l - 0.5) + 0.5 146 | endif 147 | 148 | if b > 0 && !vivid || b < 0 && vivid 149 | let color.l = color.l + b / 100.0 150 | endif 151 | 152 | call colortuner#conv#hsl2rgb(color) 153 | 154 | if c < 0 && !vivid || c > 0 && vivid 155 | let color.r = float2nr(f * (color.r - 128) + 128) 156 | let color.g = float2nr(f * (color.g - 128) + 128) 157 | let color.b = float2nr(f * (color.b - 128) + 128) 158 | endif 159 | 160 | if b < 0 && !vivid || b > 0 && vivid 161 | let color.r = float2nr(color.r + b * 2.55) 162 | let color.g = float2nr(color.g + b * 2.55) 163 | let color.b = float2nr(color.b + b * 2.55) 164 | endif 165 | 166 | let color.r = s:clamp(color.r, 0, 255) 167 | let color.g = s:clamp(color.g, 0, 255) 168 | let color.b = s:clamp(color.b, 0, 255) 169 | 170 | return color 171 | endfunction 172 | 173 | function! s:clamp(number, m, M) 174 | if a:number < a:m 175 | return a:m 176 | elseif a:number > a:M 177 | return a:M 178 | else 179 | return a:number 180 | endif 181 | endfunction 182 | 183 | " get colors of all current highlight groups 184 | function! s:get_colors() 185 | let colors = {} 186 | 187 | " loop over all highlight groups 188 | let i = 1 189 | while synIDtrans(i) 190 | let group = synIDattr(i, "name") 191 | 192 | " skip linked or invalid groups 193 | if synIDtrans(i) == i && group != '' 194 | let colors[group] = {} 195 | for key in ['fg', 'bg'] 196 | let hex = synIDattr(i, key.'#', 'gui') 197 | if len(hex) == 7 && hex[0] == '#' && hex != '#000000' 198 | let colors[group][key] = colortuner#conv#rgb2hsl(colortuner#conv#hex2rgb(hex)) 199 | endif 200 | endfor 201 | endif 202 | 203 | let i += 1 204 | endwhile 205 | 206 | return colors 207 | endfunction 208 | -------------------------------------------------------------------------------- /autoload/colortuner/conv.vim: -------------------------------------------------------------------------------- 1 | " convert hex string to rgb 2 | function! colortuner#conv#hex2rgb(hex) 3 | let rgb = {} 4 | let rgb.r = str2nr(a:hex[1:2], 16) 5 | let rgb.g = str2nr(a:hex[3:4], 16) 6 | let rgb.b = str2nr(a:hex[5:6], 16) 7 | return rgb 8 | endfunction 9 | 10 | " convert rgb to hex string 11 | function! colortuner#conv#rgb2hex(rgb) 12 | return printf('#%02x%02x%02x', a:rgb.r, a:rgb.g, a:rgb.b) 13 | endfunction 14 | 15 | " convert rgb [0, 255] to hsl [0, 1] in place 16 | function! colortuner#conv#rgb2hsl(rgb) 17 | let r = a:rgb.r / 255.0 18 | let g = a:rgb.g / 255.0 19 | let b = a:rgb.b / 255.0 20 | let rgb = [a:rgb.r, a:rgb.g, a:rgb.b] 21 | let M = max(rgb) / 255.0 22 | let m = min(rgb) / 255.0 23 | let c = M - m 24 | let l = (M + m) / 2 25 | 26 | if c == 0 27 | let h = 0.0 28 | let s = 0.0 29 | else 30 | if M == r 31 | let h = (g - b) / c + (g < b ? 6 : 0) 32 | elseif M == g 33 | let h = (b - r) / c + 2 34 | else 35 | let h = (r - g) / c + 4 36 | endif 37 | let h = h / 6 38 | let s = l > 0.5 ? c / (2 - M - m) : c / (M + m) 39 | endif 40 | 41 | let a:rgb.h = h 42 | let a:rgb.s = s 43 | let a:rgb.l = l 44 | return a:rgb 45 | endfunction 46 | 47 | " convert hsl [0, 1] to rgb [0, 255] in place 48 | function! colortuner#conv#hsl2rgb(hsl) 49 | let h = a:hsl.h 50 | let s = a:hsl.s 51 | let l = a:hsl.l 52 | let r = l 53 | let g = l 54 | let b = l 55 | 56 | if s != 0 57 | let q = l < 0.5 ? l * (1 + s) : l + s - l * s 58 | let p = 2 * l - q 59 | let r = s:hue2rgb(p, q, h + 1.0/3) 60 | let g = s:hue2rgb(p, q, h) 61 | let b = s:hue2rgb(p, q, h - 1.0/3) 62 | endif 63 | 64 | let a:hsl.r = float2nr(r * 255) 65 | let a:hsl.g = float2nr(g * 255) 66 | let a:hsl.b = float2nr(b * 255) 67 | return a:hsl 68 | endfunction 69 | 70 | " helper function for hsl2rgb 71 | function! s:hue2rgb(p, q, t) 72 | let t = a:t 73 | let t += t < 0 ? 1 : 0 74 | let t -= t > 1 ? 1 : 0 75 | if t < 1.0/6 76 | return a:p + (a:q - a:p) * 6 * t 77 | elseif t < 0.5 78 | return a:q 79 | elseif t < 2.0/3 80 | return a:p + (a:q - a:p) * (2.0/3 - t) * 6 81 | else 82 | return a:p 83 | endif 84 | endfunction 85 | -------------------------------------------------------------------------------- /autoload/colortuner/ui.vim: -------------------------------------------------------------------------------- 1 | function! colortuner#ui#open() 2 | let buffer = bufnr('__colortuner__') 3 | 4 | if buffer == -1 5 | execute 'botright new __colortuner__' 6 | else 7 | let window = bufwinnr(buffer) 8 | 9 | if window == -1 10 | execute 'botright split +buffer'.buffer 11 | else 12 | execute window.'wincmd w' 13 | endif 14 | endif 15 | 16 | call s:render() 17 | execute 3 18 | endfunction 19 | 20 | function! s:render() 21 | setlocal modifiable 22 | silent! execute '1,$ delete _' 23 | let setting = colortuner#get_setting() 24 | let s = setting.setting 25 | call append(0, [ 26 | \' Colorscheme '.setting.name, 27 | \'', 28 | \' Brightness '.s:make_slider(s.brightness, -50, 50), 29 | \' Contrast '.s:make_slider(s.contrast, -50, 50), 30 | \' Saturation '.s:make_slider(s.saturation, -50, 50), 31 | \' Hue '.s:make_slider(s.hue, -180, 180)."\u00b0", 32 | \'', 33 | \' Enabled '.(setting.enabled ? 'Yes' : 'No'), 34 | \' Inverted '.(s.inverted ? 'Yes' : 'No')]) 35 | setlocal nomodifiable 36 | endfunction 37 | 38 | function! s:make_slider(value, m, M) 39 | let width = 31 40 | let n = float2nr(1.0 * (a:value - a:m) / (a:M - a:m) * width) 41 | return repeat('#', n).repeat('-', width - n).' '.(a:value > 0 ? '+' : '').a:value 42 | endfunction 43 | 44 | function! s:tune(delta) 45 | let l = line('.') 46 | 47 | if l == 1 48 | call colortuner#rotate_colorscheme(a:delta) 49 | elseif l == 3 50 | call colortuner#set('brightness', a:delta) 51 | elseif l == 4 52 | call colortuner#set('contrast', a:delta) 53 | elseif l == 5 54 | call colortuner#set('saturation', a:delta) 55 | elseif l == 6 56 | call colortuner#set('hue', a:delta) 57 | elseif l == 8 58 | call colortuner#set('enabled', a:delta) 59 | elseif l == 9 60 | call colortuner#set('inverted', a:delta) 61 | endif 62 | 63 | call s:render() 64 | execute l 65 | endfunction 66 | 67 | function! s:reset() 68 | let l = line('.') 69 | call colortuner#reset() 70 | call s:render() 71 | execute l 72 | endfunction 73 | 74 | " setup colortuner buffer window 75 | function! colortuner#ui#setup() 76 | setlocal buftype=nofile 77 | setlocal bufhidden=hide 78 | setlocal noswapfile 79 | setlocal nobuflisted 80 | setlocal nomodifiable 81 | setlocal filetype=colortuner 82 | setlocal nolist 83 | setlocal nonumber 84 | setlocal norelativenumber 85 | setlocal nowrap 86 | setlocal colorcolumn= 87 | setlocal cursorline 88 | let &l:statusline = ' colortuner | h/l/-/+/b/w: adjust | r: reset | y: yank | q: quit' 89 | execute '10 wincmd _' 90 | 91 | nnoremap