Using C# WinForms, I have a normal label which I fill with rounded corners. The issue I am having is that the resulting corners are not consistently rounded.
For ease of understanding, I have created 4 labels with rounded corners, ranging from a radius of 1 to 5. I have added a single outline border to the label for ease of visibility, but even without the single outline the results are the same. I have also zoomed the label images so we can see the corners more clearly.
Using a radius of 1, you can clearly see that the top corners are rounded accordingly, but the bottom borders remain square.
Using a radius of 2 and 3 respectively, produces the same output at the bottom.
Using a radius of 4 and also 5 respectively, you can clearly see the inconsistencies on all the corners.
Generally it wouldn't be too much of a problem if I had a label of a small size, but the user needs to be able to zoom, and then it just looks plain ugly as the radius increases, as the inconsistencies are clearly visible.
The code I am using to create the filled rectangle is supposedly very basic as per example, taken from How To Draw a Rounded Rectangle
public static GraphicsPath draw_rectangle(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;
}
I have studied and understand what the code does and how it works, I get the angles and the sweep, but I am by no means a seasoned graphics manipulator so I don't know if I am doing something wrong or if there is a way to better the code for all corner consistency.
Your help and\or explanations are appreciated.
Related
I have a WinForms project and I'm trying to draw a rectangle at (0,0), the top, left corner of the form. For some reason it's cutting off one pixel of height and width of the rectangle. Here's the code:
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawRectangle(new Pen(Color.Red, 5), new Rectangle(0, 0, 50, 50));
}
Here is the result, blown up for clarity:
I understand I could correct for this by drawing the rectangle at (1,1), but that's not how it should work based on what I've seen from controls that are placed at (0,0) and (1,1). For instance, here's what a panel looks like at (1,1), and it clearly has a one-pixel gap:
So my question is: why does drawing a rectangle at (0,0) not behave like placing a control at (0,0)? Why does the rectangle get cut off by one pixel on the top and left?
The default value for Alignment property of Pen is PenAlignment.Center which means the drawn line will be centered over the line. So what you see is expected.
You may want to set the Alignment to PenAlignment.Inset:
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.DrawRectangle(
new Pen(Color.Red, 5) { Alignment = PenAlignment.Inset },
new Rectangle(0, 0, 50, 50));
}
You may also want to read Pen.Alignment remarks:
Center is the default value for this property and specifies that the
width of the pen is centered on the outline of the curve or polygon. A
value of Inset for this property specifies that the width of the pen
is inside the outline of the curve or polygon. The other three values,
Right, Left, and Outset, will result in a pen that is centered.
What I'm attempting to do is draw a number inside a circle so that it's positioned centrally both vertically and horizontally. I'm drawing both the circle and text string using the same rectangle structure and using center for both horizontal and vertical alignments. But, as you can see from the image where I drew a horizontal line across the white circle, the text is aligning its base line centrally and my eye says it's horizontally aligning slightly to the left.
I've tried fudging by adding string.height/2 to the top of the rectangle to shift it down 1/2 its height but it's still not correct. (I adjust the font size so the number will fit within the box according to the number of digits in the counter)
How can I do this properly please?
var rect = new Rectangle(bmpWidth - maxCircleDiameter, bmpHeight - maxCircleDiameter, maxCircleDiameter, maxCircleDiameter);
g.FillEllipse(Brushes.White, rect);
using (var format = NewClassFactory.GetFormat(StringAlignment.Center, StringAlignment.Center, StringTrimming.Character))
g.DrawString(toDisplay, newFont, Brushes.Black, rect, format);
You can use TextRenderer.DrawText to draw a text in a rectangle bound, using a color and font and specifying different text format flag:
var rect = new Rectangle(10, 10, 32, 32);
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.FillEllipse(Brushes.White, rect);
TextRenderer.DrawText(e.Graphics, "100", this.Font, rect, Color.Black,
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);
I am trying to using antialiasing but I don't why it isn't working:
{
Pen pen = new Pen(Color.Black, 3);
Pen r = new Pen(Color.YellowGreen, 3);
Graphics b = panel2.CreateGraphics();
b.DrawEllipse(pen, 6, 0, 90, 90);
b.SmoothingMode = SmoothingMode.AntiAlias;
b.DrawLine(r, new Point(50, 90), new Point(50, 0));
}
First it should be noted that the Graphics object does not contain any graphics; it is a tool that lets you draw onto a related bitmap, including a control's surface. Therefore changing any of its properties, like the SmoothingMode only influences graphics you draw from then on, not anything you have drawn before..
The circle certainly would have antialised pixels if you would draw it after setting the SmoothingMode from its default None to AntiAlias.
The Line is vertical, so it doesn't need antialiasing except at its ends, where there is some. But if you tilt it or move it to a non-integer position anti-aliasing will show!
Let's modify your code a little and look closely at the result:
Pen pen = new Pen(Color.Black, 3);
Pen r = new Pen(Color.YellowGreen, 3);
Graphics b = panel2.CreateGraphics();
b.DrawEllipse(pen, 6, 6, 90, 90);
b.SmoothingMode = SmoothingMode.AntiAlias;
b.DrawLine(r, new Point(50, 90), new Point(50, 0));
b.DrawLine(r, new Point(60, 90), new Point(70, 0));
b.DrawLine(r, new PointF(40.5f, 90), new PointF(40.5f, 0));
b.DrawEllipse(pen, 6, 6, 30, 30);
The smaller circle has many gray pixels and even the original green line has a lighter top end. The two new lines are fully anti-aliased now, one because it is tilted, the other because it sits 'between' pixels.
Btw: If it is turned on you will also see anti-alising when your Pen.Width is even or when it is a non-integer number. The reason for the latter should be obvious; the former comes from the PenAlignment property. Its default Center tries to center the pen, but not at the pixel boundary but at the center of the coordinate pixels. Therefore only an uneven width will completely fill the pixels and not cause anti-aliasing. For closed shapes you can change this behaviour by changing the Pen.Alignment to Inset:
This property determines how the Pen draws closed curves and
polygons. The PenAlignment enumeration specifies five values;
however, only two values—Center and Inset—will change the appearance
of a drawn line. Center is the default value for this property and
specifies that the width of the pen is centered on the outline of the
curve or polygon. A value of Inset for this property specifies that the
width of the pen is inside the outline of the curve or polygon. The
other three values, Right, Left, and Outset, will result in a pen that
is centered.
A Pen that has its alignment set to Inset will yield unreliable
results, sometimes drawing in the inset position and sometimes in the
centered position.Also, an inset pen cannot be used to draw compound
lines and cannot draw dashed lines with Triangle dash caps.
PS: The question was not about how to draw properly, so let me just note that you never ought to do it using control.CreateGraphics as this will always only result in non-persistent graphics. Instead you need to use the Paint event and its e.Graphics object..
I'm trying to draw half and partial circles (all BLACK lines) on a bitmap.
My INTENDED result looks like this:
My CURRENT result looks like this:
I've tried so many different alternatives but it nevers looks right.
using (var b = new Bitmap(200, 100, PixelFormat.Format24bppRgb))
{
using (var g = Graphics.FromImage(b))
{
g.FillRectangle(new SolidBrush(Color.LightGray), 0, 0, 200, 100);
// RED COLOR
Rectangle rec = new Rectangle(-15, 50, 70, 100);
g.DrawRectangle(new Pen(Color.Red, 1f), rec);
g.DrawArc(new Pen(Color.Red, 3f), rec, 50, 100);
// WHITE COLOR
Rectangle rec = new Rectangle(10, 50, 70, 70);
g.DrawRectangle(new Pen(Color.White, 1f), rec);
g.DrawEllipse(new Pen(Color.White, 3f), rec);
}
}
But it always look totally wrong and after hours of playing with the numbers, I could not find a way to control the output.
Question:
Is there a simple way to design the 3 black lines on my INTENDED image in a graphic object using C# ??
The easy way to achieve this is to draw three concentric circles and let clipping take care of the fact that two of them fall outside the drawing region.
The way to achieve the arc-based drawing you want is probably to start with the concentric circles (so you know you have the rects in the right places), and then change the DrawEllipse to DrawArc, setting the start and sweep angles to the right values.
Start angle is measured in degrees from the x axis (horizontal line towards the right of the circle's centre), so for the smaller arc you will need an angle approximately 305 degrees. From there you need it to draw for about 90 degrees. The outer arc will be similar, but a smaller arc, so it might go from about 330 degrees for a sweep of about 60 degrees.
It seems the solution is to draw a rectangle outside the boundaries of the bitmap and use the graphic.DrawEllipse method to draw the curve line.
Here is a snippet of the working code:
Pen pen = new Pen(Color.White);
Rectangle rec = new Rectangle(-30, 50, 100, 100);
g.DrawEllipse(pen, rec);
rec = new Rectangle(-30, 10, 150, 150);
g.DrawEllipse(pen, rec);
rec = new Rectangle(-30, -30, 200, 200);
g.DrawEllipse(pen, rec);
Many thanks to Hans Passant to point me to this line of thinking.
What I want to do is basically cropping a rectangle from an image. However, it should satisfy some special cases:
I want to crop an angled rectangle on image.
I don't want to rotate the image and crop a rectangle :)
If cropping exceeds the image size, I don't want to crop an empty background color.
I want to crop from back of the starting point, that will end at starting point when rectangle size completed. I know I couldn't explain well so if I show what I want visually:
The blue dot is the starting point there, and the arrow shows cropping direction. When cropping exceeds image borders, it will go back to the back of the starting point as much as, when the rectangle width and height finished the end of the rectangle will be at starting point.
Besides this is the previous question I asked:
How to crop a cross rectangle from an image using c#?
In this question, I couldn't predict that a problem can occur about image dimensions so I didn't ask for it. But now there is case 3. Except case three, this is exactly same question. How can I do this, any suggestions?
What needs to be done is to add offsets to the matrix alignment. In this case I am taking one extra length of the rectangle from each side (total 9 rectangles) and offsetting the matrix each time.
Notice that it is necessary to place offset 0 (the original crop) last, otherwise you will get the wrong result.
Also note that if you specify a rectangle that is bigger than the rotated picture you will still get empty areas.
public static Bitmap CropRotatedRect(Bitmap source, Rectangle rect, float angle, bool HighQuality)
{
int[] offsets = { -1, 1, 0 }; //place 0 last!
Bitmap result = new Bitmap(rect.Width, rect.Height);
using (Graphics g = Graphics.FromImage(result))
{
g.InterpolationMode = HighQuality ? InterpolationMode.HighQualityBicubic : InterpolationMode.Default;
foreach (int x in offsets)
{
foreach (int y in offsets)
{
using (Matrix mat = new Matrix())
{
//create the appropriate filler offset according to x,y
//resulting in offsets (-1,-1), (-1, 0), (-1,1) ... (0,0)
mat.Translate(-rect.Location.X - rect.Width * x, -rect.Location.Y - rect.Height * y);
mat.RotateAt(angle, rect.Location);
g.Transform = mat;
g.DrawImage(source, new Point(0, 0));
}
}
}
}
return result;
}
To recreate your example:
Bitmap source = new Bitmap("C:\\mjexample.jpg");
Bitmap dest = CropRotatedRect(source, new Rectangle(86, 182, 87, 228), -45, true);