简体   繁体   English

如何从 C# 中的 Graphics 对象获取位图/图像?

[英]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.我想知道 Graphics 对象正在绘制一些东西的缓冲区的中间状态是什么。 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.如果您想知道如何将Graphics对象的内容保存到位图,那么答案是没有直接的方法可以这样做。 Drawing on a Graphics object is a one-way operation.Graphics对象上Graphics是一种单向操作。

The better option is to create a new Bitmap object, obtain a Graphics object for that bitmap, and draw directly onto it.更好的选择是创建一个新的Bitmap对象,为该位图获取一个Graphics对象,然后直接在其上绘制。 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.这段代码对我有用,我正在转换图像 >> 位图 >> 字节 >> Base64 字符串。

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...由于9年后没有人回答实际问题......

// 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.要获得实际的位图位,您必须首先了解有关 GDI 位图的知识。 There are so-called device-dependent bitmaps (DDB, or often simply just "Bitmap" in the API), and device-independent bitmaps (DIB).有所谓的设备相关位图(DDB,或通常在 API 中简称为“位图”)和设备无关位图(DIB)。 A full explanation of the difference would be out of scope for this answer.对差异的完整解释超出了此答案的范围。

If you use this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);如果你使用this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true); , then the graphics object in OnPaint will use a DIB, otherwise it will use a DDB. ,那么OnPaint的图形对象将使用 DIB,否则将使用 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).如果您的HBITMAPDDB ,则无法直接读取/写入像素数据(即使技术上可行,Windows 也无法做到)。 You have to use GetDIBits to copy them to a device independent buffer, using a particular format, and then SetDIBits to copy them back.您必须使用GetDIBits使用特定格式将它们复制到设备独立缓冲区,然后使用SetDIBits将它们复制回来。

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 ):如果您的HBITMAPDIB ,那么您可以获得实际的像素位(作为指针),并使用GetObject直接在内存中读取/写入它们(不要与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.如果是 DDB,则BITMAP.bmBits将为null如果是 DIB,它将是有效的内存地址。 If you just want to copy this data, you can directly use bmBits ;如果只想复制这些数据,可以直接使用bmBits the total length is bmHeight * bmWidthBytes .总长度为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.如果你真的想操作内存中的像素数据,你需要知道 DIB 的确切像素格式才能正确操作它。 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).像素格式有多种可能性(每像素位数 1/4/8/16/24/32、RGB 与 BGR、调色板等)。 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.要做到这一点,请知道当获得HBITMAPGetObject函数将接受BITMAP结构(如上面的代码示例所示)或DIBSECTION结构。 Note that DIBSECTION starts with a BITMAP , this makes the two structs compatible.请注意, DIBSECTIONBITMAP开头,这使两个结构兼容。 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.如果HBITMAP是 DIB,则GetObject将填充有效(非空) bmBits指针,并且还将填充DIBSECTIONBITMAPINFOHEADER结构,然后您可以使用它来检查 DIB 的像素格式。 Examining the BITMAPINFOHEADER will be painful.检查BITMAPINFOHEADER会很痛苦。

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.这里不能100%确定你想要什么,但是如果你想使用图形类来绘制,然后保存到文件,你必须从一个Bitmap文件中获取Graphics对象,然后在完成后保存位图。 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.你可以得到他的 hdc,它是一个指向表面缓冲区的指针,并最终将他的内容复制到另一个具有 bitblt 功能的 hdc。 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 ?你看过这篇 MSDN 文章吗? It describes the Bitmap class, which is an object used to work with images defined by pixel data.它描述了 Bitmap 类,它是一个用于处理由像素数据定义的图像的对象。 System.Drawing.Image provides additional functionality for it. System.Drawing.Image 为其提供了附加功能。 HTH HTH

@dialer Has the best answer in this thread so far.到目前为止,@dialer 在这个线程中有最好的答案。 As an additional example, here is how to get bits from Graphics or any HWND into Emgu.CV Mat in C#.作为一个额外的例子,这里是如何从图形或任何 HWND 中获取位到 C# 中的 Emgu.CV Mat 中。

    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);
        }
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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