├── .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