DrawEllipse: Antialiasing broken with PenAlignment.Inset - c#

As suggested by #TaW on this previous question, I setted PenAlignment.Inset to draw the circle inside the Bitmap, but this caused another problem.
I want to draw a circle on a specified Bitmap with antialiasing.
SmoothingMode.AntiAlias
The problem is that, when I use PenAlignment.Inset, the antialiasing doesn't work correctly!
Instead, with PenAlignment.Center, it works correctly...
Any suggestion to resolve this problem?
Bitmap layer = new Bitmap(80, 80);
using (Graphics g = Graphics.FromImage(layer))
{
using (Pen p = new Pen(Color.Black, 4))
{
p.Alignment = PenAlignment.Inset;
g.SmoothingMode = SmoothingMode.AntiAlias;
g.DrawEllipse(p, new Rectangle(0, 0, layer.Width, layer.Height));
}
}
pictureBox3.Size = new Size(100, 100);
pictureBox3.Image = layer;
(Note the bugs on the left image)

Deflating the bounding rectangle by 1/2 of the pen's stroke width should solve this problem. By "deflate", I mean pull in all 4 sides towards the rectangle's center by 1/2 pen width:
float halfPenWidth = p.Width*0.5f;
g.DrawEllipse(p, new RectangleF(halfPenWidth, halfPenWidth, layer.Width - p.Width, layer.Height - p.Width));
or plugging in a hardcoded pen width of 4:
g.DrawEllipse(p, new Rectangle(2, 2, layer.Width - 4, layer.Height - 4));
Note that the full pen width must be subtracted from the rectangle's width and height in order to pull the right and bottom sides in by 1/2 pen width while keeping the rectangle centered on the same point.
Using this code with pen alignment centered, 1/2 of the stroke width will be drawn outside of the rectangle at the points where the ellipse touches the rectangle, but it will still be drawn inside the bitmap.

Related

DrawEllipse: Ellipse goes outside of the Bitmap size

I want to draw a circle with DrawEllipse on a specified Bitmap, with the same size of the Bitmap, but the result is that the circle appears clipped at the edges.
Why this problem?
Bitmap layer = new Bitmap(80, 80);
using (Graphics g = Graphics.FromImage(layer))
{
using (Pen p = new Pen(Color.Black, 4))
{
g.DrawEllipse(p, new Rectangle(0, 0, layer.Width, layer.Height));
}
}
pictureBox3.Size = new Size(100, 100);
pictureBox3.Image = layer;
By default a Pen has a PenAlignment.Center.
This means that half of its widh will draw outside the bounding rectangle.
You can simply avoid the issue by changing it to PenAlignment.Inset:
using (Pen p = new Pen(Color.Black, 4) { Alignment = PenAlignment.Inset})
{
g.DrawEllipse(p, new Rectangle(0, 0, layer.Width, layer.Height));
}
Update: If you want to turn on smoothing for the Graphics object you will need 1 or 2 extra pixels on both sides of the pen stroke for the anti-aliasing pixels. Using a smaller bounding rectanlge can't be avoided now. But..:
Rectangle rect = new Rectangle(Point.Empty, layer.Size);
rect.Inflate(-1, -1); // or -2
..should do..

DrawImage resized image too small

