Adjust current colorscheme by applying various color filters.
Warning
This plugin is experimental. It saves a copy of the current colorscheme which will later be used as a base for applying color changing filters. Some inconsistencies and weird color behaviour might occur. It was not tested with other color modifying plugins.
Note
I probably have near zero color theory knowledge and I am positively sure that there are plenty of inaccuracies in the implementation of builtin filters. Filter implementations and level ranges are subjected to change. Your contribution is more than welcomed to tackle these inaccuracies.
perception2.webm
- Fine-tune colorscheme using one or more builtin filters, or easily implement
your own:
- Color levels.
- Brightness.
- Contrast.
- Dimness.
- Hue.
- Saturation.
- Sepia tone.
- Luminance.
- Color temperature.
- Tint.
- Color inversion.
- Grayscale.
- Configurable priority to customize order of appliance.
- Export a new Lua colorscheme from the modified colors without ties to this plugin.
{
"moyiz/perception.nvim",
lazy = true,
cmd = { "PerceptionUI" },
opts = {},
}
See config.lua.
Color adjustments can be applied by either using the builtin UI, or by calling
require("perception").set(filter_name, value)
. A list of enabled filters can
be obtained with require("perception").list_filters()
.
- API:
require("pereption").ui:toggle()
- Command:
:PerceptionUI
A floating window (by default) containing a list of filter sliders, sorted by
order of appliance. Navigate to the line of a filter to change (<Down>
/ <Up>
/ j
/ k
/ /
/ t
/ f
/ other navigation keys) and:
- Press
h
/<Left>
to reduce the current value by a per-filter pre-configured step. Prefixing the movement with a number will override the default step. - Press
l
/<Right>
to increase the current value by a per-filter pre-configured step. Prefixing the movement with a number will override the default step. - Press
<NUM>=
to set current value to<NUM>
. Omitting<NUM>
will set the value to 0. - Press
<NUM>-
to set current value to-<NUM>
. Omitting<NUM>
will set the value to 0. - Press
o
to export current highlight groups as a new colorscheme. - Press
q
/<Esc>
to close the UI window.
- API:
require("perception").export_colorscheme(name)
Once you are satisfied with the color adjustments, it can be used to generate
Generates a lua module in the default colors directory
(~/.config/nvim/colors/<name>.lua
), containing a bit of boilerplate and
a nvim_set_hl
call for each highlight group. The generated module is
a standalone colorscheme (independent of perception.nvim
) and can be loaded as
any other colorscheme.
The method accepts the following parameters:
name
- The name of the colorscheme, will be set to the lua module name.colors_dir
- Optional. Override path to colors directory.background
- Optional. Overridebackground
options. Will be set to"dark"
by default.termguicolors
- Optional. Whether to enable 24-bit RGB (default: true).
This plugin support adding custom color filters via its config.
See config.lua. base
contains the default
values for each filter configuration.
Enabled filters are sorted by priority, and each will have its own slider rendered in the UI buffer.
The actual implementation of the filter is defined in apply
key of its
configuration.
It receives a perception.Color
object and a number value
guaranteed to
reside in the configured level range. It returns a (possibly) modified
perception.Color
object.
Note
The filter should not modify any color when value
is its default
value
(tunable, defaults to 0
).
See color.lua for perception.Color
object.
See builtins.lua for filter function examples.
See config.lua for the default filters configuration.
Some imaginary example.
Let's define a RGB Right Rotator as a filter that gradually rotates the RGB
values of a color to the right. Example: #012345
will become #450123
when
fully rotated. The filter accepts a color and a range of [0, 100]
that will
indicate the percentage of color rotation,
{
rgb_rotator = {
level = {
min = 0,
max = 100,
step = 10
},
priority = 31,
apply = function(color, value)
local u = require "perception.utils"
local r, g, b = unpack(color:as_rgb())
local rd = r * value / 100
local gd = g * value / 100
local bd = b * value / 100
return color:from_rgb(
u.truncate(u.round(r - rd + bd)),
u.truncate(u.round(g - gd + rd)),
u.truncate(u.round(b - bd + gd))
)
end,
},
}
level
- Sets the bounds of input value, and a default step size.priority
- A value of31
places the filter just before the saturation filters.apply
- Sincevalue
is "percentage", we first calculate the relative portion of each color component. Then, reduce it from the color itself and re-add it to the color component on its right.
Tip: Consider increasing { ui = { floating_win_config = { height = N } } }
to
match the number of enabled filters. Maybe it should be the default behaviour.
This plugin modifies highlight groups in the global namespace, rather than using a dedicated namespace. It holds the original highlight groups in a local variable, then uses each color as an initial value of the filter composition. This approach solves few pain points of the first implementation that used a dedicated namespace for the modified highlight groups:
- Window specific namespaces. Some plugins manage their own namespace for
highlight groups (e.g.
which-key.nvim
,telescope
), which usually depend on highlight groups from the global namespace (linked highlight groups). - "Cleared" highlight groups will revert to global namespace colors (more common
with "no color" variations e.g.
NormalNC
,StatusLineNC
).
export_colorscheme
currently dumps ALL highlight groups. The generated colorscheme is probably bigger than it has to be and depends on the number of highlight groups defined in the base colorscheme and loaded plugins (for me: ~2k LOC, ~140KB). With that in mind, exported colorschemes would miss highlight groups that were added to your configuration after their generation.- Filters process each color individually and are not aware of the specific highlight group.
- Applying filters more than once (e.g. changing hue, applying inversion and
luminance, then changing hue again). Workaround, add a new filter with
different priority that has the same
apply
method.
- Instead of persisting changes by generating a colorscheme, support exporting filter states as perception profiles that can be loaded upon any colorscheme.
- Improve
export_colorscheme
per the points stated in Limitations section. - Improve UI rendering (flickers when arrows are held).
- Highlight
PercetpionUI
sliders (in a dedicated namespace). - Save & restore filter profiles and states.
- Tests.
See License.