GithubHelp home page GithubHelp logo

colorpath's People

Contributors

ijlyttle avatar

Watchers

 avatar  avatar  avatar

colorpath's Issues

improve surface plots

  • use a second y-axis for hue
  • function to get name of color-space given, color, surface, etc., use this in the title.

API

A color-path can be thought-of as a special case of a sequential palette:

  • specified with control points using HCL coordinates.
  • start and end points have zero chroma.
  • HCL coordinated interpolated using a Bézier spline.
  • input is rescaled to aim for perceptual uniformity (optionally).

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.

luminance rescaler

Change the names and the arguments to make a smoother introduction to a luminance 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

I think it's a bit clunky to have to specify a palette twice to rescale it; provides an opportunity to make a mistake.

In the future, consider a wrapper function.

rethink the approach

I think we can remove consideration of the LUV space - we were using it for its Euclidean distance metric. If we calculate distance using farver::compare_colour(), perhaps we can rescale the input so we are perceputally uniform according to CIE2000.

As well, I think this would solve #25 (inconsistent hue) and #33 (chroma floor), as the root of both of these problems is translation to/from LUV space.

plots: limit defaults

plots should always include the entire luminance domain, as well as the limit where chroma is zero

pal_luv constructor

when building an LUV palette, keep control-points as attribute

accessor function spec_luv() to get the control points

re-implement `pth_surface`

fn_hue() becomes the main attraction; fn_max_chroma() and colors become attributes.

  structure(
    fn_hue,
    class = ("pth_surface"),
    fn_max_chroma = fn_max_chroma,
    colors = colors
  )

add new distance metrics

with colorio, we have access to a bunch of color spaces, including CAM16-UCS and Jzazbz, with the hope of "better" distance measurements.

The base color space for colorio is xyz100 - and we have a way to translate to it using farver::decode_colour()

Currently, the internal function .get_distance() has a signature:

.get_distance <- function(pal_luv, n, method, ...)

Here, method refers to farver::compare_colour(); we use a default value of "cie2000".

What if our method argument used a syntax like "farver::cie2000", and could also take a value like "colorio::CAM16-UCS"?

use colorio 0.7.3

the color spaces have moved, e.g.: colorio$CIELUV -> colorio$cs$CIELUV

also the conversion to srgb has been renamed: colorio$SrgbLinear()$to_srgb255() -> colorio$cs$SrgbLinear()$to_rgb255()

palette_hex()

Our palette-functions work in LUV space, not in hex-code space.

palette_hex <- function(palette) # returns palette-function that returns hex-codes

modify max-chroma calculation

finding out for Jzazbz, that out-of-gamut may also produce an error - we can use a try() block and return a high out-of-gamut value rather than throwing an error.

chroma floor

Converting from an HCL data frame, as_mat_luv() has a default chroma_min of 0.01. This is done so that we can preserve the hue information when the chroma is zero.

In practice, we this "gap" will have no perceptual difference.

In theory, we should be able to set the floor much lower, e.g. 1.e-6. There seems to be some rounding errors introduced by the farver color-conversion functions. Given that HCL <-> LUV is a polar transformation, we should be able to do this ourselves without risk.

rescaler functions

Function with S3 class cpath_rescaler:

  • takes a numerical input x (vector, each member between 0 and 1)
  • returns a rescaled numerical vector (call it y), each member between 0 and 1
  • there shall be a monotonic relationship between x and y
# maps (0, 1) according to equally-spaced points on Bézier spline
rescaler_bezier <- function(luv) # returns cpath_rescaler
# maps (0, 1) to (x0, x1)
rescaler_linear <- function(x0, x1) # returns cpath_rescaler

add distance rescaler

A rescaler based on perceptual distance:

rescaler_dist <- function(pal_luv, tolerance = 1.e-4, method = "cie2000", ...)

shortcuts to rescale palettes

To rescale a palette, you need also to specify a rescaler function - this feels a bit inefficient.

New functions:

pal_luv_rescale_x <- function(pal_luv, range)
pal_luv_rescale_lum <- function(pal_luv, range)

more max-chroma functions

pth_mat_max_chroma(x, ...) {
  # returns matrix with max-chroma for surface, palette, colors
}

Thinking to implement initially for a surface, needs an n_step argument.

Will also be useful to implement rbind() for pth_mat - can bind only if using the same colorspace.

surfaces, trajectories, palettes

now the fun begins, again:

  • a palette refers to a continuous-palette function that, for every input (0 <= x <= 1), returns a color.

  • a hue surface refers to a function that, for each input luminance (0 <= lum <= 100), returns a hue (0 <= hue < 360).

  • a chroma trajectory refers to a function that, for each input (0 <= x <= 1), returns a set of luminance and chroma; it is implemented using a Bézier curve.

We can construct a palette by projecting a chroma trajectory onto a hue surface.

plot method

Something that could build on colorspace::hclplot()

create luv()

I will rarely create issues with titles better than this one. 🎶 What the world needs now...

We want a convenient way to create a matrix of luv values.

