Fill Panel with gradient in three colors - c#

I'm working on project and I have to do kind of color picker using C#.
So I've decided that it will be a Panel with this background in Win Forms App.
Background should have gradient with three colors in rgb: red (0 - 255), blue (0 - 255) and green = 0.
But I can't find any information about what I should use for this.
I tried to write some code and here is what I've done.
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
panel1.Paint += new PaintEventHandler(panel1_Paint);
panel1.Refresh();
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
Point startPoint = new Point(0, 0);
Point endPoint = new Point(150, 150);
LinearGradientBrush lgb =
new LinearGradientBrush(startPoint, endPoint, Color.FromArgb(255, 255, 0, 0), Color.FromArgb(255, 255, 255, 0));
Graphics g = e.Graphics;
g.FillRectangle(lgb, 0, 0, 150, 150);
// g.DrawLine(new Pen(Color.Yellow, 1.5f), startPoint, endPoint);
}
}
}
And now I have panel with this gradient
What I should use to get gradient at first picture?
And second question: What should I do to get the pixel color after clicking on this background?

Here is an example for using a multicolor LinearGradientBrush in the Paint event:
LinearGradientBrush linearGradientBrush =
new LinearGradientBrush(panel4.ClientRectangle, Color.Red, Color.Yellow, 45);
ColorBlend cblend = new ColorBlend(3);
cblend.Colors = new Color[3] { Color.Red, Color.Yellow, Color.Green };
cblend.Positions = new float[3] { 0f, 0.5f, 1f };
linearGradientBrush.InterpolationColors = cblend;
e.Graphics.FillRectangle(linearGradientBrush, panel4.ClientRectangle);
You can freely vary the number of colors, the angle or the spread of the stop points. Just make sure you always have the same number of colors and stop points and let them start at 0 and end at 1.
The colors in the constructor are ignored, btw..
To get a clicked color you can code the MouseClick:
Color clickedColor = Color.Empty;
private void panel_MouseClick(object sender, MouseEventArgs e)
{
using (Bitmap bmp = new Bitmap( panel.ClientSize.Width, panel4.ClientSize.Height))
{
panel.DrawToBitmap(bmp,panel.ClientRectangle);
clickedColor = bmp.GetPixel(e.X, e.Y);
}
}
If you want to catch many clicks it may be better to keep the Bitmap in a class level variable instead of recreating it all the time.. Setting it as the Panel's BackgroundImage, as Kala's answer assumes may also be a good option..
This should answer the question in the title. However your first image doesn't show a gradient with three colors. It shows a 2D gradient with four colors. For such a more expensive coloring method you should put the colors in a Bitmap and set it as the Panel's BackgroundImage..
Update1 Here is a piece of code that creates a 2D Gradient:
Bitmap Gradient2D(Rectangle r, Color c1, Color c2, Color c3, Color c4)
{
Bitmap bmp = new Bitmap(r.Width, r.Height);
float delta12R = 1f * (c2.R - c1.R) / r.Height;
float delta12G = 1f * (c2.G - c1.G) / r.Height;
float delta12B = 1f * (c2.B - c1.B) / r.Height;
float delta34R = 1f * (c4.R - c3.R) / r.Height;
float delta34G = 1f * (c4.G - c3.G) / r.Height;
float delta34B = 1f * (c4.B - c3.B) / r.Height;
using (Graphics G = Graphics.FromImage(bmp) )
for (int y = 0; y < r.Height; y++)
{
Color c12 = Color.FromArgb(255, c1.R + (int)(y * delta12R),
c1.G + (int)(y * delta12G), c1.B + (int)(y * delta12B));
Color c34 = Color.FromArgb(255, c3.R + (int)(y * delta34R),
c3.G + (int)(y * delta34G), c3.B + (int)(y * delta34B));
using ( LinearGradientBrush lgBrush = new LinearGradientBrush(
new Rectangle(0,y,r.Width,1), c12, c34, 0f) )
{ G.FillRectangle(lgBrush, 0, y, r.Width, 1); }
}
return bmp;
}
Here is how you use it:
public Form1()
{
InitializeComponent();
panel.BackgroundImage = Gradient2D(panel.ClientRectangle,
Color.Black, Color.FromArgb(255, 0, 255, 0), Color.Red, Color.Yellow);
}
This uses simple LinearGradientBrushes without an extra colors list going down over the height of the Panel.
Note that Color.Green is a rather dark hue, so I used FromRgb for a brighter green. If your Panel is greater than 256 pixels you may want to optimze by filling larger stripes; if it is vertical you may want to change the loop to go over x instead of y..
Here is the result:
To pick with a click you now simply read out the color from the BackgroundImage:
private void panel_MouseClick(object sender, MouseEventArgs e)
{
clickedColor = ((Bitmap)panel.BackgroundImage).GetPixel(e.X, e.Y);
}
Update 2:
When looking over this MSDN page we can find that there actually is a built-in tool to create 2D gradients.
It is the PathGradientBrush
Here is an example..:
.. and the code:
Bitmap Gradient2D(Rectangle r, Color c1, Color c2, Color c3, Color c4)
{
List<Color> colors = new List<Color> { c1, c3, c4, c2 };
Bitmap bmp = new Bitmap(r.Width, r.Height);
using (Graphics g = Graphics.FromImage(bmp))
for (int y = 0; y < r.Height; y++)
{
using (PathGradientBrush pgb = new PathGradientBrush(getCorners(r).ToArray()))
{
pgb.CenterColor = medianColor(colors);
pgb.SurroundColors = colors.ToArray();
g.FillRectangle(pgb, 0, y, r.Width, 1);
}
}
return bmp;
}
This uses two simple helper functions. One returns the corner points of a rectangle:
public List<PointF> getCorners(RectangleF r)
{
return new List<PointF>() { r.Location, new PointF(r.Right, r.Top),
new PointF(r.Right, r.Bottom), new PointF(r.Left, r.Bottom)};
}
The other calculates a median color from a List<Color>. This is used as the CenterColor..:
public static Color medianColor(List<Color> cols)
{
int c = cols.Count;
return Color.FromArgb(cols.Sum(x => x.A) / c, cols.Sum(x => x.R) / c,
cols.Sum(x => x.G) / c, cols.Sum(x => x.B) / c);
}
The result pretty much identical to the one from using stripes of LinearGradientBrushes. It is simpler and should perform a bit better; it is what I would recommend obviously..
Note the changed order of the colors (or corners)! The SurroundColors apply to opposing corners of the rectangle..
Note:
When studying that page one can find that there actually are four different uses for that brush.
They differ in how to set it up (GraphicsPath or Point[]), which color collections to fill (SurroundColors or InterpolationColors.Colors) and how to call it (with a shape or a path). And the results also differ a lot.
Also note that only three results of the four ways are shown, although code for all four is provided!..

