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

12

我想知道 Graphics 对象绘制图像时缓冲区的中间状态。我该如何获取正在绘制的位图或图像?


不要忘记在它可用时将其标记为已接受。 - Pranay Rana
4
@PranayRana:没有一个答案能够给提问者想要的东西;相反,都是相反的结果。 - Asherah
7个回答

8
我不太确定我是否理解您所要求的,因为您的问题非常不清楚。
如果您想知道如何将Graphics对象的内容保存为位图,则答案是没有直接的方法来实现。在Graphics对象上绘制是单向操作。
更好的选择是创建一个新的Bitmap对象,获取该位图的Graphics对象,并直接在其上绘制。以下代码是您可能这样做的示例:
// 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");
}

3

这段代码可以将图片转换成位图,再转换成字节,最终生成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);
}

我不太确定为什么你把图像转换成了Base64字符串?我承认这个问题并不是特别清楚,但我在问题中没有看到任何提到它的地方。 - Cody Gray
当然可以使用Graphics.FromImage()方法从位图中获取Graphics对象。我想要的是一些API,可以将Bitmap转换为Image! - dattebayo

3

由于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);

要获取实际的位图数据,需要先了解GDI位图。有所谓的设备相关位图(DDB,在API中通常简称为“Bitmap”),和设备无关位图(DIB)。详细解释差异超出了本答案的范围。
如果使用this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);,则OnPaint中的图形对象将使用DIB,否则将使用DDB。
如果您的HBITMAP是DDB,则无法直接读取/写入像素数据(即使在技术上是可能的,Windows也没有公开的方法来执行此操作)。必须使用GetDIBits将它们复制到设备无关缓冲区中,并使用特定格式,然后使用SetDIBits将它们复制回来。
如果您的HBITMAP是DIB,则可以通过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如果是DDB,将会为null,如果是DIB,则将是一个有效的内存地址。如果您只想复制这些数据,则可以直接使用bmBits;总长度为bmHeight * bmWidthBytes

如果您真的想要在内存中操作像素数据,则需要知道DIB的确切像素格式以便正确操作它。像素格式可能有许多种可能性(每像素位数1/4/8/16/24/32、RGB vs BGR、调色板等)如果您真的想支持所有内容,那么需要做很多工作。

为此,在给定HBITMAP时,GetObject函数将接受一个BITMAP结构体(如上面的代码示例所示),或者一个DIBSECTION结构体。请注意,DIBSECTION以一个BITMAP开头,这使得两个结构体兼容。当且仅当HBITMAP是DIB时,GetObject将填充有效(非空)的bmBits指针,并且还将填充DIBSECTIONBITMAPINFOHEADER结构体,您可以使用它来检查DIB的像素格式。检查BITMAPINFOHEADER会很麻烦。


2

不是很确定你想要什么,但如果你想使用Graphics类进行绘制并将其保存到文件中,你必须从Bitmap文件中获取Graphics对象,然后在完成后保存位图。你可以像这样做:

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

1
你可以获取他的hdc,这是一个指向表面缓冲区的指针,并最终使用bitblt函数将其内容复制到另一个hdc中。这样,你就可以在位图上创建绘图表面的副本。
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;
}

0
你看过这篇MSDN文章吗?它描述了Bitmap类,这是一个用于处理由像素数据定义的图像的对象。System.Drawing.Image为其提供了额外的功能。希望对你有所帮助。

0

@dialer在这个帖子中提供了迄今为止最好的答案。作为一个额外的例子,以下是如何在C#中从Graphics或任何HWND中获取位并将其放入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);
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接