Adding antialiasing - c#

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..

Related

Invert Crop from (Cut hole into) Image

Everywhere I look online, I see people posting on how to successfully crop an image. However, I want to 'crop'/ clear a hole out of an image. I want to keep the original image, but crop out a rectangle
As you can see in the image above, I have "cropped" out the kittens face. I maintained the original image, but removed only part of it. I cannot figure out how to do that.
Assuming you want to replace the original pixel colors with transparency you run into a small problem: You can't draw or fill with transparency in GDI+.
But you can use Graphics.Clear(Color.Transparent).
To do that you restrict the region where the Graphics object will draw. Here we can use the simple cropping rectangle but you can clear more complex shapes using a GraphicsPath..
Example using a bitmap bmp:
using (Graphics g = Graphics.FromImage(bmp))
{
Rectangle crop = new Rectangle(222,222,55,55);
g.SetClip(crop);
g.Clear(Color.Transparent);
}
bmp.Save(somefilename, ImageFormat.Png);
Setting your Graphics object's CompositingMode property to CompositingMode.SourceCopy will allow your drawing operations to replace the alpha value instead of proportionally opacifying it:
public static void TestDrawTransparent()
{
//This code will, successfully, draw something transparent overwriting an opaque area.
//More precisely, it creates a 100*100 fully-opaque red square with a 50*50 semi-transparent center.
using(Bitmap bmp = new Bitmap(100, 100, PixelFormat.Format32bppArgb))
{
using(Graphics g = Graphics.FromImage(bmp))
using(Brush opaqueRedBrush = new SolidBrush(Color.FromArgb(255, 255, 0, 0)))
using(Brush semiRedBrush = new SolidBrush(Color.FromArgb(128, 255, 0, 0)))
{
g.Clear(Color.Transparent);
Rectangle bigRect = new Rectangle(0, 0, 100, 100);
Rectangle smallRect = new Rectangle(25, 25, 50, 50);
g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
g.FillRectangle(opaqueRedBrush, bigRect);
g.FillRectangle(semiRedBrush, smallRect);
}
bmp.Save(#"C:\FilePath\TestDrawTransparent.png", ImageFormat.Png);
}
}
In this code, I first draw a fully-opaque red square, then a semi-transparent red square "over" it. The result is a semi-transparent "hole" in the square:
And on a black background:
A zero-opacity brush works just as well, leaving a clear hole through the image (I checked).
With that in mind, you should be able to crop any shapes you want, simply by filling them with a zero-opacity brush.

How to paint a certain area

I am new to drawing and paints in c# & I am trying to make a simple program it has 3 intersecting circles (A,B,C). What i want to do is paint a certain (according to result I get).
For example: If I get 1 as a result I want to fill the yellow bordered region, if I get 4 I want to fill green bordered region and so on.
My Code to draw these circles:
private void button1_Click(object sender, EventArgs e)
{
Graphics A = this.CreateGraphics();
Graphics B = this.CreateGraphics();
Graphics C = this.CreateGraphics();
Pen Bluepen = new Pen(Color.Blue, 2);
Pen RedPen = new Pen(Color.Red, 2);
Pen BlackPen = new Pen(Color.Black, 2);
A.DrawEllipse(Bluepen,100, 100, 150, 150);
B.DrawEllipse(RedPen, 195, 100, 150, 150);
C.DrawEllipse(BlackPen, 145, 190, 150, 150);
}
Since you are new to this topic I have to tell you: This is a lot harder that one would hope for.
Three solutions come to mind:
Construct a GraphicsPath you could fill from three Arcs. To calculate the arcs you need the rectangles you have but also the sweeping angle and also the starting angle. This will take quite some math..
After having drawn into a Bitmap you could floodfill the area you want to color. This will only work for bitamps from which you can extract the current color of each pixel, not for drawing onto controls..
The simplest way it still a bit involved, but only mildly so
Solution 3 (Create a Region and fill it)
You can use all sorts of set operations to combine areas called Regions. And you can construct a Region from a GraphicsPath. And you can construct a GraphicsPath by adding an ellipse. And you can clip the drawing area of a Graphics object to a Region.
Let's try:
private void panel1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
Rectangle r1 = new Rectangle(100, 100, 150, 150);
Rectangle r2 = new Rectangle(195, 100, 150, 150);
Rectangle r3 = new Rectangle(145, 190, 150, 150);
GraphicsPath gp1 = new GraphicsPath();
GraphicsPath gp2 = new GraphicsPath();
GraphicsPath gp3 = new GraphicsPath();
gp1.AddEllipse(r1);
gp2.AddEllipse(r2);
gp3.AddEllipse(r3);
Region r_1 = new Region(gp1);
Region r_2 = new Region(gp2);
Region r_3 = new Region(gp3);
r_1.Intersect(r_2); // just two of five..
r_1.Exclude(r_3); // set operations supported!
g.SetClip(r_1, CombineMode.Replace);
g.Clear(Color.Magenta); // fill the remaining region
g.ResetClip();
g.DrawEllipse(Pens.Red, r1);
g.DrawEllipse(Pens.Blue, r2);
g.DrawEllipse(Pens.Green, r3);
// finally dispose of all Regions and GraphicsPaths!!
r_1.Dispose();
gp1.Dispose();
.....
}
Do note that the region operations change the current region; if you want to fill more areas you need to restore the changed region!
Also note that I draw where any persistent drawing belongs: In the Paint event and that I use its e.Graphics object..
GraphicsPaths as Regions are GDI objects and should be disposed off!
Notes on solution 1 (Create a GraphicsPath by Math)
The full math is rather involved. By making a few assumptions the task can be greatly simplified: Let's assume the circles have the same size. Also that we first look at two circles only, with the same y-position. Finally that the circles form a symmetrical figure. (Which btw they don't: the red circle should have x=190 and the green one y=186,45..)
Getting the two intersection points as well as the sweeping angle is not so hard.
Next one can rotate the two points twice around the center of the whole figure by 120° using a Matrix; see here for an example. Now we have six points; we still need the smaller sweeping angle, which is also found with simple math.
Finally we can construct all 12 (!) GraphicsPaths from the 12 arcs and combine them at will.
The good part is that we can both fill and draw those paths. But, the code is rather extensive..
Notes on solution 2 (floodfill)
While you can't floodfill directly on a control you can prepare the result in a bitmap and then display that image on the control with Graphics.DrawImage.
For an example of coding a floodfill see this post!