When I draw an image using Graphics.DrawImage and draw it at a bigger size than the original image, it ends up being a bit too small. You can see this in the following picture:
The green lines shouldn't be visible and are not part of the image. Rather they get drawn behind the image and the image should cover them.
How can I draw an image with the exact right size?
EDIT: I draw the green part with the same rectangle I pass into the DrawImage call, with the exact dimensions of how big the image should be. So no flaw in my values (I think).
EDIT 2: I draw the green rectangle using FillRectangle, so no pen calculations need to be done. Also, I logged the values that I pass into the rectangle for both the image and the green fill, and the values are correct. It's just the image that's off. I will post code later, as I'm not at my computer at the moment.
EDIT 3: This is the code I use to render the images:
// This is for zooming
public readonly float[] SCALES = { 0.05f, 0.1f, 0.125f, 0.25f, 0.333f, 0.5f, 0.667f, 0.75f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f, 2.5f, 3.0f, 3.5f, 4.0f, 4.5f, 5.0f, 6.0f, 7.0f, 8.0f, 10.0f, 12.0f, 15.0f, 20.0f, 30.0f, 36.0f };
private int scaleIndex = 8;
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
float ScaleFactor = SCALES[scaleIndex];
e.Graphics.InterpolationMode = ScaleFactor < 1 ? InterpolationMode.Bicubic : InterpolationMode.NearestNeighbor;
Image im = Properties.Resources.TSprite0;
for (int y = 0; y < TilesVertical; y++)
{
for (int x = 0; x < TilesHorizontal; x++)
{
float sx = im.Width * ScaleFactor;
float sy = im.Height * ScaleFactor;
Point p = new Point((int)(-scrollPosition.X + sx * x), (int)(-scrollPosition.Y + sy * y));
Size s = new Size((int)Math.Floor(sx), (int)Math.Floor(sy));
// The green rectangle in the background should be the same size as the image
e.Graphics.FillRectangle(Brushes.Lime, new Rectangle(p, s));
e.Graphics.DrawImage(im, new Rectangle(p, s), 0, 0, 16, 16, GraphicsUnit.Pixel);
}
}
im.Dispose();
}
EDIT 4: Also note that the image seems to be cropped on the left and top instead of resized. Take a look at this comparison of the original image upscaled in Photoshop and then how GDI+ renders it:
The issue happens when scaling to 2x or larger.
Looks like the whole problem is caused by the wrong default PixelOffsetMode.
By offsetting pixels during rendering, you can improve render quality
at the cost of render speed.
Setting it to
g.PixelOffsetMode = PixelOffsetMode.Half;
makes it go away for me.
Setting it to
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
also works fine.
Default, None and HighSpeed cause the image to be rendered a little to the left and up.
Often you will also want to set InterpolationMode.NearestNeighbor.

Difference between DrawPath and DrawRectangle

I am drawing lines using both Rectangle and graphicspath in my application and i am facing lose of width and height in drawing when using GraphicsPath rather than using Rectangle.
Below is the sample code which reproduces my issue,
protected override void OnPaint(PaintEventArgs e)
{
int left = ClientRectangle.X + 40, right = ClientRectangle.Width -80;
int bottom = ClientRectangle.Height - 80, top = ClientRectangle.Y + 40;
int borderWidth = 10;
Rectangle borderrectangle = new Rectangle(left, top, right, bottom);
Pen pen = new Pen(Color.Black, borderWidth);
//Draws lines using Rectangle.
e.Graphics.DrawRectangle(pen, borderrectangle);
Point[] points = new Point[]
{
new Point(left, top),
new Point(right, top),
new Point(right, bottom),
new Point(left, bottom),
};
GraphicsPath path = new GraphicsPath();
path.AddLines(points);
path.CloseFigure();
//Draws lines using Path.
e.Graphics.DrawPath(pen, path);
}
Here is the image ,
Inner rectangle is drawn using the DrawPath and outer rectangle is drawn with DrawRectangle.
Could anyone please update me the reason for width and height lose with GraphicsPath drawing, since i have given proper points as like the rectangle?
Any help will be appreciated.
When you create Rectangle you are passing right and bottom coordinates as width and height. Check Rectangle constructor parameters:
public Rectangle(
int x,
int y,
int width,
int height
)
When you use Path, you are drawing it by coordinates and everything is OK. You should create rectangle this way:
Rectangle borderrectangle = new Rectangle(left, top, right-left, bottom-top);
But make sure that width and height of ClientRectangle are greater than 120
When you are setting the values for your Rectangle, you are setting the X and Y coordinates of the top left corner as well as width and height. However, with your GraphicsPath, you are explicitly defining every corner as a separate point. To make the GraphicsPath draw exactly what the Rectangle is, you'll either need to offset your point array coordinates to equal the width and height of the Rectangle:
Point[] points = new Point[]
{
new Point(left, top),
new Point(right + left, top),
new Point(right + left, bottom + top),
new Point(left, bottom + top),
};
or construct the Rectangle to treat the right and bottom as coordinates instead of fixed lengths:
Rectangle borderrectangle = new Rectangle(left, top, right - left, bottom - top);
Considering that you are treating the values as sides and therefore coordinates, the second option will probably give you the most consistency.

