Skip to content

Instantly share code, notes, and snippets.

@hyakugei
Last active November 9, 2015 01:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hyakugei/41c040319ed35532e673 to your computer and use it in GitHub Desktop.
Save hyakugei/41c040319ed35532e673 to your computer and use it in GitHub Desktop.
Unity3D version of Nathan Jones' C# Port of David Merfield's javascript random color generator.
// C# Port - https://github.com/nathanpjones/randomColorSharped
// Javascript Original - https://github.com/davidmerfield/randomColor
using UnityEngine;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
namespace RandomColorGenerator
{
internal class Range
{
public int Lower { get; set; }
public int Upper { get; set; }
public Range()
{ }
public Range(int lower, int upper)
{
Lower = lower;
Upper = upper;
}
public int this[int index]
{
get
{
switch (index)
{
case 0: return Lower;
case 1: return Upper;
default: throw new ArgumentOutOfRangeException();
}
}
set
{
switch (index)
{
case 0: Lower = value; break;
case 1: Upper = value; break;
default: throw new ArgumentOutOfRangeException();
}
}
}
internal static Range ToRange(int[] range)
{
if (range == null) return null;
UnityEngine.Debug.Assert(range.Length == 2);
return new Range(range[0], range[1]);
}
}
public static class RandomColor
{
private class DefinedColor
{
public Range HueRange { get; set; }
public Vector2[] LowerBounds { get; set; }
public Range SaturationRange { get; set; }
public Range BrightnessRange { get; set; }
}
private static readonly Dictionary<ColorScheme, DefinedColor> ColorDictionary = new Dictionary<ColorScheme, DefinedColor>();
private readonly static object LockObj = new object();
private static System.Random _rng = new System.Random();
static RandomColor()
{
// Populate the color dictionary
LoadColorBounds();
}
public static Color GetColor(ColorScheme scheme, Luminosity luminosity)
{
int H, S, B;
// First we pick a hue (H)
H = PickHue(scheme);
// Then use H to determine saturation (S)
S = PickSaturation(H, luminosity, scheme);
// Then use S and H to determine brightness (B).
B = PickBrightness(H, S, luminosity);
// Then we return the HSB color in the desired format
return HsvToColor(H, S, B);
}
public static Color[] GetColors(ColorScheme scheme, Luminosity luminosity, int count)
{
var ret = new Color[count];
for (var i = 0; i < count; i++)
{
ret[i] = GetColor(scheme, luminosity);
}
return ret;
}
public static Color[] GetColors(params Options[] options)
{
if (options == null) throw new ArgumentNullException("options");
return options.Select(o => GetColor(o.ColorScheme, o.Luminosity)).ToArray();
}
/// <summary>
/// Reseeds the random number generated.
/// </summary>
/// <param name="seed">The number used to reseed the random number generator.</param>
public static void Seed(int seed)
{
lock (LockObj)
{
_rng = new System.Random(seed);
}
}
/// <summary>
/// Reseeds the random number generated.
/// </summary>
public static void Seed()
{
lock (LockObj)
{
_rng = new System.Random();
}
}
private static int PickHue(ColorScheme scheme)
{
var hueRange = GetHueRange(scheme);
var hue = RandomWithin(hueRange);
// Instead of storing red as two separate ranges,
// we group them, using negative numbers
if (hue < 0) hue = 360 + hue;
return hue;
}
private static int PickSaturation(int hue, Luminosity luminosity, ColorScheme scheme)
{
if (luminosity == Luminosity.Random)
{
return RandomWithin(0, 100);
}
if (scheme == ColorScheme.Monochrome)
{
return 0;
}
var saturationRange = GetColorInfo(hue).SaturationRange;
var sMin = saturationRange.Lower;
var sMax = saturationRange.Upper;
switch (luminosity)
{
case Luminosity.Bright:
sMin = 55;
break;
case Luminosity.Dark:
sMin = sMax - 10;
break;
case Luminosity.Light:
sMax = 55;
break;
}
return RandomWithin(sMin, sMax);
}
private static int PickBrightness(int H, int S, Luminosity luminosity)
{
var bMin = GetMinimumBrightness(H, S);
var bMax = 100;
switch (luminosity)
{
case Luminosity.Dark:
bMax = bMin + 20;
break;
case Luminosity.Light:
bMin = (bMax + bMin) / 2;
break;
case Luminosity.Random:
bMin = 0;
bMax = 100;
break;
}
return RandomWithin(bMin, bMax);
}
private static int GetMinimumBrightness(int H, int S)
{
var lowerBounds = GetColorInfo(H).LowerBounds;
for (var i = 0; i < lowerBounds.Length - 1; i++)
{
var s1 = lowerBounds[i].x;
var v1 = lowerBounds[i].y;
var s2 = lowerBounds[i + 1].x;
var v2 = lowerBounds[i + 1].y;
if (S >= s1 && S <= s2)
{
var m = (v2 - v1) / (s2 - s1);
var b = v1 - m * s1;
return (int)(m * S + b);
}
}
return 0;
}
private static Range GetHueRange(ColorScheme colorInput)
{
DefinedColor color;
if (ColorDictionary.TryGetValue(colorInput, out color))
{
if (color.HueRange != null)
{
return color.HueRange;
}
}
return new Range(0, 360);
}
private static DefinedColor GetColorInfo(int hue)
{
// Maps red colors to make picking hue easier
if (hue >= 334 && hue <= 360)
{
hue -= 360;
}
var ret = ColorDictionary.FirstOrDefault(c => c.Value.HueRange != null &&
hue >= c.Value.HueRange[0] &&
hue <= c.Value.HueRange[1]);
UnityEngine.Debug.Assert(ret.Value != null);
return ret.Value;
}
private static int RandomWithin(Range range)
{
return RandomWithin(range.Lower, range.Upper);
}
private static int RandomWithin(int lower, int upper)
{
lock (LockObj)
{
return _rng.Next(lower, upper + 1);
}
}
private static void DefineColor(ColorScheme scheme, int[] hueRange, int[,] lowerBounds)
{
int[][] jagged = new int[lowerBounds.GetLength(0)][];
for (int i = 0; i < lowerBounds.GetLength(0); i++)
{
jagged[i] = new int[lowerBounds.GetLength(1)];
for (int j = 0; j < lowerBounds.GetLength(1); j++)
{
jagged[i][j] = lowerBounds[i, j];
}
}
var sMin = jagged[0][0];
var sMax = jagged[jagged.Length - 1][0];
var bMin = jagged[jagged.Length - 1][1];
var bMax = jagged[0][1];
ColorDictionary[scheme] = new DefinedColor()
{
HueRange = Range.ToRange(hueRange),
LowerBounds = jagged.Select(j => new Vector2(j[0], j[1])).ToArray(),
SaturationRange = new Range(sMin, sMax),
BrightnessRange = new Range(bMin, bMax)
};
}
private static void LoadColorBounds()
{
DefineColor(
ColorScheme.Monochrome,
null,
new[,] { { 0, 0 }, { 100, 0 } }
);
DefineColor(
ColorScheme.Red,
new[] { -26, 18 },
new[,] { { 20, 100 }, { 30, 92 }, { 40, 89 }, { 50, 85 }, { 60, 78 }, { 70, 70 }, { 80, 60 }, { 90, 55 }, { 100, 50 } }
);
DefineColor(
ColorScheme.Orange,
new[] { 19, 46 },
new[,] { { 20, 100 }, { 30, 93 }, { 40, 88 }, { 50, 86 }, { 60, 85 }, { 70, 70 }, { 100, 70 } }
);
DefineColor(
ColorScheme.Yellow,
new[] { 47, 62 },
new[,] { { 25, 100 }, { 40, 94 }, { 50, 89 }, { 60, 86 }, { 70, 84 }, { 80, 82 }, { 90, 80 }, { 100, 75 } }
);
DefineColor(
ColorScheme.Green,
new[] { 63, 178 },
new[,] { { 30, 100 }, { 40, 90 }, { 50, 85 }, { 60, 81 }, { 70, 74 }, { 80, 64 }, { 90, 50 }, { 100, 40 } }
);
DefineColor(
ColorScheme.Blue,
new[] { 179, 257 },
new[,] { { 20, 100 }, { 30, 86 }, { 40, 80 }, { 50, 74 }, { 60, 60 }, { 70, 52 }, { 80, 44 }, { 90, 39 }, { 100, 35 } }
);
DefineColor(
ColorScheme.Purple,
new[] { 258, 282 },
new[,] { { 20, 100 }, { 30, 87 }, { 40, 79 }, { 50, 70 }, { 60, 65 }, { 70, 59 }, { 80, 52 }, { 90, 45 }, { 100, 42 } }
);
DefineColor(
ColorScheme.Pink,
new[] { 283, 334 },
new[,] { { 20, 100 }, { 30, 90 }, { 40, 86 }, { 60, 84 }, { 80, 80 }, { 90, 75 }, { 100, 73 } }
);
}
public static Color HsvToColor(int hue, int saturation, double value)
{
// this doesn't work for the values of 0 and 360
// here's the hacky fix
var h = Convert.ToDouble(hue);
if (h == 0)
{
h = 1;
}
if (h == 360)
{
h = 359;
}
// Rebase the h,s,v values
h = h / 360.0;
var s = saturation / 100.0;
var v = value / 100.0;
var hInt = (int)Math.Floor(h * 6.0);
var f = h * 6 - hInt;
var p = v * (1 - s);
var q = v * (1 - f * s);
var t = v * (1 - (1 - f) * s);
var r = 256.0;
var g = 256.0;
var b = 256.0;
switch (hInt)
{
case 0: r = v; g = t; b = p; break;
case 1: r = q; g = v; b = p; break;
case 2: r = p; g = v; b = t; break;
case 3: r = p; g = q; b = v; break;
case 4: r = t; g = p; b = v; break;
case 5: r = v; g = p; b = q; break;
}
var c = RandomColor.FromRgb((byte)Math.Floor(r * 255.0),
(byte)Math.Floor(g * 255.0),
(byte)Math.Floor(b * 255.0));
return c;
}
public static Color FromRgb(byte r, byte g, byte b){
var c = new Color();
c.r = (1f/255f)*r;
c.g = (1f/255f)*g;
c.b = (1f/255f)*b;
c.a = 1;
return c;
}
}
public enum ColorScheme
{
Random,
Monochrome,
Red,
Orange,
Yellow,
Green,
Blue,
Purple,
Pink
}
public enum Luminosity
{
Random,
Dark,
Light,
Bright,
}
public class Options
{
public ColorScheme ColorScheme { get; set; }
public Luminosity Luminosity { get; set; }
public Options()
{}
public Options(ColorScheme scheme, Luminosity luminosity)
{
ColorScheme = scheme;
Luminosity = luminosity;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment