/** This is tutorial code that goes with the article http://devmag.org.za/2012/07/29/how-to-choose-colours-procedurally-algorithms/. */ using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using Christo.GFX.Conversion; namespace ProceduralPalette { public static class RandomExtensions { public static int NextByte(this Random random) { return ProceduralPalette.FloatToByte(random.NextDouble()); } public static float NextFloat(this Random random) { return (float) random.NextDouble(); } } class ProceduralPalette { private static Random random = new Random(); public static List GenerateColors_Uniform(int colorCount) { List colors = new List(); for (int i = 0; i < colorCount; i++) { Color newColor = Color.FromArgb( 255, random.NextByte(), random.NextByte(), random.NextByte()); colors.Add(newColor); } return colors; } public static List GenerateColors_Harmony( int colorCount, float offsetAngle1, float offsetAngle2, float rangeAngle0, float rangeAngle1, float rangeAngle2, float saturation, float luminance) { List colors = new List(); float referenceAngle = random.NextFloat() * 360; for (int i = 0; i < colorCount; i++) { float randomAngle = random.NextFloat() * (rangeAngle0 + rangeAngle1 + rangeAngle2); if (randomAngle > rangeAngle0) { if (randomAngle < rangeAngle0 + rangeAngle1) { randomAngle += offsetAngle1; } else { randomAngle += offsetAngle2; } } HSL hslColor = new HSL(((referenceAngle + randomAngle) / 360.0f) % 1.0f, saturation, luminance); colors.Add(hslColor.Color); } return colors; } public static List GenerateColors_Harmony2( int colorCount, float offsetAngle1, float offsetAngle2, float rangeAngle0, float rangeAngle1, float rangeAngle2, float saturation, float saturationRange, float luminance, float luminanceRange) { List colors = new List(); float referenceAngle = random.NextFloat() * 360; for (int i = 0; i < colorCount; i++) { float randomAngle = random.NextFloat() * (rangeAngle0 + rangeAngle1 + rangeAngle2); if (randomAngle > rangeAngle0) { if (randomAngle < rangeAngle0 + rangeAngle1) { randomAngle += offsetAngle1; } else { randomAngle += offsetAngle2; } } float newSaturation = saturation + (random.NextFloat() - 0.5f) * saturationRange; float newLuminance = luminance + +(random.NextFloat() - 0.5f) * luminanceRange; HSL hslColor = new HSL(((referenceAngle + randomAngle) / 360.0f) % 1.0f, newSaturation, newLuminance); colors.Add(hslColor.Color); } return colors; } public static List GenerateColors_RandomWalk(int colorCount, Color startColor, float min, float max) { List colors = new List(); Color newColor = startColor; float range = max - min; for (int i = 0; i < colorCount; i++) { int rSign = random.Next(2) % 2 == 0 ? 1 : -1; int gSign = random.Next(2) % 2 == 0 ? 1 : -1; int bSign = random.Next(2) % 2 == 0 ? 1 : -1; newColor = Color.FromArgb( 255, FloatToByte(ByteToFloat(newColor.R) + rSign * (min + random.NextDouble() * range)), FloatToByte(ByteToFloat(newColor.G) + rSign * (min + random.NextDouble() * range)), FloatToByte(ByteToFloat(newColor.B) + rSign * (min + random.NextDouble() * range))); colors.Add(newColor); } return colors; } public static List GenerateColors_RandomMix(int colorCount, Color color1, Color color2, Color color3, float greyControl, bool paint) { List colors = new List(); for (int i = 0; i < colorCount; i++) { Color newColor; if (paint) { newColor = RandomMixHSL(color1, color2, color3, greyControl); } else { newColor = RandomMix(color1, color2, color3, greyControl); } colors.Add(newColor); } return colors; } public static List GenerateColors_RandomAdd(int colorCount, Color color1, Color color2, Color color3, float nonGrayBias) { List colors = new List(); for (int i = 0; i < colorCount; i++) { Color newColor = RandomAdd(color1, color2, color3, nonGrayBias); colors.Add(newColor); } return colors; } public static List GenerateColors_Offset(int colorCount, Color color, float maxRange) { List colors = new List(); for (int i = 0; i < colorCount; i++) { Color newColor = Color.FromArgb( 255, FloatToByte((ByteToFloat(color.R) + random.NextDouble() * 2 * maxRange - maxRange)), FloatToByte((ByteToFloat(color.G) + random.NextDouble() * 2 * maxRange - maxRange)), FloatToByte((ByteToFloat(color.B) + random.NextDouble() * 2 * maxRange - maxRange))); colors.Add(newColor); } return colors; } public static List GenerateColors_Hue(int colorCount, float saturation, float luminance) { List colors = new List(); for (int i = 0; i < colorCount; i++) { HSL hslColor = new HSL(random.NextDouble(), saturation, luminance); colors.Add(hslColor.Color); } return colors; } public static List GenerateColors_Saturation(int colorCount, float hue, float luminance) { List colors = new List(); for (int i = 0; i < colorCount; i++) { HSL hslColor = new HSL(hue, random.NextDouble(), luminance); colors.Add(hslColor.Color); } return colors; } public static List GenerateColors_Luminance(int colorCount, float hue, float saturation) { List colors = new List(); for (int i = 0; i < colorCount; i++) { HSL hslColor = new HSL(hue, saturation, random.NextDouble()); colors.Add(hslColor.Color); } return colors; } public static List GenerateColors_SaturationLuminance(int colorCount, float hue) { List colors = new List(); for (int i = 0; i < colorCount; i++) { HSL hslColor = new HSL(hue, random.NextDouble(), random.NextDouble()); colors.Add(hslColor.Color); } return colors; } public static List GenerateColors_GoldenRatioRainbow(int colorCount, float saturation, float luminance) { List colors = new List(); float goldenRatioConjugate = 0.618033988749895f; float currentHue = (float) random.NextDouble(); for (int i = 0; i < colorCount; i++) { HSL hslColor = new HSL(currentHue, saturation, luminance); colors.Add(hslColor.Color); currentHue += goldenRatioConjugate; currentHue %= 1.0f; } return colors; } public static List GenerateColors_GoldenRatioGradient(int colorCount, Color[] gradient, float saturation, float luminance) { List colors = new List(); float goldenRatioConjugate = 0.618033988749895f; float currentHue = (float)random.NextDouble(); for (int i = 0; i < colorCount; i++) { HSL hslColor = new HSL(currentHue, saturation, luminance); Color newColor = SampleLinearGradient(gradient, currentHue); colors.Add(newColor); currentHue += goldenRatioConjugate; currentHue %= 1.0f; } return colors; } public static List GenerateColors_HueRange(int colorCount, float startHue, float endHue, float saturation, float luminance) { List colors = new List(); float hueRange = endHue - startHue; if(hueRange < 0) { hueRange += 1.0f; } for (int i = 0; i < colorCount; i++) { float newHue = (float) (hueRange * random.NextDouble() + startHue); if(newHue > 1.0) { newHue -= 1.0f; } HSL hslColor = new HSL(newHue, saturation, luminance); colors.Add(hslColor.Color); } return colors; } public static List GenerateColors_JitteredRainbow(int colorCount, float startHue, float endHue, float saturation, float luminance, bool jitter) { List colors = new List(); float hueRange = endHue - startHue; if (hueRange < 0) { hueRange = 1 + hueRange; } float cellRange = hueRange / colorCount; float cellOffset = (float) (random.NextDouble() * cellRange); for (int i = 0; i < colorCount; i++) { float newHue; if (jitter) { newHue = (float)(cellRange * i + random.NextDouble() * cellRange + startHue); } else { newHue = (cellRange * i + cellOffset + startHue); } if(newHue > 1) { newHue -= 1.0f; } HSL hslColor = new HSL(newHue, saturation, luminance); colors.Add(hslColor.Color); } return colors; } public static int FloatToByte(double value) { int byteValue = (int)(value * 256); byteValue = SaturateByte(byteValue); return byteValue; } private static int SaturateByte(int byteValue) { if (byteValue > 255) { byteValue = 255; } else if (byteValue < 0) { byteValue = 0; } return byteValue; } public static float ByteToFloat(int byteValue) { float floatValue = byteValue / 256.0f; return floatValue; } public static Color RandomMix(Color color1, Color color2, Color color3, float greyControl) { int randomIndex = random.NextByte() % 3; float mixRatio1 = (randomIndex == 0) ? random.NextFloat() * greyControl : random.NextFloat(); float mixRatio2 = (randomIndex == 1) ? random.NextFloat() * greyControl : random.NextFloat(); float mixRatio3 = (randomIndex == 2) ? random.NextFloat() * greyControl : random.NextFloat(); float sum = mixRatio1 + mixRatio2 + mixRatio3; mixRatio1 /= sum; mixRatio2 /= sum; mixRatio3 /= sum; return Color.FromArgb( 255, (byte)(mixRatio1 * color1.R + mixRatio2 * color2.R + mixRatio3 * color3.R), (byte)(mixRatio1 * color1.G + mixRatio2 * color2.G + mixRatio3 * color3.G), (byte)(mixRatio1 * color1.B + mixRatio2 * color2.B + mixRatio3 * color3.B)); } public static Color RandomMixHSL(Color color1, Color color2, Color color3, float greyControl) { int randomIndex = random.NextByte() % 3; float mixRatio1 = (randomIndex == 0) ? random.NextFloat() * greyControl : random.NextFloat(); float mixRatio2 = (randomIndex == 1) ? random.NextFloat() * greyControl : random.NextFloat(); float mixRatio3 = (randomIndex == 2) ? random.NextFloat() * greyControl : random.NextFloat(); float sum = mixRatio1 + mixRatio2 + mixRatio3; mixRatio1 /= sum; mixRatio2 /= sum; mixRatio3 /= sum; HSL hsl1 = new HSL(color1); HSL hsl2 = new HSL(color2); HSL hsl3 = new HSL(color3); return new HSL( (mixRatio1 * hsl1.H + mixRatio2 * hsl2.H + mixRatio3 * hsl3.H), (mixRatio1 * hsl1.S + mixRatio2 * hsl2.S + mixRatio3 * hsl3.S), (mixRatio1 * hsl1.L + mixRatio2 * hsl2.L + mixRatio3 * hsl3.L)).Color; } public static Color RandomMixPaint(Color color1, Color color2, Color color3, float greyControl) { int randomIndex = random.NextByte() % 3; float mixRatio1 = (randomIndex == 0) ? random.NextFloat() * greyControl : random.NextFloat(); float mixRatio2 = (randomIndex == 1) ? random.NextFloat() * greyControl : random.NextFloat(); float mixRatio3 = (randomIndex == 2) ? random.NextFloat() * greyControl : random.NextFloat(); float sum = mixRatio1 + mixRatio2 + mixRatio3; mixRatio1 /= sum; mixRatio2 /= sum; mixRatio3 /= sum; return Color.FromArgb( 255, 255 - (byte)(mixRatio1 * (255 - color1.R) + mixRatio2 * (255 - color2.R) + mixRatio3 * (255 - color3.R)), 255 - (byte)(mixRatio1 * (255 - color1.G) + mixRatio2 * (255 - color2.G) + mixRatio3 * (255 - color3.G)), 255 - (byte)(mixRatio1 * (255 - color1.B) + mixRatio2 * (255 - color2.B) + mixRatio3 * (255 - color3.B))); } public static Color RandomAdd(Color color1, Color color2, Color color3, float nonGrayBias) { int rIndex = random.NextByte() % 3; float r1 = (rIndex == 0) ? random.NextFloat() * nonGrayBias : random.NextFloat(); float r2 = (rIndex == 1) ? random.NextFloat() * nonGrayBias : random.NextFloat(); float r3 = (rIndex == 2) ? random.NextFloat() * nonGrayBias : random.NextFloat(); float p = 20; float sum = (float) Math.Pow( Math.Pow(r1, p) + Math.Pow(r2, p) + Math.Pow(r3, p), 1 / p); r1 /= sum; r2 /= sum; r3 /= sum; return Color.FromArgb( 255, SaturateToByte(r1 * color1.R + r2 * color2.R + r3 * color3.R), SaturateToByte(r1 * color1.G + r2 * color2.G + r3 * color3.G), SaturateToByte(r1 * color1.B + r2 * color2.B + r3 * color3.B)); } public static int SaturateToByte(float floatValue) { int intValue = (int)floatValue; if (intValue > 255) { intValue = 255; } else if( intValue < 0) { intValue = 0; } return intValue; } public static Color SampleLinearGradient(Color [] colors, float t) { int colorCount = colors.Length; int leftIndex = (int) (t * colorCount); float cellRange = 1.0f / colorCount; float alpha = (t - leftIndex * cellRange) / cellRange; Color leftColor = colors[leftIndex]; Color rightColor = colors[(leftIndex + 1) % colorCount]; return Color.FromArgb( 255, (byte) (leftColor.R * (1 - alpha) + rightColor.R * (alpha)), (byte) (leftColor.G * (1 - alpha) + rightColor.G * (alpha)), (byte) (leftColor.B * (1 - alpha) + rightColor.B * (alpha))); } public static Bitmap GeneratePaletteImage(List colors, int width, int height, int spacing) { Bitmap image = new Bitmap(width, height); int colorCount = colors.Count; int blockWidth = (width - ((colorCount - 1) * spacing)) / colorCount; for (int i = 0; i < colorCount; i++) { DrawBlock(image, i * (blockWidth + spacing), 0, blockWidth, height, colors[i]); } return image; } public static void DrawBlock(Bitmap image, int x, int y, int width, int height, Color color) { for (int i = x; i < x + width; i++) { for (int j = y; j < y + height; j++) { image.SetPixel(i, j, color); } } } //static void Main(string[] args) //{ // //random = new Random(15);//15 // random = new Random(12);//15 // List colors; // int versionCount = 3; // int colorCount = 20; // int imageWidth = 680; // int imageHeight = 100; // int spacing = 2; // Color[] baseColors = { // Color.FromArgb(255, 128, 0), // Color.FromArgb(128, 255, 64), // Color.FromArgb(64, 128, 255)}; // float[] saturation = {1.0f, 0.7f, 0.3f}; // float[] luminance = {0.45f, 0.7f, 0.4f}; // float[] hueStart = {0.9f, 0.2f, 0.6f }; // float[] hueEnd = { 0.1f, 0.5f, 0.9f }; // Color[] mixColor1 = // { // Color.FromArgb(255, 0, 33), // Color.FromArgb(255, 100, 33), // Color.FromArgb(128, 0, 33), // }; // Color[] mixColor2 = // { // Color.FromArgb(255, 255, 0), // Color.FromArgb(255, 255, 100), // Color.FromArgb(255, 128, 0), // }; // Color[] mixColor3 = // { // Color.FromArgb(0, 100, 255), // Color.FromArgb(100, 150, 255), // Color.FromArgb(0, 80, 128), // }; // Color[] addColor1 = // { // Color.FromArgb(150, 0, 0), // Color.FromArgb(200, 20, 0), // Color.FromArgb(128, 0, 33), // }; // Color[] addColor2 = // { // Color.FromArgb(0, 150, 0), // Color.FromArgb(200, 200, 20), // Color.FromArgb(255, 128, 0), // }; // Color[] addColor3 = // { // Color.FromArgb(0, 0, 150), // Color.FromArgb(20, 20, 200), // Color.FromArgb(0, 80, 128), // }; // Color[][] gradients = // { // new Color[] // { // Color.FromArgb(255, 0, 128), // Color.FromArgb(255, 255, 0), // Color.FromArgb(0, 255, 255), // }, // new Color[] // { // Color.FromArgb(200, 0, 33), // Color.FromArgb(255, 200, 0), // Color.FromArgb(0, 100, 200), // }, // new Color[] // { // Color.FromArgb(255, 0, 33), // Color.FromArgb(255, 255, 0), // Color.FromArgb(0, 255, 0), // Color.FromArgb(255, 255, 0), // } // }; // float[] offsetAngles1 = // { // 15, 180, 120 // }; // float[] offsetAngles2 = // { // 30, 0, 240 // }; // float[] angleRanges1 = // { // 15, 40f, 40 // }; // float[] angleRanges2 = // { // 15, 40f, 40 // }; // float[] angleRanges3 = // { // 15, 0, 40 // }; // for (int version = 0; version < versionCount; version++) // { // colors = GenerateColors_Uniform(colorCount); // GeneratePaletteImage(colors, imageWidth, imageHeight, spacing).Save("Uniform_5_" + version + ".png"); // colors = GenerateColors_RandomWalk(colorCount, baseColors[version], 0.2f, 0.4f); // GeneratePaletteImage(colors, imageWidth, imageHeight, spacing).Save("RandomWalk_5_" + version + ".png"); // colors = GenerateColors_RandomMix(colorCount, mixColor1[version], mixColor2[version], mixColor3[version], 0.1f, true); // GeneratePaletteImage(colors, imageWidth, imageHeight, spacing).Save("MixPaint1_5_" + version + ".png"); // colors = GenerateColors_RandomMix(colorCount, mixColor1[version], mixColor2[version], mixColor3[version], 0.5f, true); // GeneratePaletteImage(colors, imageWidth, imageHeight, spacing).Save("MixPaint5_5_" + version + ".png"); // colors = GenerateColors_RandomMix(colorCount, mixColor1[version], mixColor2[version], mixColor3[version], 0.9f, true); // GeneratePaletteImage(colors, imageWidth, imageHeight, spacing).Save("MixPaint9_5_" + version + ".png"); // colors = GenerateColors_RandomMix(colorCount, mixColor1[version], mixColor2[version], mixColor3[version], 0.1f, false); // GeneratePaletteImage(colors, imageWidth, imageHeight, spacing).Save("Mix1_5_" + version + ".png"); // colors = GenerateColors_RandomMix(colorCount, mixColor1[version], mixColor2[version], mixColor3[version], 0.5f, false); // GeneratePaletteImage(colors, imageWidth, imageHeight, spacing).Save("Mix5_5_" + version + ".png"); // colors = GenerateColors_RandomMix(colorCount, mixColor1[version], mixColor2[version], mixColor3[version], 0.9f, false); // GeneratePaletteImage(colors, imageWidth, imageHeight, spacing).Save("Mix9_5_" + version + ".png"); // colors = GenerateColors_RandomAdd(colorCount, addColor1[version], addColor2[version], addColor3[version], 0.1f); // GeneratePaletteImage(colors, imageWidth, imageHeight, spacing).Save("Add1_5_" + version + ".png"); // colors = GenerateColors_RandomAdd(colorCount, addColor1[version], addColor2[version], addColor3[version], 0.5f); // GeneratePaletteImage(colors, imageWidth, imageHeight, spacing).Save("Add5_5_" + version + ".png"); // colors = GenerateColors_RandomAdd(colorCount, addColor1[version], addColor2[version], addColor3[version], 0.9f); // GeneratePaletteImage(colors, imageWidth, imageHeight, spacing).Save("Add9_5_" + version + ".png"); // colors = GenerateColors_Offset(colorCount, baseColors[version], 0.4f); // GeneratePaletteImage(colors, imageWidth, imageHeight, spacing).Save("Offset_5_" + version + ".png"); // colors = GenerateColors_Hue(colorCount, saturation[version], luminance[version]); // GeneratePaletteImage(colors, imageWidth, imageHeight, spacing).Save("Hue_5_" + version + ".png"); // colors = GenerateColors_Saturation(colorCount, hueStart[version], luminance[version]); // GeneratePaletteImage(colors, imageWidth, imageHeight, spacing).Save("Saturation_5_" + version + ".png"); // colors = GenerateColors_Luminance(colorCount, hueStart[version], saturation[version]); // GeneratePaletteImage(colors, imageWidth, imageHeight, spacing).Save("Luminance_5_" + version + ".png"); // colors = GenerateColors_SaturationLuminance(colorCount, hueStart[version]); // GeneratePaletteImage(colors, imageWidth, imageHeight, spacing).Save("Saturation_Luminance_5_" + version + ".png"); // colors = GenerateColors_GoldenRatioRainbow(colorCount, saturation[version], luminance[version]); // GeneratePaletteImage(colors, imageWidth, imageHeight, spacing).Save("GoldenRatioRainbow_5_" + version + ".png"); // colors = GenerateColors_GoldenRatioGradient(colorCount, gradients[version], saturation[version], luminance[version]); // GeneratePaletteImage(colors, imageWidth, imageHeight, spacing).Save("GoldenRatioGradient_5_" + version + ".png"); // colors = GenerateColors_JitteredRainbow(colorCount, 0, 1, saturation[version], luminance[version], true); // GeneratePaletteImage(colors, imageWidth, imageHeight, spacing).Save("JitteredRainbow_5_" + version + ".png"); // colors = GenerateColors_JitteredRainbow(colorCount, hueStart[version], hueEnd[version], saturation[version], luminance[version], true); // GeneratePaletteImage(colors, imageWidth, imageHeight, spacing).Save("JitteredRainbowRange_5_" + version + ".png"); // colors = GenerateColors_JitteredRainbow(colorCount, 0, 1, saturation[version], luminance[version], false); // GeneratePaletteImage(colors, imageWidth, imageHeight, spacing).Save("Grid_Rainbow_5_" + version + ".png"); // colors = GenerateColors_HueRange(colorCount, hueStart[version], hueEnd[version], saturation[version], luminance[version]); // GeneratePaletteImage(colors, imageWidth, imageHeight, spacing).Save("HueRange_5_" + version + ".png"); // colors = GenerateColors_Harmony(colorCount, // offsetAngles1[version], offsetAngles2[version], // angleRanges1[version], angleRanges2[version], angleRanges3[version], // saturation[version], luminance[version]); // GeneratePaletteImage(colors, imageWidth, imageHeight, spacing).Save("Harmony_5_" + version + ".png"); // colors = GenerateColors_Harmony2(colorCount, // offsetAngles1[version], offsetAngles2[version], // angleRanges1[version], angleRanges2[version], angleRanges3[version], // saturation[version], 0.2f, // luminance[version], 0.5f); // GeneratePaletteImage(colors, imageWidth, imageHeight, spacing).Save("Harmony2_5_" + version + ".png"); // } //} } }