Linux下C#调用XGetImage截图全黑问题求助
问题分析与解决方案
你的核心问题出在XGetImage的plane_mask参数设置错误,加上双屏场景下的屏幕尺寸获取、颜色通道解析方式不当,导致生成的Bitmap全黑。以下是具体的修复步骤:
1. 修正AllPlanes常量定义
你当前定义的AllPlanes = 0是错误的,XGetImage的plane_mask参数需要传入所有位都置1的值(表示捕获所有颜色平面),否则只会获取空平面(全黑):
// 替换原来的AllPlanes定义 public const uint AllPlanes = uint.MaxValue; public const int ZPixmap = 2; // 明确ZPixmap格式常量
然后修改XGetImage调用:
IntPtr image = Xorg.API.XGetImage(display, rootWindow, 0, 0, (uint)ScreenWidth, (uint)ScreenHeight, AllPlanes, ZPixmap);
2. 适配双屏的屏幕尺寸获取
硬编码分辨率无法适配双屏动态场景,改用Xinerama获取整个虚拟屏幕的尺寸(需要链接Xinerama库):
首先添加Xinerama的API定义:
[DllImport("Xinerama")] private static extern bool XineramaIsActive(IntPtr display); [DllImport("Xinerama")] private static extern int XineramaQueryScreens(IntPtr display, out IntPtr screens); [StructLayout(LayoutKind.Sequential)] private struct XineramaScreenInfo { public int screen_number; public int x_org; public int y_org; public int width; public int height; }
然后修改构造函数,自动计算双屏总尺寸:
public int ScreenWidth { get; } public int ScreenHeight { get; } public ScreenCapture() { display = Xorg.API.XOpenDisplay(IntPtr.Zero); if (XineramaIsActive(display)) { IntPtr screensPtr; int screenCount = XineramaQueryScreens(display, out screensPtr); if (screenCount > 0) { int maxX = 0, maxY = 0; for (int i = 0; i < screenCount; i++) { var screen = Marshal.PtrToStructure<XineramaScreenInfo>(IntPtr.Add(screensPtr, i * Marshal.SizeOf<XineramaScreenInfo>())); maxX = Math.Max(maxX, screen.x_org + screen.width); maxY = Math.Max(maxY, screen.y_org + screen.height); } ScreenWidth = maxX; ScreenHeight = maxY; } else { FallbackToDefaultScreen(); } } else { FallbackToDefaultScreen(); } rootWindow = Xorg.API.XRootWindow(display, Xorg.API.DefaultScreen(display)); } private void FallbackToDefaultScreen() { ScreenWidth = Xorg.API.XDisplayWidth(display, Xorg.API.DefaultScreen(display)); ScreenHeight = Xorg.API.XDisplayHeight(display, Xorg.API.DefaultScreen(display)); }
3. 正确解析XImage的颜色通道
不要固定偏移读取颜色字节,应该根据XImage的颜色掩码动态提取RGB值,避免因X服务器颜色格式差异导致错误:
// 在CaptureScreen方法中,获取XImage后添加以下代码计算颜色位移 int redShift = 0; ulong redMask = ximage.red_mask; while ((redMask & 1) == 0) { redShift++; redMask >>= 1; } int greenShift = 0; ulong greenMask = ximage.green_mask; while ((greenMask & 1) == 0) { greenShift++; greenMask >>= 1; } int blueShift = 0; ulong blueMask = ximage.blue_mask; while ((blueMask & 1) == 0) { blueShift++; blueMask >>= 1; }
4. 使用LockBits高效生成Bitmap
替换SetPixel为LockBits直接操作内存,既提升性能又避免潜在的像素设置错误:
// 替换原来的Bitmap生成循环 Bitmap bitmap = new Bitmap(ximage.width, ximage.height, PixelFormat.Format32bppArgb); BitmapData bmpData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.WriteOnly, bitmap.PixelFormat); IntPtr scan0 = bmpData.Scan0; int stride = bmpData.Stride; for (int y = 0; y < ximage.height; y++) { for (int x = 0; x < ximage.width; x++) { int xImageOffset = y * ximage.bytes_per_line + x * (ximage.bits_per_pixel / 8); int pixelValue = Marshal.ReadInt32(ximage.data, xImageOffset); byte r = (byte)((pixelValue & (int)ximage.red_mask) >> redShift); byte g = (byte)((pixelValue & (int)ximage.green_mask) >> greenShift); byte b = (byte)((pixelValue & (int)ximage.blue_mask) >> blueShift); // Format32bppArgb在内存中是BGRA顺序(小端) int bmpOffset = y * stride + x * 4; Marshal.WriteByte(scan0, bmpOffset, b); Marshal.WriteByte(scan0, bmpOffset + 1, g); Marshal.WriteByte(scan0, bmpOffset + 2, r); Marshal.WriteByte(scan0, bmpOffset + 3, 255); // 设置为不透明 } } bitmap.UnlockBits(bmpData);
5. 修复Display资源管理
不要在每次截图时关闭Display,改为实现IDisposable接口管理资源:
public class ScreenCapture : IDisposable { private readonly IntPtr display; private readonly IntPtr rootWindow; private bool disposed = false; // ... 其他代码 public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!disposed) { if (display != IntPtr.Zero) { Xorg.API.XCloseDisplay(display); } disposed = true; } } ~ScreenCapture() { Dispose(false); } }
使用时记得用using语句:
using (var capture = new ScreenCapture()) { Bitmap bmp = capture.CaptureScreen(); // 处理截图 }
验证步骤
- 修正AllPlanes后,先检查ximage.data的字节值是否不再全为0;
- 运行程序查看生成的Bitmap是否正常显示屏幕内容;
- 测试双屏场景下是否能捕获整个扩展桌面。
内容的提问来源于stack exchange,提问作者DAUNVH
相关产品推荐
相关产品推荐