GDI+ gradient effect

In the code below:
void f13(Graphics g)
{
g.FillRectangle(new SolidBrush(Color.Black), pictureBox1.ClientRectangle);
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
var zf = .0143;
const int w = 6000, h = 10, margin = 40;
var bmp = new Bitmap(w + 2 * margin, h + 2 * margin);
var bmpG = Graphics.FromImage(bmp);
bmpG.FillRectangle(new SolidBrush(Color.White), 0, 0, bmp.Width, bmp.Height);
var srcRect = new RectangleF(margin - .5f, margin - .5f, w, h);
zf = (float)Convert.ToInt32(w * zf) / w;
var destRect = new Rectangle(0, 0, Convert.ToInt32(w * zf), Convert.ToInt32(w * zf));
g.DrawImage(bmp, destRect, srcRect, GraphicsUnit.Pixel);
destRect.X += destRect.Width;
g.DrawImage(bmp, destRect, srcRect, GraphicsUnit.Pixel);
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
f13(e.Graphics);
}
I get a gap between two rectangles:
micro http://www.uploadup.com/di-0HXM.png
macro http://www.uploadup.com/di-G1O5.png
why is that?
If the gap line is not so clear, you may decrease margin. if you set it to 10 you'll get:
macro, less margin http://www.uploadup.com/di-P2ZT.png
That'll happen if your rectangles' boundaries aren't integers. Gradient has nothing to do with it.
Consider: Let's say you're drawing a rectangle whose right side is at X=100.5, and you're filling it with white (with the existing background being black). So the graphics library (this isn't specific to GDI+) will "half-fill" those rightmost pixels (at X=100) with white, meaning they blend the existing black with a 50% mix of white, for a result of gray.
Then you draw another rectangle whose left side is at X=100.5. Now you're once again filling the pixels at X=100 halfway with white, so the graphics library will take the existing color (gray) and blend it with a 50% white, leaving you with 75% white.
If you don't want this kind of seam, you have to either (a) make sure your rectangles overlap a little bit, or (b) manually round your coordinates to the nearest pixel, so all the pixels are getting completely written instead of blended with what's already there.

Alignment property of Pens tool in WinForms graphics

When I want to draw a rectangle in c# using pen tools If the rectangle width and height is less then the pens width then program draw nothing in from if pens alignment property Inset.But when I set alignment center then It print a rectangle. which is not size of my rectangle. Actually what happened at that time?
example:
Pen p = new Pen(Color.Black, 300);
p.Alignment = PenAlignment.Center;
g.DrawRectangle(p, 100, 100,10, 10);
p.Dispose();
The output figure is:
But how it is possible to draw a rectangle of 1o pixel width,hight with a pen of 300 pixel width?
So, basically, when the rectangle width or height smaller then 300px, you want to draw a black filled rectangle?
void DrawRectangle(Graphics g, Color pencolor, int penwidth, Rectangle x)
{
if(x.Width < penwidth || x.Height < penHeight)
{
Pen p = new Pen(pencolor);
p.Alignment = PenAlignment.Inset;
g.FillRectangle(p, x);
p.Dispose();
}
else
{
Pen p = new Pen(pencolor, penwidth);
p.Alignment = PenAlignment.Inset;
g.DrawRectangle(p, x);
p.Dispose();
}
}
Hope that helps.

Categories

Resources