#' @param df_hcl `data.frame` with variables `h`, `c`, `l`
luv <- function(df_hcl)

defining hue as function of luminance

One thing coming into focus for me is that continuous palettes are driven by luminance.

Let's say that we can define a surface in HCL space as the collection of points where

hue <- function(lum) {

  h <- hue_0 + (lum / 100) (hue_1 - hue_0) 

  h %% 360
}

Let's postulate that a family of palettes, lets say "blues", can be defined as being drawn along such a surface defined using hue_0 and hue_1.

To specify a palette function, we need would only specify control points using luminance and chroma.

To get the entire set of control points, we would need to calculate the hue, given the luminance, for the family.

polar plots and data

Need to work in CVD

# returns pth_mat
pth_mat_gamut <- function(n_point = 5, transformer = pth_to_cieluv)

# returns data frame with columns cvd (factor), severity (factor)
pth_cvd_grid <- function(cvd = c("none", "deutan", "protan", "tritan"), severity = 1)
pth_cvd_grid_full <- function(cvd = c("deutan", "protan", "tritan"), severity = c(0, 0.5, 1))

# returns data frame with luminance, chroma, hue, hex, cvd, severity 
pth_data_cvd <- function(x, cvd = pth_cvd_grid(), ...)
pth_data_cvd.pth_mat <- function(x, cvd = pth_cvd_grid(), ...)
pth_data_cvd.character <- function(x, cvd = pth_cvd_grid(), transformer = pth_to_cieluv, ...)
pth_data_cvd.pth_palette <- function(x, cvd = pth_cvd_grid(), n_point = 11, ...)
  • pth_mat_gamut()
  • pth_cvd_grid()
  • pth_cvd_grid_full()
  • pth_data_cvd()
  • pth_plot_polar()
# returns ggplot
pth_plot_polar <- function(x, ...)
pth_plot_polar.tbl_df <- function(x, ...)
pth_plot_polar.pth_mat <- function(x, cvd = pth_cvd_grid_none(), ...)
pth_plot_polar.character <- function(x, cvd = pth_cvd_grid_none(), transformer = pth_to_cieluv, ...)
pth_plot_polar.pth_palette <- function(x, cvd = pth_cvd_grid_none(), n_point = 11, ...)

Thinking not to do the last one since it would not offer much, if anything, beyond geom_point()

# returns geom_point
pth_layer_polar <- function(x, ...)
pth_layer_polar.tbl_df <- function(x, ...)
pth_layer_polar.pth_palette <- function(x, n_point = 11, ...)

visualization

# these return data frames

# if there is more than one set of attributes, it uses negative chroma for the first set
# this will make plotting more straight-forward

# lum, chroma, hue, hex
pth_data_surface_raster <- function(sfc)

# lum, chroma, hue
pth_data_control_points <- function(traj)

# lum, chroma, hue, hex
# use variation in luminance to set chroma?
pth_data_palette <- function(palette, n)

# lum, chroma, hue, hex
# use variation in luminance to set chroma?
pth_data_max_chroma <- function(palette, n)
pth_plot_palette <- function(palette, ...) # methods for pth_palette_path, pth_palette_hex
pth_plot_surface <- function(x, ...) # methods for pth_surface, pth_palette_path, pth_data_surface

pth_layer_control_points <- function(x, ...) # methods for pth_palette_path, pth_data_control_points
pth_layer_max_chroma <- function(x, ...) # methods for pth_data_max_chroma

# need layers for individual colors, palette paths

incorporate colorio

This is also a re-imagining of the API 😬

I think that we want to focus on a "class" of color spaces that I'll call Lab-100 class. These color spaces have:

  • dimensions for luminance, blue-yellow, green-red
  • difference between black and white is 100
  • a polar coordinate system is well founded, using chroma and hue (expressed in degrees)

Examples of these colorspaces are:

  • CIELAB
  • CIELUV
  • CAM02-UCS
  • CAM16-UCS
  • JzAzBz-100 (JzAzBz, normalized to 100)
#' @param mat, `matrix` 
#' @param hex, `character` with S3 class `pth_hex`, representing a six-character hex code

To convert between different expressions of color:

#' @param color object with class `pth_hex`, `pth_mat`
# @param ... additional parameters needed to define a color space
#'  if `NULL`, will look for `color_space` attribute on `color`

pth_to_cielab <- function(color, ...) #  returns `matrix` with S3 classes `pth_mat`, `pth_cielab`
pth_to_cieluv <- function(color, ...) #  returns `matrix` with S3 classes `pth_mat`, `pth_cieluv`
pth_to_cam02ucs <- function(color, ...) #  returns `matrix` with S3 classes `pth_mat`, `pth_cam02ucs`
pth_to_cam16ucs <- function(color, ...) #  returns `matrix` with S3 classes `pth_mat`, `pth_cam16ucs`
pth_to_jzazbz100 <- function(color, ...) #  returns `matrix` with S3 classes `pth_mat`, `pth_jzazbz100`
pth_to_hex <- function(color) # returns `character` with S3 class `pth_hex`

