Reputation: 1242
Could we achive turning the screen into grayscale using GammaRamp (SetDeviceGammaRamp) on Windows OS (7,8,10)? I need this to simulate E-Ink reader. I am using this class in order to control colors temperature and tried to implement algorithm for converting RGB image into grayscale using following factors: red * 0.2126; green * 0.7152; blue * 0.0722 which I read for in this article. Result was not a pure grayscale. I don't understand how exactly gammaramp array set by SetDeviceGammaRamp is changing the colors and this is why can't implement grayscale algorithm. Any suggestions how to implement grayscale convertion in this class with SetDeviceGammaRamp?
public static class GammaRamp
{
[DllImport("gdi32.dll")]
private unsafe static extern bool SetDeviceGammaRamp(Int32 hdc, ushort* ramp);
[DllImport("gdi32")]
private unsafe static extern bool GetDeviceGammaRamp(Int32 hdc, ushort* ramp);
private static Int32 hdc;
public static unsafe void Set(int aBrightness, int aRed, int aGreen, int aBlue)
{
double red = 1, green = 1, blue = 1;
red = (double)aRed / (double)255;
green = (double)aGreen / (double)255;
blue = (double)aBlue / (double)255;
//Memory allocated through stackalloc is automatically free'd by the CLR.
ushort* rgbArray = stackalloc ushort[768]; //3 * 256
ushort* idx = rgbArray;
for (int j = 0; j < 3; j++)
{
for (int i = 0; i < 256; i++)
{
double arrayVal = (double)(i * (aBrightness + 128));
if (arrayVal > 65535)
arrayVal = (double)65535;
if (j == 0) //red
arrayVal = arrayVal * red * 0.2126;
else if (j == 1) //green
arrayVal = arrayVal * green * 0.7152;
else //blue
arrayVal = arrayVal * blue * 0.0722;
*idx = (ushort)arrayVal;
idx++;
}
}
hdc = Graphics.FromHwnd(IntPtr.Zero).GetHdc().ToInt32();
SetDeviceGammaRamp(hdc, rgbArray);
}
}
If it is not possible to use GammaRamp what it is I prefer to, because it is supportted in Win 7,8 and 10, I will use a new feature but only for Windows 10
but to control this setting from WPF application I have to change following registry keys
Computer\HKEY_CURRENT_USER\Software\Microsoft\ColorFiltering\Active = 1 Computer\HKEY_CURRENT_USER\Software\Microsoft\ColorFiltering\FilterType = 0
which I can easily do, but how to make Windows OS to refresh this new settings from the registry? This will be a useful answer too.
At the end I will say that I know about MagSetColorEffect winAPI which I will use for Windows 7 if there is no way to use GammaRamp or other option, but it is last option because it require Aero to be enabled, which is a kind of limitation.
Upvotes: 1
Views: 1580
Reputation: 22251
It would not be possible to use a function like SetDeviceGammaRamp
to make a Grayscale filter, as they act on each color channel individually. The lpRamp
parameter sets the LUT used by the video card to map the pixel intensity (as it would be saved in a screenshot) to the transmitted intensity (as would be placed onto a pin in a VGA connector). This is largely a legacy API and does not affect screenshots, remote desktop, or work with all graphics cards.
To make a grayscale color filter, you must take data from each of the color channels, and mix it together. Optionally, you can apply a weighting function to make the resultant image more accurately reflect human perception.
You can use the scaling factors you specified with the MagSetFullscreenColorEffect
function (which is available on most any PC running Windows 8 or above, and on the vast majority of Windows 7 PC's). I don't have access to a FCU machine at the moment, but I would suspect that the new option in settings is just calling MagSetFullscreenColorEffect
.
MagSetFullscreenColorEffect
takes a color matrix, which allows you to transform the four-space of RGBX. You can learn about transformation matrices on MSDN or many other places. ColorMatrix Viewer (GitHub) is an excellent tool for testing and tuning these color matrices.
See the below sample for an example of how to use MagSetFullscreenColorEffect
to this end.
C++:
#include <magnification.h>
#pragma comment(lib, "magnification.lib")
float redScale = 0.2126f, greenScale = 0.7152f, blueScale = 0.0722f;
MAGCOLOREFFECT magEffectInvert =
{
{
{ redScale, redScale, redScale, 0.0f, 0.0f },
{ greenScale, greenScale, greenScale, 0.0f, 0.0f },
{ blueScale, blueScale, blueScale, 0.0f, 0.0f },
{ 0.0f, 0.0f, 0.0f, 1.0f, 0.0f },
{ 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }
}
};
MagInitialize();
if (!MagSetFullscreenColorEffect(&magEffectInvert))
{
std::cout << "Failed " << GetLastError() << std::endl;
}
system("pause");
MagUninitialize();
C#:
using System;
using System.Runtime.InteropServices;
namespace ManagedColorPlayground
{
using static NativeMethods;
class Program
{
static void Main(string[] args)
{
float redScale = 0.2126f, greenScale = 0.7152f, blueScale = 0.0722f;
var magEffectInvert = new MAGCOLOREFFECT {
transform = new [] {
redScale, redScale, redScale, 0.0f, 0.0f,
greenScale, greenScale, greenScale, 0.0f, 0.0f,
blueScale, blueScale, blueScale, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f, 1.0f
}
};
MagInitialize();
MagSetFullscreenColorEffect(ref magEffectInvert);
Console.ReadLine();
MagUninitialize();
}
}
static class NativeMethods
{
const string Magnification = "Magnification.dll";
[DllImport(Magnification, ExactSpelling = true, SetLastError = true)]
public static extern bool MagInitialize();
[DllImport(Magnification, ExactSpelling = true, SetLastError = true)]
public static extern bool MagUninitialize();
[DllImport(Magnification, ExactSpelling = true, SetLastError = true)]
public static extern bool MagSetFullscreenColorEffect(ref MAGCOLOREFFECT pEffect);
public struct MAGCOLOREFFECT
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 25)]
public float[] transform;
}
}
}
Before MagSetFullscreenColorEffect
:
After MagSetFullscreenColorEffect
:
Upvotes: 7