1

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

enter image description here

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.

vinsa
  • 1,132
  • 12
  • 25
  • 1
    Can you clarify why you don't want to use `MagSetColorEffect`? It does not require themes, only desktop composition - which has been enabled by default since Vista. Windows 8 removed the possibility to even turn it off. The population who could use `MagSetColorEffect` is certainly larger than the population who could use features added in the FCU. I [use this API to preserve night vision](https://stackoverflow.com/questions/32928212/night-vision-mode-on-wpf-windows/32929962#32929962). – Mitch Nov 20 '17 at 21:47
  • @Mitch I know few apps which turn the screen in grayscale like [negative screen](http://arcanesanctum.net/negativescreen/) and they ask you to turn the Aero on Win 7 otherwise they don't work. And the [Iris](https://iristech.co/type-dropdown-help/) require aero, so I guess it is necessarily. – vinsa Nov 21 '17 at 00:15
  • 1
    I'm not saying that it does not require DWM, but it does not require what most people call Aero. DWM is purely the compositor engine used behind the scenes. Having tested it five minutes ago, `MagSetFullscreenColorEffect` works on a VM with only a software GPU, works on a laptop with all effects disabled, works with "classic theme" enabled. On Win 8+, there is no reasonable way to disable composition. See [Disable Desktop Composition in Windows 8](https://superuser.com/questions/581673/disable-desktop-composition-in-windows-8-force-it) – Mitch Nov 21 '17 at 00:19
  • 1
    In fact, a good test is to hold alt-tab and see if you get previews of the windows. If you have previews, composition is enabled and `MagSetFullscreenColorEffect` should work. – Mitch Nov 21 '17 at 00:22
  • @Mitch it does require what most people call Aero (glass) theme on Windows 7, tested. Work if it is enabled and don't work if is disabled. – vinsa Nov 21 '17 at 22:57
  • Glass and composition are separate and distinct settings. See https://i.stack.imgur.com/g0oga.png for the two checkboxes controlling these parameters. You can have composition enabled without glass. – Mitch Nov 21 '17 at 23:51
  • @Mitch there are two options, yes, but if Aero is disabled the function is not working, no matter if ``desktop composition`` is enabled. – vinsa Nov 22 '17 at 00:23

1 Answers1

7

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.

diagram showing mixing of red, green, and blue color channels

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: color

After MagSetFullscreenColorEffect: grayscale

Mitch
  • 21,223
  • 6
  • 63
  • 86
  • **Mitch** your answer is very nice, I have tested it, but the c# code throws me and error on Windows 7 `Unable to find an entry point named 'MagSetFullscreenColorEffect' in DLL 'Magnification.dll'.`. Do you know why? – vinsa Nov 21 '17 at 23:27
  • `MagSetFullscreenColorEffect` does not exist on Windows 7. The function which exists on Windows 7 is `SetMagnificationDesktopColorEffect` (User32), but it is not documented. – Mitch Nov 21 '17 at 23:37
  • I think it is better to use **SetMagnificationDesktopColorEffect** then because it works on Win 10 and on Win 7 (if Aero is turned on), tested. `[DllImport("user32.dll", CallingConvention = CallingConvention.StdCall, SetLastError = true)] public static extern bool SetMagnificationDesktopColorEffect(ref MAGCOLOREFFECT pEffect);` If you want you could change you answer to replace MagSetFullscreenColorEffect with SetMagnificationDesktopColorEffect, if there are not any other better suggestions your answer will be accepted and you will get the bounty. – vinsa Nov 22 '17 at 15:00