To create a structure:

#' These functions put classes onto existing structures and values.
pth_new_cielab <- function(mat, ...) #  returns `matrix` with S3 classes `pth_mat`, `pth_cielab`
pth_new_cieluv <- function(mat, ...) #  returns `matrix` with S3 classes `pth_mat`, `pth_cieluv`
pth_new_cam02ucs <- function(mat, ...) #  returns `matrix` with S3 classes `pth_mat`, `pth_cam02ucs`
pth_new_cam16ucs <- function(mat, ...) #  returns `matrix` with S3 classes `pth_mat`, `pth_cam16ucs`
pth_new_jzazbz100 <- function(mat, ...) #  returns `matrix` with S3 classes `pth_mat`, `pth_jzazbz100`
pth_new_hex <- function(hex)  # returns `character` with S3 class `pth_hex`, representing a six-character hex code

Will also need helpers:

pth_to_cartesian <- function(mat, chroma_min = 1.e-4) # returns `matrix`
pth_to_polar <- function(mat) # returns `matrix`

Let's think a bit about the S3 classes:

pth_mat: has three columns representing luminance, blue-yellow, green-red
pth_cieluv: pth_mat that uses CIELUV color space,
...
pth_hex: character representing a six-character hex code

function to calculate perceptual distance

Given a palette function, a number of intervals, and a name of a method, return a vector (length n) of perceptual distances.

get_distance <- function(pal_luv, n = 20, method = "cie2000")

palette_bezier()

This will return a function with S3 class cpath_palette:

  • takes a numerical input x (vector, each member between 0 and 1)
  • returns a matrix (columns: "l", "u", "v")
# given matrix of luv values
palette_bezier <- function(luv) # returns cpath_palette

distance and metric calculations

distance is a Euclidean-like distance measured in a Cartesian color space.

metric is a measurement, e.g. "cie2000", non-Euclidean, triangle inequality does not apply.

#' @param color_a, color_b object with S3 class `character`, `pth_hex` or `pth_mat`.
#' @param transformer `function` that returns a `pth_mat`.
#' @param non_luminance_weight `numeric` non-negative number used to weight the non-luminance dimensions
#'  in the distance calculation
#' @param metric `character` indicates which distance metric to use (from the `farver` package).
#'
#' @return `double` perceptual distance
#' 
pth_distance <- function(color_a, color_b = NULL, transformer = NULL, non_luminance_weight = 1)
pth_metric <- function(color_a, color_b = NULL, metric = c("cie2000", "cie94", "cie1976", "cmc"))

Colors used to measure:

  • if color_b is NULL, we get the distances between consecutive rows of color_a.
  • if color_b has length 1, it is repeated to have the length of color_a.
  • otherwise, color_b must have the same length as color_a, in which case a pairwise comparison is made.

A transformer is a function used to convert a color to a given color space, e.g. pth_to_cieluv:

  • if transformer is specified, the distance calculation is made using that color space.
  • if transformer is NULL:
    • if color_a and color_b are both hex-codes, we use pth_to_cieluv.
    • if color_a and color_b both use the same color space, we use identity.
    • if color_a and color_b use different color spaces, we throw an error.

The non_luminance_weight is an experiment, to be determined if it can be useful or not. It is used to scale the non-luminance dimensions of the colors:

  • if non_luminance_weight is 1, the standard Euclidean distance is returned.
  • if non_luminance_weight is 0, the distance is the change in luminance only.

maximum chroma

We will need to determine, for a given color space, the extent of the RGB gamut.

# returns `logical`
pth_in_gamut <- function(color) 

# returns `double`, max chroma given luminance and hue, in provided color space
pth_max_chroma <- function(mat) 

# returns object of same type, with out-of-gamut colors replaced with max-chroma colors
pth_clip_chroma <- function(color) 

We could also use an internal function:

# returns `double`, negative means inside gamut, positive means outside
x_gamut <- function(color)

add pth_creator()

This would be an S3 generic that would act similarly to pth_transformer(), in that it would return a function that could create a pth_mat using a given color space.

I think this would be cleaner than the current pth_mat_replace_data(); I would look to deprecate that...

Distance functions

We need a set of functions to calculate perceptual distances:

# returns double
pth_distance_euclid(color_a, color_b, transformer, non_luminance_weight, ...)
pth_distance_metric(color_a, color_b, method, ...)

# returns data frame with cols: 
#   - hex_original_a
#   - hex_original_b
#   - cvd
#   - severity
#   - hex_a
#   - hex_b
#   - distance
#
pth_data_cat_euclid(color, cvd, transformer, non_luminance_weight, ...)
pth_data_cat_metric(color, cvd, method, ...)

pth_data_qnt_euclid(palette, n_step, cvd, transformer, non_luminance_weight, ...)
pth_data_qnt_metric(palette, n_step, cvd, method, ...)

rescale palette

# rescale the input to a palette
rescale_palette <- function(palette, rescaler) # returns cpath_palette

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.