Saving `Graphics` is just transparent - c#

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

Related

C# Painting a form into a MetaFile graphics object

I hope that question was not asked before. I could not find anything specific in this regard. I have a complicated custom drawn form that I would lake to save as a vector graphic (emf) using the MetaFile class in C#.
Somehow I just can't get it to work. I always end up with a 1KB empty file and maybe someone can tell me what I am doing wrong.
This is my code so far. Am I missing something?
Graphics g = Graphics.FromImage(new Bitmap(this.Width, this.Height));
IntPtr hdc = g.GetHdc();
Metafile imageMetafile = new Metafile(filepath, hdc);
using (Graphics imageGraphics = Graphics.FromImage(imageMetafile))
{
imageGraphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
imageGraphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
this.InvokePaint(this, new PaintEventArgs(imageGraphics, new Rectangle(0, 0, this.Width, this.Height)));
}
g.ReleaseHdc();
g.Dispose();
I got parts of this code structure from the source code of ZedGraph. However ZedGraph then feeds everything through its own Draw() methods that stanard winforms don't have. This is where I thought I might be able to use InvokePaint to accomplish the same result but without any success.
As a side note: The control element I try to paint is already on screen at this moment and I am calling this code from the UI thread.
How would one get a Control element use a custom graphics object instead of the regular "paint on screen" graphics object?
EDIT: Just to further clarify. I don't have an issue with generating vector graphics in general. The MetaFile graphics route works fine if I just start drawing shapes or even bitmaps into it. The only issue I face is that I can't get my UI control to use THAT graphics object instead of the default "print-to-screen" graphics object.
var imageMetafile = new Bitmap(this.Width, this.Height);
using (Graphics imageGraphics = Graphics.FromImage(imageMetafile))
{
imageGraphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
imageGraphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
imageGraphics.CopyFromScreen(this.Location.X, this.Location.Y, 0, 0, this.Size);
imageMetafile.Save(filepath);
}

How to get an image from a rectangle?

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

Graphics over a picturebox aren't showing and if they are they flicker before disappearing

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.

how this function is working. how change in bitmap object is happening

why we are using graphics object here?
Bitmap bmpNew = new Bitmap(s.Width, s.Height,pixelFormat.Format24bppRgb);
graphics.DrawImage(source, new Rectangle(4 arg), new Rectangle(4 arg), GraphicsUnit.Pixel);
graphics.Flush();
return bmpNew;
how this function (DrawImage) is changing the bitmap object please help?
A bitmap and a screen are very similar, as the screen reads from an image buffer, which is in fact a bitmap. Thus, in some frameworks as e.g. WinForms which you seem to use, the same objects are used for drawing to a screen as drawing to a bitmap.
Thus, the function DrawImage draws the given image to the given bitmap, just like you would draw an image to the screen.

How to get bitmap from painted panel in C#

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.

Categories

Resources