简体   繁体   中英

How to get the bitmap/image from a Graphics object in C#?

I want to know what the intermediate state of the buffer where the Graphics object is drawing some stuff. How do I get hold of the bitmap or the image that it is drawing on?

I'm not really sure if I understand what you're asking for, as your question is very unclear.

If you want to know how to save the contents of a Graphics object to a bitmap, then the answer is that there's no direct approach for doing so. Drawing on a Graphics object is a one-way operation.

The better option is to create a new Bitmap object, obtain a Graphics object for that bitmap, and draw directly onto it. The following code is an example of how you might do that:

// Create a new bitmap object
using (Bitmap bmp = new Bitmap(200, 300))
{
    // Obtain a Graphics object from that bitmap
    using (Graphics g = Graphics.FromImage(bmp))
    {
        // Draw onto the bitmap here
        // ....
        g.DrawRectangle(Pens.Red, 10, 10, 50, 50);
    }

    // Save the bitmap to a file on disk, or do whatever else with it
    // ...
    bmp.Save("C:\\MyImage.bmp");
}

This code working for me where I am converting image >> bitmap >> byte >> Base64 String.

System.Drawing.Image originalImage = //your image

//Create empty bitmap image of original size

Bitmap tempBmp = new Bitmap(originalImage.Width, originalImage.Height);

Graphics g = Graphics.FromImage(tempBmp);

//draw the original image on tempBmp

g.DrawImage(originalImage, 0, 0, originalImage.Width, originalImage.Height);

//dispose originalImage and Graphics so the file is now free

g.Dispose();

originalImage.Dispose();

using (MemoryStream ms = new MemoryStream())
{
    // Convert Image to byte[]
    tempBmp.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
    //dpgraphic.image.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
    byte[] imageBytes = ms.ToArray();

    // Convert byte[] to Base64 String
    string strImage = Convert.ToBase64String(imageBytes);
    sb.AppendFormat(strImage);
}

Since nobody answered the actual question after 9 years...

// System.Windows.Forms.Internal.IntUnsafeNativeMethods
[DllImport("gdi32.dll", CharSet = CharSet.Auto, EntryPoint = "GetCurrentObject", ExactSpelling = true, SetLastError = true)]
public static extern IntPtr IntGetCurrentObject(HandleRef hDC, int uObjectType);


IntPtr hdc = graphics.GetHdc();
// This is a HBITMAP, which is the actual buffer that is being drawn to by hdc.
IntPtr hbitmap = IntGetCurrentObject(new HandleRef(null, hdc), 7 /*OBJ_BITMAP*/);
// You can create a Gdiplus::Bitmap object from this, but it will copy all image data.
//Bitmap bitmap = Image.FromHbitmap(hbitmap);
// To manipulate the actual bitmap pixel data directly, see below.

// Put these in finally:
//bitmap.Dispose();
// NOTE: You cannot use the graphics object before ReleaseHdc is called.
graphics.ReleaseHdc(hdc);

To get to the actual bitmap bits , you have to know something about GDI bitmaps first. There are so-called device-dependent bitmaps (DDB, or often simply just "Bitmap" in the API), and device-independent bitmaps (DIB). A full explanation of the difference would be out of scope for this answer.

If you use this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true); , then the graphics object in OnPaint will use a DIB, otherwise it will use a DDB.