Graphics.DrawHalfCirle & Graphics.DrawPartialCircle

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.

Draw image with rounded corners, border and gradient fill in C#

I've looked everywhere and googled everything and couldn't find anything good.
What I need is a class that is able to draw an image (graphics) with rounded corners (different on each corner is a plus) with a border and gradient fill.
All the examples I find have some flaws (like bad quality, missing functionality etc).
I will use this with a ashx that will draw the image and then show it to the user.
Thanks!
The GraphicsPath allows you to draw relatively free form shapes which you can then fill with a gradient brush. The below example code will create a rectangle with two differntly rounded corners and a gradient fill.
GraphicsPath gp = new GraphicsPath();
gp.AddLine(new Point(10, 10), new Point(75, 10));
gp.AddArc(50, 10, 50, 50, 270, 90);
gp.AddLine(new Point(100, 35), new Point(100, 100));
gp.AddArc(80, 90, 20, 20, 0, 90);
gp.AddLine(new Point(90, 110), new Point(10, 110));
gp.AddLine(new Point(10, 110), new Point(10, 10));
Bitmap bm = new Bitmap(110, 120);
LinearGradientBrush brush = new LinearGradientBrush(new Point(0, 0), new Point(100, 110), Color.Red, Color.Yellow);
using (Graphics g = Graphics.FromImage(bm))
{
g.FillPath(brush, gp);
g.DrawPath(new Pen(Color.Black, 1), gp);
g.Save();
}
bm.Save(#"c:\bitmap.bmp");
This result in the following image:
I think you'll need to create your own method, using a graphics object and "manually" (read "with code") create the image. Easiest way would be to create a single graphics object, add a circle, then in each quadrant of the image add the extras you need, then split the object into fourths. Or return the whole thing as one image then use CSS sprites to place the image in the right spots with the right coordinates (probably the better solution as it uses less calls to the graphics library and returns just one file, so less calls to the web server).

Graphics transparency on PictureBox

First of all, this is not about making the PictureBox control transparent. It's about bitmap transparency on the fully opaque "canvas".
The PictureBox will always have the size of 300*300 with white background. No transparency is needed for the control.
What I need is the way to draw the transparent rectangle (or whatever else) onto the pictureBox, so anything that was already there will be seen "through" the rectangle.
Say I have a following code
Bitmap bmp = new Bitmap(300, 300);
Graphics g = Graphics.FromImage(bmp);
g.FillRectangle(new SolidBrush(Color.White), 0, 0, 300, 300);
g.FillRectangle(new SolidBrush(Color.Red), 100, 100, 100, 100);
pictureBox.Image = bmp;
This will draw a red rectangle in the middle of the white canvas. Now, I need another (transparent) "layer" on the picture containing another rectangle, but one that is transparent.
I can try
Brush brush = new SolidBrush(Color.FromArgb(128, 0, 80, 0));
g.FillRectangle(brush, 50, 50, 200, 200);
Since I am using a color by specifying its alpha = 128, the resulting rectangle should be transparent so the first red rectangle should be seen through this other green one.
However, this does not happen correctly. I can see the red rectangle behind the new green one, but the part of the green rectangle that does not overlap the red one will remain completely opaque. However, if I set the alpha value of the color to some extremely small value (say 1-5), the whole rectangle will look transparent. This is not normal in my opinion - that 5/255 is only half transparent and that 128/255 is not transparent at all... And if there was a string drawed previously with g.DrawString(), the string is either displayed behind the green rectangle or it is not, depending on the level of transparency. For example if the Alpha is greater than or equals (around) 40, the string is not visible at all, and if it is less than 40, then it will show, more visible for smaller alpha values, down to alpha = 0.
How is this brush (when created from Argb color) applied? Am I missing something? To me it seems that setting a transparent brush makes the background "more visible" instead of setting the object "less visible".
Thanks for any replies with suggestions.
[EDIT] It seems I had a nasty bug in application logic, so the drawing routine happened in a loop, so when I accumulated certain number of transparent rectangles, they became more and more thick.
The code, when taken out of the loop, works correctly.
My bad.
alt text http://lh4.ggpht.com/_1TPOP7DzY1E/S02ivAoGgTI/AAAAAAAAC6s/ZQvZQ5GdwSU/s800/Capture4.png
is done by this code:
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
Bitmap bmp = new Bitmap(300, 300);
Graphics g = Graphics.FromImage(bmp);
g.FillRectangle(new SolidBrush(Color.White), 0, 0, 300, 300);
g.FillEllipse(new SolidBrush(Color.Blue), 25, 25, 100, 200);
g.FillRectangle(new SolidBrush(Color.Red), 100, 100, 300, 100);
g.DrawString("this is a STRING", SystemFonts.DefaultFont,
Brushes.Black, new Point(150, 150));
pictureBox1.Image = bmp;
Brush brush = new SolidBrush(Color.FromArgb(40, 0, 80, 0));
g.DrawRectangle(Pens.Black, 50, 50, 200, 200);
g.FillRectangle(brush, 50, 50, 200, 200);
}
The green part is not opaque as you can see... The string is perfectly visible.
To me it seems that setting a transparent brush makes the background "more visible" instead of setting the object "less visible".
background "more visible" and object "less visible" are the same thing...

Categories

Resources