Closed ijlyttle closed 4 years ago
Functions to:
create control points using HCL coordinates.
given a set of (LUV) control-points, return a function that, given a number between 0 and 1, returns an HCL color. This function is a path function.
given a path function, return another path function that scales the input such that the function is perceptually uniform.
given a path function and a luminance, return an HCL color.
helper to convert HCL color to hex (colorspace assuredly has this).
given a path function and a begin and endpoint of luminances, return a path function that operates on this luminance range. In other words, clip a part out of a path.
given two path functions, return a path function (joining two sequential scales to form a diverging scale).
The big mistake I made in {paleval} was to use hex-code based colors as the internal representation of a color. A hex-code does not have enough precision to form meaningful derivatives or to show meaningful splines through LUV or HCL space.
I think it would be better to use a colorspace-type color class that uses double precision for each dimension.
cc @haleyjeppson
Given my newly-shaken faith in the "numbers", perhaps we need not rescale the input.
We could ask for points on the spline to be equidistant in LUV space, and rely on that to be "close enough".
In summary:
Thinking in code-sketches now:
I think this can be done entirely using {farver} and {bezier}, which is kind of a shame because the ideas are so strongly inspired by {colorspace}.
To specify using HCL:
# something like
x <- matrix(
c(h1, c1, l1, h2, c2, l2, ...),
nrow = ??,
ncol = 3,
byrow = TRUE,
dimnames = list(NULL, c("h", "c", "l")
)
# it might be easier to convert from a data.frame...
To convert to LUV
farver::convert_colour(x, from = "hcl", to = "luv")
I'll need to check to see if {farver} cares about matrix-column names. At this point, we would have a matrix on which we could make a Bézier spline, using {bezier}.
library("bezier")
## EVENLY_SPACED METHOD ##
## BEZIER CURVE CONTROL POINTS
p <- matrix(c(3,2, 3,0, 5,5), nrow=3, ncol=2, byrow=TRUE)
pob <- pointsOnBezier(p=p, n=10, method="evenly_spaced") # takes a few seconds
str(pob)
List of 3
$ points: num [1:10, 1:2] 3 3.07 3.46 3.73 3.97 ...
$ error : num [1:10] 0.00 8.37e-10 8.47e-09 2.89e-08 4.13e-08 ...
$ t : num [1:10] 0 0.193 0.48 0.605 0.696 ...
Our splined points would be in a matrix using LUV, so it remains to convert them back to hex:
farver::encode_colour(x, from = "luv")
I claim that a function with S3 class cpath_palette
:
x
(vector, each member between 0 and 1)"l", "u", "v"
)I claim that a function with S3 class cpath_rescaler
:
x
(vector, each member between 0 and 1)y
), each member between 0 and 1x
and y
With this we can consider set of functions:
# given a data frame of `h`, `c`, `l` values
luv <- function(df_hcl) # returns matrix of luv values
# given matrix of luv values
palette_bezier <- function(luv) # returns cpath_palette
rescaler_bezier <- function(luv) # returns cpath_rescaler
# linear input-rescaler, maps c(0, 1) to `range`
rescaler_linear_input <- function(range) # returns cpath_rescaler
# linear luminance-rescaler, maps c(0, 1) to luminance `range` for `palette`
rescaler_linear_luminance <- function(range, palette) # returns cpath_rescaler
# rescale the input to a palette
rescale_palette <- function(palette, rescaler) # returns cpath_palette
# join two palettes
join_palettes <- function(palette_low, palette_high) # returns cpath_palette
We might also want a a function that takes a cpath_palette
and returns a function that returns hexcodes:
palette_hex <- function(palette) # returns palette-function that returns hex-codes
This will get us off the ground, but we might also consider things like derivatives.
We might consider a helper for diverging palettes, as this is a combination of rescaling two palettes, then joining them.
Maybe also a plot()
method for cpath_palette
, which could build on the colorspace plot.
Want to "simplify" things:
be explicit about coercion functions:
luv()
-> as_mat_luv()
palette_hex()
-> as_pal_hex()
palette
argument to pal_luv
as_pal_disc()
this suggests to change palette_bezier()
:
pal_luv_bezier()
rescale_path = TRUE
rename rescaler_*()
:
rescaler_bezier()
-> rescaler_bezier_path()
, make internalrescaler_linear_input()
-> rescaler_x()
rescaler_linear_luminance()
-> rescaler_luminance()
rename rescale_palette()
to pal_luv_rescale()
rename argument luv
-> mat_luv
rename s3 class cpath_palette_luv
-> cpath_pal_luv
this is largely implemented.
A color-path can be thought-of as a special case of a sequential palette:
All paths in a family will have the same luminance for start and end points. This way, parts of different paths can be joined to create a diverging palette.