|
| 1 | +/* |
| 2 | + * COPYRIGHT: See COPYING in the top level directory |
| 3 | + * PROJECT: CommonControls |
| 4 | + * FILE: ColorPick.cs |
| 5 | + * PURPOSE: Color Palette Control, much like Color Picker but for a finer selection of Colors |
| 6 | + * PROGRAMER: Peter Geinitz (Wayfarer) |
| 7 | + * SOURCE: https://manufacture.tistory.com/33 |
| 8 | + * https://www.rapidtables.com/convert/color/rgb-to-hsv.html |
| 9 | + * https://stackoverflow.com/questions/42531608/hsv-triangle-in-c-sharp |
| 10 | + * https://en.wikipedia.org/wiki/HSL_and_HSV#From_HSV |
| 11 | + */ |
| 12 | + |
| 13 | +using System.Drawing; |
| 14 | +using System.Drawing.Imaging; |
| 15 | +using Imaging; |
| 16 | +using Point = System.Windows.Point; |
| 17 | + |
| 18 | +namespace CommonControls.Images |
| 19 | +{ |
| 20 | + /// <summary> |
| 21 | + /// Generate basic Color Wheel and Triangle |
| 22 | + /// </summary> |
| 23 | + internal static class ColorPick |
| 24 | + { |
| 25 | + /// <summary> |
| 26 | + /// Enum for the Areas |
| 27 | + /// </summary> |
| 28 | + public enum Area |
| 29 | + { |
| 30 | + Outside = 0, |
| 31 | + Wheel = 1, |
| 32 | + Triangle = 2 |
| 33 | + } |
| 34 | + |
| 35 | + /// <summary> |
| 36 | + /// Gets or sets the sat. |
| 37 | + /// </summary> |
| 38 | + private static double _sat; |
| 39 | + |
| 40 | + /// <summary> |
| 41 | + /// Gets or sets the value. |
| 42 | + /// </summary> |
| 43 | + private static double _val; |
| 44 | + |
| 45 | + /// <summary> |
| 46 | + /// Gets or sets the hue. |
| 47 | + /// </summary> |
| 48 | + private static double _hue; |
| 49 | + |
| 50 | + /// <summary> |
| 51 | + /// Center in our case 400/2, 200. |
| 52 | + /// Since it is a circle and the radius is equal we don't need to differentiate between x or y. |
| 53 | + /// </summary> |
| 54 | + private static int Center => ColorPickerRegister.Size / 2; |
| 55 | + |
| 56 | + /// <summary> |
| 57 | + /// Inner radius, in our case for Size 400, 166 2/3 |
| 58 | + /// </summary> |
| 59 | + private static int InnerRadius => ColorPickerRegister.Size * 5 / 12; |
| 60 | + |
| 61 | + /// <summary> |
| 62 | + /// Outer Circle, in our Case 200 |
| 63 | + /// </summary> |
| 64 | + private static int OuterRadius => ColorPickerRegister.Size / 2; |
| 65 | + |
| 66 | + internal static void Initiate(double h, double s, double v) |
| 67 | + { |
| 68 | + _sat = s; |
| 69 | + _val = v; |
| 70 | + _hue = h; |
| 71 | + } |
| 72 | + |
| 73 | + /// <summary> |
| 74 | + /// Hue might be changed. Redraw the Circle and change up Hue. |
| 75 | + /// </summary> |
| 76 | + /// <returns>New Background Image</returns> |
| 77 | + internal static Bitmap InitiateBackGround() |
| 78 | + { |
| 79 | + var img = DrawImage(_hue); |
| 80 | + |
| 81 | + //Generate two images, second one for the cursors |
| 82 | + using (Graphics.FromImage(img)) |
| 83 | + { |
| 84 | + return new Bitmap(img); |
| 85 | + } |
| 86 | + } |
| 87 | + |
| 88 | + /// <summary> |
| 89 | + /// Draw the Cursors again |
| 90 | + /// </summary> |
| 91 | + /// <returns> |
| 92 | + /// Image with Cursors |
| 93 | + /// </returns> |
| 94 | + internal static Bitmap InitiateCursor() |
| 95 | + { |
| 96 | + var img = new Bitmap(ColorPickerRegister.Size, ColorPickerRegister.Size, PixelFormat.Format32bppArgb); |
| 97 | + |
| 98 | + //Generate two images, second one for the cursors |
| 99 | + |
| 100 | + using (var g = Graphics.FromImage(img)) |
| 101 | + { |
| 102 | + var pen = Pens.Black; |
| 103 | + |
| 104 | + var wheelPosition = GetWheelPosition(); |
| 105 | + g.DrawEllipse(pen, (float)wheelPosition.X - 5, (float)wheelPosition.Y - 5, 15, 15); |
| 106 | + |
| 107 | + //change Color of cursor depending of Background Color |
| 108 | + pen = _val < 0.5 ? Pens.White : Pens.Black; |
| 109 | + |
| 110 | + var trianglePosition = GetTrianglePosition(); |
| 111 | + g.DrawEllipse(pen, (float)trianglePosition.X - 5, (float)trianglePosition.Y - 5, 10, 10); |
| 112 | + } |
| 113 | + |
| 114 | + return new Bitmap(img); |
| 115 | + } |
| 116 | + |
| 117 | + /// <summary> |
| 118 | + /// Selections the specified x. |
| 119 | + /// </summary> |
| 120 | + /// <param name="x">The x.</param> |
| 121 | + /// <param name="y">The y.</param> |
| 122 | + /// <param name="alpha">Alpha Channel</param> |
| 123 | + internal static void Selection(double x, double y, int alpha) |
| 124 | + { |
| 125 | + var check = ColorProcessing.InTriangle(x, y); |
| 126 | + if (check) |
| 127 | + { |
| 128 | + _sat = ColorProcessing.CalcSat(x, y); |
| 129 | + _val = ColorProcessing.CalcVal(x, y); |
| 130 | + ColorPickerRegister.Colors = ColorHsv.FromHsv(_hue, _sat, _val, alpha); |
| 131 | + |
| 132 | + return; |
| 133 | + } |
| 134 | + |
| 135 | + //Check circle |
| 136 | + check = ColorProcessing.InCircle(x, y); |
| 137 | + if (check) |
| 138 | + { |
| 139 | + _hue = ColorProcessing.CalcHue(x, y); |
| 140 | + } |
| 141 | + |
| 142 | + ColorPickerRegister.Colors = ColorHsv.FromHsv(_hue, _sat, _val, alpha); |
| 143 | + } |
| 144 | + |
| 145 | + /// <summary> |
| 146 | + /// Do not Change to global Hsv Values here! |
| 147 | + /// </summary> |
| 148 | + /// <param name="hue">The hue.</param> |
| 149 | + /// <param name="sat">The sat.</param> |
| 150 | + /// <param name="val">The value.</param> |
| 151 | + /// <returns> |
| 152 | + /// New Bitmap |
| 153 | + /// </returns> |
| 154 | + private static Bitmap DrawImage(double hue = 3.3, double sat = 1.0, double val = 1.0) |
| 155 | + { |
| 156 | + var img = new Bitmap(ColorPickerRegister.Size, ColorPickerRegister.Size, PixelFormat.Format32bppArgb); |
| 157 | + for (var y = 0; y < ColorPickerRegister.Size; y++) |
| 158 | + for (var x = 0; x < ColorPickerRegister.Size; x++) |
| 159 | + { |
| 160 | + var result = Pick(x, y); |
| 161 | + |
| 162 | + Color color; |
| 163 | + switch (result.Area) |
| 164 | + { |
| 165 | + case Area.Outside: |
| 166 | + color = Color.Transparent; |
| 167 | + break; |
| 168 | + case Area.Wheel: |
| 169 | + color = Hsv(result.Hue, sat, val, 1); |
| 170 | + break; |
| 171 | + default: |
| 172 | + color = Hsv(hue, result.Sat, result.Val, 1); |
| 173 | + break; |
| 174 | + } |
| 175 | + |
| 176 | + img.SetPixel(x, y, color); |
| 177 | + } |
| 178 | + |
| 179 | + return img; |
| 180 | + } |
| 181 | + |
| 182 | + /// <summary> |
| 183 | + /// Picks the specified Hsv. |
| 184 | + /// Do not Change to global Hsv Values here! |
| 185 | + /// </summary> |
| 186 | + /// <param name="x">The x.</param> |
| 187 | + /// <param name="y">The y.</param> |
| 188 | + /// <returns>Clicked results</returns> |
| 189 | + private static PickResults Pick(double x, double y) |
| 190 | + { |
| 191 | + var distanceFromCenter = Math.Sqrt((x - Center) * (x - Center) + (y - Center) * (y - Center)); |
| 192 | + var sqrt3 = Math.Sqrt(3); |
| 193 | + |
| 194 | + if (distanceFromCenter > OuterRadius) |
| 195 | + // Outside |
| 196 | + { |
| 197 | + return new PickResults { Area = Area.Outside }; |
| 198 | + } |
| 199 | + |
| 200 | + if (distanceFromCenter > InnerRadius) |
| 201 | + { |
| 202 | + // Wheel |
| 203 | + var angle = Math.Atan2(y - Center, x - Center) + Math.PI / 2; |
| 204 | + if (angle < 0) |
| 205 | + { |
| 206 | + angle += 2 * Math.PI; |
| 207 | + } |
| 208 | + |
| 209 | + var hue = angle; |
| 210 | + |
| 211 | + return new PickResults { Area = Area.Wheel, Hue = hue }; |
| 212 | + } |
| 213 | + |
| 214 | + // Inside |
| 215 | + var x1 = (x - Center) * 1.0 / InnerRadius; |
| 216 | + var y1 = (y - Center) * 1.0 / InnerRadius; |
| 217 | + if (0 * x1 + 2 * y1 > 1) |
| 218 | + { |
| 219 | + return new PickResults { Area = Area.Outside }; |
| 220 | + } |
| 221 | + |
| 222 | + if (sqrt3 * x1 + -1 * y1 > 1) |
| 223 | + { |
| 224 | + return new PickResults { Area = Area.Outside }; |
| 225 | + } |
| 226 | + |
| 227 | + if (-sqrt3 * x1 + -1 * y1 > 1) |
| 228 | + { |
| 229 | + return new PickResults { Area = Area.Outside }; |
| 230 | + } |
| 231 | + |
| 232 | + // Triangle |
| 233 | + var sat = (1 - 2 * y1) / (sqrt3 * x1 - y1 + 2); |
| 234 | + var val = (sqrt3 * x1 - y1 + 2) / 3; |
| 235 | + |
| 236 | + return new PickResults { Area = Area.Triangle, Sat = sat, Val = val }; |
| 237 | + } |
| 238 | + |
| 239 | + /// <summary> |
| 240 | + /// Do not Change to global Hsv Values here! |
| 241 | + /// </summary> |
| 242 | + /// <param name="hue">The hue.</param> |
| 243 | + /// <param name="sat">The sat.</param> |
| 244 | + /// <param name="val">The value.</param> |
| 245 | + /// <param name="alpha">The alpha.</param> |
| 246 | + /// <returns> |
| 247 | + /// New Hsv Values |
| 248 | + /// </returns> |
| 249 | + private static Color Hsv(double hue, double sat, double val, double alpha) |
| 250 | + { |
| 251 | + var chroma = val * sat; |
| 252 | + const double step = Math.PI / 3; |
| 253 | + var intern = chroma * (1 - Math.Abs(hue / step % 2.0 - 1)); |
| 254 | + var shift = val - chroma; |
| 255 | + |
| 256 | + if (hue < 1 * step) |
| 257 | + { |
| 258 | + return Rgb(shift + chroma, shift + intern, shift + 0, alpha); |
| 259 | + } |
| 260 | + |
| 261 | + if (hue < 2 * step) |
| 262 | + { |
| 263 | + return Rgb(shift + intern, shift + chroma, shift + 0, alpha); |
| 264 | + } |
| 265 | + |
| 266 | + if (hue < 3 * step) |
| 267 | + { |
| 268 | + return Rgb(shift + 0, shift + chroma, shift + intern, alpha); |
| 269 | + } |
| 270 | + |
| 271 | + if (hue < 4 * step) |
| 272 | + { |
| 273 | + return Rgb(shift + 0, shift + intern, shift + chroma, alpha); |
| 274 | + } |
| 275 | + |
| 276 | + if (hue < 5 * step) |
| 277 | + { |
| 278 | + return Rgb(shift + intern, shift + 0, shift + chroma, alpha); |
| 279 | + } |
| 280 | + |
| 281 | + return Rgb(shift + chroma, shift + 0, shift + intern, alpha); |
| 282 | + } |
| 283 | + |
| 284 | + /// <summary> |
| 285 | + /// Gets the wheel position. |
| 286 | + /// </summary> |
| 287 | + /// <returns>Position in wheel</returns> |
| 288 | + private static Point GetWheelPosition() |
| 289 | + { |
| 290 | + var middleRadius = (double)(InnerRadius + OuterRadius) / 2; |
| 291 | + |
| 292 | + return new Point { X = Center + middleRadius * Math.Sin(_hue), Y = Center - middleRadius * Math.Cos(_hue) }; |
| 293 | + } |
| 294 | + |
| 295 | + /// <summary> |
| 296 | + /// Gets the triangle position. |
| 297 | + /// </summary> |
| 298 | + /// <returns>Position in Triangle</returns> |
| 299 | + private static Point GetTrianglePosition() |
| 300 | + { |
| 301 | + var sqrt3 = Math.Sqrt(3); |
| 302 | + |
| 303 | + return new Point |
| 304 | + { |
| 305 | + X = Center + InnerRadius * (2 * _val - _sat * _val - 1) * sqrt3 / 2, |
| 306 | + Y = Center + InnerRadius * (1 - 3 * _sat * _val) / 2 |
| 307 | + }; |
| 308 | + } |
| 309 | + |
| 310 | + /// <summary> |
| 311 | + /// RGBs the specified red. |
| 312 | + /// </summary> |
| 313 | + /// <param name="red">The red.</param> |
| 314 | + /// <param name="green">The green.</param> |
| 315 | + /// <param name="blue">The blue.</param> |
| 316 | + /// <param name="alpha">The alpha.</param> |
| 317 | + /// <returns>New Color Range</returns> |
| 318 | + private static Color Rgb(double red, double green, double blue, double alpha) |
| 319 | + { |
| 320 | + return Color.FromArgb( |
| 321 | + Math.Min(255, (int)(alpha * 256)), |
| 322 | + Math.Min(255, (int)(red * 256)), |
| 323 | + Math.Min(255, (int)(green * 256)), |
| 324 | + Math.Min(255, (int)(blue * 256))); |
| 325 | + } |
| 326 | + } |
| 327 | +} |
0 commit comments