RGB -> XYZ conversion doesn't work right - c#

I got a XYZ to RGB converter from here and wrote it into C# but, I've been having some problems.
public static XYZColor RGBtoXYZ(RGBColor rgb)
{
XYZColor xyz = new XYZColor();
float r, g, b;
r = rgb.R;
g = rgb.G;
b = rgb.B;
if (r > 0.04045f) r = (float)Math.Pow((r + 0.055)/1.055, 2.4);
else r = r / 12.92f;
if (g > 0.04045) g =(float)Math.Pow((g + 0.055f)/1.055f , 2.4f);
else g = g/12.92f;
if (b > 0.04045f) b = (float)Math.Pow((b + 0.055f)/1.055f , 2.4f);
else b = b/12.92f;
r *= 100;
g *= 100;
b *= 100;
xyz.X = r*0.4124f + g*0.3576f + b*0.1805f;
xyz.Y = r*0.2126f + g*0.7152f + b*0.0722f;
xyz.Z = r*0.0193f + g*0.1192f + b*0.9505f;
xyz.A = rgb.A;
return xyz;
}
public static RGBColor XYZtoRGB(XYZColor xyz)
{
RGBColor rgb = new RGBColor();
float x, y, z;
x = xyz.X;
y = xyz.Y;
z = xyz.Z;
x = x/100; //X from 0 to 95.047 (Observer = 2°, Illuminant = D65)
y = y/100; //Y from 0 to 100.000
z = z/100; //Z from 0 to 108.883
rgb.R = x*3.2406f + y*-1.5372f + z*-0.4986f;
rgb.G = x*-0.9689f + y*1.8758f + z*0.0415f;
rgb.B = x*0.0557f + y*-0.2040f + z*1.0570f;
if (rgb.R > 0.0031308f) rgb.R = 1.055f*(float)Math.Pow(rgb.R, (1/2.4f)) - 0.055f;
else rgb.R = 12.92f*rgb.R;
if (rgb.G > 0.0031308f) rgb.G = 1.055f*(float)Math.Pow(rgb.G ,(1/2.4f)) - 0.055f;
else rgb.G = 12.92f*rgb.G;
if (rgb.B > 0.0031308f) rgb.B = 1.055f*(float)Math.Pow(rgb.B, (1/2.4f)) - 0.055f;
else rgb.B = 12.92f*rgb.B;
rgb.A = xyz.A;
return rgb;
}
On my testing application, I make a tilemap, each with it's own color, then give it a hue through HSV and cycle them so it shows the full spectrum. Then, every update it gets the hue added to so it moves through the spectrum.
However, when I convert the color to and from XYZ it shows up like this.
Here's the code in case you were interested. The HSVtoRGB method works as expected.
Hue += HUEINCREASE;
RGBColor c = ColorMath.HSVtoRGB(Hue, 1, 1, 1);
//Convert to XYZ and back
XYZColor xyz = ColorMath.RGBtoXYZ(c);
c = ColorMath.XYZtoRGB(xyz);
Render.Color = c;
Both XYZColor and RGBColor are structs. RGBColor holds floats, and if it's value is greater than 1, it wraps the color around. So if it had a red value of 1.1f, it would wrap it to .1f;

You can use ColorHelper library for this:
RGB to XYZ:
using ColorHelper;
RGB rgb = new RGB(100, 100, 100);
XYZ xyz = ColorConverter.RgbToXyz(rgb);
XYZ to RGB:
using ColorHelper;
XYZ xyz = new XYZ(0, 0, 0);
RGB rgb = ColorConverter.XyzToRgb(xyz);
Links:
Github
Nuget

Related

Unity blending Colors based on weight

I'm creating a script that blends 4 textures together based on a mask the code I have so far is this:
public Color GetBlend(Color32 mask, int x, int y,Color baseColor, Color redChannel, Color greenChannel, Color blueChannel) {
float val = 1f / (mask.r + mask.g + mask.b);
float rWeight = mask.r * val;
float gWeight = mask.g * val;
float bWeight = mask.b * val;
// I can't find what follows this though.
}
It is here that I am stuck though. I can't find any information on google about how this blending is supposed to work.
// EDIT //
After fiddling around with it for a bit away from my computer I came up with this idea:
public Color GetBlend(Color32 mask, int x, int y,Color baseColor, Color redChannel, Color greenChannel, Color blueChannel) {
int total = mask.r + mask.g + mask.b;
float alpha = Mathf.Max(mask.r, mask.g, mask.b) / 255f; // not sure this will work
float val = (total == 0) ? 0 : 1f / total;
float rWeight = mask.r * val;
float gWeight = mask.g * val;
float bWeight = mask.b * val;
Color c = (redChannel * rWeight) + (greenChannel * gWeight) + (blueChannel * blueWeight);
c.a = baseColor.a;
return Color.Lerp(baseColor, c, alpha);
}
More fiddling resulted in this idea:
public Color GetBlend(Color32 mask,Color baseColor, Color redChannel, Color greenChannel, Color blueChannel) {
int total = mask.r + mask.g + mask.b;
float alpha = Mathf.Max(mask.r, mask.g, mask.b) / 255f;
float r1 = mask.r == 0 ? baseColor.r : ((redChannel.r * mask.r) + (greenChannel.r * mask.r) + (blueChannel.r * mask.r)) / (mask.r * 3);
float g1 = mask.g == 0 ? baseColor.g : ((redChannel.g * mask.g) + (greenChannel.g * mask.g) + (blueChannel.g * mask.g)) / (mask.g * 3);
float b1 = mask.b == 0 ? baseColor.b : ((redChannel.b * mask.b) + (greenChannel.b * mask.b) + (blueChannel.b * mask.b)) / (mask.b * 3);
float a1 = baseColor.a;
Color blend = new Color(r1, g1, b1);
return Color.Lerp(baseColor, blend, alpha);
}

