using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
using BauzoidNET.math;
namespace BauzoidNET
.controls
{
public partial class ColorWidget
: UserControl
{
private enum MouseDownState
{
NONE,
WHEEL,
BOX
}
private Bitmap mHueWheel
= null;
private Bitmap mGradientBox
= null;
private float mCurrentHue
= 0;
private float mCurrentSaturation
= 0
.5f
;
private float mCurrentValue
= 0
.5f
;
private MouseDownState mMouseDownState
= MouseDownState
.NONE;
private float mWheelThicknessFactor
= 0
.2f
;
private int mMarkerSize
= 4;
public ColorWidget
()
{
InitializeComponent
();
/*Vector3 hsv = MathUtil.rgbToHsv(new Vector3(0.0f, 0.0f, 1.0f));
Vector3 rgb = MathUtil.hsvToRgb(hsv);
Vector3 rgb2 = MathUtil.hsvToRgb(new Vector3(240.0f, 1.0f, 1.0f));*/
createHueWheel
();
createGradientBox
();
colorWheelBox
.Invalidate();
}
private void createGradientBox
()
{
mGradientBox
= new Bitmap
(GradientBoxSize, GradientBoxSize
);
Rectangle rect
= new Rectangle
(0,
0, mGradientBox
.Width, mGradientBox
.Height);
BitmapData data
= mGradientBox
.LockBits(rect,
System.Drawing.Imaging.ImageLockMode.ReadWrite,
System.Drawing.Imaging.PixelFormat.Format32bppArgb);
IntPtr ptr
= data
.Scan0;
int bytes
= data
.Stride * mGradientBox
.Height;
var rgbValues
= new byte[bytes
];
int width
= mGradientBox
.Width;
int height
= mGradientBox
.Height;
for (int x
= 0; x
< width
; x
++)
{
for (int y
= 0; y
< height
; y
++)
{
float s
= (float)x
/ (float)width
;
float v
= 1
.0f
-(float)y
/ (float)height
;
Vector3 rgbColor
= MathUtil
.hsvToRgb(new Vector3
(mCurrentHue, s, v
));
//rgbValues[i++] = (byte)(rgbColor.x * 255);
//rgbValues[i++] = (byte)(rgbColor.y * 255);
//rgbValues[i++] = (byte)(rgbColor.z * 255);
byte a
= 255;
byte r
= (byte)(rgbColor
.x * 255);
byte g
= (byte)(rgbColor
.y * 255);
byte b
= (byte)(rgbColor
.z * 255);
rgbValues
[(x
* 4 + y
* data
.Stride) + 0] = b
;
rgbValues
[(x
* 4 + y
* data
.Stride) + 1] = g
;
rgbValues
[(x
* 4 + y
* data
.Stride) + 2] = r
;
rgbValues
[(x
* 4 + y
* data
.Stride) + 3] = a
;
}
}
//Vector3 test = MathUtil.hsvToRgb(new Vector3(mCurrentHue, 1.0f, 1.0f));
//System.Diagnostics.Debug.WriteLine("Hue: " + mCurrentHue + " RGB: " + test.x + ", " + test.y + ", " + test.z);
Marshal
.Copy(rgbValues,
0, ptr, bytes
);
mGradientBox
.UnlockBits(data
);
}
private void createHueWheel
()
{
mHueWheel
= new Bitmap
(Width
*2, Height
*2);
Rectangle rect
= new Rectangle
(0,
0, Width, Height
);
BitmapData data
= mHueWheel
.LockBits(rect,
System.Drawing.Imaging.ImageLockMode.ReadWrite,
System.Drawing.Imaging.PixelFormat.Format32bppArgb);
IntPtr ptr
= data
.Scan0;
int bytes
= data
.Stride * mHueWheel
.Height;
var rgbValues
= new byte[bytes
];
int width
= mHueWheel
.Width;
int height
= mHueWheel
.Height;
int centerX
= width
/ 2;
int centerY
= height
/ 2;
float s
= 1
.0f
;
float v
= 1
.0f
;
for (int x
= 0; x
< width
; x
++)
{
for (int y
= 0; y
< height
; y
++)
{
byte a
= 0;
byte r
= 64;
byte g
= 64;
byte b
= 64;
Vector2 vec
= new Vector2
(centerX
- x, centerY
- y
);
float dist
= (float)System.Math.Sqrt(vec
.x * vec
.x + vec
.y * vec
.y);
if ((dist
>= InnerRadius
*2) && (dist
<= OuterRadius
*2))
//if (isInsideWheel((int)x/2, (int)y/2))
{
float theta
= MathUtil
.radToDeg((float)Math
.Atan2(vec
.y,
-vec
.x));
// rotate by 90 degrees
theta
-= 90
.0f
;
if (theta
< -180
.0f
)
theta
+= 360
.0f
;
/* theta -= Math.PI / 2;
if (theta < -Math.PI)
theta += (Math.PI * 2);*/
//float h = (float)((theta + System.Math.PI) / (2 * System.Math.PI));
float h
= (theta
+ 180
.0f
);
//System.Diagnostics.Debug.WriteLine("h = " + h);
Vector3 rgbColor
= MathUtil
.hsvToRgb(new Vector3
(h, s, v
));
a
= 255;
r
= (byte)(rgbColor
.x * 255);
g
= (byte)(rgbColor
.y * 255);
b
= (byte)(rgbColor
.z * 255);
/*rgbValues[i++] = (byte)(rgbColor.x * 255);
rgbValues[i++] = (byte)(rgbColor.y * 255);
rgbValues[i++] = (byte)(rgbColor.z * 255);*/
}
rgbValues
[(x
* 4 + y
* data
.Stride) + 0] = r
;
rgbValues
[(x
* 4 + y
* data
.Stride) + 1] = g
;
rgbValues
[(x
* 4 + y
* data
.Stride) + 2] = b
;
rgbValues
[(x
* 4 + y
* data
.Stride) + 3] = a
;
}
}
Marshal
.Copy(rgbValues,
0, ptr, bytes
);
mHueWheel
.UnlockBits(data
);
drawCircle
(Graphics
.FromImage(mHueWheel
), InnerRadius
- 2);
drawCircle
(Graphics
.FromImage(mHueWheel
), OuterRadius
+ 2);
}
private void drawCircle
(Graphics g,
int radius
)
{
int diameter
= radius
* 2;
g
.DrawEllipse(new Pen
(Color
.FromArgb(192,
192,
192),
2),
new Rectangle
((Width
- diameter
),
(Height
- diameter
), diameter
* 2, diameter
* 2));
}
private void colorWheelBox_Paint
(object sender, PaintEventArgs e
)
{
Graphics g
= e
.Graphics;
g
.InterpolationMode = InterpolationMode
.HighQualityBilinear;
g
.SmoothingMode = SmoothingMode
.AntiAlias;
Rectangle rect
= new Rectangle
(0,
0, Width, Height
);
g
.DrawImage(mHueWheel, rect
);
int boxOffset
= 2;
int x
= getGradientBoxLeft
();
int y
= getGradientBoxTop
();
Rectangle rect2
= new Rectangle
(x, y, GradientBoxSize, GradientBoxSize
);
g
.DrawImage(mGradientBox, rect2
);
g
.DrawRectangle(new Pen
(Color
.FromArgb(192,
192,
192), 0
.5f
),
new Rectangle
(x
- boxOffset, y
- boxOffset, GradientBoxSize
+ boxOffset
* 2, GradientBoxSize
+ boxOffset
* 2));
drawHueMarker
(g
);
drawSaturationValueMarker
(g
);
}
private int getGradientBoxLeft
()
{
return (int)((Width
- GradientBoxSize
) / 2) + 1;
}
private int getGradientBoxTop
()
{
return (int)((Height
- GradientBoxSize
) / 2) + 1;
}
private void drawHueMarker
(Graphics g
)
{
int center_radius
= (OuterRadius
+ InnerRadius
) / 2;
float radHue
= (float)(MathUtil
.degToRad(mCurrentHue
- 150
.0f
));
float x
= (float)Math
.Cos(radHue
) * center_radius
+ Width
/ 2;
float y
= (float)Math
.Sin(radHue
) * center_radius
+ Height
/ 2;
Vector3 rgbColor
= MathUtil
.hsvToRgb(new Vector3
((float)(mCurrentHue
), 1
.0f, 1
.0f
));
/*System.Diagnostics.Debug.WriteLine("==");
System.Diagnostics.Debug.WriteLine("Marker Hue: " + mCurrentHue + " RGB: " + rgbColor.x + ", " + rgbColor.y + ", " + rgbColor.z);
System.Diagnostics.Debug.WriteLine("==");*/
/*System.Diagnostics.Debug.WriteLine("==");
System.Diagnostics.Debug.WriteLine("HSV: " + mCurrentHue);
System.Diagnostics.Debug.WriteLine("RGB: " + rgbColor.x + ", " + rgbColor.y + ", " + rgbColor.z);
System.Diagnostics.Debug.WriteLine("==");*/
Color color
= Color
.FromArgb((int)((1-rgbColor
.x) * 255),
(int)((1-rgbColor
.y) * 255),
(int)((1-rgbColor
.z) * 255));
g
.DrawEllipse(new Pen
(color,
2),
new Rectangle
((int)x
- MarkerSize,
(int)y
- MarkerSize, MarkerSize
* 2, MarkerSize
* 2));
}
private void drawSaturationValueMarker
(Graphics g
)
{
Vector3 rgbColor
= MathUtil
.hsvToRgb(new Vector3
(mCurrentHue, mCurrentSaturation, mCurrentValue
));
Color color
= Color
.FromArgb((int)((1 - rgbColor
.x) * 255),
(int)((1 - rgbColor
.y) * 255),
(int)((1 - rgbColor
.z) * 255));
int x
= (int)(getGradientBoxLeft
() + (float)(GradientBoxSize
-1) * mCurrentSaturation
);
int y
= (int)(getGradientBoxTop
() + (float)(GradientBoxSize
-1) * (1
.0f
- mCurrentValue
));
g
.DrawEllipse(new Pen
(color,
2),
new Rectangle
((int)x
- MarkerSize,
(int)y
- MarkerSize, MarkerSize
* 2, MarkerSize
* 2));
}
private void colorWheelBox_Resize
(object sender, EventArgs e
)
{
createHueWheel
();
}
private void colorWheelBox_MouseDown
(object sender, MouseEventArgs e
)
{
if (isInsideWheel
(e
.X, e
.Y))
{
mMouseDownState
= MouseDownState
.WHEEL;
updateHue
(e
.X, e
.Y);
}
else if (isInsideGradientBox
(e
.X, e
.Y))
{
mMouseDownState
= MouseDownState
.BOX;
updateSaturationValue
(e
.X, e
.Y);
}
else
{
mMouseDownState
= MouseDownState
.NONE;
}
}
private void colorWheelBox_MouseMove
(object sender, MouseEventArgs e
)
{
if (mMouseDownState
== MouseDownState
.WHEEL)
{
updateHue
(e
.X, e
.Y);
}
else if (mMouseDownState
== MouseDownState
.BOX)
{
updateSaturationValue
(e
.X, e
.Y);
}
}
private void colorWheelBox_MouseUp
(object sender, MouseEventArgs e
)
{
if (mMouseDownState
== MouseDownState
.WHEEL)
{
updateHue
(e
.X, e
.Y);
}
else if (mMouseDownState
== MouseDownState
.BOX)
{
updateSaturationValue
(e
.X, e
.Y);
}
mMouseDownState
= MouseDownState
.NONE;
}
private void updateHue
(int x,
int y
)
{
int centerX
= x
- Width
/ 2;
int centerY
= y
- Height
/ 2;
float theta
= MathUtil
.radToDeg((float)Math
.Atan2(centerY, centerX
));
theta
-= 90
.0f
;
if (theta
< -180
.0f
)
theta
+= 360
.0f
;
mCurrentHue
= theta
+ 180
.0f
+ 60
.0f
;
if (mCurrentHue
>= 360
.0f
)
mCurrentHue
-= 360
.0f
;
/*theta -= Math.PI / 2;
if (theta < -Math.PI)
theta += (Math.PI * 2);*/
// theta can go from -pi to pi
//mCurrentHue = MathUtil.clamp((float)((theta + System.Math.PI) / (2 * System.Math.PI)), 0, 1);
//System.Diagnostics.Debug.WriteLine("Hue: " + mCurrentHue);
createGradientBox
();
colorWheelBox
.Invalidate();
OnColorChange
(EventArgs
.Empty);
}
private void updateSaturationValue
(int x,
int y
)
{
int left
= getGradientBoxLeft
();
int top
= getGradientBoxTop
();
int _x
= x
- left
;
int _y
= y
- top
;
mCurrentSaturation
= MathUtil
.clamp(((float)_x
/ (float)GradientBoxSize
), 0
.0f, 1
.0f
);
mCurrentValue
= 1
.0f
-MathUtil
.clamp(((float)_y
/ (float)GradientBoxSize
), 0
.0f, 1
.0f
);
colorWheelBox
.Invalidate();
OnColorChange
(EventArgs
.Empty);
}
private void setFromColor
(Color color
)
{
Vector3 c
= new Vector3
((float)color
.R / 255
.0f,
(float)color
.G / 255
.0f,
(float)color
.B / 255
.0f
);
Vector3 hsv
= MathUtil
.rgbToHsv(c
);
mCurrentHue
= hsv
.x;
mCurrentSaturation
= hsv
.y;
mCurrentValue
= hsv
.z;
/*System.Diagnostics.Debug.WriteLine("=====");
System.Diagnostics.Debug.WriteLine("HSV Set: " + hsv.x + ", " + hsv.y + ", " + hsv.z);
System.Diagnostics.Debug.WriteLine("=====");*/
createGradientBox
();
colorWheelBox
.Invalidate();
}
private float getDistanceFromCenter
(int x,
int y
)
{
int centerX
= x
- Width
/ 2;
int centerY
= y
- Height
/ 2;
return (float)System.Math.Sqrt(centerX
* centerX
+ centerY
* centerY
);
}
private bool isInsideWheel
(int x,
int y
)
{
float dist
= getDistanceFromCenter
(x, y
);
return isInsideWheel
(dist
);
}
private bool isInsideGradientBox
(int x,
int y
)
{
int left
= getGradientBoxLeft
();
int top
= getGradientBoxTop
();
return ((x
>= left
) && (x
<= (left
+ GradientBoxSize
)) && (y
>= top
) && (y
<= (top
+ GradientBoxSize
)));
}
private bool isInsideWheel
(float distanceFromCenter
)
{
return ((distanceFromCenter
>= InnerRadius
) && (distanceFromCenter
<= OuterRadius
));
}
public int OuterRadius
{
get
{ return (Math
.Min(Width, Height
) / 2 - 4); }
}
public int InnerRadius
{
get
{ return (int)(OuterRadius
- (float)OuterRadius
* mWheelThicknessFactor
); }
}
public int GradientBoxSize
{
get
{ return (int)Math
.Sqrt(InnerRadius
* InnerRadius
/ 2) * 2 - 6; }
}
public float Hue
{
get
{ return mCurrentHue
; }
}
public float Saturation
{
get
{ return mCurrentSaturation
; }
}
public float Value
{
get
{ return mCurrentValue
; }
}
public Color CurrentColor
{
get
{
Vector3 rgbColor
= MathUtil
.hsvToRgb(new Vector3
(mCurrentHue, mCurrentSaturation, mCurrentValue
));
return Color
.FromArgb((int)((rgbColor
.x) * 255),
(int)((rgbColor
.y) * 255),
(int)((rgbColor
.z) * 255));
}
set
{
setFromColor
(value
);
}
}
[Description
("Size of the Marker"), Category
("Appearance")]
public int MarkerSize
{
get
{ return mMarkerSize
; }
set
{ mMarkerSize
= value
; }
}
[Description
("Wheel size relative to wheel radius"), Category
("Appearance")]
public float WheelThicknessFactor
{
get
{ return mWheelThicknessFactor
; }
set
{
mWheelThicknessFactor
= value
;
createHueWheel
();
colorWheelBox
.Invalidate();
}
}
[Category
("Action")]
[Description
("Fires when the color is changed")]
public event EventHandler ColorChange
;
protected virtual void OnColorChange
(EventArgs e
)
{
EventHandler handler
= this.ColorChange;
if (handler
!= null)
{
handler
(this, e
);
}
}
private void ColorWidget_Load
(object sender, EventArgs e
)
{
OnColorChange
(EventArgs
.Empty);
}
}
}