Get Rectangle's color after FillRectangles - c#

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

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.

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

Graphics.DrawImage alternatives for large images

I am trying to draw a crosshair ("plus sign") with inverted colors over an image to show the location of a selected point within the image. This is how I do it:
private static void DrawInvertedCrosshair(Graphics g, Image img, PointF location, float length, float width)
{
float halfLength = length / 2f;
float halfWidth = width / 2f;
Rectangle absHorizRect = Rectangle.Round(new RectangleF(location.X - halfLength, location.Y - halfWidth, length, width));
Rectangle absVertRect = Rectangle.Round(new RectangleF(location.X - halfWidth, location.Y - halfLength, width, length));
ImageAttributes attributes = new ImageAttributes();
float[][] invertMatrix =
{
new float[] {-1, 0, 0, 0, 0 },
new float[] { 0, -1, 0, 0, 0 },
new float[] { 0, 0, -1, 0, 0 },
new float[] { 0, 0, 0, 1, 0 },
new float[] { 1, 1, 1, 0, 1 }
};
ColorMatrix matrix = new ColorMatrix(invertMatrix);
attributes.SetColorMatrix(matrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
g.DrawImage(img, absHorizRect, absHorizRect.X, absHorizRect.Y, absHorizRect.Width, absHorizRect.Height, GraphicsUnit.Pixel, attributes);
g.DrawImage(img, absVertRect, absVertRect.X, absVertRect.Y, absVertRect.Width, absVertRect.Height, GraphicsUnit.Pixel, attributes);
}
It works as expected, however, it is really slow. I want the user to be able to move the selected location around with their mouse by setting the location to the cursor's location whenever it moves. Unfortunately, on my computer, it can update only around once per second for big images.
So, I am looking for an alternative to using Graphics.DrawImage to invert a region of an image. Are there any ways to do this with speeds proportional to the selected region area rather than the entire image area?
Sounds to me you are focusing on the wrong problem. Painting the image is slow, not painting the "cross-hairs".
Large images can certainly be very expensive when you don't help. And System.Drawing makes it very easy to not help. Two basic things you want to do to make the image paint faster, getting it more than 20 times faster is quite achievable:
avoid forcing the image painting code to rescale the image. Instead do it just once so the image can be drawn directly one-to-one without any rescaling. Best time to do so is when you load the image. Possibly again in the control's Resize event handler.
pay attention to the pixel format of the image. The fastest one by a long shot is the pixel format that's directly compatible with the way the image needs to be stored in the video adapter. So the image data can be directly copied to video RAM without having to adjust each individual pixel. That format is PixelFormat.Format32bppPArgb on 99% of all modern machines. Makes a huge difference, it is ten times faster than all the other ones.
A simple helper method that accomplishes both without otherwise dealing with the aspect ratio:
private static Bitmap Resample(Image img, Size size) {
var bmp = new Bitmap(size.Width, size.Height, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
using (var gr = Graphics.FromImage(bmp)) {
gr.DrawImage(img, new Rectangle(Point.Empty, size));
}
return bmp;
}
Draw the image once on Graphics g, then draw the crosshair on Graphics g directly instead of the image. You can optionally keep track of the places the user clicked so as to save them either in the image or elsewhere as needed.

Erase to Transparency

So I'm trying to make a 32x32 block back to transparent but everytime I try to set it to transparent it just keeps what ever is already there, I want to erase what's on the image to transparent, here's my code I tried.
public Bitmap erase_tile(Bitmap bitmap, int x, int y)
{
Graphics device = Graphics.FromImage(bitmap);
Brush brush = new SolidBrush(Color.FromArgb(0, Color.White));
device.FillRectangle(brush, new Rectangle(x * 32, y * 32, 32, 32));
return bitmap;
}
All transparency is going to be accomplished via functionality on the Bitmap class. The Graphics class is geared toward drawing, and drawing Color.Transparent is essentially a no-op.
You can use Bitmap.SetPixel() with Color.Transparent to set individual pixels.
Or you can do something like this, where you use Graphics to paint a dummy color which you will then instruct the bitmap to use as the transparent color.
using (var graphics = Graphics.FromImage(bmp))
{
graphics.FillRectangle(Brushes.Red, 0, 0, 64, 64);
graphics.FillRectangle(Brushes.Magenta, 16, 16, 32, 32);
}
bmp.MakeTransparent(Color.Magenta);
While I was searching for the same solution, I wasn't able to find the exact answer, so after some experiments I came across SetCompositingMode and it made the trick (refer to Using Compositing Mode to Control Alpha Blending for full details).
Here is a working code in C++ to demonstrate the approach (it needs some tuning to be re-used in C#):
void SetTransparent(Gdiplus::Image* image, IN INT x, IN INT y, IN INT width, IN INT height)
{
Gdiplus::Graphics graph(image);
graph.SetCompositingMode(Gdiplus::CompositingModeSourceCopy);
Gdiplus::SolidBrush transparent(0);
graph.FillRectangle(&transparent, x, y, width, height);
}

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