The only alternative to System.Drawing.Color built on actual color theory
Because color management shouldn't require a PhD in color theory
Overview
KtColor is a complete color management system for .NET WinForms that replaces the limitations of System.Drawing.Color with a developer-friendly API built on HSL color theory, variable theming, and intuitive syntax.
Why KtColor Exists
System.Drawing.Color was designed for basic color representation, not modern UI development:
No color theory support - RGB values don't map to how humans perceive color
No theme system - Can't define semantic colors like "Primary" or "Success"
No shade generation - Manual calculation of lighter/darker variants
Poor string parsing - Limited format support
No opacity management - Alpha channels are awkward to work with
KtColor solves these problems with a unified system built for human perception and modern theming.
Core Concepts
1. Color Representation
KtColor uses HSL (Hue, Saturation, Lightness) internally because it matches human color perception:
// Access HSL properties
var color = KtColor.Blue;
float hue = color.Hue; // 0-360°
float saturation = color.Saturation; // 0-1
float lightness = color.Lightness; // 0-1
int shade = color.Shade; // 0-100%2. Three Color Types
Native Colors - Predefined palette colors with names:
KtColor.Blue // @Blue
KtColor.Slate // @Slate
KtColor.PRIMARY // $Primary (variable)Custom Colors - From hex, RGB, or System.Drawing.Color:
KtColor custom = "#FF5733";
KtColor custom = Color.FromArgb(255, 87, 51);Empty/Transparent:
KtColor.Empty // No color
KtColor.Transparent // Transparent colorPalette System
Base Colors (Neutrals)
KtColor.Slate // #64748B - Cool gray
KtColor.Gray // #6B7280 - Neutral gray
KtColor.Zinc // #71717A - Warm gray
KtColor.Stone // #78716C - Earth grayAccent Colors (Main Palette)
// Cool colors
KtColor.Blue, Sky, Cyan, Teal, Emerald, Green
// Warm colors
KtColor.Red, Rose, Pink, Fuchsia, Purple, Violet, Indigo
// Yellow spectrum
KtColor.Lime, Yellow, Amber, OrangeTheme Variables
KtColor.PRIMARY // $Primary - Main brand color
KtColor.SECONDARY // $Secondary - Secondary brand
KtColor.ACCENT // $Accent - Highlight color
KtColor.NEUTRAL // $Neutral - Neutral UI elements
KtColor.BASE // $Base - Background (alias for BASE_2)
KtColor.BASE_1 // $Base_1 - Lightest background
KtColor.BASE_2 // $Base_2 - Default background
KtColor.BASE_3 // $Base_3 - Darkest background
KtColor.CONTENT // $Content - Text/content color
// Status colors
KtColor.SUCCESS // $Success - Success states
KtColor.INFO // $Info - Information
KtColor.WARNING // $Warning - Warnings
KtColor.ERROR // $Error - ErrorsShade System
Every palette color includes 11 shades (0-100 in 10% increments):
// Indexer syntax - percentage (0-100)
KtColor.Blue[0] // Darkest (almost black)
KtColor.Blue[50] // Middle (base color)
KtColor.Blue[100] // Lightest (almost white)
// Indexer syntax - float (0.0-1.0)
KtColor.Blue[0f] // Darkest
KtColor.Blue[0.5f] // Middle
KtColor.Blue[1f] // Lightest
// Operators for adjustment
var darker = KtColor.Blue - 10; // -10% lightness
var lighter = KtColor.Blue + 20; // +20% lightnessString Format Support
Parsing Formats
// Palette colors
KtColor.Parse("Blue") // Base color
KtColor.Parse("@Blue") // Explicit palette
KtColor.Parse("$Primary") // Variable
// With shades
KtColor.Parse("Blue[50]") // 50% lightness
KtColor.Parse("@Slate[30]") // 30% lightness
// With opacity
KtColor.Parse("Blue%50") // 50% opacity
KtColor.Parse("Blue[70]%80") // 70% lightness, 80% opacity
// Hex colors
KtColor.Parse("#FF5733") // RGB hex
KtColor.Parse("#FF5733%50") // With opacity
// CSS formats
KtColor.Parse("rgb(255,87,51)")
KtColor.Parse("rgba(255,87,51,0.5)")
KtColor.Parse("hsl(9,100%,60%)")
KtColor.Parse("hsla(9,100%,60%,0.5)")Rendering Formats
var color = KtColor.Blue[60]%80;
// String representation
color.ToString() // "Blue[60]%80"
color.Name // "@Blue" (for palette colors)
// Web formats
color.Web() // "#3B82F6" or color name
color.Hex() // "#3B82F6"
color.RGB() // "rgb(59,130,246)"
color.HSL() // "hsl(217,91.2%,59.8%)"
// With opacity
color.RGB(web: true) // "rgba(59,130,246,0.80)"
color.HSL() // "hsla(217,91.2%,59.8%,0.80)"
// Internal representation
color.Int() // ARGB as int32
color.Render() // System.Drawing.ColorOperators Reference
Implicit Conversions
// From string
KtColor color = "Blue";
KtColor color = "#FF5733";
// From System.Drawing.Color
KtColor color = Color.Red;
// From ARGB int
KtColor color = 0xFF0000FF;
// To System.Drawing.Color
Color systemColor = KtColor.Blue;
// To string
string str = KtColor.Blue; // "Blue"Lightness Operations
// Increment/decrement (10% steps)
var lighter = color + 1; // +10% lightness
var darker = color - 1; // -10% lightness
var lighter = ++color; // Prefix: +10%
var darker = --color; // Prefix: -10%
// By amount (percentage)
var lighter = color + 25; // +25% lightness
var darker = color - 15; // -15% lightness
// By float (0.0-1.0)
var lighter = color + 0.25f; // +25% lightness
var darker = color - 0.15f; // -15% lightnessOpacity Operations
// Set opacity (modulo operator)
var transparent = color % 50; // 50% opacity
var opaque = 100 % color; // 100% opacity (either order)
// Divide opacity
var half = color / 2; // Half opacityColor Inversion
// Invert lightness
var inverted = !color;
// For theme variables: returns content color
var content = !KtColor.PRIMARY; // Returns PRIMARY's content color
// Auto-contrast adjustment (subtle)
var adjusted = ~color; // Adjusts by ±7% for readabilityConditional Operations
// Boolean coalescing
var active = userColor | KtColor.PRIMARY; // Use userColor or fall back
// Condition-based inversion
var result = true & color; // Returns color if true
var result = false & color; // Returns inverted if false
var result = true | color; // Returns inverted if true
var result = false | color; // Returns color if falseColor Mixing
// Combine colors (alpha blending)
var mixed = color1 + color2;
// Subtract colors
var diff = color1 - color2;
// Mix with percentage
var blend = KtColor.Mix(color1, color2, 30f); // 30% color2, 70% color1Comparison
// Lightness comparison
bool isDarker = color1 < color2;
bool isLighter = color1 > color2;
// Equality
bool same = color1 == color2;
bool different = color1 != color2;Boolean Context
// Check if color has value (not empty/transparent)
if (color) // true if not Empty and not None
{
// Color is valid
}Property Inspection
Color Properties
// RGB components
int r = color.R; // 0-255
int g = color.G; // 0-255
int b = color.B; // 0-255
int a = color.A; // 0-255
// HSL components
float hue = color.Hue; // 0-360
float saturation = color.Saturation; // 0-1
float lightness = color.Lightness; // 0-1
int shade = color.Shade; // 0-100
int opacity = color.Opacity; // 0-100
// Base color
Color baseColor = color.Base; // Underlying System.Drawing.Color
object baseValue = color.@base; // String (palette) or Color (custom)State Checks
// Emptiness
bool isEmpty = color.IsEmpty; // Color.Empty
bool isTransparent = color.IsTransparent; // Color.Transparent
bool isClear = color.IsClear; // Empty or fully transparent
bool hasValue = color.Any; // Has a value
// Opacity
bool isOpaque = color.IsOpaque; // 100% opacity
bool isTranslucent = color.IsTranslucent; // Partial opacity
// Type checks
bool isNative = color.IsNative; // Palette color
bool isCustom = color.IsCustom; // Custom RGB color
bool isVariable = color.IsVariable; // Theme variable ($)
bool isTheme = color.IsTheme; // Root theme variable
bool isRoot = color.IsRoot; // No shade applied
bool isNamed = color.IsNamed; // Has a name
bool isKnownColor = color.IsKnownColor; // System.Drawing.KnownColorTheme System
Setting Theme Variables
// Set individual variables
KtColor.@default("Primary", Color.FromArgb(96, 93, 255));
KtColor.@default("Base_1", Color.FromArgb(50, 57, 74));
// Apply light/dark theme presets
KtColor.Render(isDark: true); // Dark mode
KtColor.Render(isDark: false); // Light mode
KtColor.Render(isDark: null); // Keep current
// Check current theme
bool isDarkMode = KtColor.IsDark(); // BASE < !BASE
bool isLightMode = KtColor.IsLight(); // BASE > !BASETheme Change Events
// Subscribe to theme changes
KtColor.ThemeChanged += (isDark) =>
{
// Respond to theme change
RefreshUI();
};
// Subscribe to variable changes
KtColor.VariableChanged += (sender, e) =>
{
string variableName = e.PropertyName; // "Primary", "Base_1", etc.
// Respond to specific variable change
};Default Theme Colors
Dark Mode (default):
Content: #F5F5F5 (WhiteSmoke)
Base_1: #32394A (Light surface)
Base_2: #1B2336 (Default surface)
Base_3: #0F172B (Dark surface)
Neutral: #515765 (Gray)Light Mode:
Content: #1B2336 (Dark text)
Base_1: #FFFFFF (White)
Base_2: #F8FAFG (Off-white)
Base_3: #E8EBEE (Light gray)
Neutral: #F5F5F5 (WhiteSmoke)Advanced Features
Content Color Calculation
Automatic foreground color for any background:
var background = KtColor.Blue[30];
var foreground = !background; // High contrast text color
var foreground = background.Invert(); // Same as above
var foreground = background.Content(); // For theme variablesOpaque Color Blending
Flatten translucent colors onto a background:
var translucent = KtColor.Blue % 50; // 50% opacity
var background = KtColor.White;
var opaque = translucent.Opaque(background); // Flattened resultColor Iteration
Enumerate all shades:
foreach (var shade in KtColor.Blue)
{
// Iterates 0%, 10%, 20%, ... 100%
DrawSwatch(shade);
}Random Colors
var random = KtColor.Random(); // Random palette color
var randomDark = KtColor.Random(30); // Random at 30% lightnessDesign Decisions
Why HSL over RGB?
Human perception - HSL maps to how humans naturally think about color:
"Make it lighter" → Increase L
"More vibrant" → Increase S
"Change the color" → Adjust H
RGB requires mental math to achieve these effects. With RGB, making a color "lighter" means adding white to all channels proportionally, which is unintuitive.
Predictable shades - In HSL, generating a lighter shade is trivial:
// HSL - Simple and predictable
var lighter = color[70]; // 70% lightness
// RGB - Complex calculation required
var lighter = Color.FromArgb(
(int)(color.R + (255 - color.R) * 0.3),
(int)(color.G + (255 - color.G) * 0.3),
(int)(color.B + (255 - color.B) * 0.3)
);Consistent brightness - All colors at the same L value have perceptually similar brightness, making UI consistency easier.
Why String-Based Palette Names?
Using strings ("Blue") instead of enums provides:
Extensibility - Add custom colors without recompiling
Serialization - Natural JSON/config file support
Dynamic theming - Runtime color palette switching
Designer support - PropertyGrid string editor works out of the box
The trade-off is type safety, mitigated by:
TryParsefor validationStatic properties for common colors (
KtColor.Blue)IntelliSense support through static members
Why Variable System?
Theme variables ($Primary, $Base) solve a fundamental problem in UI development: semantic colors.
Traditional approach:
// Hard-coded colors throughout application
button.BackColor = Color.FromArgb(96, 93, 255);
label.ForeColor = Color.FromArgb(96, 93, 255);
panel.BackColor = Color.FromArgb(27, 35, 54);Problems:
Can't change brand colors without find-replace
No dark mode support
Inconsistent colors across components
Can't theme at runtime
KtColor approach:
// Semantic colors
button.BackColor = KtColor.PRIMARY;
label.ForeColor = KtColor.PRIMARY;
panel.BackColor = KtColor.BASE;
// One-line theme change
KtColor.Render(isDark: true); // Everything updatesWhy Operator Overloading?
Operators make color manipulation read like natural language:
// Traditional
var hover = ColorHelper.Lighten(baseColor, 0.1f);
var disabled = ColorHelper.SetOpacity(baseColor, 0.4f);
var contrast = ColorHelper.GetContrast(background);
// KtColor
var hover = baseColor + 10;
var disabled = baseColor % 40;
var contrast = !background;The code becomes self-documenting. color + 10 obviously means "lighter", color % 50 clearly indicates opacity.
Performance Considerations
Lazy Evaluation
KtColor uses lazy property evaluation to minimize allocations:
// Properties calculated on first access
public float Hue => _hue ??= Base.GetHue();
public Color Base => _base ??= CalculateBase();Once calculated, values are cached. This means:
Creating
KtColor.Blueis cheap (no calculations)First
.Render()call performs HSL→RGB conversionSubsequent renders use cached value
Theme Variable Invalidation
When theme variables change, cached values are invalidated via hash code:
if (prevHashCode != _hashcode && IsVariable)
{
_base = _value = null; // Invalidate cache
_hue = _saturation = _lightness = null;
prevHashCode = _hashcode;
}This ensures theme changes propagate while maintaining performance during normal operation.
Allocation Patterns
// No allocation - returns cached instance
var color = KtColor.Blue;
// Small allocation - new KtColor instance
var shade = KtColor.Blue[50];
// Allocation on render
Color gdi = shade.Render(); // Creates System.Drawing.ColorBest practice: Store KtColor in properties, render only when needed for painting.
Integration Patterns
PropertyGrid Support
Full designer integration via TypeConverter:
[TypeConverter(typeof(KtColorConverter))]
public class MyControl : Control
{
public KtColor BackgroundColor { get; set; } = KtColor.PRIMARY;
}The PropertyGrid shows:
Dropdown with standard values
String editor for custom values
Color picker for RGB colors
Live preview of the color
Custom Editor
KtColor includes a custom color editor that extends the standard .NET color picker:
#if NET
[Editor("System.Drawing.Design.ColorEditor...",
"System.Drawing.Design.UITypeEditor...")]
#else
[Editor(typeof(KtColorEditor), typeof(UITypeEditor))]
#endifFeatures:
Palette color browser
Shade selector
Opacity control
Search functionality
Theme variable access
JSON Serialization
Implicit JToken conversion for JSON.NET:
var json = new JObject
{
["primary"] = KtColor.PRIMARY,
["background"] = KtColor.BASE[20]
};
// Output:
// {
// "primary": "#605DFF",
// "background": "#1B2336"
// }Deserialize back:
KtColor primary = json["primary"].ToString();
KtColor background = json["background"].ToString();Migration Guide
From System.Drawing.Color
// Before
Color btnColor = Color.Blue;
Color btnHover = ControlPaint.Light(btnColor);
Color btnDisabled = Color.FromArgb(102, btnColor);
// After
KtColor btnColor = KtColor.Blue;
KtColor btnHover = btnColor + 10;
KtColor btnDisabled = btnColor % 40;From String Color Names
// Before
public string BackgroundColor { get; set; } = "Blue";
// ... manual parsing in OnPaint
// After
public KtColor BackgroundColor { get; set; } = KtColor.Blue;
// Render() returns System.Drawing.Color directlyAdding Theme Support
// Before - hard-coded colors
public class MyButton : Button
{
public MyButton()
{
BackColor = Color.FromArgb(96, 93, 255);
ForeColor = Color.White;
}
}
// After - theme-aware
public class MyButton : Button
{
public MyButton()
{
BackColor = KtColor.PRIMARY;
ForeColor = KtColor.CONTENT;
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.FillRectangle(
new SolidBrush(BackColor), // Auto-converts KtColor→Color
ClientRectangle
);
}
}
// Theme change applies automatically
KtColor.Render(isDark: true);Best Practices
1. Use Theme Variables for Semantic Colors
// Good - semantic meaning
button.BackColor = KtColor.PRIMARY;
errorLabel.ForeColor = KtColor.ERROR;
// Avoid - loses semantic meaning
button.BackColor = KtColor.Blue;
errorLabel.ForeColor = KtColor.Red;2. Store KtColor, Render Late
// Good - single source of truth
private KtColor _buttonColor = KtColor.PRIMARY;
protected override void OnPaint(PaintEventArgs e)
{
using var brush = new SolidBrush(_buttonColor.Render());
e.Graphics.FillRectangle(brush, bounds);
}
// Avoid - loses shade/opacity information
private Color _buttonColor = KtColor.PRIMARY.Render();3. Use Operators for Variations
// Good - clear intent
var hover = normalColor + 10;
var pressed = normalColor - 10;
var disabled = normalColor % 40;
// Avoid - obscures relationship
var hover = KtColor.Parse("Blue[60]");
var pressed = KtColor.Parse("Blue[40]");
var disabled = KtColor.Parse("Blue%40");4. Leverage Content Colors
// Good - automatic contrast
var bgColor = KtColor.PRIMARY;
var textColor = !bgColor; // High contrast text
// Avoid - manual contrast calculation
var bgColor = KtColor.PRIMARY;
var textColor = bgColor.Lightness > 0.5 ? KtColor.Black : KtColor.White;5. Subscribe to Theme Changes
public class ThemedControl : Control
{
public ThemedControl()
{
KtColor.ThemeChanged += OnThemeChanged;
UpdateColors();
}
private void OnThemeChanged(bool? isDark)
{
UpdateColors();
Invalidate();
}
protected override void Dispose(bool disposing)
{
if (disposing)
KtColor.ThemeChanged -= OnThemeChanged;
base.Dispose(disposing);
}
}Common Pitfalls
1. Comparing Rendered Colors
// Wrong - compares System.Drawing.Color instances
if (color1.Render() == color2.Render()) { }
// Right - compares KtColor semantics
if (color1 == color2) { }2. Losing Shade Information
// Wrong - loses shade
Color temp = KtColor.Blue[30];
KtColor result = temp; // Now Blue[50] (root)
// Right - keep as KtColor
KtColor result = KtColor.Blue[30];3. Not Handling Empty Colors
// Wrong - potential null reference
var rendered = color.Render();
e.Graphics.FillRectangle(new SolidBrush(rendered), bounds);
// Right - check first
if (color) // or: if (!color.IsEmpty)
{
using var brush = new SolidBrush(color.Render());
e.Graphics.FillRectangle(brush, bounds);
}4. Ignoring Opacity in Rendering
// Wrong - opacity lost if background not specified
var translucent = KtColor.Blue % 50;
var final = translucent.Render(); // Still has alpha channel
// Right - flatten to opaque if needed
var translucent = KtColor.Blue % 50;
var final = translucent.Opaque(backgroundColor);API Summary
Static Properties
22 palette colors:
Slate,Blue,Red, etc.11 theme variables:
PRIMARY,BASE,CONTENT, etc.Special:
Empty,Transparent,White,Black
Instance Properties
RGB:
R,G,B,AHSL:
Hue,Saturation,Lightness,ShadeState:
IsEmpty,IsOpaque,IsNative,IsVariableMeta:
Name,Base,Opacity
Static Methods
Parse(string)- Parse any color formatTryParse(string, out KtColor)- Safe parsingRender(bool? isDark)- Set theme@default(string, Color)- Set variableMix(c1, c2, percentage)- Blend colorsRandom(shade?)- Random color
Instance Methods
Render()- Convert to System.Drawing.ColorToString()- KtColor string representationHex(),RGB(),HSL(),Web()- Format conversionInvert(),Content(),Mirror()- Color transformsOpaque(background)- Flatten alpha
Operators
+,-- Adjust lightness%- Set opacity/- Divide opacity!- Invert~- Auto-contrast|- Coalesce==,!=,<,>- Compare
KtColor - Because color management shouldn't require a PhD in color theory
Last updated
Was this helpful?

