gfoidl
myCSharp.de-Team (Moderation)

Dabei seit: 07.06.2009
Beiträge: 1.533
Entwicklungsumgebung: VS 2010 Herkunft: Waidring / Tirol
 |
|
Beschreibung:
Die statische Klasse ermöglich es zwischen den Farbräumen RGB, HSV und CIE-Lab zu konvertieren. Zusätzlich ist eine Methode enthalten mit welcher der perzeptuelle Abstand - also der wahrgenommen Abstand - zwischen zwei Farben ermittelt werden kann.
Der verwendete RGB-Farbraum entspricht dem Standard-RGB (kurz: sRGB) da dieser der in .net verwendet wird uns somit von Relevanz ist.
HSV ist gleichbedeutend mit HSB, jedoch ist das ähnlich klingende HSL verschieden!
Für den CIE-Lab wird als Referenzpunkt der Weißpunkt D65 verwendet.
Bezüglich Farbabstand dE gilt folgendes (für den Normbetrachter):
0,0...0,5 kein Unterschied merkbar
0,5...1,0 Unterschied kaum bemerkbar
1,0...2,0 merklicher Unterschied
2,0...4,0 Unterschied wahrnehmbar
4,0...5,0 wesentlicher Unterschied
5,0....... wird als andere Farbe wahrgenommen
In diesem Snippet werden keine Datentypen für das HSV- und CIE-Lab-Modell verwendet, sondern double-Felder. Dies deshalb weil das für meine Verwendung von Vorteil ist. Eine Ergänzung mit eigenen Datentypen sollte jedoch keine Probleme bereiten. Siehe auch: LowLevelGraphicsLibrary
Nun zum Code - die Bezeichnungen wurden entsprechend der Literatur gewählt.
C#-Code: |
using System;
using System.Drawing;
namespace gfoidl.Visualization
{
public static class ColorSpace
{
#region Felder
private static readonly double[] Xn = { 0.950456, 1, 1.088754 };
#endregion
#region RGB-Konvertierungen
public static double[] RGB2HSV(Color color)
{
if (color == Color.Empty) throw new ArgumentException();
double r = color.R / 255d;
double g = color.G / 255d;
double b = color.B / 255d;
double max = Math.Max(r, Math.Max(g, b));
double min = Math.Min(r, Math.Min(g, b));
double h = 0;
if (max == r && g >= b)
h = 60 * (g - b) / (max - min);
else if (max == r && g < b)
h = 60 * (g - b) / (max - min) + 360;
else if (max == g)
h = 60 * (b - r) / (max - min) + 120;
else if (max == b)
h = 60 * (r - g) / (max - min) + 240;
double s = (max == 0) ? 0 : 1 - min / max;
return new double[] { h, s, max };
}
public static double[] RGB2Lab(Color color)
{
if (color == Color.Empty) throw new ArgumentException();
double[] rgb =
{
color.R / 255d,
color.G / 255d,
color.B / 255d
};
double[] xyz = new double[3];
double[,] C_xr =
{
{ 0.412453, 0.357580, 0.180423 },
{ 0.212671, 0.715160, 0.072169 },
{ 0.019334, 0.119193, 0.950227 }
};
xyz[0] = C_xr[0, 0] * rgb[0] + C_xr[0, 1] * rgb[1] + C_xr[0, 2] * rgb[2];
xyz[1] = C_xr[1, 0] * rgb[0] + C_xr[1, 1] * rgb[1] + C_xr[1, 2] * rgb[2];
xyz[2] = C_xr[2, 0] * rgb[0] + C_xr[2, 1] * rgb[1] + C_xr[2, 2] * rgb[2];
double[] XYZ = new double[3];
for (int i = 0; i < XYZ.Length; i++)
XYZ[i] = xyz[i] / Xn[i];
const double T = 0.008856;
bool XT = XYZ[0] > T;
bool YT = XYZ[1] > T;
bool ZT = XYZ[2] > T;
double Y3 = Math.Pow(XYZ[1], 1d / 3d);
double fX = XT ? Math.Pow(XYZ[0], 1d / 3d) : 7.787 * XYZ[0] + 16d / 116d;
double fY = YT ? Y3 : 7.787 * XYZ[1] + 16d / 116d;
double fZ = ZT ? Math.Pow(XYZ[2], 1d / 3d) : 7.787 * XYZ[1] + 16d / 116d;
double[] lab = new double[3];
lab[0] = YT ? 116d * Y3 - 16d : 903.3 * XYZ[1];
lab[1] = 500d * (fX - fY);
lab[2] = 200d * (fY - fZ);
return lab;
}
#endregion
#region HSV-Konvertierungen
public static Color HSV2RGB(double[] hsv)
{
if (hsv == null) throw new ArgumentNullException();
if (hsv.Length != 3) throw new ArgumentException();
if (hsv[0] < 0 || hsv[0] > 360) throw new ArgumentException();
if (hsv[1] < 0 || hsv[1] > 1) throw new ArgumentException();
if (hsv[2] < 0 || hsv[2] > 1) throw new ArgumentException();
double[] rgb = null;
if (hsv[1] == 0)
rgb = new double[] { hsv[2], hsv[2], hsv[2] };
else
{
double sectorPos = hsv[0] / 60d;
int sectorNumber = (int)Math.Floor(sectorPos);
double fractionalSector = sectorPos - sectorNumber;
double p = hsv[2] * (1 - hsv[1]);
double q = hsv[2] * (1 - hsv[1] * fractionalSector);
double t = hsv[2] * (1 - hsv[1] * (1 - fractionalSector));
switch (sectorNumber)
{
case 0:
rgb = new double[] { hsv[2], t, p };
break;
case 1:
rgb = new double[] { q, hsv[2], p };
break;
case 2:
rgb = new double[] { p, hsv[2], t };
break;
case 3:
rgb = new double[] { p, q, hsv[2] };
break;
case 4:
rgb = new double[] { t, p, hsv[2] };
break;
case 5:
default:
rgb = new double[] { hsv[2], p, q };
break;
}
}
for (int i = 0; i < rgb.Length; i++)
rgb[i] *= 255;
return Color.FromArgb(
(int)rgb[0],
(int)rgb[1],
(int)rgb[2]);
}
#endregion
#region CIE-Lab-Konvertierungen
public static Color Lab2RGB(double[] lab)
{
if (lab == null)
throw new ArgumentNullException();
if (lab.Length != 3)
throw new ArgumentException();
double[] XYZ = new double[3];
const double T1 = 0.008856;
const double T2 = 0.206893;
double fY = Math.Pow((lab[0] + 16d) / 116d, 3);
bool YT = fY > T1;
fY = YT ? fY : lab[0] / 903.3;
XYZ[1] = fY;
fY = YT ? Math.Pow(fY, 1d / 3d) : 7.787 * fY + 16d / 116d;
double fX = lab[1] / 500d + fY;
XYZ[0] = fX > T2 ? Math.Pow(fX, 3) : (fX - 16d / 116d) / 7.787;
double fZ = fY - lab[2] / 200d;
XYZ[2] = fZ > T2 ? Math.Pow(fZ, 3) : (fZ - 16d / 116d) / 7.787;
for (int i = 0; i < XYZ.Length; i++)
XYZ[i] *= Xn[i];
double[] rgb = new double[3];
double[,] C_rx =
{
{ 3.240479, -1.537150, -0.498535 },
{ -0.969256, 1.875992, 0.041556 },
{ 0.055648, -0.204043, 1.057311 }
};
rgb[0] = C_rx[0, 0] * XYZ[0] + C_rx[0, 1] * XYZ[1] + C_rx[0, 2] * XYZ[2];
rgb[1] = C_rx[1, 0] * XYZ[0] + C_rx[1, 1] * XYZ[1] + C_rx[1, 2] * XYZ[2];
rgb[2] = C_rx[2, 0] * XYZ[0] + C_rx[2, 1] * XYZ[1] + C_rx[2, 2] * XYZ[2];
for (int i = 0; i < rgb.Length; i++)
{
rgb[i] *= 255;
if (rgb[i] < 0) rgb[i] = 0;
if (rgb[i] > 255) rgb[i] = 255;
}
return Color.FromArgb(
(int)rgb[0],
(int)rgb[1],
(int)rgb[2]);
}
#endregion
#region Farbabstände
public static double ColorDistance2(double[] lab1, double[] lab2)
{
double dist2 =
(lab1[0] - lab2[0]) * (lab1[0] - lab2[0]) +
(lab1[1] - lab2[1]) * (lab1[1] - lab2[1]) +
(lab1[2] - lab2[2]) * (lab1[2] - lab2[2]);
return dist2;
}
public static double ColorDistance(double[] lab1, double[] lab2)
{
return Math.Sqrt(ColorDistance2(lab1, lab2));
}
#endregion
}
}
|
Der Test für die Kontrolle ob die Hin- und Zurückkonvertierungen korrekt arbeiten:
C#-Code: |
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using NUnit.Framework;
namespace gfoidl.Visualization.UnitTest
{
[TestFixture]
public class ColorConversionTest
{
private IEnumerable<Color> _colors;
#region NUnit
[SetUp]
public void Setup()
{
var colorNames = Enum.GetNames(typeof(KnownColor));
_colors = from name in colorNames
where name != "Transparent"
select Color.FromName(name);
}
private void AssertColor(Color c1, Color c2, int tolerance)
{
Assert.IsTrue(Math.Abs(c1.A - c2.A) <= tolerance);
Assert.IsTrue(Math.Abs(c1.R - c2.R) <= tolerance);
Assert.IsTrue(Math.Abs(c1.G - c2.G) <= tolerance);
Assert.IsTrue(Math.Abs(c1.B - c2.B) <= tolerance);
}
[Test]
public void RGB2HSV_Test()
{
foreach (Color color in _colors)
{
double[] hsv = ColorSpace.RGB2HSV(color);
Color c = ColorSpace.HSV2RGB(hsv);
AssertColor(color, c, 1);
}
}
[Test]
public void RGB2Lab_Test()
{
foreach (Color color in _colors)
{
double[] lab = ColorSpace.RGB2Lab(color);
Color c = ColorSpace.Lab2RGB(lab);
AssertColor(color, c, 1);
}
}
#endregion
}
}
|
mfG Gü
Edit: Indexkorrektur gem. Beitrag von m0rius.
Schlagwörter: RGB, HSV, HSB, CIE-L*a*b*, CIE-Lab, Lab, Farbraum, Konvertierung, Farbmodell
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von gfoidl am 03.02.2010 15:10.
|
|