Implementing Floyd-Steinberg Dithering in C# with specific Palette

I am making a program where I want to take an image and reduce its color palette to a preset palette of 60 colors, and then add a dithering effect. This seems to involve two things:
A color distance algorithm that goes through each pixel, gets its color, and then changes it to the color closest to it in the palette so that that image doesn't have colors that are not contained in the palette.
A dithering algorithm that goes through the color of each pixel and diffuses the difference between the original color and the new palette color chosen across the surrounding pixels.
After reading about color difference, I figured I would use either the CIE94 or CIEDE2000 algorithm for finding the closest color from my list. I also decided to use the fairly common Floyd–Steinberg dithering algorithm for the dithering effect.
Over the past 2 days I have written my own versions of these algorithms, pulled other versions of them from examples on the internet, tried them both first in Java and now C#, and pretty much every single time the output image has the same issue. Some parts of it look perfectly fine, have the correct colors, and are dithered properly, but then other parts (and sometimes the entire image) end up way too bright, are completely white, or all blur together. Usually darker images or darker parts of images turn out fine, but any part that is bright or has lighter colors at all gets turned up way brighter. Here is an example of an input and output image with these issues:
Input:
]3
Output:
I do have one idea for what may be causing this. When a pixel is sent through the "nearest color" function, I have it output its RGB values and it seems like some of them have their R value (and potentially other values??) pushed way higher than they should be, and even sometimes over 255 as shown in the screenshot. This does NOT happen for the earliest pixels in the image, only for ones that are multiple pixels in already and are already somewhat bright. This leads me to believe it is the dithering/error algorithm doing this, and not the color conversion or color difference algorithms. If that is the issue, then how would I go about fixing that?
Here's the relevant code and functions I'm using. At this point it's a mix of stuff I wrote and stuff I've found in libraries or other StackOverflow posts. I believe the main dithering algorithm and C3 class are copied basically directly from this Github page (and changed to work with C#, obviously)
class Program
{
public static C3[] palette = new C3[]{
new C3(196, 76, 86),
new C3(186, 11, 39),
new C3(113, 0, 32),
new C3(120, 41, 56),
new C3(203, 125, 84),
new C3(205, 90, 40),
new C3(175, 50, 33),
new C3(121, 61, 54),
// etc... palette is 60 colors total
// each object contains an r, g, and b value
};
static void Main(string[] args)
{
// paths for original image and output path for dithered image
string path = #"C:\Users\BillehBawb\Desktop\";
string imgPath = path + "amy.jpg";
string createPath = path + "amydithered.jpg";
// pulls the original image, runs the dithering function, then saves the new image
Bitmap img = new Bitmap(imgPath);
Bitmap dithered = floydSteinbergDithering(img);
dithered.Save(createPath, ImageFormat.Jpeg);
}
// loops through every pixel in the image, populates a 2d array with the pixel colors, then loops through again to change the color to one in the palette and do the dithering algorithm
private static Bitmap floydSteinbergDithering(Bitmap img)
{
int w = img.Width;
int h = img.Height;
C3[,] d = new C3[h, w];
for (int y = 0; y < h; y++)
{
for (int x = 0; x < w; x++)
{
d[y, x] = new C3(img.GetPixel(x, y).ToArgb());
}
}
for (int y = 0; y < img.Height; y++)
{
for (int x = 0; x < img.Width; x++)
{
C3 oldColor = d[y, x];
C3 newColor = findClosestPaletteColor(oldColor, palette);
img.SetPixel(x, y, newColor.toColor());
C3 err = oldColor.sub(newColor);
if (x + 1 < w)
{
d[y, x + 1] = d[y, x + 1].add(err.mul(7.0 / 16));
}
if (x - 1 >= 0 && y + 1 < h)
{
d[y + 1, x - 1] = d[y + 1, x - 1].add(err.mul(3.0 / 16));
}
if (y + 1 < h)
{
d[y + 1, x] = d[y + 1, x].add(err.mul(5.0 / 16));
}
if (x + 1 < w && y + 1 < h)
{
d[y + 1, x + 1] = d[y + 1, x + 1].add(err.mul(1.0 / 16));
}
}
}
return img;
}
// loops through the palette, converts the input pixel and palette colors to the LAB format, finds the difference between all of them, and selects the palette color with the lowest difference
private static C3 findClosestPaletteColor(C3 c, C3[] palette)
{
double[] pixelLab = rgbToLab(c.toColor().R, c.toColor().G, c.toColor().B);
double minDist = Double.MaxValue;
int colorIndex = 0;
for (int i = 0; i < palette.Length; i++)
{
double[] colors = rgbToLab(palette[i].toColor().R, palette[i].toColor().G, palette[i].toColor().B);
double dist = labDist(pixelLab[0], pixelLab[1], pixelLab[2], colors[0], colors[1], colors[2]);
if (dist < minDist)
{
colorIndex = i;
minDist = dist;
}
}
return palette[colorIndex];
}
// finds the deltaE/difference between two sets of LAB colors with the CIE94 algorithm
public static double labDist(double l1, double a1, double b1, double l2, double a2, double b2)
{
var deltaL = l1 - l2;
var deltaA = a1 - a2;
var deltaB = b1 - b2;
var c1 = Math.Sqrt(Math.Pow(a1, 2) + Math.Pow(b1, 2));
var c2 = Math.Sqrt(Math.Pow(a2, 2) + Math.Pow(b2, 2));
var deltaC = c1 - c2;
var deltaH = Math.Pow(deltaA, 2) + Math.Pow(deltaB, 2) - Math.Pow(deltaC, 2);
deltaH = deltaH < 0 ? 0 : Math.Sqrt(deltaH);
double sl = 1.0;
double kc = 1.0;
double kh = 1.0;
double Kl = 1.0;
double K1 = .045;
double K2 = .015;
var sc = 1.0 + K1 * c1;
var sh = 1.0 + K2 * c1;
var i = Math.Pow(deltaL / (Kl * sl), 2) +
Math.Pow(deltaC / (kc * sc), 2) +
Math.Pow(deltaH / (kh * sh), 2);
var finalResult = i < 0 ? 0 : Math.Sqrt(i);
return finalResult;
}
// converts RGB colors to the XYZ and then LAB format so the color difference algorithm can be done
public static double[] rgbToLab(int R, int G, int B)
{
float[] xyz = new float[3];
float[] lab = new float[3];
float[] rgb = new float[3];
rgb[0] = R / 255.0f;
rgb[1] = G / 255.0f;
rgb[2] = B / 255.0f;
if (rgb[0] > .04045f)
{
rgb[0] = (float)Math.Pow((rgb[0] + .055) / 1.055, 2.4);
}
else
{
rgb[0] = rgb[0] / 12.92f;
}
if (rgb[1] > .04045f)
{
rgb[1] = (float)Math.Pow((rgb[1] + .055) / 1.055, 2.4);
}
else
{
rgb[1] = rgb[1] / 12.92f;
}
if (rgb[2] > .04045f)
{
rgb[2] = (float)Math.Pow((rgb[2] + .055) / 1.055, 2.4);
}
else
{
rgb[2] = rgb[2] / 12.92f;
}
rgb[0] = rgb[0] * 100.0f;
rgb[1] = rgb[1] * 100.0f;
rgb[2] = rgb[2] * 100.0f;
xyz[0] = ((rgb[0] * .412453f) + (rgb[1] * .357580f) + (rgb[2] * .180423f));
xyz[1] = ((rgb[0] * .212671f) + (rgb[1] * .715160f) + (rgb[2] * .072169f));
xyz[2] = ((rgb[0] * .019334f) + (rgb[1] * .119193f) + (rgb[2] * .950227f));
xyz[0] = xyz[0] / 95.047f;
xyz[1] = xyz[1] / 100.0f;
xyz[2] = xyz[2] / 108.883f;
if (xyz[0] > .008856f)
{
xyz[0] = (float)Math.Pow(xyz[0], (1.0 / 3.0));
}
else
{
xyz[0] = (xyz[0] * 7.787f) + (16.0f / 116.0f);
}
if (xyz[1] > .008856f)
{
xyz[1] = (float)Math.Pow(xyz[1], 1.0 / 3.0);
}
else
{
xyz[1] = (xyz[1] * 7.787f) + (16.0f / 116.0f);
}
if (xyz[2] > .008856f)
{
xyz[2] = (float)Math.Pow(xyz[2], 1.0 / 3.0);
}
else
{
xyz[2] = (xyz[2] * 7.787f) + (16.0f / 116.0f);
}
lab[0] = (116.0f * xyz[1]) - 16.0f;
lab[1] = 500.0f * (xyz[0] - xyz[1]);
lab[2] = 200.0f * (xyz[1] - xyz[2]);
return new double[] { lab[0], lab[1], lab[2] };
}
}
And here is the C3 class, its basically just the Color class with some math functions to make it cleaner to do the dithering on
class C3
{
int r, g, b;
public C3(int c)
{
Color color = Color.FromArgb(c);
r = color.R;
g = color.G;
b = color.B;
}
public C3(int r, int g, int b)
{
this.r = r;
this.g = g;
this.b = b;
}
public C3 add(C3 o)
{
return new C3(r + o.r, g + o.g, b + o.b);
}
public int clamp(int c)
{
return Math.Max(0, Math.Min(255, c));
}
public int diff(C3 o)
{
int Rdiff = o.r - r;
int Gdiff = o.g - g;
int Bdiff = o.b - b;
int distanceSquared = Rdiff * Rdiff + Gdiff * Gdiff + Bdiff * Bdiff;
return distanceSquared;
}
public C3 mul(double d)
{
return new C3((int)(d * r), (int)(d * g), (int)(d * b));
}
public C3 sub(C3 o)
{
return new C3(r - o.r, g - o.g, b - o.b);
}
public Color toColor()
{
return Color.FromArgb(clamp(r), clamp(g), clamp(b));
}
public int toRGB()
{
return toColor().ToArgb();
}
}
Sorry for the massive code dump, the functions are just pretty large and I want to provide everything I can. If anyone has any suggestions on a simpler or different way to do this, or if you know how to fix the issue I'm running into, please let me know. I have tried quite a few different algorithms for my desired result but I haven't been able to get any of them to do what I want them to. Any help or ideas are greatly appreciated, thank you!
It appears that when you shift the error to the neighbors in floydSteinbergDithering() the r,g,b values never get clamped until you cast them back to Color.
Since you're using int and not byte there is no prevention of overflows to negative or large values greater than 255 for r, g, and b.
You should consider implementing r,g, and b as properties that clamp to 0-255 when they're set.
This will ensure their values will never be outside your expected range (0 - 255).
class C3
{
private int r;
public int R
{
get => r;
set
{
r = clamp(value);
}
}
private int g;
public int G
{
get => g;
set
{
g = clamp(value);
}
}
private int b;
public int B
{
get => b;
set
{
b = clamp(value);
}
}
// rest of class
}

PhilipsHue XY Bri to RGB (without Philips Hue SDK)

I have managed to find code to convert RGB to XY. Can't make it work the other way around.
I have looked through Philips Hue SDK
https://github.com/johnciech/PhilipsHueSDK/blob/master/ApplicationDesignNotes/RGB%20to%20xy%20Color%20conversion.md
I have found this javascript code:
Philips hue, convert xy from api to HEX or RGB
and converted it to c# but it doesn't work as expected. I think my math is not strong enough.. please advise.
public class PhilipsHueRgbObject
{
public int Red { get; set; }
public int Green { get; set; }
public int Blue { get; set; }
}
public static PhilipsHueRgbObject xyBriToRgb(double x, double y, double bri)
{
double z = 1.0d - x - y;
double Y = bri / 255.0d; // Brightness of lamp
double X = (Y / y) * x;
double Z = (Y / y) * z;
double r = X * 1.612d - Y * 0.203d - Z * 0.302d;
double g = -X * 0.509d + Y * 1.412d + Z * 0.066d;
double b = X * 0.026d - Y * 0.072d + Z * 0.962d;
r = (r <= 0.0031308d ? 12.92d * r : (1.0d + 0.055d) * Math.Pow(r, (1.0d / 2.4d)) - 0.055d);
g = (g <= 0.0031308d ? 12.92d * g : (1.0d + 0.055d) * Math.Pow(g, (1.0d / 2.4d)) - 0.055d);
b = (b <= 0.0031308d ? 12.92d * b : (1.0d + 0.055d) * Math.Pow(b, (1.0d / 2.4d)) - 0.055d);
double maxValue = Math.Max(r, Math.Max(g, b));
r /= maxValue;
g /= maxValue;
b /= maxValue;
r = r * 255;
if (r < 0)
{
r = 255;
}
g = g * 255;
if (g < 0)
{
g = 255;
}
b = b * 255;
if (b < 0)
{
b = 255;
}
return new PhilipsHueRgbObject()
{
Red = (int)r,
Green = (int)g,
Blue = (int)b
};
}
public static string groupRBG(string ipAddress, string userName, int groupNumber, int red, int green, int blue)
{
try
{
float redArg = (red > 0.04045f) ? (float) Math.Pow((red + 0.055f)/(1.0f + 0.055f), 2.4f) : (red/12.92f);
// Convert RGB to Hue
float greenArg = (green > 0.04045f)
? (float) Math.Pow((green + 0.055f)/(1.0f + 0.055f), 2.4f)
: (green/12.92f);
float blueArg = (blue > 0.04045f)
? (float) Math.Pow((blue + 0.055f)/(1.0f + 0.055f), 2.4f)
: (blue/12.92f);
float X = redArg*0.664511f + greenArg*0.154324f + blueArg*0.162028f;
float Y = redArg*0.283881f + greenArg*0.668433f + blueArg*0.047685f;
float Z = redArg*0.000088f + greenArg*0.072310f + blueArg*0.986039f;
float x = X/(X + Y + Z);
float y = Y/(X + Y + Z);
return
httpRequest(
#"http://" + ipAddress + #"/api/" + userName + #"/groups/" + groupNumber.ToString() +
#"/action/", #"{" + "\"on\": true,\"xy\": [" + x.ToString() + "," + y.ToString() + "]" + #"}");
}
catch (Exception ex)
{
ErrorLog.Error("Error : {0}", ex.Message);
return null;
}
}
function xyBriToRgb(x, y, bri){
z = 1.0 - x - y;
Y = bri / 255.0; // Brightness of lamp
X = (Y / y) * x;
Z = (Y / y) * z;
r = X * 1.612 - Y * 0.203 - Z * 0.302;
g = -X * 0.509 + Y * 1.412 + Z * 0.066;
b = X * 0.026 - Y * 0.072 + Z * 0.962;
r = r <= 0.0031308 ? 12.92 * r : (1.0 + 0.055) * Math.pow(r, (1.0 / 2.4)) - 0.055;
g = g <= 0.0031308 ? 12.92 * g : (1.0 + 0.055) * Math.pow(g, (1.0 / 2.4)) - 0.055;
b = b <= 0.0031308 ? 12.92 * b : (1.0 + 0.055) * Math.pow(b, (1.0 / 2.4)) - 0.055;
maxValue = Math.max(r,g,b);
r /= maxValue;
g /= maxValue;
b /= maxValue;
r = r * 255; if (r < 0) { r = 255 };
g = g * 255; if (g < 0) { g = 255 };
b = b * 255; if (b < 0) { b = 255 };
return {
r :r,
g :g,
b :b
}
}
I expect to see exact same values coming back for the group.
http:// --HueBridgeIP --/api/ -- UserString --/groups
I can't make it work for xy. Instead I have decided to convert hsv to rgb and vice versa.
https://www.programmingalgorithms.com/algorithm/hsv-to-rgb
https://www.programmingalgorithms.com/algorithm/rgb-to-hsv
Works like a charm. Remember to convert Philips Hue 0-65535 to 0-360 and 0-254 to 0 to 1.

2D Perlin Noise

I have fully mastered the art of Perlin Noise in 3D, and now I'm trying to use my same implementation for a 2D algorithm.
The problem seems to be in picking my gradient directions. In 3D I use 16 gradients in evenly distributed directions and this works great.
In 2D I figured I'd use 8 gradients. up, down, left, right, and the four diagonal directions.
Here is what I get:
The general look of the noise is always correct, but the edges of the squares don't quite match up.
I have also tried using other gradients or fewer gradients but get similar results.
Here in another example you can see that the edges do match up sometimes and the results are fine in that area -
When I don't use gradients and instead just interpolate between a value picked randomly at each of the 4 corners I get the right results, which is what makes me think it is the gradient part that is messing it up.
Here is my code:
//8 different gradient directions
private Point[] grads = new Point[] {
new Point(0, 1), new Point(1, 1), new Point(1, 0), new Point(1, -1),
new Point(0, -1), new Point(-1, -1), new Point(-1, 0), new Point(-1, 1),};
//takes the dot product of a gradient and (x, y)
private float dot2D(int i, float x, float y)
{
return
grads[i].X * x + grads[i].Y * y;
}
public float Noise2D(float x, float y)
{
int
ix = (int)(x),
iy = (int)(y);
x = x - ix;
y = y - iy;
float
fx = fade(x),
fy = fade(y);
ix &= 255;
iy &= 255;
// here is where i get the index to look up in the list of
// different gradients.
// hashTable is my array of 0-255 in random order
int
g00 = hashTable[ix + hashTable[iy ]],
g10 = hashTable[ix + 1 + hashTable[iy ]],
g01 = hashTable[ix + hashTable[iy + 1]],
g11 = hashTable[ix + 1 + hashTable[iy + 1]];
// this takes the dot product to find the values to interpolate between
float
n00 = dot2D(g00 & 7, x, y),
n10 = dot2D(g10 & 7, x, y),
n01 = dot2D(g01 & 7, x, y),
n11 = dot2D(g11 & 7, x, y);
// lerp() is just normal linear interpolation
float
y1 = lerp(fx, n00, n10),
y2 = lerp(fx, n01, n11);
return
lerp(fy, y1, y2);
}
I'm in a bit of a rush, but this might be helpful. I adapted Perlin's reference implementation to C#. For 2D, just use the 3D Noise() function with a fixed z parameter. (public static float Noise(float x, float y, float z) towards the end of the class.)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using System.Diagnostics;
namespace GoEngine.Content.Entities
{
public class NoiseMaker
{
/// adapted from http://cs.nyu.edu/~perlin/noise/
// JAVA REFERENCE IMPLEMENTATION OF IMPROVED NOISE - COPYRIGHT 2002 KEN PERLIN.
private static int[] p = new int[512];
private static int[] permutation = { 151,160,137,91,90,15,
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180
};
static NoiseMaker()
{
CalculateP();
}
private static int _octaves;
private static int _halfLength = 256;
public static void SetOctaves(int octaves)
{
_octaves = octaves;
var len = (int)Math.Pow(2, octaves);
permutation = new int[len];
Reseed();
}
private static void CalculateP()
{
p = new int[permutation.Length * 2];
_halfLength = permutation.Length;
for (int i = 0; i < permutation.Length; i++)
p[permutation.Length + i] = p[i] = permutation[i];
}
public static void Reseed()
{
var random = new Random();
var perm = Enumerable.Range(0, permutation.Length).ToArray();
for (var i = 0; i < perm.Length; i++)
{
var swapIndex = random.Next(perm.Length);
var t = perm[i];
perm[i] = perm[swapIndex];
perm[swapIndex] = t;
}
permutation = perm;
CalculateP();
}
public static float Noise(Vector3 position, int octaves, ref float min, ref float max)
{
return Noise(position.X, position.Y, position.Z, octaves, ref min, ref max);
}
public static float Noise(float x, float y, float z, int octaves, ref float min, ref float max)
{
var perlin = 0f;
var octave = 1;
for (var i = 0; i < octaves; i++)
{
var noise = Noise(x * octave, y * octave, z * octave);
perlin += noise / octave;
octave *= 2;
}
perlin = Math.Abs((float)Math.Pow(perlin,2));
max = Math.Max(perlin, max);
min = Math.Min(perlin, min);
//perlin = 1f - 2 * perlin;
return perlin;
}
public static float Noise(float x, float y, float z)
{
int X = (int)Math.Floor(x) % _halfLength;
int Y = (int)Math.Floor(y) % _halfLength;
int Z = (int)Math.Floor(z) % _halfLength;
if (X < 0)
X += _halfLength;
if (Y < 0)
Y += _halfLength;
if (Z < 0)
Z += _halfLength;
x -= (int)Math.Floor(x);
y -= (int)Math.Floor(y);
z -= (int)Math.Floor(z);
var u = Fade(x);
var v = Fade(y);
var w = Fade(z);
int A = p[X] + Y, AA = p[A] + Z, AB = p[A + 1] + Z, // HASH COORDINATES OF
B = p[X + 1] + Y, BA = p[B] + Z, BB = p[B + 1] + Z; // THE 8 CUBE CORNERS,
return MathHelper.Lerp(
MathHelper.Lerp(
MathHelper.Lerp(
Grad(p[AA], x, y, z) // AND ADD
,
Grad(p[BA], x - 1, y, z) // BLENDED
,
u
)
,
MathHelper.Lerp(
Grad(p[AB], x, y - 1, z) // RESULTS
,
Grad(p[BB], x - 1, y - 1, z)
,
u
)
,
v
)
,
MathHelper.Lerp(
MathHelper.Lerp(
Grad(p[AA + 1], x, y, z - 1) // CORNERS
,
Grad(p[BA + 1], x - 1, y, z - 1) // OF CUBE
,
u
)
,
MathHelper.Lerp(
Grad(p[AB + 1], x, y - 1, z - 1)
,
Grad(p[BB + 1], x - 1, y - 1, z - 1)
,
u
)
,
v
)
,
w
);
}
static float Fade(float t) { return t * t * t * (t * (t * 6 - 15) + 10); }
static float Grad(int hash, float x, float y, float z)
{
int h = hash & 15; // CONVERT LO 4 BITS OF HASH CODE
float u = h < 8 ? x : y, // INTO 12 GRADIENT DIRECTIONS.
v = h < 4 ? y : h == 12 || h == 14 ? x : z;
return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
}
}
}
Update
Okay, I managed to create a working 2D version. Here's the class:
/// implements improved Perlin noise in 2D.
/// Transcribed from http://www.siafoo.net/snippet/144?nolinenos#perlin2003
/// </summary>
public static class Noise2d
{
private static Random _random = new Random();
private static int[] _permutation;
private static Vector2[] _gradients;
static Noise2d()
{
CalculatePermutation(out _permutation);
CalculateGradients(out _gradients);
}
private static void CalculatePermutation(out int[] p)
{
p = Enumerable.Range(0, 256).ToArray();
/// shuffle the array
for (var i = 0; i < p.Length; i++)
{
var source = _random.Next(p.Length);
var t = p[i];
p[i] = p[source];
p[source] = t;
}
}
/// <summary>
/// generate a new permutation.
/// </summary>
public static void Reseed()
{
CalculatePermutation(out _permutation);
}
private static void CalculateGradients(out Vector2[] grad)
{
grad = new Vector2[256];
for (var i = 0; i < grad.Length; i++)
{
Vector2 gradient;
do
{
gradient = new Vector2((float)(_random.NextDouble() * 2 - 1), (float)(_random.NextDouble() * 2 - 1));
}
while (gradient.LengthSquared() >= 1);
gradient.Normalize();
grad[i] = gradient;
}
}
private static float Drop(float t)
{
t = Math.Abs(t);
return 1f - t * t * t * (t * (t * 6 - 15) + 10);
}
private static float Q(float u, float v)
{
return Drop(u) * Drop(v);
}
public static float Noise(float x, float y)
{
var cell = new Vector2((float)Math.Floor(x), (float)Math.Floor(y));
var total = 0f;
var corners = new[] { new Vector2(0, 0), new Vector2(0, 1), new Vector2(1, 0), new Vector2(1, 1) };
foreach (var n in corners)
{
var ij = cell + n;
var uv = new Vector2(x - ij.X, y - ij.Y);
var index = _permutation[(int)ij.X % _permutation.Length];
index = _permutation[(index + (int)ij.Y) % _permutation.Length];
var grad = _gradients[index % _gradients.Length];
total += Q(uv.X, uv.Y) * Vector2.Dot(grad, uv);
}
return Math.Max(Math.Min(total, 1f), -1f);
}
}
Call it like this:
private void GenerateNoiseMap(int width, int height, ref Texture2D noiseTexture, int octaves)
{
var data = new float[width * height];
/// track min and max noise value. Used to normalize the result to the 0 to 1.0 range.
var min = float.MaxValue;
var max = float.MinValue;
/// rebuild the permutation table to get a different noise pattern.
/// Leave this out if you want to play with changing the number of octaves while
/// maintaining the same overall pattern.
Noise2d.Reseed();
var frequency = 0.5f;
var amplitude = 1f;
var persistence = 0.25f;
for (var octave = 0; octave < octaves; octave++)
{
/// parallel loop - easy and fast.
Parallel.For(0
, width * height
, (offset) =>
{
var i = offset % width;
var j = offset / width;
var noise = Noise2d.Noise(i*frequency*1f/width, j*frequency*1f/height);
noise = data[j * width + i] += noise * amplitude;
min = Math.Min(min, noise);
max = Math.Max(max, noise);
}
);
frequency *= 2;
amplitude /= 2;
}
if (noiseTexture != null && (noiseTexture.Width != width || noiseTexture.Height != height))
{
noiseTexture.Dispose();
noiseTexture = null;
}
if (noiseTexture==null)
{
noiseTexture = new Texture2D(Device, width, height, false, SurfaceFormat.Color);
}
var colors = data.Select(
(f) =>
{
var norm = (f - min) / (max - min);
return new Color(norm, norm, norm, 1);
}
).ToArray();
noiseTexture.SetData(colors);
}
Note that I've used a couple of XNA structures (Vector2 and Texture2D), but it should be pretty clear what they do.
If you want higher frequency (more "noisy") content with fewer octaves, increase the initial frequency value that used in the octave loop.
This implementation uses "improved" Perlin noise, which should be a bit faster than the standard version. You might also have a look at Simplex noise, which is quite a bit faster at higher dimensions.
I had to change this:
n00 = dot2D(g00 & 7, x, y),
n10 = dot2D(g10 & 7, x, y),
n01 = dot2D(g01 & 7, x, y),
n11 = dot2D(g11 & 7, x, y);
to this:
n00 = dot2D(g00 & 7, x , y ),
n10 = dot2D(g10 & 7, x - 1, y ),
n01 = dot2D(g01 & 7, x , y - 1),
n11 = dot2D(g11 & 7, x - 1, y - 1);
Basically just subtracting 1 from the x and y where needed.
If you plug in a zero value for z into your 3D equation and simply follow the math through, removing terms, you'll see that you end up with a simpler equation in the end.
Your implementation looks kind of different to the one I'm using though.
Here's a comparison of a 3D and 2D function I'm using (in JavaScript):
noise3d: function(x, y, z)
{
// Find unit cube that contains point.
var X = Math.floor(x) & 255,
Y = Math.floor(y) & 255,
Z = Math.floor(z) & 255;
// Find relative x,y,z of point in cube.
x -= Math.floor(x);
y -= Math.floor(y);
z -= Math.floor(z);
// Compute fade curves for each of x,y,z.
var u = fade(x),
v = fade(y),
w = fade(z);
// Hash coordinates of the corners.
var A = p[X ] + Y, AA = p[A] + Z, AB = p[A + 1] + Z,
B = p[X + 1] + Y, BA = p[B] + Z, BB = p[B + 1] + Z;
// Add blended results from 8 corners of cube.
return scale(
lerp(
w,
lerp(
v,
lerp(
u,
grad(p[AA], x, y, z),
grad(p[BA], x - 1, y, z)
),
lerp(
u,
grad(p[AB], x, y - 1, z),
grad(p[BB], x - 1, y - 1, z)
)
),
lerp(
v,
lerp(
u,
grad(p[AA + 1], x, y, z - 1),
grad(p[BA + 1], x - 1, y, z - 1)
),
lerp(
u,
grad(p[AB + 1], x, y - 1, z - 1),
grad(p[BB + 1], x - 1, y - 1, z - 1)
)
)
)
);
}
The 2D version involves fewer computations.
noise2d: function(x, y)
{
// Find unit square that contains point.
var X = Math.floor(x) & 255,
Y = Math.floor(y) & 255;
// Find relative x,y of point in square.
x -= Math.floor(x);
y -= Math.floor(y);
// Compute fade curves for each of x,y.
var u = fade(x),
v = fade(y);
// Hash coordinates of the corners.
var A = p[X ] + Y, AA = p[A], AB = p[A + 1],
B = p[X + 1] + Y, BA = p[B], BB = p[B + 1];
// Add blended results from the corners.
return scale(
lerp(
v,
lerp(
u,
grad(p[AA], x, y, 0),
grad(p[BA], x - 1, y, 0)
),
lerp(
u,
grad(p[AB], x, y - 1, 0),
grad(p[BB], x - 1, y - 1, 0)
)
)
);
}

Is there a built-in C#/.NET System API for HSV to RGB?

Is there an API built into the .NET framework for converting HSV to RGB? I didn't see a method in System.Drawing.Color for this, but it seems surprising that there wouldn't be one in the platform.
There isn't a built-in method for doing this, but the calculations aren't terribly complex.
Also note that Color's GetHue(), GetSaturation() and GetBrightness() return HSL values, not HSV.
The following C# code converts between RGB and HSV using the algorithms described on Wikipedia.
I already posted this answer here, but I'll copy the code here for quick reference.
The ranges are 0 - 360 for hue, and 0 - 1 for saturation or value.
public static void ColorToHSV(Color color, out double hue, out double saturation, out double value)
{
int max = Math.Max(color.R, Math.Max(color.G, color.B));
int min = Math.Min(color.R, Math.Min(color.G, color.B));
hue = color.GetHue();
saturation = (max == 0) ? 0 : 1d - (1d * min / max);
value = max / 255d;
}
public static Color ColorFromHSV(double hue, double saturation, double value)
{
int hi = Convert.ToInt32(Math.Floor(hue / 60)) % 6;
double f = hue / 60 - Math.Floor(hue / 60);
value = value * 255;
int v = Convert.ToInt32(value);
int p = Convert.ToInt32(value * (1 - saturation));
int q = Convert.ToInt32(value * (1 - f * saturation));
int t = Convert.ToInt32(value * (1 - (1 - f) * saturation));
if (hi == 0)
return Color.FromArgb(255, v, t, p);
else if (hi == 1)
return Color.FromArgb(255, q, v, p);
else if (hi == 2)
return Color.FromArgb(255, p, v, t);
else if (hi == 3)
return Color.FromArgb(255, p, q, v);
else if (hi == 4)
return Color.FromArgb(255, t, p, v);
else
return Color.FromArgb(255, v, p, q);
}
I don't think there's a method doing this in the .NET framework.
Check out Converting HSV to RGB colour using C#
This is the implementation code,
void HsvToRgb(double h, double S, double V, out int r, out int g, out int b)
{
double H = h;
while (H < 0) { H += 360; };
while (H >= 360) { H -= 360; };
double R, G, B;
if (V <= 0)
{ R = G = B = 0; }
else if (S <= 0)
{
R = G = B = V;
}
else
{
double hf = H / 60.0;
int i = (int)Math.Floor(hf);
double f = hf - i;
double pv = V * (1 - S);
double qv = V * (1 - S * f);
double tv = V * (1 - S * (1 - f));
switch (i)
{
// Red is the dominant color
case 0:
R = V;
G = tv;
B = pv;
break;
// Green is the dominant color
case 1:
R = qv;
G = V;
B = pv;
break;
case 2:
R = pv;
G = V;
B = tv;
break;
// Blue is the dominant color
case 3:
R = pv;
G = qv;
B = V;
break;
case 4:
R = tv;
G = pv;
B = V;
break;
// Red is the dominant color
case 5:
R = V;
G = pv;
B = qv;
break;
// Just in case we overshoot on our math by a little, we put these here. Since its a switch it won't slow us down at all to put these here.
case 6:
R = V;
G = tv;
B = pv;
break;
case -1:
R = V;
G = pv;
B = qv;
break;
// The color is not defined, we should throw an error.
default:
//LFATAL("i Value error in Pixel conversion, Value is %d", i);
R = G = B = V; // Just pretend its black/white
break;
}
}
r = Clamp((int)(R * 255.0));
g = Clamp((int)(G * 255.0));
b = Clamp((int)(B * 255.0));
}
/// <summary>
/// Clamp a value to 0-255
/// </summary>
int Clamp(int i)
{
if (i < 0) return 0;
if (i > 255) return 255;
return i;
}
It's not built in, but there's there's an open-source C# library called ColorMine which makes converting between color spaces pretty easy.
Rgb to Hsv:
var rgb = new Rgb {R = 123, G = 11, B = 7};
var hsv = rgb.To<Hsv>();
Hsv to Rgb:
var hsv = new Hsv { H = 360, S = .5, L = .17 }
var rgb = hsv.to<Rgb>();
For this you can use ColorHelper library:
RGB rgb = ColorConverter.HsvToRgb(new HSV(100, 100, 100));
There is no built-in method (I couldn't find it), but here is code that might help you out. (above solutions didn't work for me)
/// <summary>
/// Converts HSV color values to RGB
/// </summary>
/// <param name="h">0 - 360</param>
/// <param name="s">0 - 100</param>
/// <param name="v">0 - 100</param>
/// <param name="r">0 - 255</param>
/// <param name="g">0 - 255</param>
/// <param name="b">0 - 255</param>
private void HSVToRGB(int h, int s, int v, out int r, out int g, out int b)
{
var rgb = new int[3];
var baseColor = (h + 60) % 360 / 120;
var shift = (h + 60) % 360 - (120 * baseColor + 60 );
var secondaryColor = (baseColor + (shift >= 0 ? 1 : -1) + 3) % 3;
//Setting Hue
rgb[baseColor] = 255;
rgb[secondaryColor] = (int) ((Mathf.Abs(shift) / 60.0f) * 255.0f);
//Setting Saturation
for (var i = 0; i < 3; i++)
rgb[i] += (int) ((255 - rgb[i]) * ((100 - s) / 100.0f));
//Setting Value
for (var i = 0; i < 3; i++)
rgb[i] -= (int) (rgb[i] * (100-v) / 100.0f);
r = rgb[0];
g = rgb[1];
b = rgb[2];
}
I've searched the internet for better way to do this, but I can't find it.
This is a C# method to convert HSV to RGB, the method arguments are in range of 0-1, the output is in range of 0-255
private Color hsv2rgb (float h, float s, float v)
{
Func<float, int> f = delegate (float n)
{
float k = (n + h * 6) % 6;
return (int)((v - (v * s * (Math.Max(0, Math.Min(Math.Min(k, 4 - k), 1))))) * 255);
};
return Color.FromArgb(f(5), f(3), f(1));
}

Categories

Resources