From the mouse click event argument e, you can get the Point with the exact co-ordinates of the click:
Point clickPoint = e.GetPosition(backgroundControlWithImg);
Then get the colour of the image at that position using something like:
System.Drawing.Image image = backgroundControl.BackgroundImage;
Bitmap _bitmap = new Bitmap(image);
Color _color = bitmap.GetPixel(Point.x, Point.y);
Something like that. What are you using for the Color Picker, WPF or?

Related

How do I draw a Drop Shadow at any Location on a Form?

Using GDI+, how do I draw a Border Shadow or a Drop Shadow at specified coordinates? I'm not trying to attach the shadow to anything, I just need to draw a shadow from x40,0px to x140px,0px. I've not been able to find any information about this and I'm beginning to think it isn't possible.
My intention is to draw a shadow at certain location at bottom of control but I don't want it to be the entire width of the control, which is why I've asked specifically about only drawing at specified locations.
Here is a piece of code that could get you started.
The drawShadow method draws a shadow of given color and depth along a GraphicsPath.
The use of GraphicsPath allows you to draw shadows of more complex shapes than mere Rectangles.
The shadow is drawn with a vector of colors that gradually goes from the shadow to the background color and is moving to the right and down. (You can change the direction by changing the shadow vector. Values greater 1 will need an larger Pen width! (*) )
To demonstrate the routine I have added a getRectPath function that creates a GraphicsPath from a Rectangle and a Button click that calls the drawing routine.
Of course in production code you must attach it to the Paint event instead!
void drawShadow(Graphics G, Color c, GraphicsPath GP, int d)
{
Color[] colors = getColorVector(c, this.BackColor, d).ToArray();
for (int i = 0; i < d; i++)
{
G.TranslateTransform(1f, 0.75f); // <== shadow vector!
using (Pen pen = new Pen(colors[i], 1.75f ) ) // <== pen width (*)
G.DrawPath(pen, GP);
}
G.ResetTransform();
}
List<Color> getColorVector(Color fc, Color bc, int depth)
{
List<Color> cv = new List<Color>();
float dRed = 1f * (bc.R - fc.R) / depth;
float dGreen = 1f * (bc.G - fc.G) / depth;
float dBlue = 1f * (bc.B - fc.B) / depth;
for (int d = 1; d <= depth; d++)
cv.Add(Color.FromArgb(255, (int) (fc.R + dRed * d),
(int) (fc.G + dGreen * d), (int) (fc.B + dBlue * d) ));
return cv;
}
GraphicsPath getRectPath(Rectangle R)
{
byte[] fm = new byte[3];
for (int b = 0; b < 3; b++) fm[b] = 1;
List<Point> points = new List<Point>();
points.Add(new Point(R.Left, R.Bottom));
points.Add(new Point(R.Right, R.Bottom));
points.Add(new Point(R.Right, R.Top));
return new GraphicsPath(points.ToArray(), fm);
}
private void button1_Click(object sender, EventArgs e)
{
using (Graphics G = this.CreateGraphics())
drawShadow(G, Color.Black, getRectPath(new Rectangle(111, 111, 222, 222)), 17);
}
Edit: I have changed the solution to allow for complex shadows and arbitrary shadow vectors without the alpha channel's overlapping creating ugly artifacts. This assumes that the background has a uniform color!

