I have a panel and I use it's Graphics gr = panel1.CreateGraphics() to draw lines and other stuff. I need to get pixel color of the point where mouse is clicked, so I decided to use GetPixel method of Bitmap. I create bitmap this way:
Bitmap b = new Bitmap(width, height);
panel1.DrawToBitmap(b, new Rectangle(0, 0, width, height));
b.Save("D:/aaa.bmp");
but I get only white rectangle even if I've drawn anything. What's the problem?
Only things that are drawn in the Paint event will be rendered by DrawToBitmap. Instead of explicitly call panel1.CreateGraphics(), handle the Paint event of the panel and do your drawing using e.Graphics.
Related
I am creating an Circle on a bitmap but want to have a hole in it. After serching for half an hour I only found ways to crop an image to a circle. The hard thing is, that the hole in the middle should be transparent as the rest of the Image.
This is the base image and the yellow circle represents the transparent area that should be added.
Thanks for any kind of help.
The start is simple: Create a transparent bitmap by doing a g.Clear(Color.Transparent) and then draw/fill a circle in a color.
The next step is a bit trickier: You next want to paint the hole with transparency.
To do so you need to switch the Graphics object to the right CompositingMode; default is SourceOver but you want SourceCopy. The former overlays the alpha values creating mixed colors. The latter will do what we want: Draw the hole by copying the drawn colors including alpha right over the old ones..
Here is an example:
Bitmap bmp = new Bitmap(500, 500);
using (Graphics g = Graphics.FromImage(bmp))
{
g.Clear(Color.Transparent);
//g.SmoothingMode = SmoothingMode.AntiAlias;
g.CompositingMode = CompositingMode.SourceCopy;
g.FillEllipse(Brushes.DarkGreen, 100, 100, 300, 300);
g.FillEllipse(Brushes.Transparent, 200, 200, 100, 100);
}
pictureBox1.Image = bmp;
This is what is looks like in a PictureBox with a BackgroundImage:
A few notes:
You can also use a semi-transparent brush to create a 'tinted' hole; do not use anti-aliasing for this though, as it would introduce colored fringes.
We used simple circles here but with a GraphicsPath you can create and fill shapes of almost any shape and complexity..
And using a GraphicsPath would also have been an alternative to filling with transparency: By first adding the large and then the smaller, inner ellipse the path would have been created with a hole and filling it would have had the very same result! But I found the solution above more instructive..
Final note: As clarkitect noted, to save, do use a format that supports transparency. Png is always recommended..
I'm making a program that's cropping images. I have two PictureBoxes and a Button named 'crop'. One picture box contains an image and when I select a rectangle in it and press 'Crop' the selected area appears in the other picture box; so the program is working when I press crop. The problem is: How can I get the image from crop area into picture box Image?
Rectangle rectCropArea;
Image srcImage = null;
TargetPicBox.Refresh();
//Prepare a new Bitmap on which the cropped image will be drawn
Bitmap sourceBitmap = new Bitmap(SrcPicBox.Image, SrcPicBox.Width, SrcPicBox.Height);
Graphics g = TargetPicBox.CreateGraphics();
g.DrawImage(sourceBitmap, new Rectangle(0, 0, TargetPicBox.Width, TargetPicBox.Height),
rectCropArea, GraphicsUnit.Pixel);
//Good practice to dispose the System.Drawing objects when not in use.
sourceBitmap.Dispose();
Image x = TargetPicBox.Image;
The problem is that x = null and the image is showing in the picture box so how can I get the Image from this picture box into the Image variable ?
A couple of issues:
First and most important: You are being confused about the relationship between PictureBox.Image (a Property) and the Graphics you associate with the PictureBox's surface.
The Graphics object you get from Control.CreateGraphics is only able to paint onto the surface of the control; usually not what you want; and even when you do, you usually want to do it in a Paint event using e.Graphics..
So, while your code seems to work, it only paints non-persistent pixels onto the surface. Minimize/maximize and you'll see what non-persistent means..!
To change a Bitmap bmp you need to associate it with a Grahics object like this:
Graphics g = Graphics.FromImage(bmp);
Now you can draw into it:
g.DrawImage(sourceBitmap, targetArea, sourceArea, GraphicsUnit.Pixel);
After that you can assign the Bitmap to the Image Property of the TargetPicBox..
Finally dispose of the Graphics, or better, put it into a using clause..
I am assuming that you have managed to give the rectCropArea meaningful values.
Also note that the way you copy the source bitmap has an error: If you want the full image, do use its Size (*), not the one of the PictureBox!!
And instead of creating a target rectangle, with the same error, simply use the TargetPicBox.ClientRectangle!
Here is an example code for the crop Button:
// a Rectangle for testing
Rectangle rectCropArea = new Rectangle(22,22,55,99);
// see the note below about the aspect ratios of the two rectangles!!
Rectangle targetRect = TargetPicBox.ClientRectangle;
Bitmap targetBitmap = new Bitmap(targetRect.Width, targetRect.Height);
using (Bitmap sourceBitmap = new Bitmap(SrcPicBox.Image,
SrcPicBox.Image.Width, SrcPicBox.Image.Height) )
using (Graphics g = Graphics.FromImage(targetBitmap))
g.DrawImage(sourceBitmap, targetRect, rectCropArea, GraphicsUnit.Pixel);
if (TargetPicBox.Image != null) TargetPicBox.Dispose();
TargetPicBox.Image = targetBitmap;
Of course you should have prepared the Rectangle in the proper mouse events!
Here you would want to decide on the aspect ratio of the result; you probably don't want to distort the result! So you need to decide whether to crop the source cropping rectangle or whether to expand the target rectangle..!
Unless you are sure about the dpi resolution you should use SetResolution to make sure the new image has the same!
Note that since I assign targetBitmap to TargetPicBox.Image I must not dipose of it! Instead, before assigning a new Image, I first Dispose the old one..
Here is some pseudocode of what I am doing. Everything worked fine but then I tried to save my results. The saving also works but the image turns out to be transparent. Any idea what could cause this odd behavior?
static Graphics G = Panel.CreateGraphics();
//some painting -> shows up correctly on the panel
Bitmap bitmap = new Bitmap(500, 500, G);//bitmap is transparent!
bitmap.Save("path/test1.png", System.Drawing.Imaging.ImageFormat.Png);
The documentation for the Bitmap constructor you are using says:
Initializes a new instance of the Bitmap class with the specified size and with the resolution of the specified Graphics object.
Which means it just gets the resolution from the Bitmap. It does not paint anything to the bitmap. Either use Graphics.FromImage or, as Hans Passant mentions, the Control.DrawToBitmap method.
My personal preference, have I a need to paint to both the screen and a bitmap, would be to create a method that does the painting (taking a Graphics object as an argument). I can then call this either in a Paint event handler, or from other code to generate a bitmap.
Also, in general, never use Control.CreateGraphics. The proper way of drawing is in the Paint event of a control.
This will draw the Bitmap but wont show up in the panel. If showing is mandatory, then you have to implement it on the Paint event.
Bitmap bmp = new Bitmap(Panel.Width, Panel.Height);
Panel.DrawToBitmap(bmp, new Rectangle(0, 0, Panel.Width, Panel.Height));
Graphics grp = Graphics.FromImage(bmp);
Pen selPen = new Pen(Color.Blue);
grp.DrawRectangle(selPen, 10, 10, 50, 50);
bmp.Save("d:\\check3.png", System.Drawing.Imaging.ImageFormat.Png);
I'm working on a windows forms application and when I'm using the System.Drawing.Graphics on top of a picturebox the graphics either don't appear or appear only momentarily before disappearing.
This is the code that I'm using to set the picturebox (it's a simplified version and still exhibits the behavior)
private void showGraphic()
{
pictureBox1.Invalidate();
System.Drawing.Graphics graphics = this.pictureBox1.CreateGraphics();
SolidBrush semiTransBrush = new SolidBrush(Color.FromArgb(128, 0, 0, 255));
System.Drawing.Rectangle rect = new System.Drawing.Rectangle(100,100, 50, 50);
graphics.FillEllipse(semiTransBrush, rect);
}
private void button1_Click(object sender, EventArgs e)
{
showGraphic();
}
The settings for the picturebox are just the default settings with a picture from a file declared in the properties pane.
I was able to solve this problem by using a timer which was started by the button and then executed the graphic drawing before stopping itself, however this seemed a terrible solution and I wanted to do this a better way, if one exists as that could result in lack of portability to older computers.
Thanks in advance
You need to register a handler for the PictureBox's Paint method and do your drawing in that method. (Note: use the Graphics object passed in via the PaintEventArgs parameter.) That will guarantee that any time the PictureBox gets redrawn, your drawing code will run as well. Otherwise, you're just drawing over the top of something that will get refreshed for any of a number of reasons.
Once you've registered for the Paint event, anytime you want to repaint, call Invalidate() on the PictureBox and your painting code will run. You can keep track of whether or not your overlay graphics should be drawn via a private boolean member variable.
When you call pictureBox1.Invalidate() it queues up a message that the picture box needs to be drawn. Before that message gets processed you are drawing an ellipse on top of the current picture. Then message loop then processes the paint message from the invalidate and then repaints itself (which erases your image)
To make this (old and answered) question more complete I'm adding an alternative solution: sometimes you don't want to redraw your picture every refresh, for example if drawing is complicated and takes time. For these cases, you can try the following approach:
Create a new bitmap in the size of the picturebox.
Draw on bitmap.
Set bitmap as the picturebox Image.
For example:
// create a new bitmap and create graphics from it
var bitmap = new Bitmap(pictureBox1.Width, pictureBox1.Height);
var graphics = System.Drawing.Graphics.FromImage(bitmap);
// draw on bitmap
SolidBrush semiTransBrush = new SolidBrush(Color.FromArgb(128, 0, 0, 255));
System.Drawing.Rectangle rect = new System.Drawing.Rectangle(100,100, 50, 50);
graphics.FillEllipse(semiTransBrush, rect);
// set bitmap as the picturebox's image
pictureBox1.Image = bitmap;
With the code above you can draw everything once and it will survive redraw events.
I have been working on creating a program similar to MS Paint. I have several of the features it has down but the one which is currently giving me trouble is the rectangular selection tool. My program currently draws everything on the panel and saves it all in an ArrayList so each shape can be redrawn in Paint().
Like MS paint I would like the user to be able to select a section of the drawing on the panel and either copy it, move it, re-size it, or even delete it. I was thinking about having the user draw a rectangle & saving the information for it. Then taking that information for the rectangle, passing them to create a new Bitmap. I would then paint a new rectangle in the background color to give the appearance that the selected area was "removed" when the selected portion is moved. It sounded okay until I realized that I couldn't use the Graphics.FromImage() on the PaintEventArgs variable passed to Paint() which made my idea useless. Not sure if that makes sense so my apologies if it's a confusing mess.
I've been searching the internet for some assistance and I haven't found much to help so either this is very easy to do, very difficult, or "rectangle selection tool" is not the proper term. Any help or pointers would be greatly appreciated!!! Thank you for your time! :)
I understand that you actually have the Rectangle and now would like to copy an area from your painted Panel.
This is possible, assuming you have, as you should, placed all the painting in the Paint event of the Panel.
Then you can, use DrawToBitmap to ask the Panel to draw itself onto a new Bitmap; from there you can DrawImage the Rectangle onto your Panel.
Note: For this to integrate with your list of 'Paint-Actions' you will have to either now store that Bitmap or store the Rectangle and redo the whole operation.
using (Graphics G = panelCanvas.CreateGraphics() )
{
Rectangle R0 = new Rectangle(22,22,55,55); // your Rectangle!
using (Bitmap bmp = new
Bitmap(panelCanvas.ClientSize.Width, panelCanvas.ClientSize.Height))
{ panelCanvas.DrawToBitmap(bmp, panelCanvas.ClientRectangle);
G.DrawImage(bmp, 111f, 111f, R0, GraphicsUnit.Pixel);
}
}
Aside: Please do replace the ArrayList, which is depracated by the new List<T>, e.g. a List<PaintAction> or whatever name your class has!
If you simply want to extract a rectanglular area from the Panel Control you can use thsi function:
public Bitmap getAreaFrom(Control ctl, Rectangle area)
{
Bitmap bmp2 = new Bitmap(area.Width, area.Height);
using (Graphics G = ctl.CreateGraphics())
using (Bitmap bmp = new Bitmap(ctl.ClientSize.Width, ctl.ClientSize.Height))
{
ctl.DrawToBitmap(bmp, ctl.ClientRectangle);
using (Graphics G2 = Graphics.FromImage(bmp2))
G2.DrawImage(bmp, 0f, 0f, area, GraphicsUnit.Pixel);
}
return bmp2;
}