Skip to content

Commit 7d8d0d1

Browse files
committed
Shift ColorExtensions.cs to CairoExtensions.cs
1 parent 317b07d commit 7d8d0d1

4 files changed

Lines changed: 241 additions & 247 deletions

File tree

Pinta.Core/Effects/HsvColor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public HsvColor (double hue, double sat, double val)
4040

4141
public static HsvColor FromColor (Color c) => c.ToHsv ();
4242
public static HsvColor FromRgb (double r, double g, double b) => new Color (r, g, b).ToHsv ();
43-
public Color ToColor (double alpha = 1) => ColorExtensions.FromHsv (this);
43+
public Color ToColor (double alpha = 1) => Color.FromHsv (this);
4444

4545
public ColorBgra ToBgra ()
4646
{

Pinta.Core/Extensions/CairoExtensions.cs

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,12 @@
3636
using System;
3737
using System.Collections.Generic;
3838
using System.Collections.Immutable;
39+
using System.Globalization;
3940
using System.Linq;
4041
using System.Runtime.InteropServices;
4142
using System.Threading.Tasks;
4243
using Cairo;
44+
using Pinta.Core;
4345

4446
// TODO-GTK4 (bindings, unsubmitted) - should this be added to gir.core?
4547
namespace Cairo
@@ -53,6 +55,241 @@ public readonly record struct Color (
5355
public Color (double r, double g, double b)
5456
: this (r, g, b, 1.0)
5557
{ }
58+
59+
/// <summary>
60+
/// Returns the color value as a string in hex color format.
61+
/// </summary>
62+
/// <param name="addAlpha">If false, returns in format "RRGGBB" (Alpha will not affect result).<br/>
63+
/// Otherwise, returns in format "RRGGBBAA".</param>
64+
public string ToHex (bool addAlpha = true)
65+
{
66+
int r = Convert.ToInt32 (R * 255.0);
67+
int g = Convert.ToInt32 (G * 255.0);
68+
int b = Convert.ToInt32 (B * 255.0);
69+
int a = Convert.ToInt32 (A * 255.0);
70+
71+
if (addAlpha)
72+
return $"{r:X2}{g:X2}{b:X2}{a:X2}";
73+
else
74+
return $"{r:X2}{g:X2}{b:X2}";
75+
}
76+
77+
/// <summary>
78+
/// Returns a color from an RGBA hex color. Accepts the following formats:<br/>
79+
/// RRGGBBAA<br/>
80+
/// RRGGBB<br/>
81+
/// RGB (Expands to RRGGBB)<br/>
82+
/// RGBA (Expands to RRGGBBAA)<br/>
83+
/// Will accept leading #.
84+
/// </summary>
85+
/// <param name="hex">Hex color as a string.</param>
86+
/// <returns>Resulting color. If null, the method could not parse it.</returns>
87+
public static Color? FromHex (string hex)
88+
{
89+
if (hex.StartsWith ("#"))
90+
hex = hex.Remove (0, 1);
91+
92+
// handle shorthand hex
93+
if (hex.Length == 3)
94+
hex = $"{hex[0]}{hex[0]}{hex[1]}{hex[1]}{hex[2]}{hex[2]}";
95+
if (hex.Length == 4)
96+
hex = $"{hex[0]}{hex[0]}{hex[1]}{hex[1]}{hex[2]}{hex[2]}{hex[3]}{hex[3]}";
97+
98+
if (hex.Length != 6 && hex.Length != 8)
99+
return null;
100+
try {
101+
102+
int r = int.Parse (hex.Substring (0, 2), NumberStyles.HexNumber);
103+
int g = int.Parse (hex.Substring (2, 2), NumberStyles.HexNumber);
104+
int b = int.Parse (hex.Substring (4, 2), NumberStyles.HexNumber);
105+
int a = 255;
106+
if (hex.Length > 6)
107+
a = int.Parse (hex.Substring (6, 2), NumberStyles.HexNumber);
108+
return new Color (r / 255.0, g / 255.0, b / 255.0, a / 255.0);
109+
} catch {
110+
return null;
111+
}
112+
}
113+
114+
/// <summary>
115+
/// Copied from RgbColor.ToHsv<br/>
116+
/// Returns the Cairo color in HSV value.
117+
/// </summary>
118+
/// <returns>HSV struct.
119+
/// Hue varies from 0 - 360.<br/>
120+
/// Saturation and value varies from 0 - 1.
121+
/// </returns>
122+
public HsvColor ToHsv ()
123+
{
124+
// In this function, R, G, and B values must be scaled
125+
// to be between 0 and 1.
126+
// HsvColor.Hue will be a value between 0 and 360, and
127+
// HsvColor.Saturation and value are between 0 and 1.
128+
129+
double h, s, v;
130+
131+
double min = Math.Min (Math.Min (R, G), B);
132+
double max = Math.Max (Math.Max (R, G), B);
133+
134+
double delta = max - min;
135+
136+
if (max == 0 || delta == 0) {
137+
// R, G, and B must be 0, or all the same.
138+
// In this case, S is 0, and H is undefined.
139+
// Using H = 0 is as good as any...
140+
s = 0;
141+
h = 0;
142+
v = max;
143+
} else {
144+
s = delta / max;
145+
if (R == max) {
146+
// Between Yellow and Magenta
147+
h = (G - B) / delta;
148+
} else if (G == max) {
149+
// Between Cyan and Yellow
150+
h = 2 + (B - R) / delta;
151+
} else {
152+
// Between Magenta and Cyan
153+
h = 4 + (R - G) / delta;
154+
}
155+
v = max;
156+
}
157+
// Scale h to be between 0 and 360.
158+
// This may require adding 360, if the value
159+
// is negative.
160+
h *= 60;
161+
162+
if (h < 0) {
163+
h += 360;
164+
}
165+
166+
// Scale to the requirements of this
167+
// application. All values are between 0 and 255.
168+
return new HsvColor (h, s, v);
169+
}
170+
171+
/// <summary>
172+
/// Returns a copy of the original color, replacing provided HSV components.
173+
/// HSV components not changed will retain their values from the original color.
174+
/// </summary>
175+
/// <param name="hue">Hue component, 0 - 360</param>
176+
/// <param name="sat">Saturation component, 0 - 1</param>
177+
/// <param name="value">Value component, 0 - 1</param>
178+
/// <param name="alpha">Alpha component, 0 - 1</param>
179+
public Color CopyHsv (double? hue = null, double? sat = null, double? value = null, double? alpha = null)
180+
{
181+
var hsv = ToHsv ();
182+
183+
double h = hue ?? hsv.Hue;
184+
double s = sat ?? hsv.Sat;
185+
double v = value ?? hsv.Val;
186+
double a = alpha ?? A;
187+
188+
return FromHsv (h, s, v, a);
189+
}
190+
191+
/// <summary>
192+
/// Returns a RGBA Cairo color using the given HsvColor.
193+
/// </summary>
194+
/// <param name="alpha">Alpha of the new Cairo color, 0 - 1</param>
195+
public static Color FromHsv (HsvColor hsv, double alpha = 1) => FromHsv (hsv.Hue, hsv.Sat, hsv.Val, alpha);
196+
197+
/// <summary>
198+
/// Returns a RGBA Cairo color using the given HSV values.
199+
/// </summary>
200+
/// <param name="hue">Hue component, 0 - 360</param>
201+
/// <param name="sat">Saturation component, 0 - 1</param>
202+
/// <param name="value">Value component, 0 - 1</param>
203+
/// <param name="alpha">Alpha component, 0 - 1</param>
204+
public static Color FromHsv (double hue, double sat, double value, double alpha = 1)
205+
{
206+
double h = hue;
207+
double s = sat;
208+
double v = value;
209+
210+
// Stupid hack!
211+
// If v or s is set to 0, it results in data loss for hue / sat. So we force it to be slightly above zero.
212+
if (v == 0)
213+
v = 0.0001;
214+
if (s == 0)
215+
s = 0.0001;
216+
217+
// HsvColor contains values scaled as in the color wheel.
218+
// Scale Hue to be between 0 and 360. Saturation
219+
// and value scale to be between 0 and 1.
220+
h %= 360.0;
221+
222+
double r = 0;
223+
double g = 0;
224+
double b = 0;
225+
226+
if (s == 0) {
227+
// If s is 0, all colors are the same.
228+
// This is some flavor of gray.
229+
r = v;
230+
g = v;
231+
b = v;
232+
} else {
233+
// The color wheel consists of 6 sectors.
234+
// Figure out which sector you're in.
235+
double sectorPos = h / 60;
236+
int sectorNumber = (int) (Math.Floor (sectorPos));
237+
238+
// get the fractional part of the sector.
239+
// That is, how many degrees into the sector
240+
// are you?
241+
double fractionalSector = sectorPos - sectorNumber;
242+
243+
// Calculate values for the three axes
244+
// of the color.
245+
double p = v * (1 - s);
246+
double q = v * (1 - (s * fractionalSector));
247+
double t = v * (1 - (s * (1 - fractionalSector)));
248+
249+
// Assign the fractional colors to r, g, and b
250+
// based on the sector the angle is in.
251+
switch (sectorNumber) {
252+
case 0:
253+
r = v;
254+
g = t;
255+
b = p;
256+
break;
257+
258+
case 1:
259+
r = q;
260+
g = v;
261+
b = p;
262+
break;
263+
264+
case 2:
265+
r = p;
266+
g = v;
267+
b = t;
268+
break;
269+
270+
case 3:
271+
r = p;
272+
g = q;
273+
b = v;
274+
break;
275+
276+
case 4:
277+
r = t;
278+
g = p;
279+
b = v;
280+
break;
281+
282+
case 5:
283+
r = v;
284+
g = p;
285+
b = q;
286+
break;
287+
}
288+
}
289+
// return an RgbColor structure, with values scaled
290+
// to be between 0 and 255.
291+
return new Color (r, g, b, alpha);
292+
}
56293
}
57294
}
58295

0 commit comments

Comments
 (0)