You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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();
    // 处理截图
}

验证步骤

  1. 修正AllPlanes后,先检查ximage.data的字节值是否不再全为0;
  2. 运行程序查看生成的Bitmap是否正常显示屏幕内容;
  3. 测试双屏场景下是否能捕获整个扩展桌面。

内容的提问来源于stack exchange,提问作者DAUNVH

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 01:07:06