If your HBITMAP is a DDB , you cannot read/write the pixel data directly (even though it's technically possible, Windows exposes no way to do it). You have to use GetDIBits to copy them to a device independent buffer, using a particular format, and then SetDIBits to copy them back.

If your HBITMAP is a DIB , then you can get the actual pixel bits (as a pointer), and read/write them directly in memory using GetObject (not to be confused with GetCurrentObject ):

[DllImport("gdi32.dll")]
static extern unsafe int GetObject(IntPtr hobj, int cb, void* data);

[StructLayout(LayoutKind.Sequential)]
unsafe struct BITMAP
{
    int        bmType;
    int        bmWidth;
    int        bmHeight;
    int        bmWidthBytes;
    ushort     bmPlanes;
    ushort     bmBitsPixel;
    void*      bmBits;
}

BITMAP BitmapDesc = new BITMAP();
GetObject(hbitmap, sizeof(BITMAP), &BitmapDesc);

BITMAP.bmBits will be null if it was a DDB, and it will be a valid memory address if it's a DIB. If you just want to copy this data, you can directly use bmBits ; the total length is bmHeight * bmWidthBytes .

If you actually want to manipulate pixel data in memory, you need to know the exact pixel format of the DIB in order to manipulate it correctly. There are many possibilities what the pixel format can be (number of bits per pixel 1/4/8/16/24/32, RGB vs BGR, palettes, etc). It's a lot of work if you really want to support everything .

To do that, know that when being given an HBITMAP , the GetObject function will either accept a BITMAP struct (as shown in the code example above), or a DIBSECTION struct. Note that DIBSECTION starts with a BITMAP , this makes the two structs compatible. Iff the HBITMAP is a DIB, then GetObject will fill in a valid (non-null) bmBits pointer, and it will also fill in the DIBSECTION 's BITMAPINFOHEADER struct, which you can then use to inspect the pixel format of the DIB. Examining the BITMAPINFOHEADER will be painful.

Not 100% sure what you want here, but if you want to use the graphics class to draw, and then save to file, you have to obtain the Graphics object from a Bitmap file, and then save the bitmap after you are done. You can do that like this:

  Bitmap bitmap = new Bitmap(bWidth, bHeight);
  Graphics g = Graphics.FromImage(bitmap);
  //do all your operations on g here.
  bitmap.Save(fileName, imageFormat);

You can get his hdc that's a pointer to the surface buffer, and eventually copy his content to another hdc with bitblt function. This way you can create a copy of the drawing surface on a bitmap.

enum TernaryRasterOperations : uint
{
    /// <summary>dest = source</summary>
    SRCCOPY = 0x00CC0020,
    /// <summary>dest = source OR dest</summary>
    SRCPAINT = 0x00EE0086,
    /// <summary>dest = source AND dest</summary>
    SRCAND = 0x008800C6,
    /// <summary>dest = source XOR dest</summary>
    SRCINVERT = 0x00660046,
    /// <summary>dest = source AND (NOT dest)</summary>
    SRCERASE = 0x00440328,
    /// <summary>dest = (NOT source)</summary>
    NOTSRCCOPY = 0x00330008,
    /// <summary>dest = (NOT src) AND (NOT dest)</summary>
    NOTSRCERASE = 0x001100A6,
    /// <summary>dest = (source AND pattern)</summary>
    MERGECOPY = 0x00C000CA,
    /// <summary>dest = (NOT source) OR dest</summary>
    MERGEPAINT = 0x00BB0226,
    /// <summary>dest = pattern</summary>
    PATCOPY = 0x00F00021,
    /// <summary>dest = DPSnoo</summary>
    PATPAINT = 0x00FB0A09,
    /// <summary>dest = pattern XOR dest</summary>
    PATINVERT = 0x005A0049,
    /// <summary>dest = (NOT dest)</summary>
    DSTINVERT = 0x00550009,
    /// <summary>dest = BLACK</summary>
    BLACKNESS = 0x00000042,
    /// <summary>dest = WHITE</summary>
    WHITENESS = 0x00FF0062,
    /// <summary>
    /// Capture window as seen on screen.  This includes layered windows 
    /// such as WPF windows with AllowsTransparency="true"
    /// </summary>
    CAPTUREBLT = 0x40000000
}

[DllImport("gdi32.dll", EntryPoint = "BitBlt", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool BitBlt([In] IntPtr hdc, int nXDest, int nYDest, int nWidth, int nHeight, [In] IntPtr hdcSrc, int nXSrc, int nYSrc, TernaryRasterOperations dwRop);

public static Bitmap CopyGraphicsContent(Graphics source, Rectangle rect)
{
    Bitmap bmp = new Bitmap(rect.Width, rect.Height);

    using (Graphics dest = Graphics.FromImage(bmp))
    {
        IntPtr hdcSource = source.GetHdc();
        IntPtr hdcDest = dest.GetHdc();

        BitBlt(hdcDest, 0, 0, rect.Width, rect.Height, hdcSource, rect.X, rect.Y, TernaryRasterOperations.SRCCOPY);

        source.ReleaseHdc(hdcSource);
        dest.ReleaseHdc(hdcDest);
    }

    return bmp;
}

Have you taken a look at this MSDN article ? It describes the Bitmap class, which is an object used to work with images defined by pixel data. System.Drawing.Image provides additional functionality for it. HTH

@dialer Has the best answer in this thread so far. As an additional example, here is how to get bits from Graphics or any HWND into Emgu.CV Mat in C#.

    struct BITMAP
    {
        public Int32 bmType;
        public Int32 bmWidth;
        public Int32 bmHeight;
        public Int32 bmWidthBytes;
        public Int16 bmPlanes;
        public Int16 bmBitsPixel;
        public IntPtr bmBits;
    }

    [StructLayout(LayoutKind.Sequential, Pack = 4)]
    struct BITMAPINFOHEADER
    {
        public int biSize;
        public int biWidth;
        public int biHeight;
        public Int16 biPlanes;
        public Int16 biBitCount;
        public int biCompression;
        public int biSizeImage;
        public int biXPelsPerMeter;
        public int biYPelsPerMeter;
        public int biClrUsed;
        public int bitClrImportant;
    }

    [DllImport("user32.dll", SetLastError=true)]
    static extern IntPtr GetDC(IntPtr hWnd);

    // System.Windows.Forms.Internal.IntUnsafeNativeMethods
    [DllImport("gdi32.dll", CharSet = CharSet.Auto, EntryPoint = "GetCurrentObject", ExactSpelling = true, SetLastError = true)]
    static extern IntPtr IntGetCurrentObject(HandleRef hDC, int uObjectType);
    
    [DllImport("gdi32.dll", CharSet = CharSet.Auto, EntryPoint = "GetObject")]
    static extern int GetObjectBitmap(IntPtr hObject, int nCount, ref BITMAP lpObject);

    [DllImport("gdi32.dll", EntryPoint = "GetDIBits")]
    static extern int GetDIBits(IntPtr hdc, IntPtr hbmp, int uStartScan, int cScanLines, 
                                IntPtr lpvBits, ref BITMAPINFOHEADER lpbi, int uUsage);

    /// <summary>Gets GDI HDC as an Emgu.CV.Mat image as BGRA</summary>
    /// <param name="hdc">GDI HDC</param>
    /// <param name="destination">Destination Mat which will receive the window contents image</param>
    /// <param name="verticalFlip">If TRUE, pixel will be flipped vertically</param>
    /// <returns>TRUE if image was copied successfully</returns>
    public static bool GetHdcAsMat(IntPtr hdc, ref Mat destination, bool verticalFlip)
    {
        try
        {
            // This is a HBITMAP, which is the actual buffer that is being drawn to by hdc.
            IntPtr hbitmap = IntGetCurrentObject(new HandleRef(null, hdc), 7 /*OBJ_BITMAP*/);

            // Get width, height and the address of the pixel data for the native HBitmap
            BITMAP info = new BITMAP();
            if (0 == GetObjectBitmap(hbitmap, Marshal.SizeOf(info), ref info))
                return false;

            // if the image is a DIB, we can copy the bits directly from bmBits
            if (info.bmBits != IntPtr.Zero)
            {
                // data view of the DIB bits, no allocations
                Mat view = new Mat(info.bmHeight, info.bmWidth, DepthType.Cv8U, 4, 
                                   info.bmBits, info.bmWidth * 4);

                if (verticalFlip) // copy flipped:
                    CvInvoke.Flip(view, destination, FlipType.Vertical);
                else // copy directly:
                    view.CopyTo(destination); // automatically resize destination
                return true;
            }

            // otherwise, use GetDIBits to get the bitmap from the GPU
            // a copy is always needed to get the data from GPU to system memory

            if (destination.Width != info.bmWidth ||
                destination.Height != info.bmHeight)
            {
                destination.Dispose();
                destination = new Mat(info.bmHeight, info.bmWidth, DepthType.Cv8U, 4);
            }

            var desired = new BITMAPINFOHEADER();
            desired.biSize = Marshal.SizeOf(desired);
            desired.biWidth = info.bmWidth;
            desired.biHeight = verticalFlip ? -info.bmHeight : info.bmHeight;
            desired.biPlanes = 1;
            desired.biBitCount = info.bmBitsPixel;

            // Copy bits into destination
            IntPtr dest = destination.DataPointer;
            return 0 != GetDIBits(hdc, hbitmap, 0, destination.Height, dest, ref desired, 0);
        }
        catch
        {
            return false;
        }
    }
    
    /// <summary>Gets window contents as an Emgu.CV.Mat image as BGRA</summary>
    /// <param name="hwnd">Handle to desired window</param>
    /// <param name="destination">Destination Mat which will receive the window contents image</param>
    /// <param name="verticalFlip">If TRUE, pixel will be flipped vertically</param>
    /// <returns>TRUE if image was copied successfully</returns>
    public static bool GetWindowAsMat(IntPtr hwnd, ref Mat destination, bool verticalFlip)
    {
        IntPtr hdc = GetDC(hwnd); // private DC does not need to be released
        return GetHdcAsMat(hdc, ref destination, verticalFlip);
    }

    /// <summary>Gets GDI Graphics contents as an Emgu.CV.Mat image as BGRA</summary>
    /// <param name="graphics">.NET GDI Graphics instance</param>
    /// <param name="destination">Destination Mat which will receive the window contents image</param>
    /// <param name="verticalFlip">If TRUE, pixel will be flipped vertically</param>
    /// <returns>TRUE if image was copied successfully</returns>
    public static bool GetGraphicsAsMat(Graphics graphics, ref Mat destination, bool verticalFlip)
    {
        IntPtr hdc = graphics.GetHdc();
        try
        {
            return GetHdcAsMat(hdc, ref destination, verticalFlip);
        }
        finally
        {
            // NOTE: You cannot use the graphics object before ReleaseHdc is called.
            graphics.ReleaseHdc(hdc);
        }
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM