I am trying to draw a custom drop shadow in GDI+ (C#). I keep having this awkward look:
I guess I could solve it by adding a pixel to the arc's width, but then the shapes overlap.
Preview http://puu.sh/2sv2N
I have absolutely no idea what is causing this small piece of white.
Here's my code:
Rectangle drawRect = new Rectangle(10, 10, 400, 400);
radius = 10;
// TOP //
Rectangle topRect = new Rectangle(drawRect.X + radius, drawRect.Y, drawRect.Width - 2 * radius, radius);
LinearGradientBrush topBrush = new LinearGradientBrush(new Rectangle(topRect.X, topRect.Y - 1, topRect.Width,topRect.Height + 2), firstColor, secondColor, 270f);
g.FillRectangle(topBrush, topRect);
topBrush.Dispose();
// LEFT //
Rectangle leftRect = new Rectangle(drawRect.X, drawRect.Y + radius, radius, drawRect.Height - 2 * radius);
LinearGradientBrush leftBrush = new LinearGradientBrush(new Rectangle(leftRect.X - 1, leftRect.Y, leftRect.Width + 2, leftRect.Height), firstColor, secondColor, 180f);
g.FillRectangle(leftBrush, leftRect);
leftBrush.Dispose();
// TOP LEFT //
GraphicsPath topLeftPath = new GraphicsPath();
topLeftPath.StartFigure();
topLeftPath.AddArc(new Rectangle(drawRect.X, drawRect.Y, 2 * radius, 2 * radius), 180, 90);
topLeftPath.AddLine(new Point(drawRect.X + radius, drawRect.Y), new Point(drawRect.X + radius, drawRect.Y + radius));
topLeftPath.AddLine(new Point(drawRect.X + radius, drawRect.Y + radius), new Point(drawRect.X, drawRect.Y + radius + 1));
topLeftPath.CloseFigure();
PathGradientBrush topLeftBrush = new PathGradientBrush(topLeftPath);
topLeftBrush.CenterPoint = new PointF(drawRect.X + radius, drawRect.Y + radius);
topLeftBrush.CenterColor = firstColor;
topLeftBrush.SurroundColors = new Color[] { secondColor };
g.FillPath(topLeftBrush, topLeftPath);
topLeftBrush.Dispose();
topLeftPath.Dispose();
Thanks in advance
Thanks to LarsTech, I got the working solution.
e.Graphics.PixelOffsetMode = PixelOffsetMode.Half;
That did the job.
Thank you !
Related
I have this little code to use AddArc() method in a label, but when I execute the code the label disappears. I believe it is the numbers I have used, I followed instructions from the Windows documentation and it had these parameters there too.
GraphicsPath gp = new GraphicsPath();
Rectangle rec = new Rectangle(20, 20, 50, 100);
gp.AddArc(rec, 0 , 180);
label2.Region = new Region(gp);
label2.Invalidate();
I used another code to make the correct way or curve in a text
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var center = new Point(Width / 2, Height / 2);
var radius = Math.Min(Width, Height) / 3;
var text = "Hello";//txtUp.Text;
var font = new Font(FontFamily.GenericSansSerif, 24, FontStyle.Bold);
for (var i = 0; i < text.Length; ++i)
{
var c = new String(text[i], 1);
var size = e.Graphics.MeasureString(c, font);
var charRadius = radius + size.Height;
var angle = (((float)i / text.Length) - 2);
var x = (int)(center.X + Math.Cos(angle) * charRadius);
var y = (int)(center.Y + Math.Sin(angle) * charRadius);
e.Graphics.TranslateTransform(x, y);
e.Graphics.RotateTransform((float)(90 + 360 * angle / (2 * Math.PI)));
e.Graphics.DrawString(c, font, Brushes.Red, 0, 0);
e.Graphics.ResetTransform();
e.Graphics.DrawArc(new Pen(Brushes.Transparent, 2.0f), center.X - radius, center.Y - radius, radius * 2, radius * 2, 0, 360);
}
}
but it wont show in front of a panel is it possible.
This is what it looks like:
Is it possible to move that text in front of the green circle?
I want to draw ellipse excluding cross inside of it. I have a suspision that I need to use opacity mask. Here is how I am trying to do it.
Color grey = Color.FromArgb(128, Colors.Gray.R, Colors.Gray.G, Colors.Gray.B);
double radius = Math.Min(ActualWidth, ActualHeight) / 2;
Brush ellipse_brush = new SolidColorBrush(grey);
CombinedGeometry cg = new CombinedGeometry();
Drawing maskDrawing = new GeometryDrawing(Brushes.Lime, null, cg);
DrawingBrush mask = new DrawingBrush(maskDrawing);
dc.PushOpacityMask(mask);
dc.DrawEllipse(ellipse_brush, new Pen(ellipse_brush, 0), new Point(radius, radius), radius, radius);
dc.Pop();
Thing is that I don't understand how to create CombinedGeometry for ellipse and two lines. Or maybe I am on the wrong path?
You do not need an opacity mask in conjunction with a CombinedGeometry.
Create the cross outline geometry from a GeometryGroup with two lines and an appropriate Pen, then combine it Xor with an EllipseGeometry and draw the result:
var radius = Math.Min(ActualWidth, ActualHeight) / 2;
var crossSize = 0.8 * radius;
var crossThickness = 0.3 * radius;
var centerPoint = new Point(radius, radius);
var ellipseGeometry = new EllipseGeometry(centerPoint, radius, radius);
var crossGeometry = new GeometryGroup();
crossGeometry.Children.Add(new LineGeometry(
new Point(centerPoint.X - crossSize / 2, centerPoint.Y - crossSize / 2),
new Point(centerPoint.X + crossSize / 2, centerPoint.Y + crossSize / 2)));
crossGeometry.Children.Add(new LineGeometry(
new Point(centerPoint.X - crossSize / 2, centerPoint.Y + crossSize / 2),
new Point(centerPoint.X + crossSize / 2, centerPoint.Y - crossSize / 2)));
var crossPen = new Pen
{
Thickness = crossThickness,
StartLineCap = PenLineCap.Round,
EndLineCap = PenLineCap.Round
};
var crossOutlineGeometry = crossGeometry.GetWidenedPathGeometry(crossPen);
var combinedGeometry = new CombinedGeometry(GeometryCombineMode.Xor,
ellipseGeometry, crossOutlineGeometry);
dc.DrawGeometry(Brushes.Gray, null, combinedGeometry);
I have UserControl of Size 300*200.
and rectangle of size 300*200.
graphics.DrawRectangle(Pens.Black, 0, 0, 300, 200);
When I rotate rectangle in userControl by 30 degree, I get rotated rectangle but it is outsized.
PointF center = new PointF(150,100);
graphics.FillRectangle(Brushes.Black, center.X, center.Y, 2, 2); // draw center point.
using (Matrix matrix = new Matrix())
{
matrix.RotateAt(30, center);
graphics.Transform = matrix;
graphics.DrawRectangle(Pens.Black, 0, 0, 300, 200);
graphics.ResetTransform();
}
I want to fit rectangle like actual result.Check Image here
Can anyone have solution about this.
Thanks.
It's more of a math question than programming one.
Calculate bouning box of any rectangle rotated by any angle in radians.
var newWidth= Math.Abs(height*Math.Sin(angle)) + Math.Abs(width*Math.Cos(angle))
var newHeight= Math.Abs(width*Math.Sin(angle)) + Math.Abs(height*Math.Cos(angle))
Calculate scale for x and y:
scaleX = width/newWidth;
scaleY = height/newHeight;
Apply it to your rectangle.
EDIT:
Applied to your example:
PointF center = new PointF(150, 100);
graphics.FillRectangle(Brushes.Black, center.X, center.Y, 2, 2); // draw center point.
var height = 200;
var width = 300;
var angle = 30;
var radians = angle * Math.PI / 180;
var boundingWidth = Math.Abs(height * Math.Sin(radians)) + Math.Abs(width * Math.Cos(radians));
var boundingHeight = Math.Abs(width * Math.Sin(radians)) + Math.Abs(height * Math.Cos(radians));
var scaleX = (float)(width / boundingWidth);
var scaleY = (float)(height / boundingHeight);
using (Matrix matrix = new Matrix())
{
matrix.Scale(scaleX, scaleY, MatrixOrder.Append);
matrix.Translate(((float)boundingWidth - width) / 2, ((float)boundingHeight - height) / 2);
matrix.RotateAt(angle, center);
graphics.Transform = matrix;
graphics.DrawRectangle(Pens.Black, 0, 0, width, height);
graphics.ResetTransform();
}
When I draw an Ellipse or an Arc using C# DrawArc/DrawEllipse or DrawPie GDI functions, I believed that it draws for the exact angle what I have given. But, when I did a test on it by writing a small program, I found 225 degrees sweep angle in DrawArc is actually not 225 degrees in view. My test rogram draws a line from 0 degree to 360 degree every second (like a clock seconds hand) and in parallel Drawing arc using DrawArc function for the same angle.
Below function is used to get point on angle given start/end point and angle. Can somebody please explain whyis this difference? I tried to find the end point of the drawn arc by DrawArc(). I can achieve it in different way. But, I dont understand why the DrawArc function is working this way? 0, 90, 180, 270, 360 angles are fine with DrawArc.
public static Point PointOnEllipseFromAngle(Point center, int radiusX, int radiusY, int angle)
{
double x = center.X + radiusX * Math.Cos(angle * (Math.PI / 180.0));
double y = center.Y + radiusY * Math.Sin(angle * (Math.PI / 180.0));
return new Point((int)x, (int)y);
}
Form Paint goes like this
private void Form1_Paint(object sender, PaintEventArgs e)
{
Rectangle rect = Bounds;
rect.Inflate(-50, -50);
// Mid point
Point mid = new Point(rect.Left+(rect.Width / 2), rect.Top+(rect.Height / 2));
// Arc point for the given angle (angle is incremented in timer every second)
Point p1 = PointOnEllipseFromAngle(new Point(rect.Left+(rect.Width / 2), rect.Top+(rect.Height / 2)), rect.Width / 2, rect.Height / 2, angle);
// Line between mid and arc point
e.Graphics.DrawLine(new Pen(Color.Blue, 2), mid, p1);
e.Graphics.DrawString(angle.ToString(), new Font("Arial", 18), new SolidBrush(Color.Red), p1);
e.Graphics.FillEllipse(new SolidBrush(Color.Red), new Rectangle(p1.X - 5, p1.Y - 5, 10, 10)); // red circle at edge of the line
// DrawArc for the same angle
e.Graphics.DrawArc(new Pen(Color.Green,2), rect, 0, angle);
// Just Drawing axis lines (horizontal, vertical, diagonal)
e.Graphics.DrawLine(new Pen(Color.Black, 2), mid, new Point(rect.Left+rect.Width,rect.Top+(rect.Height/2)));
e.Graphics.DrawLine(new Pen(Color.Black, 2), mid, new Point(rect.Left, rect.Top + (rect.Height / 2)));
e.Graphics.DrawLine(new Pen(Color.Black, 2), mid, new Point(rect.Left + (rect.Width/2), rect.Top + rect.Height));
e.Graphics.DrawLine(new Pen(Color.Black, 2), mid, new Point(rect.Left + (rect.Width / 2), rect.Top));
e.Graphics.DrawLine(new Pen(Color.Black, 2), rect.Left,rect.Top,rect.Right,rect.Bottom);
e.Graphics.DrawLine(new Pen(Color.Black, 2), rect.Left, rect.Bottom, rect.Right, rect.Top);
}
Somehow DrawArc calculates the angles/drawing based on an arc with regular radius and it therefore takes the longer side. I could not find an explanation why it works this way, but maybe this link helps.
var radiusX = rect.Width/2;
var radiusY = rect.Height/2;
// Mid point
var mid = new Point(rect.Left + (rect.Width/2), rect.Top + (rect.Height/2));
int larger = Math.Max(radiusX, radiusY);
// Arc point for the given angle (angle is incremented in timer every second)
Point p1 = PointOnEllipseFromAngle(new Point(rect.Left + radiusX, rect.Top + radiusY), larger, larger, angle);
//Point p1 = PointOnEllipseFromAngle(new Point(rect.Left + radiusX, rect.Top + radiusY), radiusX, radiusY, angle);
Having a bit of a drawing complication you would call it. My math is a bit rusty when it comes to Matrices and drawing rotations on shapes. Here is a bit of code:
private void Form1_Paint(object sender, PaintEventArgs e)
{
g = e.Graphics;
g.SmoothingMode = SmoothingMode.HighQuality;
DoRotation(e);
g.DrawRectangle(new Pen(Color.Black), r1);
g.DrawRectangle(new Pen(Color.Black), r2);
// draw a line (PEN, CenterOfObject(X, Y), endpoint(X,Y) )
g.DrawLine(new Pen(Color.Black), new Point((r1.X + 50), (r1.Y + 75)), new Point((/*r1.X + */50), (/*r1.Y - */25)));
this.lblPoint.Text = "X-pos: " + r1.X + " Y-pos: " + r1.Y;
//this.Invalidate();
}
public void DoRotation(PaintEventArgs e)
{
// move the rotation point to the center of object
e.Graphics.TranslateTransform((r1.X + 50), (r1.Y + 75));
//rotate
e.Graphics.RotateTransform((float)rotAngle);
//move back to the top left corner of the object
e.Graphics.TranslateTransform(-(r1.X + 50), -(r1.Y + 75));
}
public void Form1_KeyDown(object sender, KeyEventArgs e)
{
case Keys.T:
rotAngle += 1.0f;
}
when I rotate (what I think should be r1) both r1 and r2 rotate. I need to be able to rotate each shape individually as I add more shapes.
I would use a function similar to this:
public void RotateRectangle(Graphics g, Rectangle r, float angle) {
using (Matrix m = new Matrix()) {
m.RotateAt(angle, new PointF(r.Left + (r.Width / 2),
r.Top + (r.Height / 2)));
g.Transform = m;
g.DrawRectangle(Pens.Black, r);
g.ResetTransform();
}
}
It uses a matrix to perform the rotation at a certain point, which should be the middle of each rectangle.
Then in your paint method, use it to draw your rectangles:
g.SmoothingMode = SmoothingMode.HighQuality;
//g.DrawRectangle(new Pen(Color.Black), r1);
//DoRotation(e);
//g.DrawRectangle(new Pen(Color.Black), r2);
RotateRectangle(g, r1, 45);
RotateRectangle(g, r2, 65);
Also, here is the line to connect the two rectangles:
g.DrawLine(Pens.Black, new Point(r1.Left + r1.Width / 2, r1.Top + r1.Height / 2),
new Point(r2.Left + r2.Width / 2, r2.Top + r2.Height / 2));
Using these settings:
private Rectangle r1 = new Rectangle(100, 60, 32, 32);
private Rectangle r2 = new Rectangle(160, 100, 32, 32);
resulted in: