C#: How to change the color/gamma and brightness

My new tool, EyeLight, is a application which changes the color and brightness of your display based on the time of the day and your location.
So whenever it get dark outside and the main source of light next to your monitor are artificial light sources (which are way less intense than the sun) the intensity of the monitor will also decrease. which should decrease the effect of displays on your sleep.

In order to be able to change the color of a display we need to change the gamma ramps of the video adapter. Back in the old day these 'lookup tables' we used for analog values of video data (like with VGA). The ramps were the conversion between digital value and a analog voltage.

To change the ramps, we need to call SetDeviceGammaRamp in gdi32.dll. To do this from C# we can use the following code.

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

The arguments are as explained over here. The hdc is a handle for the device we want to use. In our case that will be the main video adapter which is located at '0x0'. The 'ramp' argument is a point to a 3*256 array of 16 bit unsigned shorts. Each color has 256 entries and the order of the colors is as expected red, green, blue.

To update the ramps we need to calculate the new ramps before we can call the SetDeviceGammaRamp function. Usually the ramps are configured with a particular gamma value. When the gamma is 1 the ramp will be linear and the lower the gamma get, the more convex the ramp will be.


Figure 1: Gamma correction

With this information we can form an equation to calculate the values of the ramp (where is gamma and the ramp index).

I've wrapped this into a static C# class. Use the code as you like (MIT license).

References:
"Light level and duration of exposure determine the impact of self-luminous tablets on melatonin suppression"
Brittany Wood, Mark S. Rea, Barbara Plitnick, and Mariana G. Figueiro
Applied Ergonomics. August 2012. http://dx.doi.org/10.1016/j.apergo.2012.07.008

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Drawing;

namespace eyeLight
{
	static class Display
	{
		[DllImport("gdi32.dll")]
		private unsafe static extern bool SetDeviceGammaRamp(Int32 hdc, void* ramp);

		private static bool initialized = false;
		private static Int32 hdc;

		private static double[] _gammas = { 1, 1, 1 };
		private static short _brightness = 100;

		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 bool SetGamma(double gammaRed, double gammaGreen, double gammaBlue)
		{
			_gammas[0] = gammaRed;
			_gammas[1] = gammaGreen;
			_gammas[2] = gammaBlue;

			return UpdateRamps();
		}

		public static bool SetBrightness(short brightness)
		{
			if (brightness > 100)
				brightness = 100;

			if (brightness < 0)
				brightness = 0;

			_brightness = brightness;

			return UpdateRamps();
		}

		private static unsafe bool UpdateRamps()
		{
			InitializeClass();

			double brightness = (double)_brightness/100f;

			ushort* gArray = stackalloc ushort[3 * 256];
			ushort* idx = gArray;

			for (int j = 0; j < 3; j++)
			{
				for (int i = 0; i < 256; i++)
				{
					double arrayVal;
					// gamma calculation
					arrayVal = (Math.Pow((double)i / 256.0, 1.0 / _gammas[j]) * 65535) + 0.5;
					arrayVal *= brightness;

					if (arrayVal > 65535)
						arrayVal = 65535;
					if (arrayVal < 0)
						arrayVal = 0;

					*idx = (ushort)arrayVal;
					idx++;
				}
			}

			bool retVal = SetDeviceGammaRamp(hdc, gArray);

			//Memory allocated through stackalloc is automatically free'd
			//by the CLR.

			return retVal;
		}

	}
}

1 Comment

Leave a Reply to Russ Lynch Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.