omgovich / colord

👑 A tiny yet powerful tool for high-performance color manipulations and conversions
https://colord.omgovich.ru
MIT License
1.67k stars 49 forks source link

plugin/monochromatic #65

Closed EricRovell closed 3 years ago

EricRovell commented 3 years ago

I think it would be really useful to have an ability to generate monochromatic colors (tints, shades, and tones). You mentioned usefulness of it in #63 and it was on my list of future proposals.

I have already working implementation, it looks like this:

color.tints(5); // generates 5 tint instances of original color;
color.shades(5); // generates 5 shade instances of original color;
color.tones(5); // generates 5 tone instances of original color;

During the prototyping, I have got some questions regarding the API:

  1. Should it be one method with type of palette as the parameter like color.monochromatic("tones", 5) or to be a separate methods for each type? I think it would be more readable for this particular case to have different methods.
  2. Should be the original color included and for example, pure white / pure black to tints and shades? It looks like it is a good idea to include them, but the problem arises with the steps parameter, it gets not so understandable. For example, user wants 3 shades and gets: original color, shade, black. Don't look good.

I would like to hear you ideas and suggestions.

omgovich commented 3 years ago

Hi @EricRovell! Sorry for the delay: was busy at work. I like the idea!

image

I just checked the Wikipedia article and feels like this functionality can be a part of the existing mix plugin and use Colord's color mixing magic.

Example:

  // plugins/mix.ts

  ColordClass.prototype.tones = function (count = 5) {
    const tints = []
    for (let ratio = 0; ratio <= 1; ratio += 1 / count) tints.push(this.mix('#808080', ratio));
    return tints;
  };
EricRovell commented 3 years ago

No problem! Right now I have implemented it for myself using saturation and lightness values from HSL model. I have read about your mix plugin and it says it has better results. I guess I will dive in and try extend your plugin then!

lbragile commented 3 years ago

Hi @omgovich & @EricRovell,

I really like colord and wanted some experience creating NPM packages, so I decided to work on similar package called ColorMaster.

I implemented the above functionality and thought I would share my findings/results here. The code is open source, so you can check it to confirm.

Since tints and shades, are just lightness manipulations (adding white/black), we can use the HSLA color-space for this as it has a lightness channel. Likewise, for tones, we can use the saturation channel of the HSLA color-space:

...
case "monochromatic": {
  // tones uses saturation, tints/shades use lightness
  const valueToAdjust = effect === "tones" ? s : l;  // effect is a function argument - 'tints' | 'shades' | 'tones'

  // form array of n (amount) evenly spaced items from current saturation/lightness to min/max value
  let delta = (effect === "tints" ? 100 - valueToAdjust : valueToAdjust) / amount; // amount is a function argument
  delta = effect === "tints" ? delta : -1 * delta;

  const valArr: number[] = [valueToAdjust];
  for (let i = 0; i < amount; i++) {
    valArr.push(valArr[i] + delta);
  }

  return effect === "tones"
    ? valArr.map((sat) => new HSLColors(h, sat, l, a))
    : valArr.map((light) => new HSLColors(h, s, light, a));
}
...

This returns the original color, along with amount extra items representing the harmony.

You can see that the results match expectations as these tests pass: image

image

I hope this can provide some insight and is helpful to you in some way.

Cheers, Lior

EricRovell commented 3 years ago

Hi, @lbragile! Same situation here :) Working on similar package for self-education. For my package I used the lightness values to generate tints and shades and I was thinking about one edge-case as I see it.

For example, in case of such a color that is too close to pure black/white and user wants too many variations, these colors would be too similar and it is not practical. I have decided to put a minimal step as 1% of lightness. This way it would generate less variations that are more distinguishable.

Do you think it is a good idea?

lbragile commented 3 years ago

@EricRovell That is a great consideration.

Let's consider tints, assuming the user has a color with lightness of 95% and they decide to get an array of 10 - extra - tints (amount parameter has a clamped upper value of 10).

In this case the step size will be 0.5% (difference between neighboring tints). As this is a user decision, we would want to provide them with 10 different tints, regardless of how close they are in value, right?

If we suddenly make the minimal step 1%, the user will now only get 5 extra tints, rather than the 10 they asked for. Namely, they will get only 95%, 96%, 97%, 98%, 99%, 100%, ..., 100% where (96%, 97%, 98%, 99%, 100%) are "new" tints.

The same logic applied to shades and tones.

What I am hinting at is that the user made the decision to get 10 extra tints, so that decision should be respected. Otherwise, we are limiting their choices in a way that they cannot control (for such extreme cases).

This is what the approach I outlined in https://github.com/omgovich/colord/issues/65#issuecomment-890753941 aims to achieve.

Do you agree?

EricRovell commented 3 years ago

@lbragile I think it is quite important to respect the user's decision. I just got some sort of inner conflict, so to speak, about this edge-case with almost pure color and was thinking that generated palette would be to similar or even consist of same colors.

What do you think about limiting the number of tints/shades/tones the user asks? It would we definitely a bad idea to allow user to ask for 1000 tints.

omgovich commented 3 years ago

In my opinion, we should return the number of colors the developer asked for — another behavior would be unpredictable and will cause errors in projects using the library.

omgovich commented 3 years ago

I doubt someone will request 1000 tints, but if they did, we should return 1000 colors even if some of them are equal.

EricRovell commented 3 years ago

Alright, I got the idea. Before I begin to work on implementation, I would like to specify one detail. Should I use mix plugin to implement this, or should I use lightness and saturation increments to handle palette creation?

omgovich commented 3 years ago

I think mix is the better option here, since it uses LAB color space which will produce a better perceptual difference between tones. Developers might find a lot of different libraries that produce tints and tones using HSL values, so it would be cool for Colord to offer another better alternative.

EricRovell commented 3 years ago

Alright, I will extend the functionality of mix plugin then.