3636using System ;
3737using System . Collections . Generic ;
3838using System . Collections . Immutable ;
39+ using System . Globalization ;
3940using System . Linq ;
4041using System . Runtime . InteropServices ;
4142using System . Threading . Tasks ;
4243using Cairo ;
44+ using Pinta . Core ;
4345
4446// TODO-GTK4 (bindings, unsubmitted) - should this be added to gir.core?
4547namespace 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