Why does my metafile "lose" a bitmap? - c#

Recently I found a bug while drawing stuff with metafiles. Right now I am not sure if I am doing something wrong or if there is a bug within the drawing of metafiles itself:
While drawing images on a metafile which is drawn on another metafile itself by PlayEnhMetafile I lose images far down or to the right. I guess it has something to do with screen coordinates (I run dual screen 1280*1024, so 2560*1024), 'cause the bottom lane where the images begin to vanish is around 500.
Here is some example-code I created to show you the problem more specifically. (You can just replace the Form1.cs of a freshly created Windows C# project with this code and place a button on it)
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
namespace MetaFileDrawing
{
public partial class Form1
: Form
{
[DllImport("gdi32.dll", SetLastError = true)]
private static extern bool PlayEnhMetaFile(IntPtr hdc, IntPtr hEmf, ref Rectangle rectangle);
[DllImport("gdi32.dll", SetLastError = true)]
public static extern bool DeleteObject(IntPtr hGdiObj);
public Form1()
{
InitializeComponent();
}
/// <summary>
/// Creates the sub-metafile where the actual drawing is done (and the problems occur).
/// </summary>
private Metafile GetSubMetafile()
{
Metafile metafile = null;
using(Graphics controlGraphics = this.CreateGraphics())
{
using(MemoryStream memoryStream = new MemoryStream())
{
metafile = new Metafile(memoryStream, controlGraphics.GetHdc(), EmfType.EmfOnly, string.Empty);
using(Graphics metafileGraphics = Graphics.FromImage(metafile))
{
Bitmap bitmap = new Bitmap("Fibonacci.png");
// Draw the image 3 times... if pushed to far down, it wont show up?
metafileGraphics.DrawRectangle(Pens.Yellow, new Rectangle(0, 0, 40, 1200));
metafileGraphics.DrawImage(bitmap, new Point(0, 0));
metafileGraphics.DrawImage(bitmap, new Point(10, 950));
metafileGraphics.DrawImage(bitmap, new Point(20, 1150));
}
}
controlGraphics.ReleaseHdc();
}
return metafile;
}
/// <summary>
/// Creates and draws the metafile.
/// </summary>
private void DrawMetafile()
{
using(Graphics controlGraphics = this.CreateGraphics())
{
using(MemoryStream memoryStream = new MemoryStream())
{
// EmfType.EmfOnly is a restriction defined by my project limitations
Metafile metafile = new Metafile(memoryStream, controlGraphics.GetHdc(), EmfType.EmfOnly, string.Empty);
using(Graphics metafileGraphics = Graphics.FromImage(metafile))
{
// A large red rect for orientation
metafileGraphics.DrawRectangle(Pens.Red, new Rectangle(0, 0, 4000, 4000));
// Create the sub metafile
Metafile subMetafile = GetSubMetafile();
// To check, draw the subMetafile with DrawImage (works fine, the inlined image is drawn 3 times)
metafileGraphics.DrawImage(subMetafile, new Point(10, 0));
// On the right side, draw the sub metafile using PlayEnhMetaFile (dont work correctly, only 2 images)
IntPtr hMetafile = subMetafile.GetHenhmetafile();
Rectangle rectangle1 = new Rectangle(100, 0, 170, 1230);
PlayEnhMetaFile(metafileGraphics.GetHdc(), hMetafile, ref rectangle1);
metafileGraphics.ReleaseHdc();
DeleteObject(hMetafile);
}
metafile.Save("Output.emf");
}
controlGraphics.ReleaseHdc();
}
}
private void button1_Click(object sender, EventArgs e)
{
DrawMetafile();
}
}
}
As you can see, using the PlayEnhMetaFile function causes me to lose one of the three images. Any ideas?

I ran into the same problem using C++, and found two workarounds. I could use GDI+ to attach to the metafile and play it, but the coordinates were slightly off. The more complicated alternative which I am currently using is using EnumEnhMetaFile, and manually doing the bitblt/stretchblt/stretchdibits calls, this seems to work. If you found a better solution let me know.

Related

Properly disposing of Bitmap object

I am drawing images in a C# Winforms panel with:
private void DrawPanel_Paint(object sender, PaintEventArgs e)
{
DrawOnPanel(e.Graphics);
}
The called method takes an existing image from my resources (myImage), gives it to another method which resizes the image and returns the resized image so it can be drawn.
public static void DrawOnPanel(Graphics g)
{
var _resizedImage = ResizeImage(Resources.myImage);
g.DrawImage(_resizedImage, destX, destY);
// ... several other images resized & manipulated then drawn
}
The resize image function is:
public Bitmap ResizeImage(Bitmap image)
{
int scale = 3;
var destImage= new Bitmap(image.Width * scale, image.Height * scale);
destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
using (var graphics = Graphics.FromImage(destImage))
{
graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.CompositingQuality = CompositingQuality.HighSpeed;
graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
graphics.SmoothingMode = SmoothingMode.HighSpeed;
graphics.PixelOffsetMode = PixelOffsetMode.None;
using var wrapMode = new ImageAttributes();
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
}
return destImage;
}
The program keeps calling DrawPanel.Invalidate() in its loop.
I am detecting a memory leak each time DrawPanel.Invalidate() is called. The memory consumption is rising steadily until the GC takes care of it. While this isn't a game breaking problem, I'm still wondering where and how should I dispose of my objects in the above code.
I tried using using var _resizedImage = ResizeImage(Resources.myImage); in the above DrawOnPanel method but the program returns an error System.ArgumentException: 'Parameter is not valid.'. If I remove using there is no error.
Every time that ResizeImage is called, you create a new Bitmap. This Bitmap should be Disposed as soon as you know that it is not needed anymore. It seems that you don't need the resized image after you've drawn it on the Graphics. Therefore I suggest the following change:
public static void DrawOnPanel(Graphics g)
{
using (Image_resizedImage = ResizeImage(Resources.myImage))
{
g.DrawImage(_resizedImage, destX, destY);
}
// resizedImage is Disposed
// ... several other images resized & manipulated then drawn
}
Room for improvement
Your current design will create a new Resized Bitmap every time DrawOnPanel is called. It seems to me that most of time that Resources.myImage is resized the resized Bitmap will be the same. Why don't you just remember it until the parameters that influence the resized Bitmap are changed?
If I look at the code it seems that you always create the same resized image from an original image. So you could even put all your resized images into one Dictionary for fast lookup:
// get all Image resources that you will resize and put them in a Dictionary
// Key original Bitmap, Value: resized Bitmap
private Dictionary<Bitmap, BitMap> images = this.FetchImages()
.ToDictionary(
// Parameter keySelector: key is original image
image => image,
// Parameter valueSelector: value is the resized image
imgage => ResizeImage(original));
Displaying the resized images will now be much faster: only a Lookup.
public static void DrawOnPanel(Graphics g)
{
var _resizedImage = this.Images[Resources.myImage];
g.DrawImage(_resizedImage, destX, destY);
// ... several other images resized & manipulated then drawn
}
Don't forget to Dispose the bitmaps when your Form is Closed, or at its latest when your Form is Disposed:
protected override void Dispose(bool disposing)
{
if (disposing)
{
foreach (var resizedImage in images.Values)
resizedImage.Dispose();
}
}

Screenshot comes out black

I'm trying to make a screen capture manager & recorder, both screenshot & video, although the latter is not important for this question.
The user can select a process he/she wants to be captured and set a hotkey.
The screenshots work fine for the regular desktop and some games. However, when trying to capture a screenshot from some games (e.g Splinter Cell Blacklist), if these games are full-screen, and depending on Windows 7 Aero, the contents are black, or the desktop is showing with a small bar at the top left of the screen.
Here's my initial code:
public static Bitmap GetScreen()
{
Bitmap bmpScreenCapture = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
using (Graphics g = Graphics.FromImage(bmpScreenCapture))
{
g.CopyFromScreen(Screen.PrimaryScreen.Bounds.X,
Screen.PrimaryScreen.Bounds.Y,
0, 0,
bmpScreenCapture.Size,
CopyPixelOperation.SourceCopy);
}
return bmpScreenCapture;
}
During my search I found a code project article:
http://www.codeproject.com/Articles/274461/Very-fast-screen-capture-using-DirectX-in-Csharp
And slightly modified the code
public Surface CaptureScreen()
{
Surface s = Surface.CreateOffscreenPlain(d, rc.Width, rc.Height, Format.A8R8G8B8, Pool.Scratch);
d.GetFrontBufferData(0, s);
Surface.ToFile(s, #"C:\test\img.jpg", ImageFileFormat.Jpg);
return s;
}
However, also that image comes out black.
Does anyone have any suggestions as to how this is done?
Use this code (which I found here) to disable Aero before taking any screenshots, then re-enable it.
public readonly uint DWM_EC_DISABLECOMPOSITION = 0;
public readonly uint DWM_EC_ENABLECOMPOSITION = 1;
[DllImport("dwmapi.dll", EntryPoint = "DwmEnableComposition")]
protected static extern uint Win32DwmEnableComposition(uint uCompositionAction);
public void ManageAero(bool a)
{
if (a)
Win32DwmEnableComposition(DWM_EC_ENABLECOMPOSITION );
if (!a)
Win32DwmEnableComposition(DWM_EC_DISABLECOMPOSITIO N);
}

Converting Print page Graphics to Bitmap C#

I have an application where the user can print a document of selected items in the form of an invoice. Everything works well however on the PrintPage event of the PrintDocument I want to capture the document or he graphics, turn it into a bitmap so I can save to it a .bmp for later use / viewing. (Note: There are multiple pages in this document) I have it set up like this:
PrintDocument doc = new PrintDocument();
doc.PrintPage += new PrintPageEventHandler(doc_PrintPage);
doc.Print();
Then on the PrintPage event:
private void doc_PrintPage(object sender, PrintPageEventArgs ev)
{
// Use ev.Graphics to create the document
// I create the document here
// After I have drawn all the graphics I want to get it and turn it into a bitmap and save it.
}
I have cut out all the ev.Graphics code just because it is a lot of lines. Is there a way to turn the Graphics into a Bitmap without changing any of the code that draws graphics onto the PrintDocument? Or do something similar to that, maybe copying the document and converting it into a bitmap?
You should actually draw the page into the bitmap, and then use ev.Graphics to draw that bitmap on the page.
private void doc_PrintPage(object sender, PrintPageEventArgs ev)
{
var bitmap = new Bitmap((int)graphics.ClipBounds.Width,
(int)graphics.ClipBounds.Height);
using (var g = Graphics.FromImage(bitmap))
{
// Draw all the graphics using into g (into the bitmap)
g.DrawLine(Pens.Black, 0, 0, 100, 100);
}
// And maybe some control drawing if you want...?
this.label1.DrawToBitmap(bitmap, this.label1.Bounds);
ev.Graphics.DrawImage(bitmap, 0, 0);
}
Actually, Yorye Nathan's answer on Jun 3'12 at 7:33 is correct and it was the starting point that helped me. However, I was not able to get it to work as-is, so I made some corrections to make it work in my application. The correction is to get the Printer's page size from the PrintPgeEventArgs.Graphics Device Context, and include the PrintPage Graphics as a third parameter in the new Bitmap(...) construction.
private void doc_PrintPage(object sender, PrintPageEventArgs ppea)
{
// Retrieve the physical bitmap boundaries from the PrintPage Graphics Device Context
IntPtr hdc = ppea.Graphics.GetHdc();
Int32 PhysicalWidth = GetDeviceCaps(hdc, (Int32)PHYSICALWIDTH);
Int32 PhysicalHeight = GetDeviceCaps(hdc, (Int32)PHYSICALHEIGHT);
ppea.Graphics.ReleaseHdc(hdc);
// Create a bitmap with PrintPage Graphic's size and resolution
Bitmap myBitmap = new Bitmap(PhysicalWidth, PhysicalHeight, ppea.Graphics);
// Get the new work Graphics to use to draw the bitmap
Graphics myGraphics = Graphics.FromImage(myBitmap);
// Draw everything on myGraphics to build the bitmap
// Transfer the bitmap to the PrintPage Graphics
ppea.Graphics.DrawImage(myBitmap, 0, 0);
// Cleanup
myBitmap.Dispose();
}
////////
// Win32 API GetDeviceCaps() function needed to get the DC Physical Width and Height
const int PHYSICALWIDTH = 110; // Physical Width in device units
const int PHYSICALHEIGHT = 111; // Physical Height in device units
// This function returns the device capability value specified
// by the requested index value.
[DllImport("GDI32.DLL", CharSet = CharSet.Auto, SetLastError = true)]
public static extern Int32 GetDeviceCaps(IntPtr hdc, Int32 nIndex);
Thank you again Yorye Nathan for providing the original answer.
//AJ

Merging graphics objects does not render text correctly

I'm trying to solve a problem with a custom control ported from a VCL C++ application. The idea is that individual parts of the control are drawn first on a new Graphics object and then merged with the Graphics object from the control's paint method.
I've created a simplified example:
using System.Drawing;
using System.Windows.Forms;
namespace Test
{
public class Form1 : Form
{
private PictureBox pictureBox;
public Form1()
{
pictureBox = new PictureBox();
pictureBox.Dock = DockStyle.Fill;
pictureBox.Paint += new PaintEventHandler(pictureBox_Paint);
ClientSize = new Size(100, 50);
Controls.Add(pictureBox);
}
private void pictureBox_Paint(object sender, PaintEventArgs e)
{
SolidBrush blackBrush = new SolidBrush(Color.Black);
Bitmap bitmap = new Bitmap(pictureBox.Width, pictureBox.Height);
Graphics graphics = Graphics.FromImage(bitmap);
Font font = new Font(pictureBox.Font, FontStyle.Regular);
graphics.DrawString("simple test", font, Brushes.Black, 0, 0);
e.Graphics.DrawImage(bitmap, 0, 0);
}
}
}
Running the above code results in the text being drawn too thick:
When i modify the code to draw the text directly to the Graphics object of the PictureBox i get the correct result:
This problem only occurs with text rendering. Lines and other shapes are rendered correctly. Any ideas how to solve this?
Thanks in advance.
This happens because you forgot to initialize the bitmap pixels. By default they are Color.Transparent, which is black with an alpha of 0. This goes wrong when you draw anti-aliased text into the bitmap, the aliasing algorithm will draw pixels that blend from the foreground (black) to the background (also black). Blobby letters that are not anti-aliased is the result.
This is not a problem in the 2nd screenshot because the background pixels were drawn to battleship gray by the default Form.OnPaintBackground() method. Simply add the Graphics.Clear() method to fix your problem:
using (var bitmap = new Bitmap(pictureBox.Width, pictureBox.Height)) {
using (var graphics = Graphics.FromImage(bitmap)) {
graphics.Clear(this.BackColor); // <== NOTE: added
graphics.DrawString("simple test", pictureBox1.Font, Brushes.Black, 0, 0);
}
e.Graphics.DrawImage(bitmap, 0, 0);
}
With using statements added to prevent your program from crashing when the garbage collector doesn't run often enough.

How to draw directly on the Windows desktop, C#?

This question has been asked for other languages, and even for those other languages, I have found their answers lacking in how to exactly do it, cleanly (no messed up screen repaints, etc..).
Is it possible to draw onto the Windows Desktop from C#? I am looking for an example if possible.
Try the following:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
class Program {
[DllImport("User32.dll")]
static extern IntPtr GetDC(IntPtr hwnd);
[DllImport("User32.dll")]
static extern int ReleaseDC(IntPtr hwnd, IntPtr dc);
static void Main(string[] args) {
IntPtr desktop = GetDC(IntPtr.Zero);
using (Graphics g = Graphics.FromHdc(desktop)) {
g.FillRectangle(Brushes.Red, 0, 0, 100, 100);
}
ReleaseDC(IntPtr.Zero, desktop);
}
}
You can try:
Graphics.FromHwnd(IntPtr.Zero)
You can see a real-world code example within https://uiautomationverify.codeplex.com/SourceControl/latest#UIAVerify/Tools/visualuiverify/utils/screenrectangle.cs
This draws a rectangle that will appear on the screen until the user chooses to remove it at an arbitrary position (wont be repainted over). It uses a windows form thats hidden/ appears as a popup.
This is the code behind the UIAVerify.exe tool in the current Windows SDK.
If you want to use the above, copy the following files into your project:
utils\screenboundingrectangle.cs
utils\screenrectangle.cs
win32\*
Might need to update namespaces accordingly + add references to System.Drawing + System.Windows.Forms
Then you can draw a rectangle with the following code:
namespace Something
{
public class Highlighter
{
ScreenBoundingRectangle _rectangle = new ScreenBoundingRectangle();
public void DrawRectangle(Rectangle rect)
{
_rectangle.Color = System.Drawing.Color.Red;
_rectangle.Opacity = 0.8;
_rectangle.Location = rect;
this._rectangle.Visible = true;
}
}
}
and
var rect = Rectangle.FromLTRB(100, 100, 100, 100);
var hi = new Highlighter();
hi.DrawRectangle(rect);

Categories