With System.Drawing.Graphics, how can I have "nested graphics objects".
The main purpose is to have several clipped regions.
This is somehow the thing I want to do:
The whole screen is a Graphics object
Red area is another Graphics inside it and clipped
Green area is another one, clipped
Inside Graphics objects can be anything NOT only DrawString
The code should look like this:
using (var bmp = new System.Drawing.Bitmap(200, 200))
using (var mainG = System.Drawing.Graphics.FromImage(bmp))
using (var redG = ???)
using (var greenG = ???)
{
redG.SetClip(new RectangleF(...));
greenG.SetClip(new RectangleF(...));
// fill redG and greenG
}
NOTE: the result should go to a meta file and be vector graphic, so creating bitmaps and placing them around the mainG is NOT an option.
Assuming it's okay for the two vector contexts to be seperate while they are drawn into, you can use System.Drawing.Imaging.Metafile to catch the vector operations and then combine them into the bigger context.. Something like this:
using (Graphics gRef = this.CreateGraphics())
{
IntPtr hdc = gRef.GetHdc();
using (System.Drawing.Imaging.Metafile mf =
new System.Drawing.Imaging.Metafile(hdc,
System.Drawing.Imaging.EmfType.EmfPlusDual))
{
gRef.ReleaseHdc();
using (Graphics redG = Graphics.FromImage(mf))
{
redG.SetClip(new RectangleF(...));
// .. draw on redG
}
// repeat for greenG
// .. save and or combine as desired
}
}
}
An alternative approach would be to study the Enhanced Metafile format (http://msdn.microsoft.com/en-us/library/cc230724.aspx) and try to reproduce clipping masks manually.
Related
I am currently using this code to screen capture the panel I created, but whenever I am saving it, the quality is bad. Is there any way to maintain the good quality when saving it?
I tried resizing the panel but the result is still the same.
I tried doing a normal screen shot with the snipping tool and it also has the same result with the codes I use.
Any suggestions? or help?
private void SaveControlImage(Control theControl)
{
snapCount++;
int width = panel1.Size.Width;
int height = panel1.Size.Height;
Bitmap bm = new Bitmap(width, height, PixelFormat.Format64bppPArgb);
panel1.DrawToBitmap(bm, new Rectangle(0, 0, width, height));
//bm.Save(#"D:\TestDrawToBitmap.bmp", ImageFormat.Bmp);
bm.Save(deskTopPath + #"/Offer_" + snapCount.ToString() + "_" + DateTime.Now.ToString("yyyyMMdd") + #".jpg", System.Drawing.Imaging.ImageFormat.Jpeg);
}
Like here, it looks pixelated if you compare it to what you're reading now (like this website). I tried to screen cap the form but it looks like the uploaded picture so its useless
Screenshot:
Bitmap bmp= new Bitmap(controls.Width, controls.Height-50, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
Graphics Grap = Graphics.FromImage(bmp);
Grap.CopyFromScreen(PointToScreen(controls.Location).X, PointToScreen(controls.Location).Y, 0, 0, AnhDoThi.Size, CopyPixelOperation.SourceCopy);
SaveFileDialog save = new SaveFileDialog();
save.Filter = "JPEG|*.jpg";
DialogResult tl = save.ShowDialog();
if (tl == DialogResult.OK)
{
bmp.Save(save.FileName);
MessageBox.Show("Completed !");
}
This is what I use to save a screenshot:
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
private void SaveControlAsImage(Control control, string path)
{
Bitmap bitmap = new Bitmap(control.Width, control.Height);
control.DrawToBitmap(bitmap, control.Bounds);
using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.ReadWrite))
{
/* using ImageFormat.Png or ImageFormat.Bmp saves the image with better quality */
bitmap.Save(fs, ImageFormat.Png);
}
}
Control screenshot using Windows Snipping Tool
Control screenshot using SaveControlAsImage (ImageFormat.Png)
Control screenshot using SaveControlAsImage (ImageFormat.Jpeg)
It's not the best quality (just a little blurry), but this removes the distortion around the text and keeps the right color.
Sidenote: You're not using the parameter (theControl) passed to your method, so you might as well remove it (not recommended), or change all the calls to panel1 inside the method to theControl and call the method like
this.SaveControlImage(this.panel1);
Also, when you're accessing just a property (i.e. this.panel1.Size.Width) it's better to just call the property instead of assign it to a variable. If you're calling a method and getting a value back (which you'll be using more than once), you must assign it to a variable (i.e. int arrayCount = array.Count()).
Windows Forms is not using vector graphics to draw the user interface. Therefore, all you can get generically is to draw the control to a Bitmap instead of to the screen. This works independently of whether your control is visible on the screen, but there is not more than that. If you want a higher-resolution image from a Windows Forms control, the only way is to resize the control and hope that it supports zooming.
I am trying to merge two images using C Sharp's System.Drawing.Graphics.
Here is my code:
Point p = new Point(Convert.ToInt32(OffsetX), Convert.ToInt32(OffsetY));
Image i = Image.FromFile("1.jpg");
Image toDraw = Image.FromFile("2.jpg");
using (Graphics g = Graphics.FromImage(i))
{
g.DrawImage(toDraw, p);
g.Save();
Directory.CreateDirectory(Path.Combine(Directory.GetCurrentDirectory(), "saved"));
i.Save(Path.Combine("saved", "saved1.jpg"));
}
The code works fine, but the second image is enlarged in the output from the program.
Made with Paint:
Made with code above:
Use the Graphics.DrawImage(Image, Rectangle) overload to control the size of the image. The overload you are using takes note of the Image.HorizontalResolution and VerticalResolution properties to ensure that the drawn image is (roughly) as large in inches as it was when it was created. Fix:
g.DrawImage(toDraw, new Rectangle(p, new Size(i.Width, i.Height)));
I have a Graphics object that I've drawn on the screen and I need to save it to a png or bmp file. Graphics doesn't seem to support that directly, but it must be possible somehow.
What are the steps?
Here is the code:
Bitmap bitmap = new Bitmap(Convert.ToInt32(1024), Convert.ToInt32(1024), System.Drawing.Imaging.PixelFormat.Format32bppArgb);
Graphics g = Graphics.FromImage(bitmap);
// Add drawing commands here
g.Clear(Color.Green);
bitmap.Save(#"C:\Users\johndoe\test.png", ImageFormat.Png);
If your Graphics is on a form, you can use this:
private void DrawImagePointF(PaintEventArgs e)
{
... Above code goes here ...
e.Graphics.DrawImage(bitmap, 0, 0);
}
In addition, to save on a web page, you could use this:
MemoryStream memoryStream = new MemoryStream();
bitmap.Save(memoryStream, ImageFormat.Png);
var pngData = memoryStream.ToArray();
<img src="data:image/png;base64,#(Convert.ToBase64String(pngData))"/>
Graphics objects are a GDI+ drawing surface. They must have an attached device context to draw on ie either a form or an image.
Copy it to a Bitmap and then call the bitmap's Save method.
Note that if you're literally drawing to the screen (by grabbing the screen's device context), then the only way to save what you just drew to the screen is to reverse the process by drawing from the screen to a Bitmap. This is possible, but it would obviously be a lot easier to just draw directly to a Bitmap (using the same code you use to draw to the screen).
Try this, works fine for me...
private void SaveControlImage(Control ctr)
{
try
{
var imagePath = #"C:\Image.png";
Image bmp = new Bitmap(ctr.Width, ctr.Height);
var gg = Graphics.FromImage(bmp);
var rect = ctr.RectangleToScreen(ctr.ClientRectangle);
gg.CopyFromScreen(rect.Location, Point.Empty, ctr.Size);
bmp.Save(imagePath);
Process.Start(imagePath);
}
catch (Exception)
{
//
}
}
Graphics graph = CreateGraphics();
Bitmap bmpPicture = new Bitmap("filename.bmp");
graph.DrawImage(bmpPicture, width, height);
You are likely drawing either to an image or on a control. If on image use
Image.Save("myfile.png",ImageFormat.Png)
If drawing on control use Control.DrawToBitmap() and then save the returned image as above.
Thanks for the correction - I wasn't aware you can draw directly to the screen.
So on my site, a user has the ability to create an avatar. It's created by the user selecting from several images; there is a base "skin" image, and png's that overlap that image that depict hair, eyes, mouth, etc.
I cannot save the user's avatar to the project's files, so the user's avatar data is stored in the database, and the png's are overlapped on the fly and displayed to the user.
However, I'd like to have the ability for the user to download their avatar as a jpeg by going to a page.
I have this little example set up that is working correctly:
protected void Page_Load(object sender, EventArgs e)
{
//User has skin1.png, eyes3.png, and mouth8.png
Bitmap bit = new Bitmap(System.Drawing.Image.FromFile(Server.MapPath("/images/skin1.png")), 80, 106);
Response.ContentType = "image/jpeg";
bit.Save(Response.OutputStream, ImageFormat.Jpeg);
}
But, as you can see, I only have this working for a single image. I'd like to create a bitmap from the multiple png's and output a jpeg.
Can someone help?
You can just paint the images on top of each other. The transparency of PNG images are handled correctly, but as JPEG images doesn't have any transparency you would want a background color to draw on.
Remember to be careful to dispose of the Graphics and Bitmap objects properly. They are not even recommended to be used in a web application...
// create image to draw on
using (Bitmap bit = new Bitmap(80, 106)) {
// fill with background
bit.Clear(Color.White);
// images to load
string[] skins = new string[] { "skin1.png", "eyes3.png", "mouth8.png" };
// create graphics object for drawing on the bitmap
using (Graphics g = Graphics.FromImage(bit)) {
foreach (string skin in skins) {
// load image
using (Image skinImage = Image.FromFile(Server.MapPath("/images/" + skin)) {
// draw image
g.DrawImage(skinImage, 0, 0, 80, 106);
}
}
}
Response.ContentType = "image/jpeg";
bit.Save(Response.OutputStream, ImageFormat.Jpeg);
}
I think you want to look into Graphics.FromImage when you're overlapping images. I assume there aren't any special effects (simply overlapping and positioning each layer).
So you could have something like this:
Graphics gfxAvatar = Graphics.FromImage(bit) //bit is your base image
gfxAvatar.DrawImage(secondLayer, new Point(X,Y)); //Draw secondLayer at X, Y
Continue that with the other layers. (It might be faster to have a Using loop around the initial Graphics gfxAvatar section since there are multiple layers. Then once that's done, you can convert it to a JPG using your bit.Save method.
If I use TextRenderer.DrawText() using the Graphics object provided in the OnPaintBackground my text looks perfect. If I create my own Bitmap and use the Graphics object obtained from my Bitmap my text looks terrible. It looks like it is anti-aliasing the text using black, not the bitmap's background color. I can avoid this problem if I use Graphics.DrawString(), but this method has horrible kerning problems. What should I do? How can I get TextRenderer.DrawText() to anti-alias properly using the Bitmap's contents?
Looks terrible:
Bitmap bmp = new Bitmap(100, 100, PixelFormat.Format32bppArgb);
using (Graphics g = Graphics.FromImage(bmp))
{
g.Clear(Color.Red);
TextFormatFlags tf = TextFormatFlags.Left;
TextRenderer.DrawText(g, #"C:\Development\Testing\blag", font, clip, Color.White,
Color.Transparent, tf);
}
Looks good, but I want to render this onto a bitmap, NOT onto the control's surface:
protected override void OnPaintBackground(PaintEventArgs e)
{
e.Graphics.Clear(Color.Red);
TextFormatFlags tf = TextFormatFlags.Left;
TextRenderer.DrawText(e.Graphics, #"C:\Development\Testing\blag", font, clip,
Color.White, Color.Transparent, tf);
}
What is the difference?
The answer is not to use TextRenderer. TextRenderer is a wrapper for the GDI (not GDI+) implementation of text rendering, which has lots of features, but doesn't interoperate well with in-memory DCs as you have discovered.
Use Graphics.DrawString & Graphics.MeasureString, but remember to pass it StringFormat.GenericTypographic to get accurate size and positioning.
The reason TextRenderer was introduced initially was that GDI+ didn't support all the complex scripts that GDI's Uniscribe engine did. Over time however GDI+ support for complex scripts has been expanded, and these days there aren't any good reasons left to use TextRenderer (it's not even the faster of the two anymore, in fact quite the opposite it appears).
Really, though, unless you are running into serious, measurable performance issues just use Graphics.DrawString.
I believe the problem is that the clear type text rendering doesn't work if the background is transparent. A few possible solutions.
Option 1. Fill the background of your bitmap with a color.
If you do this (as Tim Robinson did above in his code example by using g.Clear(Color.Red)) clear type will do the right thing. But your bitmap won't be completely transparent which might not be acceptable. If you use Graphics.MeasureText, you can fill just the rectangle around your text, if you like.
Option 2. Set TextRenderingHint = TextRenderingHintAntiAliasGridFit
This appears to turn off clear type. The text will be rendered at a lower quality than clear type on a background, but much better than the mess clear type on no background creates.
Option 3. Fill the text rectangle with white, draw the text and then find all the non-text pixels and put them back to transparent.
using (Bitmap bmp = new Bitmap(someWidth, someHeight))
{
using (Graphics g = Graphics.FromImage(bmp))
{
// figure out where our text will go
Point textPoint = new Point(someX, someY);
Size textSize = g.MeasureString(someText, someFont).ToSize();
Rectangle textRect = new Rectangle(textPoint, textSize);
// fill that rect with white
g.FillRectangle(Brushes.White, textRect);
// draw the text
g.DrawString(someText, someFont, Brushes.Black, textPoint);
// set any pure white pixels back to transparent
for (int x = textRect.Left; x <= textRect.Left + textRect.Width; x++)
{
for (int y = textRect.Top; y <= textRect.Top + textRect.Height; y++)
{
Color c = bmp.GetPixel(x, y);
if (c.A == 255 && c.R == 255 && c.G == 255 && c.B == 255)
{
bmp.SetPixel(x, y, Color.Transparent);
}
}
}
}
}
I know, it's a horrible hack, but it appears to work.
The answer is to use a BuffersGraphicsContext. This is the same system that .NET uses internally when you set the ControlStyles.OptimizedDoubleBuffer style on a control.
See http://msdn.microsoft.com/en-us/library/b367a457.aspx for more information about double buffering in .NET.
Another possible solution: Draw the whole thing to the screen, bitmap with text on top, and then write some code to 'screen capture' that portion of the screen. Not practical in all cases but you're right, DrawString creates weird text and DrawText onto a bitmap looks horrible.
If your bitmap is not the same size as your display area, it might just be a resizing issue, where .NET scales the bitmap to the display size and you get funny looking text.
Can you test with a bitmap created at the same size as your display area?
Can you post the smallest program that suffers from this problem? I can't reproduce it like this -- the antialiasing looks fine:
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
public class Program
{
public static void Main()
{
Bitmap bmp = new Bitmap(100, 100, PixelFormat.Format32bppArgb);
using (Font font = new Font("Arial", 10, GraphicsUnit.Point))
using (Graphics g = Graphics.FromImage(bmp))
{
Rectangle clip = Rectangle.FromLTRB(0, 0, 100, 100);
g.Clear(Color.Red);
TextFormatFlags tf = TextFormatFlags.Left;
TextRenderer.DrawText(g, #"C:\Development\Testing\blag", font, clip, Color.White, Color.Transparent, tf);
}
Form form = new Form();
form.BackgroundImage = bmp;
Application.Run(form);
}
}