When trying to use FillPie on my graphics object, if I actually draw the entire pie it all comes out as a solid color (I'm using the print-preview object)
I've tried moving objects around to pinpoint the issue, here is what I have now:
//Each brush is made static, mostly because I wanted to make sure I wasn't mucking them up
internal static SolidBrush Red = new SolidBrush(Color.Red);
internal static SolidBrush Blue = new SolidBrush(Color.Blue);
internal static SolidBrush Green = new SolidBrush(Color.Green);
internal static SolidBrush Yellow = new SolidBrush(Color.Yellow);
internal static SolidBrush Purple = new SolidBrush(Color.Purple);
internal static SolidBrush Teal = new SolidBrush(Color.Teal);
internal static SolidBrush Gold = new SolidBrush(Color.Gold);
internal static SolidBrush Fuchsia = new SolidBrush(Color.Fuchsia);
//Add each brush to an array for easier access
SolidBrush[] brushes = new SolidBrush[] {
Red,
Blue,
Green,
Yellow,
Purple,
Teal,
Gold,
Fuchsia
};
//The outlining pen for our pie
Pen BlackPen = new Pen(Color.Black, 10);
private void printDocument1_PrintPage(object sender, PrintPageEventArgs e)
{
//The bounding box of our Pie
Rectangle FirstPie = new Rectangle(100, 100, 400, 400);
//How many slices of pie?
float maxParts = 7;
//Get the angle for each slice
float partSize = 360.00F / maxParts;
for (int i = 0; i < maxParts; i++)
{
//Get the angles for this slice
float AngleStart = (float)i * partSize;
float AngleEnd = AngleStart + partSize;
//Check that we aren't overflowing somehow (issue persists without these lines)
if (AngleStart < 0F) AngleStart = 0F;
if (AngleEnd > 360F) AngleEnd = 360F;
//Figure out which brush we are using
int brush = (int)(i % 8);
//Output for Debug
Console.WriteLine(AngleStart + " " + AngleEnd + " " + brush);
//Draw the pie and overlay
e.Graphics.FillPie(brushes[brush], FirstPie, AngleStart, AngleEnd);
e.Graphics.DrawPie(BlackPen, FirstPie, AngleStart, AngleEnd);
}
}
I get a solid-color Pie with a line running from the upper right to the center (from the DrawPie):
I can verify the angles are correct by commenting out the FillPie line and only using DrawPie:
If you look at the array and extrapolate what is happening, the entire pie is being drawn by the last color-brush being used, Gold. I output the angles and which brush is being used to the console, it is properly cycling through the brush array:
and if I change the number of Pie parts to a different number the final color still gets used (with 11 parts):
And the angles / each brush index used here
What am I doing wrong here?
You keep growing the size of the AngleEnd value. Don't do that:
//float AngleEnd = AngleStart + partSize;
float AngleEnd = partSize;
From Graphics.FillPie Method
sweepAngle
Angle in degrees measured clockwise from the startAngle parameter to the second side of the pie section.
Also, please use
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
to get rid of those rough edges.
Related
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?
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!
I've got a simple line graph and I'd like to highlight some parts of this graph by drawing a rectangle around the line (ideally a filled rectangle with transparency...). I haven't any idea if this is possible with the MS chart control ?
I recommend you download the code samples from MS and checkout the section on annotations. In there you will find all the documentation you require to achieve what you described:
private void AddRectangleAnnotation()
{
RectangleAnnotation annotation = new RectangleAnnotation();
annotation.AnchorDataPoint = Chart1.Series[0].Points[2];
annotation.Text = "I am a\nRectangleAnnotation";
annotation.ForeColor = Color.Black;
annotation.Font = new Font("Arial", 12);;
annotation.LineWidth = 2;
annotation.BackColor = Color.PaleYellow;
annotation.LineDashStyle = ChartDashStyle.Dash;
Chart1.Annotations.Add(annotation);
}
Do you mean:
using (Graphics g = Graphics.FromImage(pictureBox1.Image))
{
using(Brush brush = new SolidBrush(your_color))
{
g.FillRectangle(brush , x, y, width, height);
}
}
or you can use
Brush brush = new SolidBrush(Color.FromArgb(alpha, red, green, blue))
where alpha goes from 0 to 255, so a value of 128 for your alpha will give you 50% opactity.
When you'd like to draw on a chart you can take add a LineAnnotation or RectangleAnnotation. if however you'd like more control you can use the chart's PrePaint and PostPaint events. And if you can paint, well then you can paint anything. Also using this will make the chart "printing" and "exporting" look the same as you painted it. Paining over it will look funny when the chart's location is changed on the screen, so always paint in it.
Say you have a trading chart and you need to draw a line as to where you become profitable or as square as to state where you're "To Something" tzhen just add the coordinates from where to where you'd like to be and of you go...
MS Chart sample project shows how to do this with the following code (vb.net also available):
using System.Windows.Forms.DataVisualization.Charting;
...
private void chart1_PostPaint(object sender, System.Windows.Forms.DataVisualization.Charting.ChartPaintEventArgs e)
{
if(sender is ChartArea)
{
ChartArea area = (ChartArea)sender;
if(area.Name == "Default")
{
// If Connection line is not checked return
if( !ConnectionLine.Checked )
return;
double max;
double min;
double xMax;
double xMin;
// Find Minimum and Maximum values
FindMaxMin( out max, out min, out xMax, out xMin );
// Get Graphics object from chart
Graphics graph = e.ChartGraphics.Graphics;
// Convert X and Y values to screen position
float pixelYMax = (float)e.ChartGraphics.GetPositionFromAxis("Default",AxisName.Y,max);
float pixelXMax = (float)e.ChartGraphics.GetPositionFromAxis("Default",AxisName.X,xMax);
float pixelYMin = (float)e.ChartGraphics.GetPositionFromAxis("Default",AxisName.Y,min);
float pixelXMin = (float)e.ChartGraphics.GetPositionFromAxis("Default",AxisName.X,xMin);
PointF point1 = PointF.Empty;
PointF point2 = PointF.Empty;
// Set Maximum and minimum points
point1.X = pixelXMax;
point1.Y = pixelYMax;
point2.X = pixelXMin;
point2.Y = pixelYMin;
// Convert relative coordinates to absolute coordinates.
point1 = e.ChartGraphics.GetAbsolutePoint(point1);
point2 = e.ChartGraphics.GetAbsolutePoint(point2);
// Draw connection line
graph.DrawLine(new Pen(Color.Yellow,3), point1,point2);
}
}
}
private void chart1_PrePaint(object sender, System.Windows.Forms.DataVisualization.Charting.ChartPaintEventArgs e)
{
if(sender is ChartArea){
ChartArea area = (ChartArea)sender;
if(area.Name == "Default")
{
double max;
double min;
double xMax;
double xMin;
// Find Minimum and Maximum values
FindMaxMin( out max, out min, out xMax, out xMin );
// Get Graphics object from chart
Graphics graph = e.ChartGraphics.Graphics;
// Convert X and Y values to screen position
float pixelYMax = (float)e.ChartGraphics.GetPositionFromAxis("Default",AxisName.Y,max);
float pixelXMax = (float)e.ChartGraphics.GetPositionFromAxis("Default",AxisName.X,xMax);
float pixelYMin = (float)e.ChartGraphics.GetPositionFromAxis("Default",AxisName.Y,min);
float pixelXMin = (float)e.ChartGraphics.GetPositionFromAxis("Default",AxisName.X,xMin);
// Specify width of triangle
float width = 3;
// Set Maximum points
PointF [] points = new PointF[3];
points[0].X = pixelXMax - width;
points[0].Y = pixelYMax - width - 2;
points[1].X = pixelXMax + width;
points[1].Y = pixelYMax - width - 2;
points[2].X = pixelXMax;
points[2].Y = pixelYMax - 1;
// Convert relative coordinates to absolute coordinates.
points[0] = e.ChartGraphics.GetAbsolutePoint(points[0]);
points[1] = e.ChartGraphics.GetAbsolutePoint(points[1]);
points[2] = e.ChartGraphics.GetAbsolutePoint(points[2]);
// Draw Maximum trangle
graph.FillPolygon(new SolidBrush(Color.Red), points);
// Set Minimum points
points = new PointF[3];
points[0].X = pixelXMin - width;
points[0].Y = pixelYMin + width + 2;
points[1].X = pixelXMin + width;
points[1].Y = pixelYMin + width + 2;
points[2].X = pixelXMin;
points[2].Y = pixelYMin + 1;
// Convert relative coordinates to absolute coordinates.
points[0] = e.ChartGraphics.GetAbsolutePoint(points[0]);
points[1] = e.ChartGraphics.GetAbsolutePoint(points[1]);
points[2] = e.ChartGraphics.GetAbsolutePoint(points[2]);
// Draw Minimum triangle
graph.FillPolygon(new SolidBrush(Color.Blue), points);
}
}
}
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:
I would like to 60 pie slices with of 5 degree and each with 1 degree of separation. However, with the code below, the outcome looks like the image below with a big black centre, is there anyway that I can modify the code to avoid that?
Graphics g = panel1.CreateGraphics();
// Create pen.
Pen blackPen = new Pen(Color.Black, 1);
// Create rectangle for ellipse.
//RectangleF rect = new RectangleF(150.0F, 10.0F, 200F, 300.0F);
// Create start and sweep angles.
float startAngle = 270F;
float sweepAngle = 4F;
for (int i = 0; i < 60; i++ )
{
RectangleF rect = new RectangleF(50F, 10.0F, 200F, 200F);
startAngle = startAngle + 6F;
g.DrawPie(blackPen, rect, startAngle, sweepAngle);
//start = start + 1f;
}
Regards
This effect is called the Moiré pattern. http://en.wikipedia.org/wiki/Moir%C3%A9_pattern
To avoid it make the distance between slices bigger. The amount depends on the size of the pie and the display resolution. To make the distance bigger you could draw a bigger pie but you'll find that the slices will be fighting over the pixels. You could just live with this effect or you could push the slices outward.