How to draw a rectangle that looks like CAD elevation drawings

What brush should i use to draw rectangles with white interior of the line and lines for the perimeter of the rectangle like the elevations below.
The form1 winform is what i am working on and the image behind the winform is how i need to the rectangles to look in my winform.
To make the question easier, how can i fill the interior portion of the rectangles with white?
How do i fill the LINES of the rectangle with white? I do not need to fill the inside of the rectangle, I need to fill a portion of the 4 lines that make up the rectangle with white.
void BuildShopDrawing(ElevationResponse elevation)
{
float penWidth = (float)((2f / 12f) * PIXELS_PER_FOOT);
Pen blackPen = new Pen(Color.FromArgb(40, 84, 149), penWidth);
Bitmap canvas = new Bitmap((((int)elevation.TotalWidthFeet) * PIXELS_PER_FOOT) + 55, (((int)elevation.TotalHeightFeet) * PIXELS_PER_FOOT) + 25);
Graphics dc = Graphics.FromImage(canvas);
RectangleF[] bays = new RectangleF[elevation.Bays.Count];
float x = 10F;
float width = 0F;
float height = 0F;
for (int i = 0; i < elevation.Bays.Count; i++)
{
if (i > 0)
{
x += (float)((elevation.Bays[i - 1].WidthInches / 12) * PIXELS_PER_FOOT);
}
width = (float)(elevation.Bays[i].WidthInches / 12) * PIXELS_PER_FOOT;
height = (float)(elevation.Bays[i].HeightInches / 12) * PIXELS_PER_FOOT;
bays[i] =
new RectangleF(new PointF(x, 10),
new SizeF(width, height));
}
dc.DrawRectangles(blackPen, bays);
this.picBx.Image = canvas;
this.Size = new System.Drawing.Size(canvas.Width + 10, canvas.Height + 50);
}
You need to look a bit more thoroughly at the Pen Class more specifically the CompoundArray Property, it will give you something like you are wanting, You will need to play around some other of the Pen Class properties to get your transitions right. And as a side note when you post example code that depends on external custom classes you make it harder for someone to help, it is always best to make sure that the code can run by itself.
Try adding this after you declare your pen.
float[] cmpArray = new float[4]{0.0F, 0.2F, 0.7F, 1.0F};
blackPen.CompoundArray = cmpArray;
It looks something like this:

