├── .gitignore
├── AvaloniaColorPicker
├── .gitignore
├── AnimatableColourCanvas.cs
├── AnimatableLAB.cs
├── AnimatablePath.cs
├── AnimatableTransform.cs
├── AvaloniaColorPicker.csproj
├── ColorButton.cs
├── ColorPicker.axaml
├── ColorPicker.axaml.cs
├── ColorPickerWindow.axaml
├── ColorPickerWindow.axaml.cs
├── ColorVisualBrush.cs
├── ColourBlindness.cs
├── Colours.cs
├── CustomColorPicker
│ ├── AControl.axaml
│ ├── AControl.axaml.cs
│ ├── CIELABControls.axaml
│ ├── CIELABControls.axaml.cs
│ ├── ColorBlindnessSelector.axaml
│ ├── ColorBlindnessSelector.axaml.cs
│ ├── ColorCanvasControls.axaml
│ ├── ColorCanvasControls.axaml.cs
│ ├── ColorComponentSelector.axaml
│ ├── ColorComponentSelector.axaml.cs
│ ├── ColorPreview.axaml
│ ├── ColorPreview.axaml.cs
│ ├── ColorSpacePreview.axaml
│ ├── ColorSpacePreview.axaml.cs
│ ├── ColorSpaceSelector.axaml
│ ├── ColorSpaceSelector.axaml.cs
│ ├── CustomColorPicker.cs
│ ├── HSBControls.axaml
│ ├── HSBControls.axaml.cs
│ ├── HexControl.axaml
│ ├── HexControl.axaml.cs
│ ├── OutsideRGBWarning.axaml
│ ├── OutsideRGBWarning.axaml.cs
│ ├── PaletteControl.cs
│ ├── RGBControls.axaml
│ └── RGBControls.axaml.cs
├── HSB.cs
├── Lab.cs
├── LabColorSpace.bin
├── LabEllipsePosition.cs
├── Palette.cs
├── PaletteSelector.cs
├── Palettes
│ ├── Colorful.palette
│ ├── IBM.palette
│ ├── OkabeIto.palette
│ ├── Tol_Bright.palette
│ ├── Tol_Light.palette
│ ├── Tol_Muted.palette
│ └── Tol_Vibrant.palette
└── RGB.cs
├── ColorButtonDemo
├── App.axaml
├── App.axaml.cs
├── ColorButtonDemo.csproj
├── MainWindow.axaml
├── MainWindow.axaml.cs
└── Program.cs
├── ColorPicker.sln
├── ColorPickerDemo
├── App.axaml
├── App.axaml.cs
├── ColorPickerDemo.csproj
├── MainWindow.axaml
├── MainWindow.axaml.cs
└── Program.cs
├── CustomColorPickerExample
├── App.axaml
├── App.axaml.cs
├── CustomColorPickerExample.csproj
├── MainWindow.axaml
├── MainWindow.axaml.cs
└── Program.cs
├── LICENSE
├── LICENSE.GPL
├── LabColorSpaceCache
├── LabColorSpaceCache.csproj
└── Program.cs
├── Readme.md
├── icon.png
├── icon.svg
├── parts.svg
└── screenshot.png
/.gitignore:
--------------------------------------------------------------------------------
1 | .vs
2 | */bin/
3 | */obj/
4 |
--------------------------------------------------------------------------------
/AvaloniaColorPicker/.gitignore:
--------------------------------------------------------------------------------
1 | # The following command works for downloading when using Git for Windows:
2 | # curl -LOf http://gist.githubusercontent.com/kmorcinek/2710267/raw/.gitignore
3 | #
4 | # Download this file using PowerShell v3 under Windows with the following comand:
5 | # Invoke-WebRequest https://gist.githubusercontent.com/kmorcinek/2710267/raw/ -OutFile .gitignore
6 | #
7 | # or wget:
8 | # wget --no-check-certificate http://gist.githubusercontent.com/kmorcinek/2710267/raw/.gitignore
9 |
10 | # User-specific files
11 | *.suo
12 | *.user
13 | *.sln.docstates
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Rr]elease/
18 | x64/
19 | [Bb]in/
20 | [Oo]bj/
21 | # build folder is nowadays used for build scripts and should not be ignored
22 | #build/
23 |
24 | # NuGet Packages
25 | *.nupkg
26 | # The packages folder can be ignored because of Package Restore
27 | **/packages/*
28 | # except build/, which is used as an MSBuild target.
29 | !**/packages/build/
30 | # Uncomment if necessary however generally it will be regenerated when needed
31 | #!**/packages/repositories.config
32 |
33 | # MSTest test Results
34 | [Tt]est[Rr]esult*/
35 | [Bb]uild[Ll]og.*
36 |
37 | *_i.c
38 | *_p.c
39 | *.ilk
40 | *.meta
41 | *.obj
42 | *.pch
43 | *.pdb
44 | *.pgc
45 | *.pgd
46 | *.rsp
47 | *.sbr
48 | *.tlb
49 | *.tli
50 | *.tlh
51 | *.tmp
52 | *.tmp_proj
53 | *.log
54 | *.vspscc
55 | *.vssscc
56 | .builds
57 | *.pidb
58 | *.scc
59 |
60 | # Visual C++ cache files
61 | ipch/
62 | *.aps
63 | *.ncb
64 | *.opensdf
65 | *.sdf
66 | *.cachefile
67 |
68 | # Visual Studio profiler
69 | *.psess
70 | *.vsp
71 | *.vspx
72 |
73 | # Guidance Automation Toolkit
74 | *.gpState
75 |
76 | # ReSharper is a .NET coding add-in
77 | _ReSharper*/
78 | *.[Rr]e[Ss]harper
79 |
80 | # TeamCity is a build add-in
81 | _TeamCity*
82 |
83 | # DotCover is a Code Coverage Tool
84 | *.dotCover
85 |
86 | # NCrunch
87 | *.ncrunch*
88 | .*crunch*.local.xml
89 |
90 | # Installshield output folder
91 | [Ee]xpress/
92 |
93 | # DocProject is a documentation generator add-in
94 | DocProject/buildhelp/
95 | DocProject/Help/*.HxT
96 | DocProject/Help/*.HxC
97 | DocProject/Help/*.hhc
98 | DocProject/Help/*.hhk
99 | DocProject/Help/*.hhp
100 | DocProject/Help/Html2
101 | DocProject/Help/html
102 |
103 | # Click-Once directory
104 | publish/
105 |
106 | # Publish Web Output
107 | *.Publish.xml
108 |
109 | # Windows Azure Build Output
110 | csx
111 | *.build.csdef
112 |
113 | # Windows Store app package directory
114 | AppPackages/
115 |
116 | # Others
117 | *.Cache
118 | ClientBin/
119 | [Ss]tyle[Cc]op.*
120 | ~$*
121 | *~
122 | *.dbmdl
123 | *.[Pp]ublish.xml
124 | *.pfx
125 | *.publishsettings
126 | modulesbin/
127 | tempbin/
128 |
129 | # EPiServer Site file (VPP)
130 | AppData/
131 |
132 | # RIA/Silverlight projects
133 | Generated_Code/
134 |
135 | # Backup & report files from converting an old project file to a newer
136 | # Visual Studio version. Backup files are not needed, because we have git ;-)
137 | _UpgradeReport_Files/
138 | Backup*/
139 | UpgradeLog*.XML
140 | UpgradeLog*.htm
141 |
142 | # vim
143 | *.txt~
144 | *.swp
145 | *.swo
146 |
147 | # Temp files when opening LibreOffice on ubuntu
148 | .~lock.*
149 |
150 | # svn
151 | .svn
152 |
153 | # CVS - Source Control
154 | **/CVS/
155 |
156 | # Remainings from resolving conflicts in Source Control
157 | *.orig
158 |
159 | # SQL Server files
160 | **/App_Data/*.mdf
161 | **/App_Data/*.ldf
162 | **/App_Data/*.sdf
163 |
164 |
165 | #LightSwitch generated files
166 | GeneratedArtifacts/
167 | _Pvt_Extensions/
168 | ModelManifest.xml
169 |
170 | # =========================
171 | # Windows detritus
172 | # =========================
173 |
174 | # Windows image file caches
175 | Thumbs.db
176 | ehthumbs.db
177 |
178 | # Folder config file
179 | Desktop.ini
180 |
181 | # Recycle Bin used on file shares
182 | $RECYCLE.BIN/
183 |
184 | # OS generated files #
185 | Icon?
186 |
187 | # Mac desktop service store files
188 | .DS_Store
189 |
190 | # SASS Compiler cache
191 | .sass-cache
192 |
193 | # Visual Studio 2014 CTP
194 | **/*.sln.ide
195 |
196 | # Visual Studio temp something
197 | .vs/
198 |
199 | # dotnet stuff
200 | project.lock.json
201 |
202 | # VS 2015+
203 | *.vc.vc.opendb
204 | *.vc.db
205 |
206 | # Rider
207 | .idea/
208 |
209 | # Visual Studio Code
210 | .vscode/
211 |
212 | # Output folder used by Webpack or other FE stuff
213 | **/node_modules/*
214 | **/wwwroot/*
215 |
216 | # SpecFlow specific
217 | *.feature.cs
218 | *.feature.xlsx.*
219 | *.Specs_*.html
220 |
221 | # UWP Projects
222 | AppPackages/
223 |
224 | #####
225 | # End of core ignore list, below put you custom 'per project' settings (patterns or path)
226 | #####
227 |
--------------------------------------------------------------------------------
/AvaloniaColorPicker/AnimatableColourCanvas.cs:
--------------------------------------------------------------------------------
1 | /*
2 | AvaloniaColorPicker - A color picker for Avalonia.
3 | Copyright (C) 2021 Giorgio Bianchini
4 |
5 | This program is free software: you can redistribute it and/or modify
6 | it under the terms of the GNU Lesser General Public License as published by
7 | the Free Software Foundation, version 3.
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Lesser General Public License for more details.
12 | You should have received a copy of the GNU Lesser General Public License
13 | along with this program. If not, see .
14 | */
15 |
16 | using Avalonia;
17 | using Avalonia.Animation;
18 | using Avalonia.Animation.Easings;
19 | using Avalonia.Controls;
20 | using Avalonia.Media;
21 | using Avalonia.Media.Imaging;
22 | using System;
23 |
24 | namespace AvaloniaColorPicker
25 | {
26 | internal class AnimatableColourCanvas : Animatable
27 | {
28 | public static readonly StyledProperty RGBProperty = AvaloniaProperty.Register(nameof(RGB));
29 | public Color RGB
30 | {
31 | get
32 | {
33 | return GetValue(RGBProperty);
34 | }
35 | set
36 | {
37 | SetValue(RGBProperty, value);
38 | }
39 | }
40 |
41 | public static readonly StyledProperty HSVProperty = AvaloniaProperty.Register(nameof(HSV));
42 | public HSV HSV
43 | {
44 | get
45 | {
46 | return GetValue(HSVProperty);
47 | }
48 | set
49 | {
50 | SetValue(HSVProperty, value);
51 | }
52 | }
53 |
54 | public static readonly StyledProperty LABProperty = AvaloniaProperty.Register(nameof(LAB));
55 | public LAB LAB
56 | {
57 | get
58 | {
59 | return GetValue(LABProperty);
60 | }
61 | set
62 | {
63 | SetValue(LABProperty, value);
64 | }
65 | }
66 |
67 | private ColorPicker.ColorComponents ColorComponent { get; set; }
68 |
69 | public Image Canvas2D { get; }
70 | public Image Canvas1D { get; }
71 | public Image AlphaCanvas { get; }
72 |
73 | private HSVTransition HSVTransition { get; }
74 | private RGBTransition RGBTransition { get; }
75 | private LABTransition LABTransition { get; }
76 |
77 | public AnimatableColourCanvas()
78 | {
79 | Canvas2D = new Image() { Width = 256, Height = 256 };
80 | Canvas1D = new Image() { Width = 24, Height = 256, Stretch = Avalonia.Media.Stretch.Fill };
81 | AlphaCanvas = new Image() { Width = 24, Height = 256, Stretch = Avalonia.Media.Stretch.Fill };
82 |
83 | if (!ColorPicker.TransitionsDisabled)
84 | {
85 | this.Transitions = new Transitions();
86 |
87 | HSVTransition = new HSVTransition() { Property = HSVProperty, Duration = new TimeSpan(0, 0, 0, 0, 100) };
88 | this.Transitions.Add(HSVTransition);
89 |
90 | LABTransition = new LABTransition() { Property = LABProperty, Duration = new TimeSpan(0, 0, 0, 0, 100) };
91 | this.Transitions.Add(LABTransition);
92 |
93 | RGBTransition = new RGBTransition() { Property = RGBProperty, Duration = new TimeSpan(0, 0, 0, 0, 100) };
94 | this.Transitions.Add(RGBTransition);
95 | }
96 | }
97 |
98 |
99 | protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
100 | {
101 | base.OnPropertyChanged(change);
102 |
103 | if (change.Property == RGBProperty)
104 | {
105 | Update(ColorPicker.ColorSpaces.RGB);
106 | }
107 | else if (change.Property == HSVProperty)
108 | {
109 | Update(ColorPicker.ColorSpaces.HSB);
110 | }
111 | else if (change.Property == LABProperty)
112 | {
113 | Update(ColorPicker.ColorSpaces.LAB);
114 | }
115 | }
116 |
117 | public void UpdateR(byte R, byte G, byte B, bool instantTransition)
118 | {
119 | if (!ColorPicker.TransitionsDisabled)
120 | {
121 | if (instantTransition)
122 | {
123 | this.RGBTransition.Easing = new InstantEasing();
124 | }
125 | else
126 | {
127 | this.RGBTransition.Easing = new LinearEasing();
128 | }
129 | }
130 |
131 | this.ColorComponent = ColorPicker.ColorComponents.R;
132 |
133 | Color newValue = Color.FromRgb(R, G, B);
134 |
135 | if (this.RGB != newValue)
136 | {
137 | this.RGB = newValue;
138 |
139 | if (instantTransition)
140 | {
141 | Update(ColorPicker.ColorSpaces.RGB);
142 | }
143 | }
144 | else
145 | {
146 | Update(ColorPicker.ColorSpaces.RGB);
147 | }
148 | }
149 |
150 | public void UpdateG(byte R, byte G, byte B, bool instantTransition)
151 | {
152 | if (!ColorPicker.TransitionsDisabled)
153 | {
154 | if (instantTransition)
155 | {
156 | this.RGBTransition.Easing = new InstantEasing();
157 | }
158 | else
159 | {
160 | this.RGBTransition.Easing = new LinearEasing();
161 | }
162 | }
163 |
164 | this.ColorComponent = ColorPicker.ColorComponents.G;
165 |
166 | Color newValue = Color.FromRgb(R, G, B);
167 |
168 | if (this.RGB != newValue)
169 | {
170 | this.RGB = newValue;
171 |
172 | if (instantTransition)
173 | {
174 | Update(ColorPicker.ColorSpaces.RGB);
175 | }
176 | }
177 | else
178 | {
179 | Update(ColorPicker.ColorSpaces.RGB);
180 | }
181 | }
182 |
183 | public void UpdateB(byte R, byte G, byte B, bool instantTransition)
184 | {
185 | if (!ColorPicker.TransitionsDisabled)
186 | {
187 | if (instantTransition)
188 | {
189 | this.RGBTransition.Easing = new InstantEasing();
190 | }
191 | else
192 | {
193 | this.RGBTransition.Easing = new LinearEasing();
194 | }
195 | }
196 |
197 | this.ColorComponent = ColorPicker.ColorComponents.B;
198 |
199 | Color newValue = Color.FromRgb(R, G, B);
200 |
201 | if (this.RGB != newValue)
202 | {
203 | this.RGB = newValue;
204 |
205 | if (instantTransition)
206 | {
207 | Update(ColorPicker.ColorSpaces.RGB);
208 | }
209 | }
210 | else
211 | {
212 | Update(ColorPicker.ColorSpaces.RGB);
213 | }
214 | }
215 |
216 | public void UpdateH(double H, double S, double V, bool instantTransition)
217 | {
218 | if (!ColorPicker.TransitionsDisabled)
219 | {
220 | if (instantTransition)
221 | {
222 | this.HSVTransition.Easing = new InstantEasing();
223 | }
224 | else
225 | {
226 | this.HSVTransition.Easing = new LinearEasing();
227 | }
228 | }
229 |
230 | this.ColorComponent = ColorPicker.ColorComponents.H;
231 |
232 | HSV newValue = new HSV(H, S, V);
233 |
234 | if (this.HSV.H != newValue.H || this.HSV.S != newValue.S || this.HSV.V != newValue.V)
235 | {
236 | this.HSV = newValue;
237 |
238 | if (instantTransition)
239 | {
240 | Update(ColorPicker.ColorSpaces.HSB);
241 | }
242 | }
243 | else
244 | {
245 | Update(ColorPicker.ColorSpaces.HSB);
246 | }
247 | }
248 |
249 | public void UpdateS(double H, double S, double V, bool instantTransition)
250 | {
251 | if (!ColorPicker.TransitionsDisabled)
252 | {
253 | if (instantTransition)
254 | {
255 | this.HSVTransition.Easing = new InstantEasing();
256 | }
257 | else
258 | {
259 | this.HSVTransition.Easing = new LinearEasing();
260 | }
261 | }
262 |
263 | this.ColorComponent = ColorPicker.ColorComponents.S;
264 |
265 | HSV newValue = new HSV(H, S, V);
266 |
267 | if (this.HSV.H != newValue.H || this.HSV.S != newValue.S || this.HSV.V != newValue.V)
268 | {
269 | this.HSV = newValue;
270 |
271 | if (instantTransition)
272 | {
273 | Update(ColorPicker.ColorSpaces.HSB);
274 | }
275 | }
276 | else
277 | {
278 | Update(ColorPicker.ColorSpaces.HSB);
279 | }
280 | }
281 |
282 | public void UpdateV(double H, double S, double V, bool instantTransition)
283 | {
284 | if (!ColorPicker.TransitionsDisabled)
285 | {
286 | if (instantTransition)
287 | {
288 | this.HSVTransition.Easing = new InstantEasing();
289 | }
290 | else
291 | {
292 | this.HSVTransition.Easing = new LinearEasing();
293 | }
294 | }
295 |
296 | this.ColorComponent = ColorPicker.ColorComponents.B;
297 |
298 | HSV newValue = new HSV(H, S, V);
299 |
300 | if (this.HSV.H != newValue.H || this.HSV.S != newValue.S || this.HSV.V != newValue.V)
301 | {
302 | this.HSV = newValue;
303 |
304 | if (instantTransition)
305 | {
306 | Update(ColorPicker.ColorSpaces.HSB);
307 | }
308 | }
309 | else
310 | {
311 | Update(ColorPicker.ColorSpaces.HSB);
312 | }
313 | }
314 |
315 | public void UpdateL(double L, double a, double b, bool instantTransition)
316 | {
317 | if (!ColorPicker.TransitionsDisabled)
318 | {
319 | if (instantTransition)
320 | {
321 | this.LABTransition.Easing = new InstantEasing();
322 | }
323 | else
324 | {
325 | this.LABTransition.Easing = new LinearEasing();
326 | }
327 | }
328 |
329 | this.ColorComponent = ColorPicker.ColorComponents.L;
330 |
331 | LAB newValue = new LAB(L, a, b);
332 |
333 | if (this.LAB.L != newValue.L || this.LAB.a != newValue.a || this.LAB.b != newValue.b)
334 | {
335 | this.LAB = newValue;
336 |
337 | if (instantTransition)
338 | {
339 | Update(ColorPicker.ColorSpaces.LAB);
340 | }
341 | }
342 | else
343 | {
344 | Update(ColorPicker.ColorSpaces.LAB);
345 | }
346 | }
347 |
348 | public void Updatea(double L, double a, double b, bool instantTransition)
349 | {
350 | if (!ColorPicker.TransitionsDisabled)
351 | {
352 | if (instantTransition)
353 | {
354 | this.LABTransition.Easing = new InstantEasing();
355 | }
356 | else
357 | {
358 | this.LABTransition.Easing = new LinearEasing();
359 | }
360 | }
361 |
362 | this.ColorComponent = ColorPicker.ColorComponents.A;
363 |
364 | LAB newValue = new LAB(L, a, b);
365 |
366 | if (this.LAB.L != newValue.L || this.LAB.a != newValue.a || this.LAB.b != newValue.b)
367 | {
368 | this.LAB = newValue;
369 |
370 | if (instantTransition)
371 | {
372 | Update(ColorPicker.ColorSpaces.LAB);
373 | }
374 | }
375 | else
376 | {
377 | Update(ColorPicker.ColorSpaces.LAB);
378 | }
379 | }
380 |
381 | public void Updateb(double L, double a, double b, bool instantTransition)
382 | {
383 | if (!ColorPicker.TransitionsDisabled)
384 | {
385 | if (instantTransition)
386 | {
387 | this.LABTransition.Easing = new InstantEasing();
388 | }
389 | else
390 | {
391 | this.LABTransition.Easing = new LinearEasing();
392 | }
393 | }
394 |
395 | this.ColorComponent = ColorPicker.ColorComponents.B;
396 |
397 | LAB newValue = new LAB(L, a, b);
398 |
399 | if (this.LAB.L != newValue.L || this.LAB.a != newValue.a || this.LAB.b != newValue.b)
400 | {
401 | this.LAB = newValue;
402 |
403 | if (instantTransition)
404 | {
405 | Update(ColorPicker.ColorSpaces.LAB);
406 | }
407 | }
408 | else
409 | {
410 | Update(ColorPicker.ColorSpaces.LAB);
411 | }
412 | }
413 |
414 |
415 | private void Update(ColorPicker.ColorSpaces colorSpace)
416 | {
417 | WriteableBitmap canvas2Dimage = null;
418 | WriteableBitmap canvas1Dimage = null;
419 |
420 | (byte R, byte G, byte B) = (RGB.R, RGB.G, RGB.B);
421 |
422 | (double H, double S, double V) = (HSV.H, HSV.S, HSV.V);
423 | (double L, double a, double b) = (LAB.L, LAB.a, LAB.b);
424 |
425 | switch (colorSpace)
426 | {
427 | case ColorPicker.ColorSpaces.RGB:
428 | switch (ColorComponent)
429 | {
430 | case ColorPicker.ColorComponents.R:
431 | canvas1Dimage = AvaloniaColorPicker.RGB.GetR(G, B);
432 | canvas2Dimage = AvaloniaColorPicker.RGB.GetGB(R);
433 | break;
434 | case ColorPicker.ColorComponents.G:
435 | canvas1Dimage = AvaloniaColorPicker.RGB.GetG(R, B);
436 | canvas2Dimage = AvaloniaColorPicker.RGB.GetRB(G);
437 | break;
438 | case ColorPicker.ColorComponents.B:
439 | canvas1Dimage = AvaloniaColorPicker.RGB.GetB(R, G);
440 | canvas2Dimage = AvaloniaColorPicker.RGB.GetRG(B);
441 | break;
442 | }
443 | break;
444 | case ColorPicker.ColorSpaces.HSB:
445 | HSB.HSVToRGB(H, S, V, out R, out G, out B);
446 | switch (ColorComponent)
447 | {
448 | case ColorPicker.ColorComponents.H:
449 | canvas1Dimage = HSB.GetH();
450 | canvas2Dimage = HSB.GetSB(H);
451 | break;
452 | case ColorPicker.ColorComponents.S:
453 | canvas1Dimage = HSB.GetS(H, V);
454 | canvas2Dimage = HSB.GetHB(S);
455 | break;
456 | case ColorPicker.ColorComponents.B:
457 | canvas1Dimage = HSB.GetB(H, S);
458 | canvas2Dimage = HSB.GetHS(V);
459 | break;
460 | }
461 | break;
462 | case ColorPicker.ColorSpaces.LAB:
463 | Lab.FromLab(L, a, b, out R, out G, out B);
464 | switch (ColorComponent)
465 | {
466 | case ColorPicker.ColorComponents.L:
467 | canvas1Dimage = Lab.GetL(a, b);
468 | canvas2Dimage = Lab.GetAB(L);
469 | break;
470 | case ColorPicker.ColorComponents.A:
471 | canvas1Dimage = Lab.GetA(L, b);
472 | canvas2Dimage = Lab.GetLB(a);
473 | break;
474 | case ColorPicker.ColorComponents.B:
475 | canvas1Dimage = Lab.GetB(L, a);
476 | canvas2Dimage = Lab.GetLA(b);
477 | break;
478 | }
479 | break;
480 | }
481 |
482 | Canvas2D.Source = canvas2Dimage;
483 | Canvas1D.Source = canvas1Dimage;
484 | AlphaCanvas.Source = AvaloniaColorPicker.RGB.GetA(R, G, B);
485 | }
486 | }
487 | }
488 |
--------------------------------------------------------------------------------
/AvaloniaColorPicker/AnimatableLAB.cs:
--------------------------------------------------------------------------------
1 | /*
2 | AvaloniaColorPicker - A color picker for Avalonia.
3 | Copyright (C) 2021 Giorgio Bianchini
4 |
5 | This program is free software: you can redistribute it and/or modify
6 | it under the terms of the GNU Lesser General Public License as published by
7 | the Free Software Foundation, version 3.
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Lesser General Public License for more details.
12 | You should have received a copy of the GNU Lesser General Public License
13 | along with this program. If not, see .
14 | */
15 |
16 | using Avalonia;
17 | using Avalonia.Animation;
18 | using Avalonia.Animation.Easings;
19 | using Avalonia.Controls;
20 | using Avalonia.Controls.Shapes;
21 | using Avalonia.Media;
22 | using Avalonia.Media.Imaging;
23 | using System;
24 |
25 | namespace AvaloniaColorPicker
26 | {
27 | internal class AnimatableLABCanvas : Animatable
28 | {
29 | public Image LabImage { get; }
30 |
31 | public Ellipse PositionEllipse { get; }
32 |
33 | private Lab.LabComponents LabComponent { get; set; }
34 |
35 | private LABTransition Transition { get; }
36 |
37 | public static readonly StyledProperty LABProperty = AvaloniaProperty.Register(nameof(LAB));
38 |
39 | public LAB LAB
40 | {
41 | get
42 | {
43 | return GetValue(LABProperty);
44 | }
45 | set
46 | {
47 | SetValue(LABProperty, value);
48 | }
49 | }
50 |
51 | public AnimatableLABCanvas(double L, double a, double b, Lab.LabComponents labComponent)
52 | {
53 | LabImage = new Image() { Width = 96, Height = 96 };
54 | PositionEllipse = new Ellipse();
55 | LabComponent = labComponent;
56 |
57 | this.LAB = new LAB(L, a, b);
58 |
59 | if (!ColorPicker.TransitionsDisabled)
60 | {
61 | this.Transitions = new Transitions();
62 | this.Transition = new LABTransition() { Property = LABProperty, Duration = new TimeSpan(0, 0, 0, 0, 100) };
63 | this.Transitions.Add(Transition);
64 | }
65 | }
66 |
67 | protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
68 | {
69 | base.OnPropertyChanged(change);
70 |
71 | if (change.Property == LABProperty)
72 | {
73 | LAB lab = (change.NewValue as LAB?).Value;
74 |
75 | double L = lab.L;
76 | double a = lab.a;
77 | double b = lab.b;
78 |
79 | Update(L, a, b);
80 | }
81 | }
82 |
83 | private void Update(double L, double a, double b)
84 | {
85 | WriteableBitmap bitmap = new WriteableBitmap(new PixelSize(96, 96), new Vector(96, 96), Avalonia.Platform.PixelFormat.Rgba8888, Avalonia.Platform.AlphaFormat.Unpremul);
86 |
87 | int offset = -1;
88 |
89 | if (LabComponent == Lab.LabComponents.L)
90 | {
91 | int normL = Math.Max(0, Math.Min(128, (int)Math.Round(L * 128.0)));
92 |
93 | offset = 96 * 96 * 4 * normL;
94 | }
95 | else if (LabComponent == Lab.LabComponents.a)
96 | {
97 | int normA = Math.Max(0, Math.Min(128, 64 + (int)Math.Round(a * 64.0)));
98 |
99 | offset = 96 * 96 * 4 * normA + 96 * 96 * 129 * 4;
100 | }
101 | else if (LabComponent == Lab.LabComponents.b)
102 | {
103 | int normB = Math.Max(0, Math.Min(128, 64 + (int)Math.Round(b * 64.0)));
104 |
105 | offset = 96 * 96 * 4 * normB + 96 * 96 * 129 * 4 * 2;
106 | }
107 |
108 | using (var fb = bitmap.Lock())
109 | {
110 | unsafe
111 | {
112 | byte* rgbaValues = (byte*)fb.Address;
113 |
114 | for (int i = 0; i < 96 * 96 * 4; i++)
115 | {
116 | rgbaValues[i] = Lab.LabImageData[offset + i];
117 | }
118 | }
119 | }
120 |
121 | LabImage.Source = bitmap;
122 |
123 |
124 | Lab.FromLab(L, a, b, out _, out _, out _, out byte A);
125 |
126 | if (A > 0)
127 | {
128 | (double x, double y, double w, double h) = Lab.GetEllipsePosition(L, a, b, LabComponent);
129 |
130 | double C = Math.Sqrt(a * a + b * b);
131 | double S = C / Math.Sqrt(C * C + L * L);
132 | PositionEllipse.Width = w;
133 | PositionEllipse.Height = h;
134 | PositionEllipse.Fill = (S < 0.5 && L > 0.5 ? Brushes.Black : Brushes.White);
135 | PositionEllipse.RenderTransform = new TranslateTransform(x - (w * 0.5), y - h * 0.5);
136 | }
137 | else
138 | {
139 | PositionEllipse.Fill = null;
140 | }
141 | }
142 |
143 | public void Update(double L, double a, double b, Lab.LabComponents labComponent, bool instantTransition)
144 | {
145 | if (!ColorPicker.TransitionsDisabled)
146 | {
147 | if (instantTransition)
148 | {
149 | this.Transition.Easing = new InstantEasing();
150 | }
151 | else
152 | {
153 | this.Transition.Easing = new LinearEasing();
154 | }
155 | }
156 |
157 | this.LabComponent = labComponent;
158 |
159 | if (this.LAB.L != L || this.LAB.a != a || this.LAB.b != b)
160 | {
161 | this.LAB = new LAB(L, a, b);
162 | }
163 | else
164 | {
165 | Update(L, a, b);
166 | }
167 | }
168 | }
169 |
170 | internal struct LAB
171 | {
172 | public double L;
173 | public double a;
174 | public double b;
175 |
176 | public LAB(double L, double a, double b)
177 | {
178 | this.L = L;
179 | this.a = a;
180 | this.b = b;
181 | }
182 | }
183 |
184 | internal class LABTransition : InterpolatingTransitionBase
185 | {
186 | protected override LAB Interpolate(double f, LAB oldValue, LAB newValue)
187 | {
188 | return new LAB(oldValue.L + (newValue.L - oldValue.L) * f, oldValue.a + (newValue.a - oldValue.a) * f, oldValue.b + (newValue.b - oldValue.b) * f);
189 | }
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/AvaloniaColorPicker/AnimatableTransform.cs:
--------------------------------------------------------------------------------
1 | /*
2 | AvaloniaColorPicker - A color picker for Avalonia.
3 | Copyright (C) 2021 Giorgio Bianchini
4 |
5 | This program is free software: you can redistribute it and/or modify
6 | it under the terms of the GNU Lesser General Public License as published by
7 | the Free Software Foundation, version 3.
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Lesser General Public License for more details.
12 | You should have received a copy of the GNU Lesser General Public License
13 | along with this program. If not, see .
14 | */
15 |
16 | using Avalonia;
17 | using Avalonia.Animation;
18 | using Avalonia.Animation.Easings;
19 | using Avalonia.Media;
20 | using System;
21 |
22 | namespace AvaloniaColorPicker
23 | {
24 | internal class AnimatableTransform : Animatable
25 | {
26 | public static readonly StyledProperty MatrixProperty = AvaloniaProperty.Register(nameof(Matrix));
27 |
28 | public Matrix Matrix
29 | {
30 | get
31 | {
32 | return GetValue(MatrixProperty);
33 | }
34 | set
35 | {
36 | SetValue(MatrixProperty, value);
37 | }
38 | }
39 |
40 | public MatrixTransform MatrixTransform { get; }
41 | private MatrixTransition Transition { get; }
42 |
43 | public AnimatableTransform(Matrix matrix)
44 | {
45 | this.MatrixTransform = new MatrixTransform() { Matrix = matrix };
46 | this.Matrix = matrix;
47 |
48 | if (!ColorPicker.TransitionsDisabled)
49 | {
50 | this.Transitions = new Transitions();
51 | Transition = new MatrixTransition() { Property = MatrixProperty, Duration = new TimeSpan(0, 0, 0, 0, 100) };
52 | this.Transitions.Add(Transition);
53 | }
54 | }
55 |
56 | public void Update(Matrix matrix, bool instantTransition)
57 | {
58 | if (!ColorPicker.TransitionsDisabled)
59 | {
60 | if (instantTransition)
61 | {
62 | this.Transition.Easing = new InstantEasing();
63 | }
64 | else
65 | {
66 | this.Transition.Easing = new LinearEasing();
67 | }
68 | }
69 |
70 | this.Matrix = matrix;
71 | }
72 |
73 | protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
74 | {
75 | base.OnPropertyChanged(change);
76 |
77 | if (change.Property == MatrixProperty)
78 | {
79 | this.MatrixTransform.Matrix = (change.NewValue as Matrix?).Value;
80 | }
81 | }
82 | }
83 |
84 | internal class MatrixTransition : InterpolatingTransitionBase
85 | {
86 | protected override Matrix Interpolate(double f, Matrix oldValue, Matrix newValue)
87 | {
88 | return new Matrix(oldValue.M11 + (newValue.M11 - oldValue.M11) * f,
89 | oldValue.M12 + (newValue.M12 - oldValue.M12) * f,
90 | oldValue.M21 + (newValue.M21 - oldValue.M21) * f,
91 | oldValue.M22 + (newValue.M22 - oldValue.M22) * f,
92 | oldValue.M31 + (newValue.M31 - oldValue.M31) * f,
93 | oldValue.M32 + (newValue.M32 - oldValue.M32) * f);
94 | }
95 | }
96 |
97 | internal class AnimatableAngleTransform : Animatable
98 | {
99 | public static readonly StyledProperty AngleProperty = AvaloniaProperty.Register(nameof(Angle));
100 |
101 | public double Angle
102 | {
103 | get
104 | {
105 | return GetValue(AngleProperty);
106 | }
107 | set
108 | {
109 | SetValue(AngleProperty, value);
110 | }
111 | }
112 |
113 | public MatrixTransform MatrixTransform { get; }
114 | private DoubleTransition Transition { get; }
115 |
116 | public AnimatableAngleTransform(double angle)
117 | {
118 | this.MatrixTransform = new MatrixTransform();
119 | this.Angle = angle;
120 | if (!ColorPicker.TransitionsDisabled)
121 | {
122 | this.Transitions = new Transitions();
123 | Transition = new DoubleTransition() { Property = AngleProperty, Duration = new TimeSpan(0, 0, 0, 0, 100) };
124 | this.Transitions.Add(Transition);
125 | }
126 | }
127 |
128 | public void Update(double angle, bool instantTransition)
129 | {
130 | if (!ColorPicker.TransitionsDisabled)
131 | {
132 | if (instantTransition)
133 | {
134 | this.Transition.Easing = new InstantEasing();
135 | }
136 | else
137 | {
138 | this.Transition.Easing = new LinearEasing();
139 | }
140 | }
141 |
142 | this.Angle = angle;
143 | }
144 |
145 | protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
146 | {
147 | base.OnPropertyChanged(change);
148 |
149 | if (change.Property == AngleProperty)
150 | {
151 | double H = (change.NewValue as double?).Value;
152 |
153 | Point v23 = new Point(64 + 64 * Math.Cos(H * 2 * Math.PI), 20 + 20 * Math.Sin(H * 2 * Math.PI));
154 | Point v14 = new Point(64, 20);
155 | Point v67 = new Point(64 + 64 * Math.Cos(H * 2 * Math.PI), 76 + 20 * Math.Sin(H * 2 * Math.PI));
156 |
157 | double m11 = (v23.X - v14.X) / 256;
158 | double m12 = (v67.X - v23.X) / 256;
159 | double m21 = (v23.Y - v14.Y) / 256;
160 | double m22 = (v67.Y - v23.Y) / 256;
161 |
162 | this.MatrixTransform.Matrix = new Matrix(m11, m21, m12, m22, v14.X, v14.Y);
163 | }
164 | }
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/AvaloniaColorPicker/AvaloniaColorPicker.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | AvaloniaColorPicker
6 | AvaloniaColorPicker
7 | Giorgio Bianchini
8 | true
9 | University of Bristol
10 | A colour picker control for Avalonia, with support for RGB, HSB and CIELAB colour spaces, palettes and colour blindness simulation.
11 | 1.4.0
12 | LGPL-3.0-only
13 | https://github.com/arklumpus/AvaloniaColorPicker
14 | icon.png
15 |
16 |
17 |
18 |
19 | true
20 | .
21 |
22 |
23 |
24 |
25 | true
26 |
27 |
28 |
29 | true
30 | true
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/AvaloniaColorPicker/ColorPickerWindow.axaml:
--------------------------------------------------------------------------------
1 |
15 |
16 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/AvaloniaColorPicker/ColorPickerWindow.axaml.cs:
--------------------------------------------------------------------------------
1 | /*
2 | AvaloniaColorPicker - A color picker for Avalonia.
3 | Copyright (C) 2021 Giorgio Bianchini
4 |
5 | This program is free software: you can redistribute it and/or modify
6 | it under the terms of the GNU Lesser General Public License as published by
7 | the Free Software Foundation, version 3.
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Lesser General Public License for more details.
12 | You should have received a copy of the GNU Lesser General Public License
13 | along with this program. If not, see .
14 | */
15 |
16 | using Avalonia.Controls;
17 | using Avalonia.Markup.Xaml;
18 | using Avalonia.Media;
19 | using System.Threading.Tasks;
20 |
21 | namespace AvaloniaColorPicker
22 | {
23 | ///
24 | /// Interface describing a contract for a colour picker window. Note that, even though this is not enforced by the interface, a class implementing this interface MUST have a constructor which takes a single ? parameter representing the previous colour in the colour picker.
25 | ///
26 | public interface IColorPickerWindow
27 | {
28 | ///
29 | /// The color that is currently selected in the .
30 | ///
31 | Color Color { get; set; }
32 |
33 | ///
34 | /// Represents the previously selected (e.g. if the is being used to change the colour of an object, it would represent the previous colour of the object. Set to to hide the previous colour display.
35 | ///
36 | Color? PreviousColor { get; set; }
37 |
38 | ///
39 | /// Shows the as a dialog.
40 | ///
41 | /// The 's owner window.
42 | /// The selected if the user clicks on the "OK" button; otherwise.
43 | Task ShowDialog(Window parent);
44 | }
45 |
46 |
47 | ///
48 | /// A containing a control.
49 | ///
50 | public partial class ColorPickerWindow : Window, IColorPickerWindow
51 | {
52 | ///
53 | /// The color that is currently selected in the .
54 | ///
55 | public Color Color
56 | {
57 | get => this.FindControl("ColorPicker").Color;
58 | set
59 | {
60 | this.FindControl("ColorPicker").Color = value;
61 | }
62 | }
63 |
64 | ///
65 | /// Represents the previously selected (e.g. if the is being used to change the colour of an object, it would represent the previous colour of the object. Set to to hide the previous colour display.
66 | ///
67 | public Color? PreviousColor
68 | {
69 | get => this.FindControl("ColorPicker").PreviousColor;
70 | set
71 | {
72 | this.FindControl("ColorPicker").PreviousColor = value;
73 | }
74 | }
75 |
76 | ///
77 | /// Determines whether the palette selector is visible or not.
78 | ///
79 | public bool IsPaletteVisible
80 | {
81 | get => this.FindControl("ColorPicker").IsPaletteVisible;
82 | set { this.FindControl("ColorPicker").IsPaletteVisible = value; }
83 | }
84 |
85 | ///
86 | /// Determines whether the colour blindness selector is visible or not.
87 | ///
88 | public bool IsColourBlindnessSelectorVisible
89 | {
90 | get => this.FindControl("ColorPicker").IsColourBlindnessSelectorVisible;
91 | set { this.FindControl("ColorPicker").IsColourBlindnessSelectorVisible = value; }
92 | }
93 |
94 | ///
95 | /// Determines whether the hex value text box is visible or not.
96 | ///
97 | public bool IsHexVisible
98 | {
99 | get => this.FindControl("ColorPicker").IsHexVisible;
100 | set { this.FindControl("ColorPicker").IsHexVisible = value; }
101 | }
102 |
103 | ///
104 | /// Determines whether the alpha value text box and slider are visible or not.
105 | ///
106 | public bool IsAlphaVisible
107 | {
108 | get => this.FindControl("ColorPicker").IsAlphaVisible;
109 | set { this.FindControl("ColorPicker").IsAlphaVisible = value; }
110 | }
111 |
112 | ///
113 | /// Determines whether the CIELAB component text boxes are visible or not.
114 | ///
115 | public bool IsCIELABVisible
116 | {
117 | get => this.FindControl("ColorPicker").IsCIELABVisible;
118 | set { this.FindControl("ColorPicker").IsCIELABVisible = value; }
119 | }
120 |
121 | ///
122 | /// Determines whether the HSB component text boxes are visible or not.
123 | ///
124 | public bool IsHSBVisible
125 | {
126 | get => this.FindControl("ColorPicker").IsHSBVisible;
127 | set { this.FindControl("ColorPicker").IsHSBVisible = value; }
128 | }
129 |
130 | ///
131 | /// Determines whether RGB component text boxes are visible or not.
132 | ///
133 | public bool IsRGBVisible
134 | {
135 | get => this.FindControl("ColorPicker").IsRGBVisible;
136 | set { this.FindControl("ColorPicker").IsRGBVisible = value; }
137 | }
138 |
139 | ///
140 | /// Determines whether the colour space preview is visible or not.
141 | ///
142 | public bool IsColourSpacePreviewVisible
143 | {
144 | get => this.FindControl("ColorPicker").IsColourSpacePreviewVisible;
145 | set { this.FindControl("ColorPicker").IsColourSpacePreviewVisible = value; }
146 | }
147 |
148 | ///
149 | /// Determines whether the colour space selector is visible or not.
150 | ///
151 | public bool IsColourSpaceSelectorVisible
152 | {
153 | get => this.FindControl("ColorPicker").IsColourSpaceSelectorVisible;
154 | set { this.FindControl("ColorPicker").IsColourSpaceSelectorVisible = value; }
155 | }
156 |
157 | ///
158 | /// Determines whether the CIELAB colour space can be selected or not.
159 | ///
160 | public bool IsCIELABSelectable
161 | {
162 | get => this.FindControl("ColorPicker").IsCIELABSelectable;
163 | set { this.FindControl("ColorPicker").IsCIELABSelectable = value; }
164 | }
165 |
166 | ///
167 | /// Determines whether the HSB colour space can be selected or not.
168 | ///
169 | public bool IsHSBSelectable
170 | {
171 | get => this.FindControl("ColorPicker").IsHSBSelectable;
172 | set { this.FindControl("ColorPicker").IsHSBSelectable = value; }
173 | }
174 |
175 | ///
176 | /// Determines whether the RGB colour space can be selected or not.
177 | ///
178 | public bool IsRGBSelectable
179 | {
180 | get => this.FindControl("ColorPicker").IsRGBSelectable;
181 | set { this.FindControl("ColorPicker").IsRGBSelectable = value; }
182 | }
183 |
184 | ///
185 | /// The currently selected colour space.
186 | ///
187 | public ColorPicker.ColorSpaces ColorSpace
188 | {
189 | get => this.FindControl("ColorPicker").ColorSpace;
190 | set { this.FindControl("ColorPicker").ColorSpace = value; }
191 | }
192 |
193 | private bool Result = false;
194 |
195 | ///
196 | /// Creates a new instance.
197 | ///
198 | public ColorPickerWindow()
199 | {
200 | this.InitializeComponent();
201 |
202 | this.WindowStartupLocation = WindowStartupLocation.CenterScreen;
203 |
204 | this.FindControl