1

I'm trying to find a way to generate random shades of an RGB color.

As you can see in the snippet, most shades are pretty ok but there are some that are completely irrelevant visually. Is there a way of improving this ?

let resultsContainer = document.getElementById('results')
let original = { r: 244, g: 102, b: 255 } // generate random shades of this color
let max = 100
let min = 100
let results = []

document.getElementById('original').style.background = `rgb(${original.r}, ${original.g}, ${original.b})`

function randomBetween(min, max) {
  min = min < 0 ? 0 : min
  max = max > 255 ? 255 : max
  return min + Math.floor(Math.random() * (max - min + 1))
}


while (results.length < 10) {
  results.push({
    r: randomBetween(original.r-min, original.r+max),
    g: randomBetween(original.g-min, original.g+max),
    b: randomBetween(original.b-min, original.b+max),
  })
}


results.forEach(({ r, g, b }) => {
  let result = document.createElement('div')
  result.className = 'result'
  result.style.background = `rgb(${r}, ${g}, ${b})`
  resultsContainer.appendChild(result)
})
.result {
  float:left;
  width: 50px;
  height: 50px;
}
<div id="original" class="result"></div>
<br>
<br>
<br>
<br>
<div id="results"></div>
Kawd
  • 4,122
  • 10
  • 37
  • 68
  • would be better to have a large list of pre-approved colors, then pull randomly from that to avoid getting several grays. – dandavis Dec 01 '21 at 21:14
  • What do you mean by irrelevant? – ggorlen Dec 01 '21 at 21:22
  • @ggorlen visually irrelevant – Kawd Dec 01 '21 at 21:25
  • 1
    No surprise, per hue, there are only 5-6 at first glance distinguishable brightness levels. And only for the topmost one you have 3 distinguishable saturation levels, and proportionally less for the lower brightness values. So only like 12 sensible colors die a single hue to pick from. All of which you can just construct systematically in HSV color space. – Ext3h Dec 01 '21 at 21:34
  • Sorry, but I'm still not sure what "visual relevance" means for you. Too much gray? Too many overlapping/similar colors? – ggorlen Dec 01 '21 at 21:39
  • Colors that are too different from the original one – Kawd Dec 01 '21 at 23:36

2 Answers2

2

instead of RGB , it's better to use HSL( Hue ,Saturation ,Lightness) , and only randomize Saturation and Lightness to get different shades.

