For a longer time I was thinking about working on library like yours, so I have lots of ideas you may be interested in. Colours in practice are very complicated as they have lots of models and variants which is not obvious for everyone …
integer_0_to_255
in many languages it’s called unsigned (bits info) integer
i.e. unsigned 8bit integer
or u8
in for shorter versions.
At least in CSS
(just tried in inspector) Space
before ,
character is valid. Also Firefox
removes whitespaces around CSS
values. The alpha channel could be specified also using integer and percentage, so for example this value should be valid: rgba( 255 , 0 , 0 , 50% )
.
I would recommend to trim leading whitespaces, trim trailing ~r/\s*;?\s*/
regex and split values by ~r/\s*,\s*/
regex. You could use also nimble_parse
for this if you don’t like to work with regular expressions.
rgb_percents
may be seen as confusing when it returns float
. I understand why it’s useful, but by x%
I understand 43
percent and not 0.43
. Since you have a limited number of cases you can call color_to_percentage/1
and generate it on compile time, so it would be faster with pattern-matching especially that you have behaviour for min
and max
values - you can define them in module attribute at compile time.
The same compile-time optimisation could be applied for 0 < value < 1
as in most cases people expects up to 2 and sometimes 3 digits after 0. The rest could be done at runtime as there is no sense to work with 0.232151521421412
at compile-time. Maybe you could consider pattern-matching
+ ceil/floor/round
, so you would not have any math logic in runtime except said Float.floor(float, 3)
- you would have to confirm that in benchmarks, but I would not be surprised if that would be faster.
–
Nice one with Colorex.ANSI
, but it’s definitely too long. I would expect something like ~a"#f00"
or ~ANSI"#f00"
and ~ANSI_BG"f00"
instead, so for example:
IO.puts(Colorex.ansi_background(Colorex.parse!("#1188FF")) <> "Blue background" <> IO.ANSI.reset())
would become:
import Colorex.ANSI
IO.puts([~ANSI_BG"#1188FF", "Blue background", IO.ANSI.reset()])
Looks like you have duplicated typespec from Colorex
in Colorex.Color
and Colorex.Utils
It would be nice to link some references in documentation like for a standard page (W3C for example) and MDN
where developers may find more information about the color model and color palettes.
Here is a CSV
file I have created some time ago. Hope it would be helpful for you …
name;alpha;superset variant;other variants
CMYK;-;16bit per channel (0-65535);8bit per channel (0-255)
HSL;HSLA;hue 0 to 360 degrees|saturation 0 to 100 percent|lightness 0 to 100 percent;-
HSV/HSB;HSVA/HSBA;hue 0 to 360 degrees|saturation 0 to 100 percent|value 0 to 100 percent;-
HWB;HWBa;hue 0 to 360 degrees|whiteness 0 to 100 percent|blackness 0 to 100 percent;-
LAB;LABa;lightness 0 to 100|a negative128 to positive127|b negative128 to positive127;-
LCH;LCHa;lightness 0 to 100|chroma 0 to 230|hue 0 to 360 degrees;-
Linear RGB;Linear RGBA;32bit float per channel;16bit per channel (0-65535)
RGB;RGBA;32bit float per channel;8bit per channel (0-255),16bit per channel (0-65535)
sRGB;sRGBA;16bit per channel (0-65535);8bit per channel (0-255)
Adobe RGB;Adobe RGBA;16bit per channel (0-65535);8bit per channel (0-255)
P3;P3A;16bit per channel (0-65535);-
ProPhoto RGB;ProPhoto RGBA;16bit per channel (0-65535);-
XYZ-D50;-;32bit float CIE XYZ with D50 white point;-
XYZ-D65;-;32bit float CIE XYZ with D65 white point;-
XYZ-D50
and XYZ-D65
requires chromatic adaptation transform and are usually converted through Bradford
or CAT02
adaptation matrices as far as I know.
Also different RGB working spaces in many cases require conversion through XYZ
space because they use different primaries
and white points
In Colorex.Palette.get_palettes/2
it’s not clear when you return 4 or 5 colors in list. From what I can see in your code it could generate from 3 to 5 colours in each list and it’s because of predefined palettes defined in priv
directory. You should document why it’s happening or maybe (not sure) change/update said file.
You support only 4 colour palettes. When I was doing research I have found 8 and maybe there are more types/variants of them … The ones I’m aware of are: monochromatic
, analogous
, complementary
, split-complementary
, double-complementary
, triadic
, tetradic
and hexadic
.
Looks like you made a naming change as Colorex.Support.Guards
is in utils
directory. Anyway, it would be nice to make it public to make others write functions to manipulate on colours much easier.
You can add more features like accents
, shades
and tints
. Looks like you made already some progress here with darken
and lighten
functions, but it would be even more amazing to make it easier for others by adding functions that would return for example n shades
. In options you can accept min
and max
changes between original colour and every next shade
.
Often developers work on colours from configuration or user input and use it to generate themes based on palette-related functions. Here a useful feature is to detect if the colour is dark and generate it’s light version, so for example:
iex> Colorex.light_dark("#000")
{Colorex.parse!("#fff"), Colorex.parse("#000")}
In other cases you may want to have a passed colour as a references and depending on ligh
or dark
theme generate n
number of shades
and tints
between 0%
and 50%
or 50%
and 100%
of light.
Consider below example:
# colour could have any % of light, so we start with 50%
iex> {tints, shades} = Colorex.lighten_darken(colour, 4)
{
# 10%, 20%, 30% and 40% of light
[Colorex.parse!(…), Colorex.parse!(…), Colorex.parse!(…), Colorex.parse!(…)],
# 60%, 70%, 80% and 90% of light
[Colorex.parse!(…), Colorex.parse!(…), Colorex.parse!(…), Colorex.parse!(…)],
}
{colours, background_colours} = if MyApp.Account.prefers_dark_theme?(current_user) do
{tints, shades}
else
{shades, tints}
end
Of course this is a simple example, but we could pass options with for example a min_step
(of light) or max_range
(of light) and therefore generate 50% - max_range / min_step
number of tints and shades. As you can see there are lots of possible features to simplify many cases.
Of course you may say that some feature should not be a part of your package - just giving a possible features following Elixir
’s 10x less code rule. I believe that making as much reasonable features in colorex
may help a lot of people.
The number of named colours supported is impressing. What you can do is something like:
parse(named_colour) when named_colour in @named_colours, do: # …
# RGB and others …
parse(named_colour) do
named_colour
|> Macro.underscore()
|> parse()
end
This way said configurable colour would be more human-friendly. Please keep in mind end-users often are not aware or just don’t care about format and the developer is supposed to handle any kind of input, so this way or another we have to support all cases.
You may also consider to add many effects like: blur
, brightness
, contrast
, grayscale
, hue rotation
, invert
, opacity
, saturate
and sepia
, for example:
iex> Colorex.brightness(colour, 0)
Colorex.parse!("#000")
Hope those suggestions are helpful. Good luck with colorex
.