1using System; 2using System.Drawing; 3using System.Windows.Forms; 4using WindowCapture.Helpers; 5 6namespace WindowCapture.UI 7{ 8 // Custom dark color picker dialog 9 public class DarkColorPicker : Form 10 { 11 private static readonly Color DarkBg = Color.FromArgb(30, 30, 32); 12 private static readonly Color DarkPanel = Color.FromArgb(40, 40, 42); 13 private static readonly Color DarkControl = Color.FromArgb(50, 50, 52); 14 private static readonly Color TextColor = Color.FromArgb(200, 200, 200); 15 private static readonly Color DimText = Color.FromArgb(120, 120, 120); 16 17 public Color SelectedColor { get; private set; } 18 19 private Panel colorSquare; // Saturation/Brightness square 20 private Panel hueSlider; // Hue vertical slider 21 private Panel alphaSlider; // Alpha vertical slider 22 private Panel previewPanel; // Color preview 23 private DarkNumeric nudR, nudG, nudB, nudA; 24 private TextBox txtHex; 25 26 private Bitmap colorSquareBmp; 27 private Bitmap hueSliderBmp; 28 29 private float currentHue = 0; // 0-360 30 private float currentSat = 1; // 0-1 31 private float currentBri = 1; // 0-1 32 private int currentAlpha = 255; 33 34 private bool draggingSquare, draggingHue, draggingAlpha; 35 private bool updatingControls; 36 private bool closing; 37 38 public DarkColorPicker(Color initialColor) 39 { 40 SelectedColor = initialColor; 41 currentAlpha = initialColor.A; 42 43 // Convert RGB to HSB 44 ColorToHSB(initialColor, out currentHue, out currentSat, out currentBri); 45 46 InitializeComponents(); 47 UpdateAllFromHSB(); 48 } 49 50 private void InitializeComponents() 51 { 52 Text = "Color Picker"; 53 Size = new Size(420, 340); 54 FormBorderStyle = FormBorderStyle.None; 55 StartPosition = FormStartPosition.CenterParent; 56 BackColor = DarkBg; 57 DoubleBuffered = true; 58 59 // Title bar 60 Panel titleBar = new Panel(); 61 titleBar.Location = new Point(0, 0); 62 titleBar.Size = new Size(Width, 28); 63 titleBar.BackColor = DarkPanel; 64 65 Label lblTitle = new Label(); 66 lblTitle.Text = "Color Picker"; 67 lblTitle.Location = new Point(10, 5); 68 lblTitle.AutoSize = true; 69 lblTitle.ForeColor = TextColor; 70 lblTitle.Font = new Font("Segoe UI", 9f, FontStyle.Bold); 71 titleBar.Controls.Add(lblTitle); 72 73 // Dragging 74 bool dragging = false; 75 Point dragStart = Point.Empty; 76 titleBar.MouseDown += (s, e) => { dragging = true; dragStart = e.Location; }; 77 titleBar.MouseMove += (s, e) => { if (dragging) Location = new Point(Location.X + e.X - dragStart.X, Location.Y + e.Y - dragStart.Y); }; 78 titleBar.MouseUp += (s, e) => { dragging = false; }; 79 lblTitle.MouseDown += (s, e) => { dragging = true; dragStart = e.Location; }; 80 lblTitle.MouseMove += (s, e) => { if (dragging) Location = new Point(Location.X + e.X - dragStart.X, Location.Y + e.Y - dragStart.Y); }; 81 lblTitle.MouseUp += (s, e) => { dragging = false; }; 82 83 Controls.Add(titleBar); 84 85 // Color square (Saturation X, Brightness Y) 86 colorSquare = new Panel(); 87 colorSquare.Location = new Point(15, 40); 88 colorSquare.Size = new Size(200, 200); 89 colorSquare.BackColor = DarkControl; 90 colorSquare.MouseDown += ColorSquare_MouseDown; 91 colorSquare.MouseMove += ColorSquare_MouseMove; 92 colorSquare.MouseUp += (s, e) => draggingSquare = false; 93 colorSquare.Paint += ColorSquare_Paint; 94 Controls.Add(colorSquare); 95 96 // Hue slider 97 hueSlider = new Panel(); 98 hueSlider.Location = new Point(225, 40); 99 hueSlider.Size = new Size(25, 200); 100 hueSlider.BackColor = DarkControl; 101 hueSlider.MouseDown += HueSlider_MouseDown; 102 hueSlider.MouseMove += HueSlider_MouseMove; 103 hueSlider.MouseUp += (s, e) => draggingHue = false; 104 hueSlider.Paint += HueSlider_Paint; 105 Controls.Add(hueSlider); 106 107 // Alpha slider 108 alphaSlider = new Panel(); 109 alphaSlider.Location = new Point(260, 40); 110 alphaSlider.Size = new Size(25, 200); 111 alphaSlider.BackColor = DarkControl; 112 alphaSlider.MouseDown += AlphaSlider_MouseDown; 113 alphaSlider.MouseMove += AlphaSlider_MouseMove; 114 alphaSlider.MouseUp += (s, e) => draggingAlpha = false; 115 alphaSlider.Paint += AlphaSlider_Paint; 116 Controls.Add(alphaSlider); 117 118 // Preview 119 Label lblPreview = new Label(); 120 lblPreview.Text = "Preview:"; 121 lblPreview.Location = new Point(300, 40); 122 lblPreview.AutoSize = true; 123 lblPreview.ForeColor = TextColor; 124 lblPreview.Font = new Font("Segoe UI", 9f); 125 Controls.Add(lblPreview); 126 127 previewPanel = new Panel(); 128 previewPanel.Location = new Point(300, 60); 129 previewPanel.Size = new Size(100, 60); 130 previewPanel.Paint += PreviewPanel_Paint; 131 Controls.Add(previewPanel); 132 133 // RGB inputs 134 int inputY = 135; 135 AddLabel("R:", 300, inputY); 136 nudR = AddNumericInput(330, inputY, 0, 255, SelectedColor.R); 137 nudR.ValueChanged += (s, e) => { if (!updatingControls) UpdateFromRGB(); }; 138 inputY += 28; 139 140 AddLabel("G:", 300, inputY); 141 nudG = AddNumericInput(330, inputY, 0, 255, SelectedColor.G); 142 nudG.ValueChanged += (s, e) => { if (!updatingControls) UpdateFromRGB(); }; 143 inputY += 28; 144 145 AddLabel("B:", 300, inputY); 146 nudB = AddNumericInput(330, inputY, 0, 255, SelectedColor.B); 147 nudB.ValueChanged += (s, e) => { if (!updatingControls) UpdateFromRGB(); }; 148 inputY += 28; 149 150 AddLabel("A:", 300, inputY); 151 nudA = AddNumericInput(330, inputY, 0, 255, SelectedColor.A); 152 nudA.ValueChanged += (s, e) => { if (!updatingControls) { currentAlpha = nudA.Value; UpdatePreview(); } }; 153 154 // Hex input 155 AddLabel("Hex:", 15, 252); 156 txtHex = new TextBox(); 157 txtHex.Location = new Point(50, 250); 158 txtHex.Size = new Size(80, 24); 159 txtHex.BackColor = DarkControl; 160 txtHex.ForeColor = TextColor; 161 txtHex.BorderStyle = BorderStyle.FixedSingle; 162 txtHex.Font = new Font("Consolas", 9f); 163 txtHex.Text = ColorToHex(SelectedColor); 164 txtHex.TextChanged += TxtHex_TextChanged; 165 Controls.Add(txtHex); 166 167 // Buttons 168 Button btnOK = CreateButton("OK", 220, 290, 80, 30); 169 btnOK.Click += (s, e) => { if (closing) return; closing = true; DialogResult = DialogResult.OK; Close(); }; 170 Controls.Add(btnOK); 171 172 Button btnCancel = CreateButton("Cancel", 310, 290, 80, 30); 173 btnCancel.Click += (s, e) => { if (closing) return; closing = true; DialogResult = DialogResult.Cancel; Close(); }; 174 Controls.Add(btnCancel); 175 176 // Generate bitmaps 177 GenerateHueSliderBitmap(); 178 GenerateColorSquareBitmap(); 179 } 180 181 private Label AddLabel(string text, int x, int y) 182 { 183 Label lbl = new Label(); 184 lbl.Text = text; 185 lbl.Location = new Point(x, y + 3); 186 lbl.AutoSize = true; 187 lbl.ForeColor = TextColor; 188 lbl.Font = new Font("Segoe UI", 9f); 189 Controls.Add(lbl); 190 return lbl; 191 } 192 193 private DarkNumeric AddNumericInput(int x, int y, int min, int max, int value) 194 { 195 DarkNumeric nud = new DarkNumeric(); 196 nud.Location = new Point(x, y); 197 nud.Size = new Size(60, 24); 198 nud.Minimum = min; 199 nud.Maximum = max; 200 nud.Value = value; 201 Controls.Add(nud); 202 return nud; 203 } 204 205 private Button CreateButton(string text, int x, int y, int w, int h) 206 { 207 var btn = new Button(); 208 btn.Text = text; 209 btn.Location = new Point(x, y); 210 btn.Size = new Size(w, h); 211 btn.FlatStyle = FlatStyle.Flat; 212 btn.FlatAppearance.BorderSize = 0; 213 btn.BackColor = DarkControl; 214 btn.ForeColor = TextColor; 215 btn.Font = new Font("Segoe UI", 9f); 216 btn.Cursor = Cursors.Hand; 217 return btn; 218 } 219 220 private void GenerateHueSliderBitmap() 221 { 222 hueSliderBmp = new Bitmap(hueSlider.Width, hueSlider.Height); 223 using (var g = Graphics.FromImage(hueSliderBmp)) 224 { 225 for (int y = 0; y < hueSliderBmp.Height; y++) 226 { 227 float hue = y * 360f / hueSliderBmp.Height; 228 Color c = HSBToColor(hue, 1, 1); 229 using (var pen = new Pen(c)) 230 g.DrawLine(pen, 0, y, hueSliderBmp.Width, y); 231 } 232 } 233 } 234 235 private void GenerateColorSquareBitmap() 236 { 237 int W = colorSquare.Width, H = colorSquare.Height; 238 if (W <= 0 || H <= 0) return; 239 240 // Fill via LockBits instead of 40k SetPixel calls (which made dragging the hue slider lag). 241 var bmp = new Bitmap(W, H, System.Drawing.Imaging.PixelFormat.Format32bppArgb); 242 var data = bmp.LockBits(new Rectangle(0, 0, W, H), 243 System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); 244 try 245 { 246 unsafe 247 { 248 byte* basePtr = (byte*)data.Scan0; 249 for (int y = 0; y < H; y++) 250 { 251 byte* row = basePtr + y * data.Stride; 252 float bri = 1f - (float)y / H; 253 for (int x = 0; x < W; x++) 254 { 255 float sat = (float)x / W; 256 Color c = HSBToColor(currentHue, sat, bri); 257 int i = x * 4; 258 row[i] = c.B; row[i + 1] = c.G; row[i + 2] = c.R; row[i + 3] = 255; 259 } 260 } 261 } 262 } 263 finally { bmp.UnlockBits(data); } 264 265 // Dispose the previous bitmap (the old code leaked it by reassigning). 266 if (colorSquareBmp != null) colorSquareBmp.Dispose(); 267 colorSquareBmp = bmp; 268 } 269 270 private void ColorSquare_Paint(object sender, PaintEventArgs e) 271 { 272 if (colorSquareBmp != null) 273 e.Graphics.DrawImage(colorSquareBmp, 0, 0); 274 275 // Draw cursor 276 int cx = (int)(currentSat * colorSquare.Width); 277 int cy = (int)((1 - currentBri) * colorSquare.Height); 278 using (var pen = new Pen(Color.White, 2)) 279 { 280 e.Graphics.DrawEllipse(pen, cx - 6, cy - 6, 12, 12); 281 } 282 using (var pen = new Pen(Color.Black, 1)) 283 { 284 e.Graphics.DrawEllipse(pen, cx - 5, cy - 5, 10, 10); 285 } 286 } 287 288 private void HueSlider_Paint(object sender, PaintEventArgs e) 289 { 290 if (hueSliderBmp != null) 291 e.Graphics.DrawImage(hueSliderBmp, 0, 0); 292 293 // Draw cursor 294 int cy = (int)(currentHue / 360 * hueSlider.Height); 295 using (var pen = new Pen(Color.White, 2)) 296 { 297 e.Graphics.DrawRectangle(pen, 0, cy - 2, hueSlider.Width - 1, 4); 298 } 299 } 300 301 private void AlphaSlider_Paint(object sender, PaintEventArgs e) 302 { 303 // Checkerboard background 304 int checkSize = 5; 305 for (int x = 0; x < alphaSlider.Width; x += checkSize) 306 { 307 for (int y = 0; y < alphaSlider.Height; y += checkSize) 308 { 309 bool isWhite = ((x / checkSize) + (y / checkSize)) % 2 == 0; 310 using (var brush = new SolidBrush(isWhite ? Color.White : Color.LightGray)) 311 e.Graphics.FillRectangle(brush, x, y, checkSize, checkSize); 312 } 313 } 314 315 // Alpha gradient 316 Color baseColor = HSBToColor(currentHue, currentSat, currentBri); 317 for (int y = 0; y < alphaSlider.Height; y++) 318 { 319 int alpha = 255 - (y * 255 / alphaSlider.Height); 320 Color c = Color.FromArgb(alpha, baseColor); 321 using (var pen = new Pen(c)) 322 e.Graphics.DrawLine(pen, 0, y, alphaSlider.Width, y); 323 } 324 325 // Draw cursor 326 int cy = (255 - currentAlpha) * alphaSlider.Height / 255; 327 using (var pen = new Pen(Color.White, 2)) 328 { 329 e.Graphics.DrawRectangle(pen, 0, cy - 2, alphaSlider.Width - 1, 4); 330 } 331 } 332 333 private void PreviewPanel_Paint(object sender, PaintEventArgs e) 334 { 335 // Checkerboard for transparency 336 int checkSize = 8; 337 for (int x = 0; x < previewPanel.Width; x += checkSize) 338 { 339 for (int y = 0; y < previewPanel.Height; y += checkSize) 340 { 341 bool isWhite = ((x / checkSize) + (y / checkSize)) % 2 == 0; 342 using (var brush = new SolidBrush(isWhite ? Color.White : Color.LightGray)) 343 e.Graphics.FillRectangle(brush, x, y, checkSize, checkSize); 344 } 345 } 346 347 // Draw color 348 using (var brush = new SolidBrush(SelectedColor)) 349 e.Graphics.FillRectangle(brush, 0, 0, previewPanel.Width, previewPanel.Height); 350 351 // Border 352 using (var pen = new Pen(DarkPanel, 1)) 353 e.Graphics.DrawRectangle(pen, 0, 0, previewPanel.Width - 1, previewPanel.Height - 1); 354 } 355 356 private void ColorSquare_MouseDown(object sender, MouseEventArgs e) 357 { 358 draggingSquare = true; 359 UpdateFromSquare(e.Location); 360 } 361 362 private void ColorSquare_MouseMove(object sender, MouseEventArgs e) 363 { 364 if (draggingSquare) UpdateFromSquare(e.Location); 365 } 366 367 private void UpdateFromSquare(Point pt) 368 { 369 currentSat = Math.Max(0, Math.Min(1, (float)pt.X / colorSquare.Width)); 370 currentBri = Math.Max(0, Math.Min(1, 1 - (float)pt.Y / colorSquare.Height)); 371 UpdateAllFromHSB(); 372 } 373 374 private void HueSlider_MouseDown(object sender, MouseEventArgs e) 375 { 376 draggingHue = true; 377 UpdateFromHueSlider(e.Location); 378 } 379 380 private void HueSlider_MouseMove(object sender, MouseEventArgs e) 381 { 382 if (draggingHue) UpdateFromHueSlider(e.Location); 383 } 384 385 private void UpdateFromHueSlider(Point pt) 386 { 387 currentHue = Math.Max(0, Math.Min(360, (float)pt.Y / hueSlider.Height * 360)); 388 GenerateColorSquareBitmap(); 389 UpdateAllFromHSB(); 390 } 391 392 private void AlphaSlider_MouseDown(object sender, MouseEventArgs e) 393 { 394 draggingAlpha = true; 395 UpdateFromAlphaSlider(e.Location); 396 } 397 398 private void AlphaSlider_MouseMove(object sender, MouseEventArgs e) 399 { 400 if (draggingAlpha) UpdateFromAlphaSlider(e.Location); 401 } 402 403 private void UpdateFromAlphaSlider(Point pt) 404 { 405 currentAlpha = Math.Max(0, Math.Min(255, 255 - pt.Y * 255 / alphaSlider.Height)); 406 UpdateAllFromHSB(); 407 } 408 409 private void UpdateFromRGB() 410 { 411 Color c = Color.FromArgb(currentAlpha, nudR.Value, nudG.Value, nudB.Value); 412 ColorToHSB(c, out currentHue, out currentSat, out currentBri); 413 GenerateColorSquareBitmap(); 414 UpdateAllFromHSB(); 415 } 416 417 private void TxtHex_TextChanged(object sender, EventArgs e) 418 { 419 if (updatingControls) return; 420 string hex = txtHex.Text.Trim().TrimStart('#'); 421 if (hex.Length == 6 || hex.Length == 8) 422 { 423 try 424 { 425 int argb = Convert.ToInt32(hex.Length == 8 ? hex : "FF" + hex, 16); 426 Color c = Color.FromArgb(argb); 427 currentAlpha = c.A; 428 ColorToHSB(c, out currentHue, out currentSat, out currentBri); 429 GenerateColorSquareBitmap(); 430 UpdateAllFromHSB(); 431 } 432 catch { } 433 } 434 } 435 436 private void UpdateAllFromHSB() 437 { 438 updatingControls = true; 439 440 Color rgb = HSBToColor(currentHue, currentSat, currentBri); 441 SelectedColor = Color.FromArgb(currentAlpha, rgb); 442 443 nudR.Value = rgb.R; 444 nudG.Value = rgb.G; 445 nudB.Value = rgb.B; 446 nudA.Value = currentAlpha; 447 txtHex.Text = ColorToHex(SelectedColor); 448 449 updatingControls = false; 450 451 UpdatePreview(); 452 } 453 454 private void UpdatePreview() 455 { 456 Color rgb = HSBToColor(currentHue, currentSat, currentBri); 457 SelectedColor = Color.FromArgb(currentAlpha, rgb); 458 459 colorSquare.Invalidate(); 460 hueSlider.Invalidate(); 461 alphaSlider.Invalidate(); 462 previewPanel.Invalidate(); 463 } 464 465 private static void ColorToHSB(Color color, out float hue, out float sat, out float bri) 466 { 467 int max = Math.Max(color.R, Math.Max(color.G, color.B)); 468 int min = Math.Min(color.R, Math.Min(color.G, color.B)); 469 470 bri = max / 255f; 471 sat = (max == 0) ? 0 : (max - min) / (float)max; 472 473 if (max == min) 474 { 475 hue = 0; 476 } 477 else 478 { 479 float delta = max - min; 480 if (max == color.R) 481 hue = (color.G - color.B) / delta + (color.G < color.B ? 6 : 0); 482 else if (max == color.G) 483 hue = (color.B - color.R) / delta + 2; 484 else 485 hue = (color.R - color.G) / delta + 4; 486 hue *= 60; 487 } 488 } 489 490 private static Color HSBToColor(float hue, float sat, float bri) 491 { 492 if (sat == 0) 493 { 494 int gray = (int)(bri * 255); 495 return Color.FromArgb(gray, gray, gray); 496 } 497 498 float h = hue / 60f; 499 int i = (int)Math.Floor(h) % 6; 500 float f = h - (float)Math.Floor(h); 501 float p = bri * (1 - sat); 502 float q = bri * (1 - sat * f); 503 float t = bri * (1 - sat * (1 - f)); 504 505 float r, g, b; 506 switch (i) 507 { 508 case 0: r = bri; g = t; b = p; break; 509 case 1: r = q; g = bri; b = p; break; 510 case 2: r = p; g = bri; b = t; break; 511 case 3: r = p; g = q; b = bri; break; 512 case 4: r = t; g = p; b = bri; break; 513 default: r = bri; g = p; b = q; break; 514 } 515 516 return Color.FromArgb((int)(r * 255), (int)(g * 255), (int)(b * 255)); 517 } 518 519 private static string ColorToHex(Color c) 520 { 521 return ColorHelper.ColorToHexWeb(c); 522 } 523 524 protected override void Dispose(bool disposing) 525 { 526 if (disposing) 527 { 528 if (colorSquareBmp != null) colorSquareBmp.Dispose(); 529 if (hueSliderBmp != null) hueSliderBmp.Dispose(); 530 } 531 base.Dispose(disposing); 532 } 533 } 534}