Click here to Skip to main content
Click here to Skip to main content
Go to top

Setting Screen Brightness in C#

, 13 Dec 2009
Rate this:
Please Sign up or sign in to vote.
Tutorial on programmatically setting the screen brightness using C#.

Introduction

There are a lot of parts of the Windows API that are difficult to interface with through the .NET framework due to the liberal use of pointer references. This article provides an example of interfacing to a Windows API call that utilizes a pointer to a large buffer (3 * 256 * 2 bytes large). We also look at how StackAlloc works and using the unsafe context in our code.

Background

I work in the offshore oil field and marine industry, developing user interfaces to the hardware my company develops. Working offshore, a major concern to those on the bridge of a vessel is the preservation of their night vision. This requires dimming screens down as far as they can go, but many industrial panel mount monitors don't provide brightness controls on the front panel. Using Windows API calls, we can dim the screen down about as far as the hardware dimmer will go.

Using the Code

The Microsoft Windows API provides a call titled SetDeviceGammaRamp that takes two arguments: an HDC (hardware device context), and a pointer to an array of gamma values to load into the video card. Not all video card architectures support the SetDeviceGammaRamp, so your mileage with this API call may vary.

Let's take a look at the prototype declaration for the Windows API call:

[DllImport("GDI32.dll")]
private unsafe static extern bool SetDeviceGammaRamp(Int32 hdc, void* ramp); 

As you can see, there are a number of parts here. First is the DllImport; this is contained in the System.Runtime.InteropServices namespace, and provides the hook into the GDI32.dll where the SetDeviceGammaRamp function is contained. The function has two arguments, hdc and ramp. The first argument, hdc, is the pointer to the "hardware device context" that we are setting the gamma ramp on. The second argument ramp is the array of new gamma values, which are stored as a short array with dimensions [3][256].

Let's take a look at the entire class code so we can see everything in context:

[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();

    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;   
}

The first function InitializeClass is used because we have a static class and we need to store some variables that only need to be found once. Because the class is static, after the InitializeClass function is called, the variables will be initialized for any caller in the same application context. The line of interest is:

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

This is where we find our hardware device context. We use the Graphics class to create a Graphics object from an Hwnd of null (IntPtr.Zero). This returns a device context that references the entire graphics display. We then use the GetHdc function to get an IntPtr to the HDC, and then convert that to an Int32 value that we can pass to the Windows API function.

The function with the meat of this article is SetBrightness. Let's look at the function declaration:

public static unsafe bool SetBrightness(short brightness)

Here, the function is declared as public, static, unsafe, and returns a type of bool. The unsafe keyword means that the entire function is to be considered unsafe; that is, type checking is turned off and pointer use is allowed.

Next, we call InitializeClass to make sure that the class has been initialized. If the initialization is already done, the function call returns immediately. We then do some checking on the input data (always, always assume that the input data is bad, and clean it as necessary).

short* gArray = stackalloc short[3 * 256]; 

This line uses stackalloc to allocate a section of memory on the stack where we can do our work. We can't declare multiple dimension arrays with stackalloc, so we just set aside as much space as we need for the entire element count. The actual size in bytes of the array is 3 * 256 * sizeof(short).

After that, we need to declare an indexing variable that will traverse the allocated array:

short* idx = gArray; 

This is initialized to point at the first element in gArray. We will use this variable to traverse down the array and fill it with the values needed.

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++;
    }
}

Here, we loop through all the elements in the array and fill it with the desired Gamma values. Idx++ at the end of the loop automatically increases the pointer sizeof(short) so that we go to the next element. This is the magic of pointer arithmetic.

All that is left is to call the API function with our HDC and Gamma array:

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

which is supposed to return true or false depending on if the call succeeds, but I've found on my machine that the function call always returns false, regardless of the function working or not.

And there we have it. It's best to compile this into a separate DLL that you've marked to compile as unsafe (either add the /unsafe escape to the compiler line, or go to Properties>Build, and check the "Allow Unsafe Code" box).

Points of Interest

Interfacing back-and-forth with .NET code and pointers isn't the easiest thing in the world. Many of us got away from C/C++ because of pointer arithmetic and "DLL Hell", but occasionally, we have to get sucked back in. This just goes to show you that we can write a lot of code to interface to unmanaged code without having to write complicated wrapper functions in separate languages.

Note 1: The API function always returns false on my machine.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Ron Beyer
President 6D Systems LLC
United States United States
I studied Software Engineering at Milwaukee School of Engineering for 2 years before switching to Management of Information Systems for a more business oriented approach. I've been developing software since the age of 14, and have waded through languages such as QBasic, TrueBasic, C, C++, Java, VB6, VB.NET, C#, etc. I've been developing professionally since 2002 in .NET.

Comments and Discussions

 
QuestionNeed full compiled solution Pinmemberpeela8528-Aug-12 17:53 
AnswerRe: Need full compiled solution PinmemberSimonQuah2122-Jan-13 18:40 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140921.1 | Last Updated 13 Dec 2009
Article Copyright 2009 by Ron Beyer
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid