I'm trying to convert a Drawing.Bitmap to an Imaging.Metafile for the purposes of inserting the metafile into a Forms.RichTextBox. (For reference, embedding a bitmap in a metafile is the recommended practice for putting a bitmap into richtext (see Rich Text Format (RTF) Specification Version 1.9.1, p. 149.) Unfortunately it also appears to be the only way to embed an image into a Forms.RichTextBox, as I can't get any device-dependent or device-independent methods of inserting an bitmap into a RichTextBox to work.)
Later, I must retrieve the pixel-data from the metafile. I require that the pixels of the metafile exactly match those of the bitmap. When I perform the conversion, however, the pixels are slightly altered. (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 in this way:
metafile.Save(stream, Imaging.ImageFormat.Png);
Bitmap bitmap = new Bitmap(stream, false);
bitmap.GetPixel(x, y);
I have also tried to use a BitBlt technique with no success.
BitBlt technique:
[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 this technique is correctly copying the pixel-data. This technique fails 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 do I convert a bitmap into a metafile without altering the pixels, and then retrieve the pixel-data again later? Thank you!
Setting Bitmap Resolution
This is how I try to set the bitmap resolution so that the metafile's 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;
The rtfBox is the same one sent to BitmapToMetafileViaGraphicsDrawImage.
You can create and process the metafile manually (e.g. enumerate its records), in which case you can actually insert a bitmap with its exact data into the metafile stream. However, this is not as simple as it may sound.
When playing a GDI metafile, all operations are internally converted into GDI+, which is actually a completely different API and which handles a lot of things differently. Unfortunately, the built-in way to do it as soon as the metafile has some complexity or the output needs to be transformed is to let GDI render the metafile on a low-res bitmap and then draw this, which never gives you the results you're looking for.
For a project (closed-source - so don't bother asking for the source ;) ) I had to implement a complete GDI-to-GDI+ playback engine similar to the functionality of EMFExplorer, but using managed code only and with single character re-aligning for exact textual output. That is only to say that it can be done, if you're willing to invest some time dealing with all the metafile records you need youself (which should be a small subset only, but still).
Edit to address the questions asked in the comments:
The metafile is nothing but a series of drawing instructions, basically a recording of GDI(+) operations performed. So you should be able to construct a metafile as binary data consisting basically only of the header and the bitmap, and in that case (since you don't pass through drwing operations but always handle the bitmap at a binary level) you will be able to retrieve the exact same data from the metafile you wrote into it.
Fortunately, since a few years Microsoft has been documenting and making their file formats and protocols available to the public, so that a precise documentation of the WMF/EMF file format is available. The metafile format is explained by MS here:
http://msdn.microsoft.com/en-us/library/cc230514(PROT.10).aspx
It structure is outlined here:
http://msdn.microsoft.com/en-us/library/cc230516(v=PROT.10).aspx
The bitmap records are described here:
http://msdn.microsoft.com/en-us/library/cc231160(v=PROT.10).aspx
Using this information, you should be able to put together binary data (maybe using a BinaryWriter) and then also load/enumerate the file to get the data back (e.g. finding the bitmap again in the metafile and extract the required data from it).
There are a number of possible answers here, the two most likely culprits being aliasing and resolution. My guess is resolution because when you save a Bitmap without specifying a resolution, I believe it is set to 120 DPI automatically, which could affect aliasing.
Before someone remarks that resolution doesn't matter and that DPI is only used for printing and blah blah, please resist the urge to make that comment. I'm happy to have that debate with you on your own question.
Set the resolution of the bitmap you're converting back to with your metafile's resolution.
This is just a shot in the dark, but have a look at some of the ImageFlags flag values and see if they can be incorporated into the generation or rendering of the metafile.
it's a funny way to solve it, but if all you need is to put it inside 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 opposite way (reading the bitmap from RTF text)
Related
TL;DR mode:
Is there a way to get a BitmapSource from a DrawingVisual without using RenderTargetBitmap, whilst still having the 'crop' utility a RenderTargetBitmap affords you.
Full Question:
My issue is an awkward one, I'm using a number of image editing processes to effectively crop, resize, rotate and perform all that usual stuff expected of even basic image editing in an automated manner, and everything is working just fine except for one incredibly annoying caveat: Images profiled as CMYK can't be processed then saved again as CMYK without doing a little dance with RenderTargetBitmap (which breaks and refuses to create an instance of itself with any PixelFormat other than "PixelFormats.Default" or "PixelFormats.Pbgra32") and the FormatConvertedBitmap class to even get the image to go back to being a CMYK
So where you might think, 'oh this is easy, just...'
var newCanvas = new RenderTargetBitmap(
width, height, // Dimensions
96, 96, // DPI
PixelFormats.Cmyk32); // <---- Like so
The class just breaks and gives the error:
System.ArgumentException: ''Cmyk32' PixelFormat is not supported for this operation. Arg_ParamName_Name'
So instead I'm having to do a little trick with the full-code involved being as such:
string myUri = "" // Some CMYK Image
int newWidth = 600; // Just for this example, real values come in elsewhere
int newHeight = 400; // As above
// Load the image
BitmapDecoder decoder = BitmapDecoder.Create(new Uri(FullURL), BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
//Define the new size
Rect rect = new Rect(0, 0, newWidth, newHeight) // Set new size of img.
DrawingGroup group = new DrawingGroup(); // Create a group for img layers
group.Children.Add(new ImageDrawing(image, decoder.Frames[0])); // Add loaded image
// (sometimes more children will be added, so it must be a DrawingGroup)
// Prepare the group to be rendered.
var drawingVisual = new DrawingVisual();
using (var drawing = drawingVisual.RenderOpen()) {
// Additional things such as shapes may be added here later
drawing.DrawDrawing(group);
}
// Create what is effectively the canvas the image goes into
var resizedImage = new RenderTargetBitmap(
newWidth, newHeight,
96, 96,
PixelFormats.Default); // <---- I'm forced to do this
// And then render it
resizedImage.Render(drawingVisual);
// THEN after that, convert it back too CMYK32
var fcb = new FormatConvertedBitmap(resizedImage, PixelFormats.Cmyk32, null, 0);
This "FormatConvertedBitmap fcb" is then saved as a TIFF, and a CMYK TIFF at that. Success, right?
Well no, during this little dance between "PixelFormats.Default" and "PixelFormats.Cmyk32" done with RenderTargetBitmap -> FormatConvertedBitmap, it seems the Default conversion is enough to upset all of the colours inside the file, not a lot, but enough that it's no longer the true image that was input at the start.
For example, what might have been a block of CMYK(0,0,0,100) pixels, so pure jet-black, might now be CMYK(2,3,2,96). So it's dropped the blac(K) down 4%, and pushed the (C)yan, (M)agenta, and (Y)ellow up 2-3%, creating a foggier image that looks washed-out.
My working theory, although i have no way yet of proving it 100% true, is that this problem is being caused by the RenderTargetBitmap effectively making it an RGB image first.
Is there any way at all to truly preserve CMYK images whilst editing them in the layered and 'croppable' way I showed, or some way to effectively save the image without needing to use RenderTargetBitmap? It seems insane that Microsoft even has this class that has the option to set PixelFormat, except the only format that works is what amounts to "Default". Why even give the option in the function? (I realize I'm ranting now).
Does anyone know of a way to solve this weird issue?
I'm using Microsoft Surface 2.0 SDK with SUR40 PixelSense compatible computer. I need to capture image from it's touch and save it as .bmp. Since Surface SDK comes with RawImageVisualizer example, which displays picture from touch on the screen, I've tried to modify program for writing picture to HDD. The problem is, I get ArgumentException: Parameter is invalid during building Image from byte array captured from touch.
This is how I retrieve byte array with image data from FrameReceivedEventArgs on FrameReceived event:
event.UpdateRawImage(
ImageType.Normalized,
normalizedImage,
0, 0,
InteractiveSurface.PrimarySurfaceDevice.WorkingAreaWidth,
InteractiveSurface.PrimarySurfaceDevice.WorkingAreaHeight);
And that's how I try to write bytes as .bmp to disk:
System.Drawing.Image img;
using (System.Drawing.Image raw = System.Drawing.Image.FromStream(new MemoryStream(normalizedImage)))
{
img = raw.Clone() as System.Drawing.Bitmap;
}
img.Save("C:/img.bmp", System.Drawing.Imaging.ImageFormat.Bmp);
So I get the exception trying to create Image from stream. Nevertheless this byte array works totally fine with Texture2D and SpriteBatch which displays it. How can I fix ArgumentException?
i've just realized, that UpdateRawImage does not return a byte representation of PNG file, but only an array of pixels. So, to build an image from it, one have to write all other parts of file structure to the array: header and color table (if needed). In many cases this can be simply done with one of System.Drawing.Bitmap constructors:
public Bitmap(
int width,
int height,
int stride,
PixelFormat format,
IntPtr scan0
)
But I was not so lucky, because UpdateRawImage returns 8bpp grayscale pixels, and PixelFormat enum doesn't support them (the most close is Format16bppGrayScale, but it uses 2 bytes for pixel, not one). So, in this particular situation, there are two obvious solutions. The first is making a new array of pixels, which meets one of PixelFormat standards (that was my choice, because I need 24-bit RGB image, despite it's actually black-white with only 256 shades). The second is writing BMP headers manually (and it's not very difficult due to open specs).
i simply want to load a .BMP file and get the Bitmap object in 24bit RGB format (or 32bit in RGB format).
All methods I tried return a Bitmap/Image object with PixelFormat = Format32bppArgb. Even if of course BMPs don't have alpha.
new Bitmap(System.Drawing.Image.FromFile(fileName, true));
new Bitmap(fileName);
I currently solve the problem by copying the first object to another in memory bitmap at 24bit RBG.
Is there a single method to do it?
Thanks
As far as I can tell it is not possible to specify the PixelFormat for loading bitmaps using the classes in System.Drawing. To convert the bitmap check this question: Converting Bitmap PixelFormats in C#
This is currently the top answer there:
Bitmap orig = new Bitmap(#"c:\temp\24bpp.bmp");
Bitmap clone = new Bitmap(orig.Width, orig.Height, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
using (Graphics gr = Graphics.FromImage(clone)) {
gr.DrawImage(orig, new Rectangle(0, 0, clone.Width, clone.Height));
}
// Dispose orig as necessary..
One option would be to put that in a function which takes a filename and PixelFormat. That hides the ugly code, which has its ups and downs since it also hides the fact it is probably not that efficient.
According to the linked SO question using the Clone method not always works.
You can clone it to a RGB format one:
var bitmapInRgbFormat = loadedBitmap.Clone(new Rectangle(0, 0, loadedBitmap.Width, loadedBitmap.Height), PixelFormat.Format32bppRgb)
Exactly what do you mean by copying the first object to another?
To achieve what you want to do, meaning loading an image and converting it to 24 bit, just get the graphics context of a second bitmap that matches the size and that is RGB, and paint the original bitmap onto this one.
If I have a .Net Bitmap, I can create from it a GDI bitmap by calling the Bitmap's GetHbitmap() method.
Bitmap bmp = new Bitmap(100, 100);
IntPtr gdiBmp = bmp.GetHbitmap();
This works fine, but every time you call GetHbitmap, Windows has to allocate the new memory that the returned IntPtr references.
What I'd like to do - if possible - is write a function (I know PInvoke will be necessary here) that also generates a GDI bitmap copy of a Bitmap, but that overwrites an existing chunk of memory already referenced by an IntPtr returned from GetHbitmap instead of allocating new memory. So it would look something like this (if it were an extension method of Bitmap):
// desired method signature:
void OverwriteHbitmap(IntPtr gdi)
{
}
// ex:
Bitmap bmp1 = new Bitmap(100, 100);
IntPtr gdi1 = bmp1.GetHbitmap();
Bitmap bmp2 = new Bitmap(100, 100);
bmp2.OverwriteHbitmap(gdi1); // gdi1 is still pointing to the same block
// of memory, which now contains the pixel data from bmp2
How can I do this? I assume I'll need to know the structure of a GDI bitmap, and probably I can use LockBits and BitmapData for this, but I'm not sure exactly how.
Clues for the bounty hunters:
Bitmap has a method LockBits which locks the bitmap in memory and returns a BitmapData object. The BitmapData object has a Scan0 property which is an IntPtr pointing to the start of the locked bitmap's pixel data (i.e it doesn't point to the bitmap's header a.k.a. the start of the bitmap itself).
I'm pretty sure the solution looks something like this:
Bitmap bmp1 = new Bitmap(100, 100);
IntPtr gdi1 = bmp1.GetHbitmap(); // now we have a pointer to a
// 100x100 GDI bitmap
Bitmap bmp2 = new Bitmap(100, 100);
BitmapData data = bmp2.LockBits();
IntPtr gdi1Data = gdi1 + 68; // magic number = whatever the size
// of a GDI bitmap header is
CopyMemory(data.Scan0, gdi1Data, 40000);
The solution does not have to be generic - it only needs to work for bitmaps with pixel format Format32bppArgb (the default GDI+ format).
I think you answered your own question: use LockBits.
See
How to: Use LockBits
and
Bob Powell - Locking Bits.
I used this method to rapidly draw fractals in a .NET (1.1!) program that I was writing for fun. Of all the methods that I experimented with, this was by far the fastest.
Create a Graphics from the IntPtr with Graphics.FromHdc and use Graphics.DrawImage to paste the Bitmap into it.
You should not assume that a HANDLE or HBITMAP is a pointer to anything. It can be implemented as an index into a handle table, key of a hash table, etc.
However, if you call the GDI GetObject function, the resultant BITMAP structure contains a pointer to the real bits.
Not sure if it would be as fast (I think in the case of the same pixel format for source and destination it will), but SetDIBits should do exactly what you want (replace the data in an existing HBITMAP).
I am messing around with Conway's Game of Life - http://en.wikipedia.org/wiki/Conway's_Game_of_Life
I started out coding algorithmns for winforms and now want to port my work onto windows mobile 6.1 (compact framework). I came across an article by Jon Skeet where he compared several different algorithmns for calculating next generations in the game. He used an array of bytes to store a cells state (alive or dead) and then he would copy this array to an 8bpp bitmap. For each new generation, he works out the state of each byte, then copies the array to a bitmap, then draws that bitmap to a picturebox.
void CreateInitialImage()
{
bitmap = new Bitmap(Width, Height, PixelFormat.Format8bppIndexed);
ColorPalette palette = bitmap.Palette;
palette.Entries[0] = Color.Black;
palette.Entries[1] = Color.White;
bitmap.Palette = palette;
}
public Image Render()
{
Rectangle rect = new Rectangle(0, 0, Width, Height);
BitmapData bmpData = bitmap.LockBits(rect, ImageLockMode.ReadWrite, bitmap.PixelFormat);
Marshal.Copy(Data, 0, bmpData.Scan0, Data.Length);
bitmap.UnlockBits(bmpData);
return bitmap;
}
His code above is beautifully simple and very fast to render. Jon is using Windows Forms but now I want to port my own version of this onto Windows Mobile 6.1 (Compact Framework) but . . . .there is no way to format a bitmap to 8bpp in the cf.
Can anyone suggest a way of rendering an array of bytes to a drawable image in the CF. This array is created in code on the fly (it is NOT loaded from an image file on disk). I basically need to store an array of cells represented by bytes, they are either alive or dead and I then need to draw that array as an image. The game is particularly slow on the CF so I need to implement clever optimised algoritmns but also need to render as fast as possible and the above solution would be pretty dam perfect if only it was available on the compact framework.
Many thanks for any help
Any suggestions?
You could have a look at GDI+ for CF. It's basically a wrapper for most of the GDI implemented in WinCE. Here's a link to the source code and a writeup: http://community.opennetcf.com/articles/cf/archive/2007/10/31/using-gdi-on-windows-mobile.aspx
I think ImagingFactoryClass.CreateBitmapFromBuffer() looks like a good place to start.
Ok, how about this:
use the Bitmap.Save() method to save to a MemoryStream instead of a file;
when you save to the MemoryStream, you get to name the ImageFormat as "GIF" (this is equivalent to 8bpp in .Net, according to this: http://support.microsoft.com/kb/318343)
use MemoryStream.Write() to change whatever data you want in the image, or copy the data using MemoryStream.ToArray() if that jives better.
After you change the MemoryStream, you'll probably have to copy it back into the Bitmap, or make a new Bitmap. If you do make a new Bitmap, be sure to Dispose() the old one, to avoid memory leaks.
Hi Rocjoe and thanks again for the help, I have tried the following
Image bmp = new Bitmap(10, 10);
byte[] array = ImageToByteArray(bmp);
public byte[] ImageToByteArray(Image img)
{
MemoryStream ms = new MemoryStream();
img.Save(ms, System.Drawing.Imaging.ImageFormat.Gif );
return ms.ToArray();
}
The array coming back has over 870 bytes in it, It seems to hold all sorts of header info, padding and what have you. so again it does not work...