Skip to content
This repository was archived by the owner on Dec 27, 2023. It is now read-only.

Commit e1df83a

Browse files
committed
Big refactor. Now we take Y=1 for white in CIE's XYZ color space
1 parent 9c3f271 commit e1df83a

File tree

10 files changed

+231
-248
lines changed

10 files changed

+231
-248
lines changed

ColorSharp/Properties/AssemblyInfo.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/**
1+
/*
22
* The MIT License (MIT)
33
* Copyright (c) 2014 Andrés Correa Casablanca
44
*
@@ -21,10 +21,10 @@
2121
* SOFTWARE.
2222
*/
2323

24-
/**
25-
* Contributors:
26-
* - Andrés Correa Casablanca <castarco@gmail.com , castarco@litipk.com>
27-
*/
24+
/*
25+
* Contributors:
26+
* - Andrés Correa Casablanca <castarco@gmail.com , castarco@litipk.com>
27+
*/
2828

2929

3030
using System.Reflection;

ColorSharp/src/ColorSpaces/AConvertibleColor.cs

Lines changed: 29 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -35,27 +35,13 @@ namespace Litipk.ColorSharp
3535
{
3636
namespace ColorSpaces
3737
{
38-
// TODO : Refactor to allow static conversors
39-
public delegate T ColorConversor<out T> (ConversionStrategy strategy=ConversionStrategy.Default) where T : AConvertibleColor;
40-
4138
public abstract class AConvertibleColor
4239
{
43-
#region constants
44-
45-
const int MAX_ACCEPTABLE_PATH_LENGH = 32;
46-
47-
#endregion
48-
49-
5040
#region properties
5141

5242
// If this "color" comes from another data source, then we keep the original data.
5343
protected AConvertibleColor DataSource = null;
5444

55-
// TODO : Make it private static... (and add accessors)
56-
// delegates
57-
protected readonly Dictionary<Type, ColorConversor<AConvertibleColor>> Conversors = new Dictionary<Type, ColorConversor<AConvertibleColor>>();
58-
5945
#endregion
6046

6147

@@ -72,116 +58,39 @@ protected AConvertibleColor(AConvertibleColor dataSource = null)
7258

7359
#region conversion skeleton
7460

75-
// Tells us the number of needed steps to convert the object to another color space.
76-
int ConversionPathLength(Type target, List<Type> visited=null)
61+
public T ConvertTo<T> (ConversionStrategy strategy=ConversionStrategy.Default) where T : AConvertibleColor
7762
{
78-
if (Conversors.ContainsKey (target)) {
79-
// TODO: This should depend on the inclusion relations between color spaces
80-
return 1;
81-
}
82-
83-
int pathLength = MAX_ACCEPTABLE_PATH_LENGH + 1;
84-
85-
Type tt = this.GetType ();
86-
87-
if (visited == null) {
88-
visited = new List<Type> { tt };
89-
} else {
90-
// We make a copy to avoid problems
91-
visited = new List<Type> (visited);
92-
if (!visited.Contains (tt)) {
93-
visited.Add (tt);
94-
}
95-
}
96-
97-
foreach (Type k in Conversors.Keys) {
98-
if (visited.Contains (k)) {
99-
continue;
100-
}
101-
102-
int tmpCpl = 1 + ((AConvertibleColor)Activator.CreateInstance(k)).ConversionPathLength (
103-
target, visited
104-
);
105-
106-
pathLength = Math.Min (pathLength, tmpCpl);
107-
}
108-
109-
return pathLength;
110-
}
111-
112-
public T ConvertTo<T> (ConversionStrategy strategy=ConversionStrategy.Default, List<Type> visited = null) where T : AConvertibleColor
113-
{
114-
int basePathLength, dataSourcePathLength;
115-
11663
Type t = typeof(T);
11764
Type tt = GetType ();
118-
Type ist = null; // Intermediate color space type
11965

12066
if (t == tt) {
12167
// Dumb conversion
12268
return (T)(AConvertibleColor)this;
12369
}
12470

125-
// We are going to compare the conversion path lengths in order to chose the best option
126-
// (with least precission loss).
12771
if (DataSource != null) {
12872
if (DataSource.GetType () == t) {
129-
// Recovering original data
13073
return (T)DataSource;
13174
}
13275

133-
dataSourcePathLength = DataSource.ConversionPathLength (t, new List<Type> {tt});
134-
} else {
135-
dataSourcePathLength = MAX_ACCEPTABLE_PATH_LENGH;
136-
}
137-
138-
if (visited == null) {
139-
visited = new List<Type> { tt };
140-
} else {
141-
// We make a copy to avoid problems
142-
visited = new List<Type> (visited);
143-
if (!visited.Contains (tt)) {
144-
visited.Add (tt);
145-
}
146-
}
147-
148-
if (Conversors.ContainsKey (t)) {
149-
// We don't use the direct conversor because maybe we can use the dataSource direct conversor
150-
basePathLength = 1;
151-
} else {
152-
basePathLength = MAX_ACCEPTABLE_PATH_LENGH + 1;
153-
154-
foreach(Type k in Conversors.Keys)
155-
{
156-
if (visited.Contains (k)) {
157-
continue;
158-
}
159-
160-
int tmpCpl = 1 + ((AConvertibleColor)Activator.CreateInstance(k)).ConversionPathLength (
161-
t, visited
162-
);
163-
164-
if (tmpCpl < basePathLength) {
165-
ist = k;
166-
basePathLength = tmpCpl;
167-
}
168-
}
76+
return DataSource.ConvertTo<T> (strategy);
16977
}
17078

171-
if (dataSourcePathLength > MAX_ACCEPTABLE_PATH_LENGH && basePathLength > MAX_ACCEPTABLE_PATH_LENGH) {
172-
throw new InvalidCastException ("Unable to find a conversion path to this color space.");
173-
}
79+
return InnerConvertTo<T> (strategy);
80+
}
17481

175-
if (DataSource != null && dataSourcePathLength <= basePathLength + 1) {
176-
return DataSource.ConvertTo<T> (strategy);
177-
}
82+
protected T InnerConvertTo<T> (ConversionStrategy strategy = ConversionStrategy.Default) where T : AConvertibleColor
83+
{
84+
Type t = typeof(T);
17885

179-
if (basePathLength == 1) {
180-
return (T)Conversors [t] (strategy);
181-
}
86+
if (t == typeof(CIEXYZ))
87+
return (T)(AConvertibleColor)ToCIEXYZ (strategy);
88+
if (t == typeof(CIExyY))
89+
return (T)(AConvertibleColor)ToCIExyY(strategy);
90+
if (t == typeof(SRGB))
91+
return (T)(AConvertibleColor)ToSRGB (strategy);
18292

183-
visited.Add (ist);
184-
return Conversors [ist] ().ConvertTo<T>(strategy, visited);
93+
throw new NotImplementedException ("This conversion isn't implemented.");
18594
}
18695

18796
#endregion
@@ -194,6 +103,21 @@ public T ConvertTo<T> (ConversionStrategy strategy=ConversionStrategy.Default, L
194103
*/
195104
public abstract bool IsInsideColorSpace ();
196105

106+
/**
107+
* <summary>Converts the color sample to a CIE's 1931 XYZ color sample.</summary>
108+
*/
109+
public abstract CIEXYZ ToCIEXYZ(ConversionStrategy strategy = ConversionStrategy.Default);
110+
111+
/**
112+
* <summary>Converts the color sample to a CIE's 1931 xyY color sample.</summary>
113+
*/
114+
public abstract CIExyY ToCIExyY (ConversionStrategy strategy = ConversionStrategy.Default);
115+
116+
/**
117+
* <summary>Converts the color sample to an HP's & Microsoft's 1996 sRGB sample.</summary>
118+
*/
119+
public abstract SRGB ToSRGB(ConversionStrategy strategy = ConversionStrategy.Default);
120+
197121
#endregion
198122
}
199123
}

ColorSharp/src/ColorSpaces/CIEXYZ.cs

Lines changed: 70 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -36,34 +36,40 @@ namespace Litipk.ColorSharp
3636
namespace ColorSpaces
3737
{
3838
/**
39-
* CIE 1931 (2º) XYZ Color Space.
39+
* <summary>CIE's 1931 (2º) XYZ Color Space.</summary>
4040
*/
4141
public sealed class CIEXYZ : AConvertibleColor
4242
{
43-
#region readonly properties
43+
#region properties
4444

45-
public readonly double X, Y, Z;
45+
/**
46+
* <value>X component of the CIE's 1931 XYZ color space.</value>
47+
*/
48+
public readonly double X;
49+
50+
/**
51+
* <value>Y component of the CIE's 1931 XYZ color space.</value>
52+
*/
53+
public readonly double Y;
54+
55+
/**
56+
* <value>Z component of the CIE's 1931 XYZ color space.</value>
57+
*/
58+
public readonly double Z;
4659

4760
#endregion
4861

4962

5063
#region constructors
5164

52-
public CIEXYZ() {
53-
Conversors.Add (typeof(SRGB), ToSRGB);
54-
Conversors.Add (typeof(CIExyY), ToxyY);
55-
}
56-
5765
/**
58-
* This constructor "installs" the conversor methods into the instance
66+
* <summary>Creates a new color sample in the CIE's 1931 xyY color space</summary>
67+
* <param name="X">CIE's 1931 XYZ X coordinate</param>
68+
* <param name="Y">CIE's 1931 XYZ Y coordinate</param>
69+
* <param name="Z">CIE's 1931 XYZ Z coordinate</param>
70+
* <param name="dataSource">If you aren't working with ColorSharp internals, don't use this parameter</param>
5971
*/
60-
private CIEXYZ(AConvertibleColor dataSource=null) : base(dataSource) {
61-
Conversors.Add (typeof(SRGB), ToSRGB);
62-
Conversors.Add (typeof(CIExyY), ToxyY);
63-
}
64-
65-
// Constructor
66-
public CIEXYZ (double X, double Y, double Z, AConvertibleColor dataSource=null) : this(dataSource)
72+
public CIEXYZ (double X, double Y, double Z, AConvertibleColor dataSource=null) : base(dataSource)
6773
{
6874
this.X = X;
6975
this.Y = Y;
@@ -73,53 +79,74 @@ public CIEXYZ (double X, double Y, double Z, AConvertibleColor dataSource=null)
7379
#endregion
7480

7581

76-
#region conversors
82+
#region AConvertibleColor methods
7783

78-
public CIExyY ToxyY (ConversionStrategy strategy=ConversionStrategy.Default)
84+
/**
85+
* <inheritdoc />
86+
*/
87+
public override bool IsInsideColorSpace()
7988
{
80-
double XYZ = X + Y + Z;
81-
return new CIExyY (X / XYZ, Y / XYZ, Y, DataSource ?? this);
89+
return ToCIExyY ().IsInsideColorSpace ();
90+
}
91+
92+
/**
93+
* <inheritdoc />
94+
*/
95+
public override CIEXYZ ToCIEXYZ (ConversionStrategy strategy=ConversionStrategy.Default)
96+
{
97+
return this;
8298
}
8399

84100
/**
85-
* Converts the CIE 1931 XYZ sample to a HP's & Microsoft's 1996 sRGB sample
101+
* <inheritdoc />
86102
*/
87-
public SRGB ToSRGB (ConversionStrategy strategy=ConversionStrategy.Default)
103+
public override CIExyY ToCIExyY (ConversionStrategy strategy=ConversionStrategy.Default)
88104
{
89-
double tx = X / 100.0;
90-
double ty = Y / 100.0;
91-
double tz = Z / 100.0;
105+
double XYZ = X + Y + Z;
106+
return new CIExyY (X / XYZ, Y / XYZ, Y, DataSource ?? this);
107+
}
92108

109+
/**
110+
* <inheritdoc />
111+
*/
112+
public override SRGB ToSRGB (ConversionStrategy strategy=ConversionStrategy.Default)
113+
{
93114
// Linear transformation
94-
double r = tx * 3.2406 + ty * -1.5372 + tz * -0.4986;
95-
double g = tx * -0.9689 + ty * 1.8758 + tz * 0.0415;
96-
double b = tx * 0.0557 + ty * -0.2040 + tz * 1.0570;
115+
double r = X * 3.2406 + Y * -1.5372 + Z * -0.4986;
116+
double g = X * -0.9689 + Y * 1.8758 + Z * 0.0415;
117+
double b = X * 0.0557 + Y * -0.2040 + Z * 1.0570;
97118

98119
// Gamma correction
99120
r = r > 0.0031308 ? 1.055 * Math.Pow(r, 1 / 2.4) - 0.055 : 12.92 * r;
100121
g = g > 0.0031308 ? 1.055 * Math.Pow(g, 1 / 2.4) - 0.055 : 12.92 * g;
101122
b = b > 0.0031308 ? 1.055 * Math.Pow(b, 1 / 2.4) - 0.055 : 12.92 * b;
102123

103-
if ((strategy & ConversionStrategy.ForceStretching) != 0) {
124+
if ((strategy & ConversionStrategy.ForceLowStretch) != 0) {
104125
double minChannelValue = Math.Min (r, Math.Min (g, b));
105126

106127
if (minChannelValue < 0.0) {
107128
r -= minChannelValue;
108129
g -= minChannelValue;
109130
b -= minChannelValue;
110131
}
111-
} else if ((strategy & ConversionStrategy.ForceTruncating) != 0) {
132+
} else if ((strategy & ConversionStrategy.ForceLowTruncate) != 0) {
112133
r = Math.Max (0.0, r);
113134
g = Math.Max (0.0, g);
114135
b = Math.Max (0.0, b);
115136
}
116137

117-
double maxChannelValue = Math.Max (r, Math.Max (g, b));
138+
if ((strategy & ConversionStrategy.ForceHighStretch) != 0) {
139+
double maxChannelValue = Math.Max (r, Math.Max (g, b));
118140

119-
if (maxChannelValue > 1.0) {
120-
r = r / maxChannelValue;
121-
g = g / maxChannelValue;
122-
b = b / maxChannelValue;
141+
if (maxChannelValue > 1.0) {
142+
r = r / maxChannelValue;
143+
g = g / maxChannelValue;
144+
b = b / maxChannelValue;
145+
}
146+
} else if ((strategy & ConversionStrategy.ForceHighTruncate) != 0) {
147+
r = Math.Min (1.0, r);
148+
g = Math.Min (1.0, g);
149+
b = Math.Min (1.0, b);
123150
}
124151

125152
return new SRGB(r, g, b, DataSource ?? this);
@@ -128,13 +155,11 @@ public SRGB ToSRGB (ConversionStrategy strategy=ConversionStrategy.Default)
128155
#endregion
129156

130157

131-
#region inherited methods
132-
133-
public override bool IsInsideColorSpace()
134-
{
135-
return ConvertTo<CIExyY> ().IsInsideColorSpace ();
136-
}
158+
#region Object methods
137159

160+
/**
161+
* <inheritdoc />
162+
*/
138163
public override bool Equals(Object obj)
139164
{
140165
CIEXYZ xyzObj = obj as CIEXYZ;
@@ -148,6 +173,10 @@ public override bool Equals(Object obj)
148173
Math.Abs (Z - xyzObj.Z) <= double.Epsilon
149174
);
150175
}
176+
177+
/**
178+
* <inheritdoc />
179+
*/
151180
public override int GetHashCode ()
152181
{
153182
int hash = 23;
@@ -159,7 +188,6 @@ public override int GetHashCode ()
159188
return hash;
160189
}
161190

162-
163191
#endregion
164192
}
165193
}

0 commit comments

Comments
 (0)