Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Functions for composing colors #21

Open
hauswirth opened this issue Aug 29, 2023 · 3 comments
Open

Functions for composing colors #21

hauswirth opened this issue Aug 29, 2023 · 3 comments

Comments

@hauswirth
Copy link
Contributor

hauswirth commented Aug 29, 2023

Currently we have functions to create colors (e.g., hsv_color), and we have graphics function that use colors, but we have no function that combine colors.

I suggest to add functions to access the components (of different color spaces) of a Color:

  • rgb_red(color: Color) -> int
  • rgb_green(color: Color) -> int
  • rgb_blue(color: Color) -> int
  • hsv_hue(color: Color) -> float
  • hsv_saturation(color: Color) -> float
  • hsv_value(color: Color) -> float
  • hsl_hue(color: Color) -> float (the same as hsv_hue)
  • hsl_saturation(color: Color) -> float (NOT the same as hsv_saturation)
  • hsl_lightness(color: Color) -> float
  • color_opacity(color: Color) -> int

These functions would allow it for users to create their own color combinators and converters.

Moreover, similar to having a beside, above, and overlay, in addition to compose, we should have a couple of key high-level color combinators:

  • blend(weightA: float, colorA: Color, colorB: Color) -> Color -- this mixes the two colors (within the HSV color space to produce nice results) -- this can be used to do nice gradients
  • darken(factor: float, color: Color) -> Color -- this produces a darker/brighter version (not sure about the name) by multiplying the lightness
  • translucent(alpha: int, color: Color) -> Color -- this produces a more or less transparent/opaque version of the given color

Currently, if we want to create a function that takes a color, but we want to then produce multiple shades of that color (e.g., to produce a darker/briger version for shadows or highlights), we cannot do that. We have to pass the components of the color around (e.g., hue, saturation, lightness), with is counter to the idea of having a type for colors.

@lucach
Copy link
Contributor

lucach commented Aug 29, 2023

I concur we should add color combinators. Their choice and implementation should be done carefully, however. In particular, to account for the human perception of colors, blending ought to be done in a linear color space (e.g., sRGB with a linear transfer function) and not directly on the coordinates of a color model like RGB or HSV.

The Colour Haskell library can serve as a good source of inspiration, although the explanations in the documentation could use better clarity. Porter's 1984 paper "Composing Digital Images" is also a classic and gives historical names to some of the combinators suggested above. Skia itself offers several blend modes and implements several color spaces we should be able to leverage (without getting crazy ourselves).

I'm not particularly convinced about adding the selector to access the coordinates. There are definitely legitimate use cases, but it would leave users with the complex task of doing such calculations, and would also not be on par with the treatment we are giving to graphics (which cannot be destructured). If we pick the right choice of basic color combinators, on top of those combinators users could build theirs when occasionally needed, perfectly following PyTamaro's spirit.

@hauswirth
Copy link
Contributor Author

I knew about the Haskell colour library, and their statement:

Most software does not properly blend colours because they fail to
gamma-correct the colours before blending. Hopefully by using this library,
Haskell programs dealing with colour blending will avoid this problem.

I don't really understand yet why one needs to gamma-correct before mixing two colors. E.g., why is taking the weighted average of all the color's components, with code similar to the following, not appropriate?

def blend(w: float, ca: Color, cb: Color) -> Color:
  return hsv_color(
    hsv_hue(ca) * w + hsv_hue(cb) * (1 - w),
    hsv_saturation(ca) * w + hsv_saturation(cb) * (1 - w),
    hsv_value(ca) * w + hsv_value(cb) * (1 - w),
    color_opacity(ca) * w + color_opacity(cb) * (1 - w)
  )

Maybe once I read the Porter paper this will clear up that need to transform to a color space that's different?

@lucach
Copy link
Contributor

lucach commented Aug 30, 2023

Obligatory xkcd

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants