我想知道 Graphics 对象绘制图像时缓冲区的中间状态。我该如何获取正在绘制的位图或图像?
我想知道 Graphics 对象绘制图像时缓冲区的中间状态。我该如何获取正在绘制的位图或图像?
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");
}
这段代码可以将图片转换成位图,再转换成字节,最终生成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);
}
由于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);
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
指针,并且还将填充DIBSECTION
的BITMAPINFOHEADER
结构体,您可以使用它来检查DIB的像素格式。检查BITMAPINFOHEADER
会很麻烦。
不是很确定你想要什么,但如果你想使用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);
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;
}
@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);
}
}