创建整个屏幕的图形

本文关键字:图形 屏幕 创建 | 更新日期: 2023-09-27 18:29:55

我开始使用一个类来增加和减少我所有屏幕的gamma,然后我启动程序并增加或减少gamma,它工作得很好,但过了一段时间(20秒左右)它就不再工作了,我找到了问题,似乎是Graphics.FromHwnd(IntPtr.Zero).GetHdc().ToInt32();,我需要刷新它,然后它又工作了。在示例代码中,这在初始化期间只做过一次,但为了使其发挥作用,我在SetBrightness()方法中粘贴了这一行,所以每次都会刷新它。这样做可以吗?还是我会遇到问题?

这是代码:

public static class Brightness
{
    [DllImport("gdi32.dll")]
    private unsafe static extern bool SetDeviceGammaRamp(Int32 hdc, void* ramp);
    private static bool initialized = false;
    private static Int32 hdc;
    private static void InitializeClass()
    {
        if (initialized)
            return;
        //Get the hardware device context of the screen, we can do
        //this by getting the graphics object of null (IntPtr.Zero)
        //then getting the HDC and converting that to an Int32.
        hdc = Graphics.FromHwnd(IntPtr.Zero).GetHdc().ToInt32();
        initialized = true;
    }
    public static unsafe bool SetBrightness(short brightness)
    {
        InitializeClass();
        hdc = Graphics.FromHwnd(IntPtr.Zero).GetHdc().ToInt32();
        if (brightness > 255)
            brightness = 255;
        if (brightness < 0)
            brightness = 0;
        short* gArray = stackalloc short[3 * 256];
        short* idx = gArray;
        for (int j = 0; j < 3; j++)
        {
            for (int i = 0; i < 256; i++)
            {
                int arrayVal = i * (brightness + 128);
                if (arrayVal > 65535)
                    arrayVal = 65535;
                *idx = (short)arrayVal;
                idx++;
            }
        }
        //For some reason, this always returns false?
        bool retVal = SetDeviceGammaRamp(hdc, gArray);
        //Memory allocated through stackalloc is automatically free'd
        //by the CLR.
        return retVal;
    }
}

这就是它的名称:

short gammaValue = 128;
    void gammaUp_OnButtonDown(object sender, EventArgs e)
    {
        if (gammaValue < 255)
        {
            gammaValue += 10;
            if (gammaValue > 255)
                gammaValue = 255;
            Brightness.SetBrightness(gammaValue);
        }
    }
    void gammaDown_OnButtonDown(object sender, EventArgs e)
    {
        if (gammaValue > 0)
        {
            gammaValue -= 10;
            if (gammaValue < 0)
                gammaValue = 0;
            Brightness.SetBrightness(gammaValue);
        }
    }

创建整个屏幕的图形

如果您想在代码失败时查看错误代码,您可以使用:

[DllImport("kernel32.dll")]
public static extern uint GetLastError();
bool retVal = SetDeviceGammaRamp(hdc, gArray);
if (retVal == false)
{
    System.Console.WriteLine(GetLastError());
}

error code is 87: ERROR_INVALID_PARAMETER

显然,hdc是无效的参数。发生这种情况是因为在您的:中

hdc = Graphics.FromHwnd(IntPtr.Zero).GetHdc()

Graphics.FromHwnd(IntPtr.Zero)是图形对象,从中可以获得hdc。此对象已不存在,因此出现错误。

每次调用SetBrightness函数时获得一个hdc:hdc = Graphics.FromHwnd(IntPtr.Zero).GetHdc()可以解决问题,但它会通过调用Graphics.FromHwnd(IntPtr.Zero).ReleaseHdc();而崩溃(正如您在评论中所说)。这是因为Graphics.FromHwnd(IntPtr.Zero)与第一个不同。

解决方案

有很多:

1。创建图形对象,并在每次调用SetBrightness时从中获取dc句柄,最终释放资源:

public static unsafe bool SetBrightness(short brightness)
{
    Graphics gr;
    gr = Graphics.FromHwnd(IntPtr.Zero);
    IntPtr hdc = gr.GetHdc(); //Use IntPtr instead of Int32, don't need private static Int32 hdc;
    ...
    ...
    //For some reason, this always returns false?
    bool retVal = SetDeviceGammaRamp(hdc, gArray);
    //Memory allocated through stackalloc is automatically free'd
    //by the CLR.
    gr.ReleaseHdc();        
    gr.Dispose();
    return retVal;
}

2。创建图形对象并从中获取InitializeClass()函数中的dc句柄一次,在程序结束时释放资源:

