I have been looking for quite a while for an already pre-made function for creating a rounded rectangle that doesn't cause tearing/glitching like the one below. Credits to #György Kőszeg. This function works fine if the rectangle is big enough. When you start making the rectangle smaller you run into issues like the below image. I am looking for an easy fix for this.
If this issue is on this website and I have missed it, I do apologize for re-asking. (I remember asking this a while back either on here or on another website and receiving an answer that worked amazingly) This issue has been paining me for quite awhile (again).
public static GraphicsPath RoundedRect(Rectangle bounds, int radius)
{
int diameter = radius * 2;
Size size = new Size(diameter, diameter);
Rectangle arc = new Rectangle(bounds.Location, size);
GraphicsPath path = new GraphicsPath();
if (radius == 0)
{
path.AddRectangle(bounds);
return path;
}
// top left arc
path.AddArc(arc, 180, 90);
// top right arc
arc.X = bounds.Right - diameter;
path.AddArc(arc, 270, 90);
// bottom right arc
arc.Y = bounds.Bottom - diameter;
path.AddArc(arc, 0, 90);
// bottom left arc
arc.X = bounds.Left;
path.AddArc(arc, 90, 90);
path.CloseFigure();
return path;
}
UPDATE:
In the meantime I have started to use the code below which just overrides the rectangle if the width is less than the height. This creates a perfect circle (the smallest the rounded rectangle can be without the glitching/tearing). A comment was placed on this stating I should not use "tearing" because its simply caused by the math which I understand but I really have no idea what else to call the glitchy rectangle in the image.
Basically I want a oval instead of a circle to correctly reflex the "Exp" value.
public static GraphicsPath RoundedRect(Rectangle bounds, int radius)
{
int diameter = radius * 2;
Size size = new Size(diameter, diameter);
Rectangle arc = new Rectangle(bounds.Location, size);
GraphicsPath path = new GraphicsPath();
//new code here//
if(bounds.Height >= bounds.Width)
{
bounds.Width = bounds.Height;
}
if (radius >= diameter) {
path.AddRectangle(bounds);
return path;
}
// top left arc
path.AddArc(arc, 180, 90);
// top right arc
arc.X = bounds.Right - diameter;
path.AddArc(arc, 270, 90);
// bottom right arc
arc.Y = bounds.Bottom - diameter;
path.AddArc(arc, 0, 90);
// bottom left arc
arc.X = bounds.Left;
path.AddArc(arc, 90, 90);
path.CloseFigure();
return path;
}
Something like this may work better. Try it with various values for curveSize. Note that curveSize must be between 1 and the minimum of rect.Width/4 and rect.Height/4:
public static GraphicsPath RoundedRect(Rectangle rc, float curveSize)
{
if (curveSize < 0 || curveSize > rc.Height / 4.0f || curveSize > rc.Width / 4.0f)
curveSize = 0;
var result = new GraphicsPath();
if (curveSize > 0)
{
float size4 = curveSize * 4;
result.AddArc(rc.Right - size4, rc.Top, size4, size4, 270, 90);
result.AddArc(rc.Right - size4, rc.Bottom - size4, size4, size4, 0, 90);
result.AddArc(rc.Left, rc.Bottom - size4, size4, size4, 90, 90);
result.AddArc(rc.Left, rc.Top, size4, size4, 180, 90);
result.CloseFigure();
}
else if (rc.Width > 0 && rc.Height > 0)
{
result.AddRectangle(rc);
}
return result;
}
Related
I'm running into the problem that my rounded corners method returns non-uniform corners.
Method:
GraphicsPath GetRoundedPath(Rectangle r, int radius, bool topRight = true, bool topLeft = true, bool bottomLeft = true, bool bottomRight = true)
{
GraphicsPath path = new GraphicsPath();
if (topLeft)
path.AddArc(r.X, r.Y, radius, radius, 180, 90);
else path.AddLine(r.X, r.Y, r.Right, r.Y);
if (topRight)
path.AddArc(r.Right - radius, r.Y, radius, radius, 270, 90);
else path.AddLine(r.Right, r.Y, r.Right, r.Height);
if (bottomRight)
path.AddArc(r.Right - radius, r.Bottom - radius, radius, radius, 0, 90);
else path.AddLine(r.Right, r.Bottom, r.Right, r.Height);
if (bottomLeft)
path.AddArc(r.X, r.Bottom - radius, radius, radius, 90, 90);
else path.AddLine(r.X, r.Bottom, r.X, r.Y);
return path;
}
When invoke:
Panel p = new Panel();
p.BackColor = Color.Red;
p.Size = new Size(200, 200);
Controls.Add(p);
p.Region = new Region(GetRoundedPath(new Rectangle(p.ClientRectangle, 30, true, true, true, true));
p.Location = new Point(200, 200);
Return:
It is noticeable that the upper left corner is drawn normally, the upper right and lower left are crooked, and the lower left is even crooked.
After, I noticed that exactly one pixel was not visible on the right and bottom, then I opened the Paint(program) and painted it myself and got even corners:
What I tried:
Passed less Rectangle to GetRoundedPath
GetRoundedPath(new Rectangle(p.ClientRectangle.X, p.ClientRectangle.Y, p.ClientRectangle.Width - 1, p.ClientRectangle.Height - 1), 30, true, true, true, true)
The control just became a pixel smaller in width and height.
Don't to change Control.Region, to use Graphics.FillPath instead
p.Paint += (s, e) =>
{
e.Graphics.FillPath(Brushes.Red, GetRoundedPath(p.ClientRectangle, 30, true, true, true, true));
};
Nothing has changed, I also tried to use it in conjunction with the first
Increase radius for individual corners
int oppositeRadius = (int)Math.Round(radius * 1.5)
...
The corners became stretched and looked more like slanted lines
UPD: I found similar post - Strange GraphicsPath.AddArc() behaviour but it work only with Region and all corners, when I have problem with GraphicsPath
It looks like off by 1 pixel for center of arc error. Maybe try something like:
GraphicsPath GetRoundedPath(Rectangle r, int radius, bool topRight = true, bool topLeft = true, bool bottomLeft = true, bool bottomRight = true)
{
GraphicsPath path = new GraphicsPath();
if (topLeft)
path.AddArc(r.X, r.Y, radius, radius, 180, 90);
else path.AddLine(r.X, r.Y, r.Right, r.Y);
if (topRight)
path.AddArc(r.Right - radius - 1, r.Y, radius, radius, 270, 90);
else path.AddLine(r.Right, r.Y, r.Right, r.Height);
if (bottomRight)
path.AddArc(r.Right - radius - 1, r.Bottom - radius - 1, radius, radius, 0, 90);
else path.AddLine(r.Right, r.Bottom, r.Right, r.Height);
if (bottomLeft)
path.AddArc(r.X, r.Bottom - radius - 1, radius, radius, 90, 90);
else path.AddLine(r.X, r.Bottom, r.X, r.Y);
return path;
}
Both labels have AutoSize true & TextAlign MiddleCenter.
How can also label2 show smooth borders?
Here is the test code for handlers Form.Load(...) & Form.Paint(...):
int _cornerRadius = 10;
Point _locationLabel2;
// Form.Load(...)
private void Form3_Load(object sender, EventArgs e)
{
// Step 1: Cut the label regions (seems to be ok, result is the same for both labels)
GraphicsPath graphicsPath = _getRoundPath(label1.ClientRectangle, _cornerRadius);
label1.Region = new Region(graphicsPath);
graphicsPath = _getRoundPath(label2.ClientRectangle, _cornerRadius);
label2.Region = new Region(graphicsPath);
_locationLabel2 = this.PointToClient(label2.Parent.PointToScreen(label2.Location));
}
// Form.Paint(...)
private void Form3_Paint(object sender, PaintEventArgs e)
{
using (Pen pen = new Pen(label1.BackColor, 3.0f))
{
// Step 2: Smooth the label borders (ok only for label1)
_drawRoundedRectangle(e.Graphics, pen, label1.Location.X, label1.Location.Y,
label1.ClientRectangle.Width, label1.ClientRectangle.Height, _cornerRadius);
_drawRoundedRectangle(e.Graphics, pen, _locationLabel2.X, _locationLabel2.Y,
label2.ClientRectangle.Width, label2.ClientRectangle.Height, _cornerRadius);
}
}
// Helper 1/3
private static GraphicsPath _getRoundPath(Rectangle rectangle, int radius)
{
int x = rectangle.X;
int y = rectangle.Y;
int width = rectangle.Width;
int height = rectangle.Height;
radius = radius << 1;
GraphicsPath path = new GraphicsPath();
if (radius > 0)
{
if (radius > height) radius = height;
if (radius > width) radius = width;
path.AddArc(x, y, radius, radius, 180, 90);
path.AddArc(x + width - radius, y, radius, radius, 270, 90);
path.AddArc(x + width - radius, y + height - radius, radius, radius, 0, 90);
path.AddArc(x, y + height - radius, radius, radius, 90, 90);
path.CloseFigure();
}
else
{
path.AddRectangle(rectangle);
}
return path;
}
// Helper 2/3
private void _drawRoundedRectangle(Graphics graphics, Pen pen, int x, int y, int width, int height, int radius)
{
RectangleF rectangle = new RectangleF(x, y, width, height);
GraphicsPath path = _generateRoundedRectangle(graphics, rectangle, radius);
SmoothingMode old = graphics.SmoothingMode;
graphics.SmoothingMode = SmoothingMode.AntiAlias;
graphics.DrawPath(pen, path);
graphics.SmoothingMode = old;
}
// Helper 3/3
private static GraphicsPath _generateRoundedRectangle(Graphics graphics, RectangleF rectangle, int radius)
{
GraphicsPath path = new GraphicsPath();
float diameter = radius * 2.0F;
SizeF sizeF = new SizeF(diameter, diameter);
RectangleF arc = new RectangleF(rectangle.Location, sizeF);
path.AddArc(arc, 180, 90);
arc.X = rectangle.Right - diameter;
path.AddArc(arc, 270, 90);
arc.Y = rectangle.Bottom - diameter;
path.AddArc(arc, 0, 90);
arc.X = rectangle.Left;
path.AddArc(arc, 90, 90);
path.CloseFigure();
return path;
}
Main code parts are from Arun Reginald Zaheeruddin
Solved it according to this answer by #Reza Aghaei.
Solution
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
public class RoundLabel : Label
{
[Browsable(true)]
public Color _BackColor { get; set; }
public RoundLabel()
{
this.DoubleBuffered = true;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
using (var graphicsPath = _getRoundRectangle(this.ClientRectangle))
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
using (var brush = new SolidBrush(_BackColor))
e.Graphics.FillPath(brush, graphicsPath);
using (var pen = new Pen(_BackColor, 1.0f))
e.Graphics.DrawPath(pen, graphicsPath);
TextRenderer.DrawText(e.Graphics, Text, this.Font, this.ClientRectangle, this.ForeColor);
}
}
private GraphicsPath _getRoundRectangle(Rectangle rectangle)
{
int cornerRadius = 15; // change this value according to your needs
int diminisher = 1;
GraphicsPath path = new GraphicsPath();
path.AddArc(rectangle.X, rectangle.Y, cornerRadius, cornerRadius, 180, 90);
path.AddArc(rectangle.X + rectangle.Width - cornerRadius - diminisher, rectangle.Y, cornerRadius, cornerRadius, 270, 90);
path.AddArc(rectangle.X + rectangle.Width - cornerRadius - diminisher, rectangle.Y + rectangle.Height - cornerRadius - diminisher, cornerRadius, cornerRadius, 0, 90);
path.AddArc(rectangle.X, rectangle.Y + rectangle.Height - cornerRadius - diminisher, cornerRadius, cornerRadius, 90, 90);
path.CloseAllFigures();
return path;
}
}
I made a function that draws a rectangle with rounded corners using examples I got researching here, now I want to call this function with a button click, but I'm not sure how to provide the arguments to the function, can someone help in how to I call this function?
public void DrawCond(Graphics g, Pen p, float width, float height, float x, float y)
{
// Auxiliary variables
float radius;
if (width>=3.55)
{
radius = 1F;
}
else if (width>2.25)
{
radius = 0.8F;
}
else if (width>1.61)
{
radius = 0.65F;
}
else
{
radius = 0.5F;
}
GraphicsPath gp = new GraphicsPath();
//Draw lines
gp.AddLine(x + radius, y, x + width - 2 * radius, y); //bottom horizontal line
gp.AddLine(x + radius, y + height, x + width - 2 * radius, y + height); //top horizontal line
gp.AddLine(x + width, y + radius, x + width, y + height - 2 * radius); //inner vertical line
gp.AddLine(x + radius, y + radius, x + radius, y + height - 2 * radius); //outer vertical line
//Draw arcs
gp.AddArc(x, y + radius, radius, radius, 90, 90); //bottom left corner
gp.AddArc(x + width - radius, y + radius, radius, radius, 0, 90); //bottom right corner
gp.AddArc(x, y + height, radius, radius, 180, 90); //top left corner
gp.AddArc(x + width - radius, y + height, radius, radius, 270, 90); //top right corner
g.DrawPath(p, gp);
gp.Dispose();
}
You can create Graphics from panel you want to draw to like var g = panel.CreateGraphics() and pass it to your function.
Also you can create Pen by using one of its constructors. See MSDN for reference about Pen.
I'm trying to create custom control that will be used to present timeline.
I found nice looking design on net:
and I started coding, couple of minutes later I had this:
As You can see big marker looks quite good, but smallest could look better.
My current code responsible for marker drawing looks like this:
enum Position
{
Left,
Right
}
private void DrawMarker(Graphics gfx, Rectangle Bounds, int CornerRadius, Pen DrawPen, Position direction)
{
GraphicsPath gfxPath = new GraphicsPath();
gfxPath.AddArc(new Rectangle(Bounds.X + Bounds.Width - CornerRadius, Bounds.Y + 0, CornerRadius, CornerRadius), 270, 45); //tr
if(direction==Position.Right)
gfxPath.AddArc(new Rectangle(Bounds.X + Bounds.Width - CornerRadius + 40, Bounds.Y + Bounds.Height / 2, CornerRadius, CornerRadius), 315, 90); //right
gfxPath.AddArc(new Rectangle(Bounds.X + Bounds.Width - CornerRadius, Bounds.Y + Bounds.Height - CornerRadius, CornerRadius, CornerRadius), 45, 45); //br
gfxPath.AddArc(new Rectangle(Bounds.X + 0, Bounds.Y + Bounds.Height - CornerRadius, CornerRadius, CornerRadius), 90, 90); //bl
gfxPath.AddArc(new Rectangle(Bounds.X + 0, Bounds.Y + 0, CornerRadius, CornerRadius), 180, 90); //tl
gfxPath.CloseAllFigures();
//gfx.FillPath(new SolidBrush(FillColor), gfxPath);
gfx.DrawPath(DrawPen, gfxPath);
}
I need a way to improve look of those markers.
I think that problem are angles of arcs in top right and bottom right corners.
I have them set to 45, but this looks bad if markes is small.
Angles on left side will be always 90, but angles on right and on marked need to be calculated.
How should I modify my function to calculate arcs correctly?
It looks like the "nose" of the tag rectangle isn't compensating for the radius size, which is causing the "nose" to dip downward:
r.Y + r.Height / 2 - radius / 2
I also shortened the width from 40 to 20.
private void DrawMarker(Graphics gfx, Rectangle r, int radius, Pen drawPen, Position direction) {
using (GraphicsPath gfxPath = new GraphicsPath()) {
gfxPath.AddArc(new Rectangle(r.X + r.Width - radius, r.Y + 0, radius, radius), 270, 45); //tr
if (direction == Position.Right) {
gfxPath.AddArc(new Rectangle(r.X + r.Width - radius + 20, r.Y + r.Height / 2 - radius / 2, radius, radius), 315, 90); //right
}
gfxPath.AddArc(new Rectangle(r.X + r.Width - radius, r.Y + r.Height - radius, radius, radius), 45, 45); //br
gfxPath.AddArc(new Rectangle(r.X + 0, r.Y + r.Height - radius, radius, radius), 90, 90); //bl
gfxPath.AddArc(new Rectangle(r.X + 0, r.Y + 0, radius, radius), 180, 90); //tl
gfxPath.CloseAllFigures();
gfx.DrawPath(drawPen, gfxPath);
}
}
protected override void OnPaint(PaintEventArgs e) {
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
using (Pen pen = new Pen(Color.DarkGray, 2)) {
DrawMarker(e.Graphics, new Rectangle(16, 16, 100, 32), 8, pen, Position.Right);
}
base.OnPaint(e);
}
Result:
I've got question,
I have rounded rectangle. It's only rounded on right side, and straight on left. I want to make that in other way - left rounded, right straight.
What should I change?
int ArcWidth = 10 * 2;
int ArcHeight = 10 * 2;
int ArcX1 = Rect.Left;
int ArcX2 = Rect.Right -(ArcWidth + 1);
int ArcY1 = Rect.Top;
int ArcY2 = Rect.Bottom -(ArcHeight + 1);
path.AddArc(ArcX1, ArcY1, 1, 1, 180, 90); // Top Left
path.AddArc(ArcX2, ArcY1, ArcWidth, ArcHeight, 270, 90); //Top Right
path.AddArc(ArcX2, ArcY2, ArcWidth, ArcHeight, 360, 90); //Bottom Right
path.AddArc(ArcX1, ArcY2, 1, ArcHeight, 90, 90); //Bottom Left
Switch the sides that are rounded. Looks like you have all of your sides laid out. Switch all your numbers.
I found solution here:
http://tech.pro/tutorial/656/csharp-creating-rounded-rectangles-using-a-graphics-path
The way is to draw a Line, not Arc... (yes I know that was obvious)