UPDATE: code updated based on OP suggestions, HSL<->RGB converters adopted from here

  function hslToRgb(h, s, l) {
      h /= 360.0, s /= 100.0, l /= 100.0;
      var r, g, b;

      if(s == 0) {
          r = g = b = l; // achromatic
      } else {
          var hue2rgb = function hue2rgb(p, q, t) {
              if(t < 0) t += 1;
              if(t > 1) t -= 1;
              if(t < 1 / 6) return p + (q - p) * 6 * t;
              if(t < 1 / 2) return q;
              if(t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
              return p;
          }

          var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
          var p = 2 * l - q;
          r = hue2rgb(p, q, h + 1 / 3);
          g = hue2rgb(p, q, h);
          b = hue2rgb(p, q, h - 1 / 3);
      }

      return [Math.round(r * 255), Math.round(g * 255), Math.round(b *
          255)];
  }

  function rgbToHsl(r, g, b) {
      r /= 255, g /= 255, b /= 255;
      var max = Math.max(r, g, b),
          min = Math.min(r, g, b);
      var h, s, l = (max + min) / 2;

      if(max == min) {
          h = s = 0; // achromatic
      } else {
          var d = max - min;
          s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
          switch(max) {
              case r:
                  h = (g - b) / d + (g < b ? 6 : 0);
                  break;
              case g:
                  h = (b - r) / d + 2;
                  break;
              case b:
                  h = (r - g) / d + 4;
                  break;
          }
          h /= 6;
      }

      return [h * 360, s * 100, l * 100]
  }

  function randomBetween(min, max) {
      min = min < 10 ? 10 : min // under 10 it becomes too dark
      max = max > 90 ? 90 : max // over 90 it becomes too light
      return min + Math.floor(Math.random() * (max - min + 1))
  }

  let resultsContainer = document.getElementById('results')

  let originalAsRGB = [244, 102, 255]

  let originalAsHSL = rgbToHsl(
      originalAsRGB[0],
      originalAsRGB[1],
      originalAsRGB[2]
  )

  let max = 40
  let min = 40
  let results = []

  document.getElementById('original-as-rgb')
      .style.background =
      `rgb(${originalAsRGB[0]}, ${originalAsRGB[1]}, 
      ${originalAsRGB[2]})`

  document.getElementById('original-as-hsl')
      .style.background =
      `hsl(${originalAsHSL[0]}deg, ${originalAsHSL[1]}%, 
       ${originalAsHSL[2]}%)`


  while(results.length < 10) {
      results.push({
          h: originalAsHSL[0],
          s: randomBetween(originalAsHSL[1] - min, originalAsHSL[1] +
              max),
          l: randomBetween(originalAsHSL[2] - min, originalAsHSL[2] +
              max)
      })
  }


  results.forEach(({h,s,l}) => {
      let result = document.createElement('div')
      result.className = 'result'
      let [r, g, b] = hslToRgb(h, s, l)
      result.style.background = `rgb(${r}, ${g}, ${b})`
      resultsContainer.appendChild(result)
  })
.result {
  float:left;
  width: 50px;
  height: 50px;
}
<strong>original RGB:</strong>
<br>
<div id="original-as-rgb" class="result"></div>
<br>
<br>
<br>
<br>
<strong>original RGB converted to HSL:</strong>
<br>
<div id="original-as-hsl" class="result"></div>
<br>
<br>
<br>
<br>
<strong>random shades of the above HSL:</strong>
<br>
<div id="results"></div>
  • My problem now is that I need to convert RGB to HSL. I can't find a method to do that. – Kawd Dec 01 '21 at 22:27
  • 1
    @SproutCoder there is a good solution [here](https://stackoverflow.com/a/9493060/11841240), but you may need to do some conversion – Babak Karimi Asl Dec 01 '21 at 22:33
  • Indeed when I use rgbToHsl to convert my original RGB to HSL, use it in your example and then take the HSL result and pass it back to hslToRgb, the resulting RGB value is non-sensical – Kawd Dec 01 '21 at 22:54
  • You can see the problem here: https://jsfiddle.net/3vysprc0/ – Kawd Dec 01 '21 at 22:59
  • 1
    @SproutCoder sorry for late response , in jsfiddle, line 44 needs to be: `return [h*360, s*100, l*100];` – Babak Karimi Asl Dec 02 '21 at 05:18
  • Thanks!. However, the `hslToRgb` conversion also doesn't work on line 84. I'm getting all white `[255,255,255]`. – Kawd Dec 02 '21 at 11:37
  • You could also mention the correct rgbToHsl and hslToRgb methods to be used in your answer for the future reader. As the OP (and my use case) needs to have RGB as input and ouput, regardless of the HSL used in between. Thanks. – Kawd Dec 02 '21 at 11:40
  • Sorry I forgot to include the latest fiddle: https://jsfiddle.net/b4w1qtox/ (it includes a working `rgbToHsl` conversion and a faulty `hslToRgb` conversion on line 84) – Kawd Dec 02 '21 at 11:45
  • 1
    @SproutCoder lol we are in different timezones, answer updated, the only change is adding the following at the start of `hslToRgb` => `h /= 360.0, s /= 100.0, l /= 100.0;` – Babak Karimi Asl Dec 02 '21 at 16:15
  • 1
    Thank you for taking the time to update <3 – Kawd Dec 02 '21 at 18:29
0

To shade RGB, multiply each r, g, b, by a percentage. To tint, for each r, g, b, use: color + value% * (255 - color). Easy peasy.

Steven
  • 1