Convert Drawing.Bitmap to Imaging.Metafile - Pixel Perfect

I am trying to convert Drawing.Bitmap to Imaging.Metafile in order to insert a metafile into Forms.RichTextBox . (For reference, inserting a bitmap into a metafile is the recommended practice for placing a bitmap in a richtext (see RTF specification), version 1.9.1, page 149.) Unfortunately, this is also the only way to embed an image in Forms.RichTextBox, so as I cannot get any device-dependent or device-independent methods for inserting a bitmap into a RichTextBox to work.)

Later I have to get the pixel data from the metafile. I require that the pixels of the metafile exactly match the pixels of the bitmap. However, when I do the conversion, the pixels are slightly changed. (Perhaps due to GDI Image Color Management (ICM)?)

Here is my technique:

 public static Imaging.Metafile BitmapToMetafileViaGraphicsDrawImage(Forms.RichTextBox rtfBox, Drawing.Bitmap bitmap) { Imaging.Metafile metafile; using (IO.MemoryStream stream = new IO.MemoryStream()) using (Drawing.Graphics rtfBoxGraphics = rtfBox.CreateGraphics()) { IntPtr pDeviceContext = rtfBoxGraphics.GetHdc(); metafile = new Imaging.Metafile(stream, pDeviceContext); using (Drawing.Graphics imageGraphics = Drawing.Graphics.FromImage(metafile)) { //imageGraphics.DrawImage(bitmap, new Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height)); imageGraphics.DrawImageUnscaled(bitmap, new Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height)); } rtfBoxGraphics.ReleaseHdc(pDeviceContext); } return metafile; } 

In this case, I access the pixels of the metafile this way:

 metafile.Save(stream, Imaging.ImageFormat.Png); Bitmap bitmap = new Bitmap(stream, false); bitmap.GetPixel(x, y); 

I also tried using the BitBlt technique without success.

BitBlt Technology:

 [System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")] static extern int BitBlt( IntPtr hdcDest, // handle to destination DC (device context) int nXDest, // x-coord of destination upper-left corner int nYDest, // y-coord of destination upper-left corner int nWidth, // width of destination rectangle int nHeight, // height of destination rectangle IntPtr hdcSrc, // handle to source DC int nXSrc, // x-coordinate of source upper-left corner int nYSrc, // y-coordinate of source upper-left corner System.Int32 dwRop // raster operation code ); public static Imaging.Metafile BitmapToMetafileViaBitBlt(Forms.RichTextBox rtfBox, Drawing.Bitmap bitmap) { const int SrcCopy = 0xcc0020; Graphics bitmapGraphics = Graphics.FromImage(bitmap); IntPtr pBitmapDeviceContext = bitmapGraphics.GetHdc(); RectangleF rect = new RectangleF(new PointF(0, 0), new SizeF(bitmap.Width, bitmap.Height)); Imaging.Metafile metafile = new Imaging.Metafile(pBitmapDeviceContext, rect); Graphics metafileGraphics = Graphics.FromImage(metafile); IntPtr metafileDeviceContext = metafileGraphics.GetHdc(); BitBlt(pBitmapDeviceContext, 0, 0, bitmap.Width, bitmap.Height, metafileDeviceContext, 0, 0, SrcCopy); return metafile; } 

I'm not even sure if this method copies pixel data correctly. This method does not work when I try to access the data in the metafile later:

 IntPtr h = metafile.GetHenhmetafile(); // ArgumentException "Parameter is not valid." byte[] data; uint size = GetEnhMetaFileBits(h, 0, out data); data = new byte[size]; GetEnhMetaFileBits(h, size, out data); stream = new IO.MemoryStream(data); 

How to convert a bitmap to a metafile without changing pixels, and then get the pixel data again? Thanks!


Bitmap setup

This is how I try to set the raster resolution so that the metafile resolution matches:

 Drawing.Bitmap bitmap = new Drawing.Bitmap(width, height, Imaging.PixelFormat.Format32bppArgb); // Use 32-bit pixels so that each component (ARGB) matches up with a byte // Try setting the resolution to see if that helps with conversion to/from metafiles Drawing.Graphics rtfGraphics = rtfBox.CreateGraphics(); bitmap.SetResolution(rtfGraphics.DpiX, rtfGraphics.DpiY); // Set the pixel data ... // Return the bitmap return bitmap; 

RtfBox is the same as in BitmapToMetafileViaGraphicsDrawImage.

+4
source share
4 answers

You can create and process a metafile manually (for example, list its entries), in which case you can actually insert a bitmap with its exact data into the metafile stream. However, it is not as simple as it might seem.

When playing the GDI metafile, all operations are internally converted to GDI +, which is actually a completely different API and that handles a lot of things differently. Unfortunately, the built-in way to do this as soon as the metafile has a certain complexity or the output needs to be converted is to allow GDI to render the metafile on a low-resolution bitmap and then draw it, which will never give you the β€œSearch” results.

For the project (closed source - so no need to ask about the source;)) I had to implement the full GDI-to-GDI + playback engine, similar to the EMFExplorer functions, but using only managed code and re-aligning one character for accurate text output. This is just to say that this can be done if you want to spend some time processing all the metafiles that you need yourself (this should be only a small subset, but still).


Edit to answer the questions asked in the comments:

A metafile is nothing more than a series of drawing instructions, mostly recording completed GDI (+) operations. Thus, you should be able to create a metafile as binary data, consisting mainly of only the header and bitmap, and in this case (since you do not go through drwing operations, but always process the bitmap at the binary level), you can get Accurate data from the metafile that you entered into it.

Fortunately, over the years, Microsoft has documented and made its file formats and protocols available to the public, so accurate documentation on the WMF / EMF file is available. The metafile format is explained here by MS: http://msdn.microsoft.com/en-us/library/cc230514(PROT.10).aspx

The structure is described here: http://msdn.microsoft.com/en-us/library/cc230516(v=PROT.10).aspx

Raster image records are described here: http://msdn.microsoft.com/en-us/library/cc231160(v=PROT.10).aspx

Using this information, you can collect binary data (possibly using BinaryWriter ), and then upload / list the file to return the data (for example, find the raster image in the metafile again and extract the necessary data from it).

+5
source

There are several possible answers, the two most likely culprits are aliases and resolution. I suppose this is a resolution, because when you save a bitmap without specifying a resolution, I believe that it is set to 120 DPI automatically, which may affect anti-aliasing.

Before anyone notes that resolution does not matter, and that DPI is used only for printing and blah blah, please do not mind the desire to make this comment. I am glad to have a debate with you on your own question.

Set the resolution of the bitmap you are converting back to the resolution of your metafile.

+2
source

It's just a shot in the dark, but look at some of the ImageFlags flag values and see if they can be included in the generation or rendering of the metafile.

0
source

This is a fun way to solve it, but if you just need to put it in a RichTextBox - you can use the clipboard:

  private void button1_Click(object sender, EventArgs e) { System.Drawing.Bitmap bmp = new Bitmap("g.bmp"); Clipboard.SetData(DataFormats.Bitmap, bmp); richTextBox1.Paste(); } 

but I'm not sure about the return path (reading a bitmap from RTF text)

0
source

All Articles