Drawing a polygon according to the input coordinates

How can i draw a polygon according to the input coordinates which are given in C#.
You didn't show any code because based on those coordinate, you are applying some form of scaling to the image.
Using the Paint event of a PictureBox, here is an example using those coordinates on the screen. It fills in the polygon, then draws the border, then it loops through all the points to draw the red circle:
void pictureBox1_Paint(object sender, PaintEventArgs e) {
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.Clear(Color.White);
// draw the shading background:
List<Point> shadePoints = new List<Point>();
shadePoints.Add(new Point(0, pictureBox1.ClientSize.Height));
shadePoints.Add(new Point(pictureBox1.ClientSize.Width, 0));
shadePoints.Add(new Point(pictureBox1.ClientSize.Width,
pictureBox1.ClientSize.Height));
e.Graphics.FillPolygon(Brushes.LightGray, shadePoints.ToArray());
// scale the drawing larger:
using (Matrix m = new Matrix()) {
m.Scale(4, 4);
e.Graphics.Transform = m;
List<Point> polyPoints = new List<Point>();
polyPoints.Add(new Point(10, 10));
polyPoints.Add(new Point(12, 35));
polyPoints.Add(new Point(22, 35));
polyPoints.Add(new Point(24, 22));
// use a semi-transparent background brush:
using (SolidBrush br = new SolidBrush(Color.FromArgb(100, Color.Yellow))) {
e.Graphics.FillPolygon(br, polyPoints.ToArray());
}
e.Graphics.DrawPolygon(Pens.DarkBlue, polyPoints.ToArray());
foreach (Point p in polyPoints) {
e.Graphics.FillEllipse(Brushes.Red,
new Rectangle(p.X - 2, p.Y - 2, 4, 4));
}
}
}
You may use Graphics.DrawPolygon. You can store the coordinates in an array of Point and then you can pass that to DrawPolygon method. You may wanna see:
Drawing with Graphics in WinForms using C#
private System.Drawing.Graphics g;
System.Drawing.Point[] p = new System.Drawing.Point[6];
p[0].X = 0;
p[0].Y = 0;
p[1].X = 53;
p[1].Y = 111;
p[2].X = 114;
p[2].Y = 86;
p[3].X = 34;
p[3].Y = 34;
p[4].X = 165;
p[4].Y = 7;
g = PictureBox1.CreateGraphics();
g.DrawPolygon(pen1, p);
This simple function is able to generate an array of PointF equal to the vertices of the regular polygon to be drawn, where "center" is the center of the polygon, "sides" is its number of sides, "sideLength" is the size of each side in pixels and "offset" is its slope.
public PointF[] GetRegularPolygonScreenVertex(Point center, int sides, int sideLength, float offset)
{
var points = new PointF[sides];
for (int i = 0; i < sides; i++)
{
points[i] = new PointF(
(float)(center.X + sideLength * Math.Cos((i * 360 / sides + offset) * Math.PI / 180f)),
(float)(center.Y + sideLength * Math.Sin((i * 360 / sides + offset) * Math.PI / 180f))
);
}
return points;
}
The result obtained can be used to draw a polygon, e.g. with the function:
GraphicsObject.DrawPolygon(new Pen(Brushes.Black, GetRegularPolygonScreenVertex(new Point(X, Y), 6, 30, 60f));
Which will generate a regular hexagon with a side of 30 pixels inclined by 30°.
hex

Draw a polyline with a linear gradient along it's stroke width in GDI+

Is it possible to draw a polyline that has a linear gradient along it's stroke width? That is, if you have a gradient with black on 0 and 100% and white 50%, the black will always be on the edge of the line and the white in the middle, regardless of the angle. Think of it as some sort of 3D pipes. Of course, the line will have a stroke width of at least 10px. All the questions here ask how to fill a line between it's ends. I'm definitely not interested in that. I'm working in C# using GDI+, can be any .NET version.
I think this is what you want:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode=SmoothingMode.AntiAlias;
DrawPipe(e.Graphics, 10f, new PointF(10, 10), new PointF(250, 80), Color.White, Color.Black);
DrawPipe(e.Graphics, 10f, new PointF(15, 60), new PointF(280, 120), Color.BlueViolet, Color.Black);
}
private void DrawPipe(Graphics g, float width, PointF p1, PointF p2, Color mid_color, Color edge_color)
{
SizeF along=new SizeF(p2.X-p1.X, p2.Y-p1.Y);
float mag=(float)Math.Sqrt(along.Width*along.Width+along.Height*along.Height);
along=new SizeF(along.Width/mag, along.Height/mag);
SizeF perp=new SizeF(-along.Height, along.Width);
PointF p1L=new PointF(p1.X+width/2*perp.Width, p1.Y+width/2*perp.Height);
PointF p1R=new PointF(p1.X-width/2*perp.Width, p1.Y-width/2*perp.Height);
PointF p2L=new PointF(p2.X+width/2*perp.Width, p2.Y+width/2*perp.Height);
PointF p2R=new PointF(p2.X-width/2*perp.Width, p2.Y-width/2*perp.Height);
GraphicsPath gp=new GraphicsPath();
gp.AddLines(new PointF[] { p1L, p2L, p2R, p1R});
gp.CloseFigure();
Region region=new Region(gp);
using(LinearGradientBrush brush=new LinearGradientBrush(
p1L, p1R, Color.Black, Color.Black))
{
ColorBlend color_blend=new ColorBlend();
color_blend.Colors=new Color[] { edge_color, mid_color, edge_color };
color_blend.Positions=new float[] { 0f, 0.5f, 1f };
brush.InterpolationColors=color_blend;
g.FillRegion(brush, region);
}
}
}
Edit 1
An alternative is to use a PathGradientBrush
GraphicsPath gp = new GraphicsPath();
gp.AddLines(new PointF[] { p1, p1L, p2L, p2, p2R, p1R });
gp.CloseFigure();
Region region = new Region(gp);
using (PathGradientBrush brush = new PathGradientBrush(gp))
{
brush.CenterColor = mid_color;
brush.SurroundColors = new Color[]
{
mid_color, edge_color,edge_color,mid_color,edge_color,edge_color
};
g.FillRegion(brush, region);
}
Edit 2
To make the edges smoother use some alpha transparency:
using(LinearGradientBrush brush=new LinearGradientBrush(
p1L, p1R, Color.Black, Color.Black))
{
ColorBlend color_blend=new ColorBlend();
color_blend.Colors=new Color[] {
Color.FromArgb(0, edge_color), edge_color, mid_color,
edge_color, Color.FromArgb(0, edge_color) };
color_blend.Positions=new float[] { 0f, 0.1f, 0.5f, 0.9f, 1f };
brush.InterpolationColors=color_blend;
g.FillRegion(brush, region);
}
Edit 3
With some artifacts multiple lines are drawing, by rendering circles between then first and then the lines
private void DrawPipes(Graphics g, float width, PointF[] points, Color mid_color, Color edge_color)
{
for (int i = 0; i < points.Length; i++)
{
using (GraphicsPath gp = new GraphicsPath())
{
gp.AddEllipse(points[i].X - width / 2, points[i].Y - width / 2, width, width);
using (PathGradientBrush brush = new PathGradientBrush(gp))
{
brush.CenterColor = mid_color;
brush.SurroundColors = new Color[] { edge_color };
brush.CenterPoint = points[i];
g.FillPath(brush, gp);
}
}
if (i > 0)
{
DrawPipe(g, width, points[i - 1], points[i], mid_color, edge_color);
}
}
}

