ColorGradientControl.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720
  1. using System;
  2. using System.ComponentModel;
  3. using System.Drawing;
  4. using System.Drawing.Drawing2D;
  5. using System.Windows.Forms;
  6. namespace PaintDotNet
  7. {
  8. public sealed class ColorGradientControl
  9. : UserControl
  10. {
  11. private Point lastTrackingMouseXY = new Point(-1, -1);
  12. private int tracking = -1;
  13. private int highlight = -1;
  14. private const int triangleSize = 7;
  15. private const int triangleHalfLength = (triangleSize - 1) / 2;
  16. private Orientation orientation = Orientation.Vertical;
  17. private Color[] customGradient = null;
  18. private bool drawNearNub = true;
  19. public bool DrawNearNub
  20. {
  21. get
  22. {
  23. return this.drawNearNub;
  24. }
  25. set
  26. {
  27. this.drawNearNub = value;
  28. Invalidate();
  29. }
  30. }
  31. private bool drawFarNub = true;
  32. public bool DrawFarNub
  33. {
  34. get
  35. {
  36. return this.drawFarNub;
  37. }
  38. set
  39. {
  40. this.drawFarNub = value;
  41. Invalidate();
  42. }
  43. }
  44. private int[] vals;
  45. // value from [0,255] that specifies the hsv "value" component
  46. // where we should draw little triangles that show the value
  47. public int Value
  48. {
  49. get
  50. {
  51. return GetValue(0);
  52. }
  53. set
  54. {
  55. SetValue(0, value);
  56. }
  57. }
  58. public Color[] CustomGradient
  59. {
  60. get
  61. {
  62. if (this.customGradient == null)
  63. {
  64. return null;
  65. }
  66. else
  67. {
  68. return (Color[])this.customGradient.Clone();
  69. }
  70. }
  71. set
  72. {
  73. if (value != this.customGradient)
  74. {
  75. if (value == null)
  76. {
  77. this.customGradient = null;
  78. }
  79. else
  80. {
  81. this.customGradient = (Color[])value.Clone();
  82. }
  83. Invalidate();
  84. }
  85. }
  86. }
  87. public Orientation Orientation
  88. {
  89. get
  90. {
  91. return this.orientation;
  92. }
  93. set
  94. {
  95. if (value != this.orientation)
  96. {
  97. this.orientation = value;
  98. Invalidate();
  99. }
  100. }
  101. }
  102. public int Count
  103. {
  104. get
  105. {
  106. return vals.Length;
  107. }
  108. set
  109. {
  110. if (value < 0 || value > 16)
  111. {
  112. throw new ArgumentOutOfRangeException("value", value, "Count must be between 0 and 16");
  113. }
  114. vals = new int[value];
  115. if (value > 1)
  116. {
  117. for (int i = 0; i < value; i++)
  118. {
  119. vals[i] = i * 255 / (value - 1);
  120. }
  121. }
  122. else if (value == 1)
  123. {
  124. vals[0] = 128;
  125. }
  126. OnValueChanged(0);
  127. Invalidate();
  128. }
  129. }
  130. public int GetValue(int index)
  131. {
  132. if (index < 0 || index >= vals.Length)
  133. {
  134. throw new ArgumentOutOfRangeException("index", index, "Index must be within the bounds of the array");
  135. }
  136. int val = vals[index];
  137. return val;
  138. }
  139. public void SetValue(int index, int val)
  140. {
  141. int min = -1;
  142. int max = 256;
  143. if (index < 0 || index >= vals.Length)
  144. {
  145. throw new ArgumentOutOfRangeException("index", index, "Index must be within the bounds of the array");
  146. }
  147. if (index - 1 >= 0)
  148. {
  149. min = vals[index - 1];
  150. }
  151. if (index + 1 < vals.Length)
  152. {
  153. max = vals[index + 1];
  154. }
  155. if (vals[index] != val)
  156. {
  157. int newVal = Utility.Clamp(val, min + 1, max - 1);
  158. vals[index] = newVal;
  159. OnValueChanged(index);
  160. Invalidate();
  161. }
  162. Update();
  163. }
  164. public event IndexEventHandler ValueChanged;
  165. private void OnValueChanged(int index)
  166. {
  167. if (ValueChanged != null)
  168. {
  169. ValueChanged(this, new IndexEventArgs(index));
  170. }
  171. }
  172. [Obsolete("Use MinColor property instead", true)]
  173. public Color BottomColor
  174. {
  175. get
  176. {
  177. return MinColor;
  178. }
  179. set
  180. {
  181. MinColor = value;
  182. }
  183. }
  184. [Obsolete("Use MaxColor property instead", true)]
  185. public Color TopColor
  186. {
  187. get
  188. {
  189. return MaxColor;
  190. }
  191. set
  192. {
  193. MaxColor = value;
  194. }
  195. }
  196. private Color maxColor;
  197. public Color MaxColor
  198. {
  199. get
  200. {
  201. return maxColor;
  202. }
  203. set
  204. {
  205. if (maxColor != value)
  206. {
  207. maxColor = value;
  208. Invalidate();
  209. }
  210. }
  211. }
  212. private Color minColor;
  213. public Color MinColor
  214. {
  215. get
  216. {
  217. return minColor;
  218. }
  219. set
  220. {
  221. if (minColor != value)
  222. {
  223. minColor = value;
  224. Invalidate();
  225. }
  226. }
  227. }
  228. public ColorGradientControl()
  229. {
  230. // This call is required by the Windows.Forms Form Designer.
  231. InitializeComponent();
  232. this.DoubleBuffered = true;
  233. this.ResizeRedraw = true;
  234. this.Count = 1;
  235. }
  236. private void DrawGradient(Graphics g)
  237. {
  238. g.PixelOffsetMode = PixelOffsetMode.Half;
  239. Rectangle gradientRect;
  240. float gradientAngle;
  241. switch (this.orientation)
  242. {
  243. case Orientation.Horizontal:
  244. gradientAngle = 180.0f;
  245. break;
  246. case Orientation.Vertical:
  247. gradientAngle = 90.0f;
  248. break;
  249. default:
  250. throw new InvalidEnumArgumentException();
  251. }
  252. // draw gradient
  253. gradientRect = ClientRectangle;
  254. switch (this.orientation)
  255. {
  256. case Orientation.Horizontal:
  257. gradientRect.Inflate(-triangleHalfLength, -triangleSize + 3);
  258. break;
  259. case Orientation.Vertical:
  260. gradientRect.Inflate(-triangleSize + 3, -triangleHalfLength);
  261. break;
  262. default:
  263. throw new InvalidEnumArgumentException();
  264. }
  265. if (this.customGradient != null && gradientRect.Width > 1 && gradientRect.Height > 1)
  266. {
  267. Surface gradientSurface = new Surface(gradientRect.Size);
  268. using (RenderArgs ra = new RenderArgs(gradientSurface))
  269. {
  270. Utility.DrawColorRectangle(ra.Graphics, ra.Bounds, Color.Transparent, false);
  271. if (Orientation == Orientation.Horizontal)
  272. {
  273. for (int x = 0; x < gradientSurface.Width; ++x)
  274. {
  275. double index = (double)(x * (this.customGradient.Length - 1)) / (double)(gradientSurface.Width - 1);
  276. int indexL = (int)Math.Floor(index);
  277. double t = 1.0 - (index - indexL);
  278. int indexR = (int)Math.Min(this.customGradient.Length - 1, Math.Ceiling(index));
  279. Color colorL = this.customGradient[indexL];
  280. Color colorR = this.customGradient[indexR];
  281. double a1 = colorL.A / 255.0;
  282. double r1 = colorL.R / 255.0;
  283. double g1 = colorL.G / 255.0;
  284. double b1 = colorL.B / 255.0;
  285. double a2 = colorR.A / 255.0;
  286. double r2 = colorR.R / 255.0;
  287. double g2 = colorR.G / 255.0;
  288. double b2 = colorR.B / 255.0;
  289. double at = (t * a1) + ((1.0 - t) * a2);
  290. double rt;
  291. double gt;
  292. double bt;
  293. if (at == 0)
  294. {
  295. rt = 0;
  296. gt = 0;
  297. bt = 0;
  298. }
  299. else
  300. {
  301. rt = ((t * a1 * r1) + ((1.0 - t) * a2 * r2)) / at;
  302. gt = ((t * a1 * g1) + ((1.0 - t) * a2 * g2)) / at;
  303. bt = ((t * a1 * b1) + ((1.0 - t) * a2 * b2)) / at;
  304. }
  305. int ap = Utility.Clamp((int)Math.Round(at * 255.0), 0, 255);
  306. int rp = Utility.Clamp((int)Math.Round(rt * 255.0), 0, 255);
  307. int gp = Utility.Clamp((int)Math.Round(gt * 255.0), 0, 255);
  308. int bp = Utility.Clamp((int)Math.Round(bt * 255.0), 0, 255);
  309. for (int y = 0; y < gradientSurface.Height; ++y)
  310. {
  311. ColorBgra src = gradientSurface[x, y];
  312. // we are assuming that src.A = 255
  313. int rd = ((rp * ap) + (src.R * (255 - ap))) / 255;
  314. int gd = ((gp * ap) + (src.G * (255 - ap))) / 255;
  315. int bd = ((bp * ap) + (src.B * (255 - ap))) / 255;
  316. gradientSurface[x, y] = ColorBgra.FromBgra((byte)bd, (byte)gd, (byte)rd, 255);
  317. }
  318. }
  319. g.DrawImage(ra.Bitmap, gradientRect, ra.Bounds, GraphicsUnit.Pixel);
  320. }
  321. else if (Orientation == Orientation.Vertical)
  322. {
  323. }
  324. else
  325. {
  326. throw new InvalidEnumArgumentException();
  327. }
  328. }
  329. gradientSurface.Dispose();
  330. }
  331. else
  332. {
  333. using (LinearGradientBrush lgb = new LinearGradientBrush(this.ClientRectangle,
  334. maxColor, minColor, gradientAngle, false))
  335. {
  336. g.FillRectangle(lgb, gradientRect);
  337. }
  338. }
  339. // fill background
  340. using (PdnRegion nonGradientRegion = new PdnRegion())
  341. {
  342. nonGradientRegion.MakeInfinite();
  343. nonGradientRegion.Exclude(gradientRect);
  344. using (SolidBrush sb = new SolidBrush(this.BackColor))
  345. {
  346. g.FillRegion(sb, nonGradientRegion.GetRegionReadOnly());
  347. }
  348. }
  349. // draw value triangles
  350. for (int i = 0; i < this.vals.Length; i++)
  351. {
  352. int pos = ValueToPosition(vals[i]);
  353. Brush brush;
  354. Pen pen;
  355. if (i == highlight)
  356. {
  357. brush = Brushes.Blue;
  358. pen = (Pen)Pens.White.Clone();
  359. }
  360. else
  361. {
  362. brush = Brushes.Black;
  363. pen = (Pen)Pens.Gray.Clone();
  364. }
  365. g.SmoothingMode = SmoothingMode.AntiAlias;
  366. Point a1;
  367. Point b1;
  368. Point c1;
  369. Point a2;
  370. Point b2;
  371. Point c2;
  372. switch (this.orientation)
  373. {
  374. case Orientation.Horizontal:
  375. a1 = new Point(pos - triangleHalfLength, 0);
  376. b1 = new Point(pos, triangleSize - 1);
  377. c1 = new Point(pos + triangleHalfLength, 0);
  378. a2 = new Point(a1.X, Height - 1 - a1.Y);
  379. b2 = new Point(b1.X, Height - 1 - b1.Y);
  380. c2 = new Point(c1.X, Height - 1 - c1.Y);
  381. break;
  382. case Orientation.Vertical:
  383. a1 = new Point(0, pos - triangleHalfLength);
  384. b1 = new Point(triangleSize - 1, pos);
  385. c1 = new Point(0, pos + triangleHalfLength);
  386. a2 = new Point(Width - 1 - a1.X, a1.Y);
  387. b2 = new Point(Width - 1 - b1.X, b1.Y);
  388. c2 = new Point(Width - 1 - c1.X, c1.Y);
  389. break;
  390. default:
  391. throw new InvalidEnumArgumentException();
  392. }
  393. if (this.drawNearNub)
  394. {
  395. g.FillPolygon(brush, new Point[] { a1, b1, c1, a1 });
  396. }
  397. if (this.drawFarNub)
  398. {
  399. g.FillPolygon(brush, new Point[] { a2, b2, c2, a2 });
  400. }
  401. if (pen != null)
  402. {
  403. if (this.drawNearNub)
  404. {
  405. g.DrawPolygon(pen, new Point[] { a1, b1, c1, a1 });
  406. }
  407. if (this.drawFarNub)
  408. {
  409. g.DrawPolygon(pen, new Point[] { a2, b2, c2, a2 });
  410. }
  411. pen.Dispose();
  412. }
  413. }
  414. }
  415. protected override void OnPaint(PaintEventArgs e)
  416. {
  417. base.OnPaint(e);
  418. DrawGradient(e.Graphics);
  419. }
  420. protected override void OnPaintBackground(PaintEventArgs pevent)
  421. {
  422. DrawGradient(pevent.Graphics);
  423. }
  424. /// <summary>
  425. /// Clean up any resources being used.
  426. /// </summary>
  427. protected override void Dispose(bool disposing)
  428. {
  429. if (disposing)
  430. {
  431. }
  432. base.Dispose(disposing);
  433. }
  434. private int PositionToValue(int pos)
  435. {
  436. int max;
  437. switch (this.orientation)
  438. {
  439. case Orientation.Horizontal:
  440. max = Width;
  441. break;
  442. case Orientation.Vertical:
  443. max = Height;
  444. break;
  445. default:
  446. throw new InvalidEnumArgumentException();
  447. }
  448. int val = (((max - triangleSize) - (pos - triangleHalfLength)) * 255) / (max - triangleSize);
  449. if (this.orientation == Orientation.Horizontal)
  450. {
  451. val = 255 - val;
  452. }
  453. return val;
  454. }
  455. private int ValueToPosition(int val)
  456. {
  457. int max;
  458. if (this.orientation == Orientation.Horizontal)
  459. {
  460. val = 255 - val;
  461. }
  462. switch (this.orientation)
  463. {
  464. case Orientation.Horizontal:
  465. max = Width;
  466. break;
  467. case Orientation.Vertical:
  468. max = Height;
  469. break;
  470. default:
  471. throw new InvalidEnumArgumentException();
  472. }
  473. int pos = triangleHalfLength + ((max - triangleSize) - (((val * (max - triangleSize)) / 255)));
  474. return pos;
  475. }
  476. private int WhichTriangle(int val)
  477. {
  478. int bestIndex = -1;
  479. int bestDistance = int.MaxValue;
  480. int v = PositionToValue(val);
  481. for (int i = 0; i < this.vals.Length; i++)
  482. {
  483. int distance = Math.Abs(this.vals[i] - v);
  484. if (distance < bestDistance)
  485. {
  486. bestDistance = distance;
  487. bestIndex = i;
  488. }
  489. }
  490. return bestIndex;
  491. }
  492. protected override void OnMouseDown(MouseEventArgs e)
  493. {
  494. base.OnMouseDown(e);
  495. if (e.Button == MouseButtons.Left)
  496. {
  497. int val = GetOrientedValue(e);
  498. tracking = WhichTriangle(val);
  499. Invalidate();
  500. OnMouseMove(e);
  501. }
  502. }
  503. protected override void OnMouseUp(MouseEventArgs e)
  504. {
  505. base.OnMouseUp(e);
  506. if (e.Button == MouseButtons.Left)
  507. {
  508. OnMouseMove(e);
  509. tracking = -1;
  510. Invalidate();
  511. }
  512. }
  513. private int GetOrientedValue(MouseEventArgs me)
  514. {
  515. return GetOrientedValue(new Point(me.X, me.Y));
  516. }
  517. private int GetOrientedValue(Point pt)
  518. {
  519. int pos;
  520. switch (this.orientation)
  521. {
  522. case Orientation.Horizontal:
  523. pos = pt.X;
  524. break;
  525. case Orientation.Vertical:
  526. pos = pt.Y;
  527. break;
  528. default:
  529. throw new InvalidEnumArgumentException();
  530. }
  531. return pos;
  532. }
  533. protected override void OnMouseMove(MouseEventArgs e)
  534. {
  535. base.OnMouseMove(e);
  536. int pos = GetOrientedValue(e);
  537. Point newMouseXY = new Point(e.X, e.Y);
  538. if (tracking >= 0 && newMouseXY != this.lastTrackingMouseXY)
  539. {
  540. int val = PositionToValue(pos);
  541. this.SetValue(tracking, val);
  542. this.lastTrackingMouseXY = newMouseXY;
  543. }
  544. else
  545. {
  546. int oldHighlight = highlight;
  547. highlight = WhichTriangle(pos);
  548. if (highlight != oldHighlight)
  549. {
  550. this.InvalidateTriangle(oldHighlight);
  551. this.InvalidateTriangle(highlight);
  552. }
  553. }
  554. }
  555. protected override void OnMouseLeave(EventArgs e)
  556. {
  557. int oldhighlight = highlight;
  558. highlight = -1;
  559. this.InvalidateTriangle(oldhighlight);
  560. }
  561. private void InvalidateTriangle(int index)
  562. {
  563. if (index < 0 || index >= this.vals.Length)
  564. {
  565. return;
  566. }
  567. int value = ValueToPosition(this.vals[index]);
  568. Rectangle rect;
  569. switch (this.orientation)
  570. {
  571. case Orientation.Horizontal:
  572. rect = new Rectangle(value - triangleHalfLength, 0, triangleSize, this.Height);
  573. break;
  574. case Orientation.Vertical:
  575. rect = new Rectangle(0, value - triangleHalfLength, this.Width, triangleSize);
  576. break;
  577. default:
  578. throw new InvalidEnumArgumentException();
  579. }
  580. this.Invalidate(rect, true);
  581. }
  582. #region Component Designer generated code
  583. /// <summary>
  584. /// Required method for Designer support - do not modify
  585. /// the contents of this method with the code editor.
  586. /// </summary>
  587. private void InitializeComponent()
  588. {
  589. }
  590. #endregion
  591. }
  592. }