private static IntPtr hdc;
private static Graphics gr;
private static void InitializeClass()
{
    if (initialized)
        return;
    gr = Graphics.FromHwnd(IntPtr.Zero);
    hdc = gr.GetHdc();
    initialized = true;
}
public static unsafe bool SetBrightness(short brightness)
{
    InitializeClass();
    //hdc = Graphics.FromHwnd(IntPtr.Zero).GetHdc(); //You don't need it since we get the hdc once.
    ...
    ...
    //For some reason, this always returns false?
    bool retVal = SetDeviceGammaRamp(hdc, gArray);
    //Memory allocated through stackalloc is automatically free'd
    //by the CLR.
    return retVal;
}

3。使用GetDC api:

[DllImport("user32.dll")]
public static extern IntPtr GetDC(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDC);
private static IntPtr hdc;
private static void InitializeClass()
{
    if (initialized)
        return;
    hdc = GetDC(IntPtr.Zero);
    initialized = true;
}
public static unsafe bool SetBrightness(short brightness)
{
    InitializeClass();
    //hdc = Graphics.FromHwnd(IntPtr.Zero).GetHdc(); //You don't need it since we get the hdc once.
    ...
    ...
    //For some reason, this always returns false?
    bool retVal = SetDeviceGammaRamp(hdc, gArray);
    //Memory allocated through stackalloc is automatically free'd
    //by the CLR.
    return retVal;
}

在程序发布的最后hdc:

ReleaseDC(IntPtr.Zero, hdc);

这是一个非常简单、经典的内存管理问题。

你需要保留的参考

Graphics.FromHwnd(IntPtr.Zero)

否则垃圾收集器(通常也是GC)会认为您没有使用图形上下文(GC)。

保留引用意味着保留一个有效的引用,有时称为"活动对象"。在你的课堂亮度情况下,它翻译为(来自内存,而不是编译!)

private static bool initialized = false;
private static Graphics gc;
private static void InitializeClass()
{
    if (initialized)
        return;
    //Get the hardware device context of the screen, we can do
    //this by getting the graphics object of null (IntPtr.Zero)
    gc = Graphics.FromHwnd(IntPtr.Zero);
    initialized = true;
}

然后调用SetRamp(gc.GetHdc().ToInt32(),…);

关键的一点是,只要亮度被初始化,就需要Graphics对象,所以垃圾收集器不会释放它

您只记得它的数字句柄值,当显式调用ReleaseDC()时,您正在执行运行时的工作,当运行时执行相同操作时崩溃。

我认为两次使用Graphics.FromHwnd(IntPtr.Zero).GetHdc().ToInt32();没有任何问题,尽管我希望两次的值都相同,除非程序外部的东西影响了它。

我更担心的是下面的部分总是返回false。

 //For some reason, this always returns false?
 bool retVal = SetDeviceGammaRamp(hdc, gArray);

文件规定了

返回值

如果此函数成功,则返回值为TRUE。

如果此函数失败,则返回值为FALSE。

所以我想说,有些事情不正常,调用总是返回false。

我想这是因为GetHdc()返回的设备上下文句柄必须在使用后释放,假设您会尽快这样做;您应该能够在通过ReleaseHdc()呼叫SetDeviceGammaRamp()之后立即释放它。从本质上讲,任何时候你得到DC,你都应该尽快发布它。

C#在Win32 GDI API上只提供了一个薄薄的贴面,因此应用的规则是GetDC()ReleaseDC()的规则,您可以使用IntPtr.Zero来pinvoke GetDC()以达到相同的效果(并且存在相同的问题)。

GDI是这样挑剔的,你只是在"借用"句柄,而你得到的句柄通常是从某个大小的池中分配的,一旦用完,你就不会得到更多的DC,因为你还没有发布以前的DC。如果一个人重复地分配GDI画笔或笔来绘画,也是如此;最终你会筋疲力尽。此外,保留池句柄通常效果不佳;我不能确切地告诉你GDI内部的问题是什么,但我以前也遇到过类似的问题,除了驱动程序的问题,如果你得到DC,设置斜坡,然后立即释放DC,我希望你的方法能奏效。

顺便说一句,MSDN中提到的这些成对规则也有例外,例如,通过Win32的CS_OWNDC分配给窗口的专用DC;那些不是从游泳池里来的,所以你可以把它们留多久就留多久。

顺便说一句,除非你强制使用x86构建,否则我建议在SetDeviceGammaRamp()中使用IntPtr而不是Int32,因为这是一个指针大小的整数,而不是一个总是32位的值。而不是void*,我会将RAMP结构或MarshalAs定义为固定长度的数组。但我不认为这两个都是你的问题。当你在做的时候,你可以切换到通过pinvoke调用GetDC()ReleaseDC(),并剪切出一个你不使用的Graphics对象。PInvoke.net(在撰写本文时)对SetDeviceGammaRamp()以及GetDC()ReleaseDC()有一个更安全的定义。