How to apply Graphics scale and translate to the TextRenderer

I'm using scaling and transforming my graphics object when painting a custom control, in order to apply zooming and scrolling. I use the following:
Matrix mx = new Matrix();
mx.Scale(mZoomFactor, mZoomFactor);
mx.Translate(-clip.X + mGraphicsOffsetx, -clip.Y + mGraphicsOffsety);
e.Graphics.Clip = new Region(this.Bounds);
e.Graphics.Transform = mx;
Then when I paint my strings using:
Graphics g = ...
g.DrawString(...)
The scalling and transforming is correctly applied to the strings, they are zoomed out and in and so on.
However if I use the following to paint my strings:
TextRenderer.DrawText(...)
The text is not correctly scaled and transformed.
Do you know how to apply this concepts to the TextRenderer?
The comments above are accurate--TextRenderer.DrawText, being GDI, has limited support for coordinate transformation given its resolution dependence. As you've noticed, coordinate translation is supported but scaling is not (and neither is coordinate rotation).
The solution we've used (and the only resource I've been able to find on the internet) is to manually scale the Font and Rectangle objects to reflect the scaling applied by Matrix.Scale(float, float) in conjunction with Graphics.Transform:
private Font GetScaledFont(Graphics g, Font f, float scale)
{
return new Font(f.FontFamily,
f.SizeInPoints * scale,
f.Style,
GraphicsUnit.Point,
f.GdiCharSet,
f.GdiVerticalFont);
}
private Rectangle GetScaledRect(Graphics g, Rectangle r, float scale)
{
return new Rectangle((int)Math.Ceiling(r.X * scale),
(int)Math.Ceiling(r.Y * scale),
(int)Math.Ceiling(r.Width * scale),
(int)Math.Ceiling(r.Height * scale));
}
Here is the entire test form:
public partial class Form1 : Form
{
private PictureBox box = new PictureBox();
public Form1()
{
InitializeComponent();
this.Load += new EventHandler(Form1_Load);
}
public void Form1_Load(object sender, EventArgs e)
{
box.Dock = DockStyle.Fill;
box.BackColor = Color.White;
box.Paint += new PaintEventHandler(DrawTest);
this.Controls.Add(box);
}
public void DrawTest(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
string text = "Test Text";
float scale = 1.5F;
float translate = 200F;
var flags = TextFormatFlags.PreserveGraphicsClipping | TextFormatFlags.PreserveGraphicsTranslateTransform;
var mx = new Matrix();
mx.Scale(scale, scale);
mx.Translate(translate, translate);
g.Clip = new Region(Bounds);
g.Transform = mx;
Size rendererPSize = Bounds.Size;
Font f = GetScaledFont(g, new Font("Arial", 12), scale);
Size rendererRSize = TextRenderer.MeasureText(g, text, f, rendererPSize, flags);
Rectangle rendererRect = new Rectangle(0, 0, rendererRSize.Width, rendererRSize.Height);
Rectangle r = GetScaledRect(g, rendererRect, scale);
TextRenderer.DrawText(g, text, f, realRect, Color.Black, flags);
}
private Font GetScaledFont(Graphics g, Font f, float scale)
{
return new Font(f.FontFamily,
f.SizeInPoints * scale,
f.Style,
GraphicsUnit.Point,
f.GdiCharSet,
f.GdiVerticalFont);
}
private Rectangle GetScaledRect(Graphics g, Rectangle r, float scale)
{
return new Rectangle((int)Math.Ceiling(r.X * scale),
(int)Math.Ceiling(r.Y * scale),
(int)Math.Ceiling(r.Width * scale),
(int)Math.Ceiling(r.Height * scale));
}
}

Categories

Resources