Graphics transparency on PictureBox - c#

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

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.

Get Rectangle's color after FillRectangles

How can I get the color of a Rectangle after creating it?
I'm using this code to create them :
SolidBrush sb = new SolidBrush(Color.Red);
Graphics g = panel1.CreateGraphics();
Rectangle[] rects = { new Rectangle(0, 0, 15, 15), new Rectangle(16, 16, 15, 15), new Rectangle(16, 0, 15, 15), new Rectangle(0, 16, 15, 15) };
g.FillRectangles(sb,rects);
And now I want to get the color of the 3rd rectangle
rects[2] = ....
Is it possible to get this? And it should return Color.Red.
You can find the position of center pixel of your rectangle, then you can use GetPixel method to get information about such as color.
Color pixelColor = myBitmap.GetPixel(50, 50);
The FillRectangles draws the rectangles with the brush (color) you passed. There is no reference from any rectangle to any color. It just executes a drawing command to a Graphics object.
A Rectangle doesn't have a color. So, nope, you cannot. If you explain why you would need it, there might be other solutions to get the desired results.
If you store the image, you only store the result (i.e. the grid of pixels). There is no information on how you created this image.
E.g. based on the bitmap image alone, you cannot differentiate between two adjacent squares (new Rectangle(0, 0, 15, 15), new Rectangle(15, 0, 15, 15) and a single rectangle that has twice the width (new Rectangle(0, 0, 30, 15)).
So the short answer to your question is no, you cannot do that.
Unless you store your information (the rectangles you drew) separately and then use that to find the appropriate pixel on the image (and this only works in simple cases - if you overlapped an earlier rectangle, it's going to be impossible)
But if you're going to store the rectangle information anyway, you might as well store its color and then you don't need to reverse engineer the image anymore. So the answer remains that you cannot do this based on an image alone.
You could write a new Class, for example:
class ColorRectangles
{
public Color color;
public Rectangle[] rects;
public ColorRectangle(Rectangle[] rects, Color color) : base(x, y, width, height)
{
this.color = color
this.rects = rects;
}
}
and then set the Rectangles Color with g.FillRectangles(new SolidBrush(Rectangle.color), Rectangle.rects);

Adding antialiasing

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

Why doesn't FillPolygon draw after FillRectangle?

I'm drawing directly onto a form with two FillPolygon statements to create two arrows - one black, one white. (The white arrow is slightly smaller and drawn over the black arrow.)
Here's the code in the form's OnPaint.
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
g.FillPolygon(brushBlack, travelArrow);
g.FillPolygon(brushWhite, featureArrow);
}
Works great. Now since the white arrow is going to be drawn several times in different rotations, I decided to use double-buffering to avoid as much flicker as possible.
I first created a DrawFeatureArrow method that I call in OnPaint.
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
g.FillPolygon(brushBlack, travelArrow);
DrawFeatureArrow(this, e);
}
Note: There's no rotation of the white arrow coded yet, I'm just trying to get the double-buffering set up.
DrawFeatureArrow looks like this.
private void DrawFeatureArrow(object sender, PaintEventArgs e)
{
Bitmap buffer = new Bitmap(60, 159);
Graphics gOff = Graphics.FromImage(buffer);
gOff.FillRectangle(brushGreen, 0, 0, buffer.Width, buffer.Height);
gOff.FillPolygon(brushWhite, featureArrow);
ImageAttributes attr = new ImageAttributes();
attr.SetColorKey(Color.Green, Color.Green);
Rectangle srcRect = new Rectangle(0, 0, bugger.Width, buffer.Height);
Rectangle destRect = new Rectangle(90, 66, 60, 159);
Graphics f = e.Graphics;
// Should draw green rectangle and white arrow
f.DrawImage(buffer, 90, 66); // Draws just a green rectangle
// If uncommented, should draw just white arrow (green rectangle hidden by SetColorKey)
// f.DrawImage(buffer, destRect, 0, 0, buffer.Width, buffer.Height, GraphicsUnit.Pixel, attr);
f.Dispose();
gOff.Dispose();
buffer.Dispose();
}
When run, the green rectangle is drawn but not the white arrow.
Strangely enough, in DrawFeatureArrow, if you replace this
gOff.FillPolygon(brushWhite, featureArrow);
With this
gOff.FillRectangle(brushWhite, 10, 10, 20, 20);
You get a tiny white rectangle in the upper left of the green rectangle.
Very strange behavior. Hope someone can point out what I'm doing incorrectly.
Thanks in advance for the help.
You're drawing your feature arrow over buffer that has dimensions of 60, 159. Maybe the location of the feature arrow is outside of those dimensions and therefore did not end in the bitmap.

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

Categories

Resources