Last active
November 9, 2015 01:03
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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