Night Vision Mode on WPF Windows

2019-03-21 16:52发布

问题:

We've made a WPF application with a traditional UI (common controls like tabs, buttons, labels, textboxes, and so on).

We need to add a "night vision" mode, which would make it look like Stellarium's night vision mode, so that it can be comfortably used in places with few or just no light at all.

As far as I've seen, we only have two options:

  1. A technique called "shading" (I don't know how this could be implemented in WPF).
  2. The brute-force way: defining control's style templates. As you know, this would imply a tremendous work, since we need to redefine every single property for each control being used (borders, background, brushes, etc, etc, etc).

The questions are:

  1. What's the best way to achieve this in WPF?
  2. Would it be more complicated if we work with MahApps Metro Style? I know MahApps lets you customize their style, but the problem then would be switching between to different templates (the two used for day and night vision, respectively).

Thank you very much!

回答1:

One suggestion is of course MahappsThemes. You could switch between Light and Dark by this :

// get the theme from the current application
var theme = ThemeManager.DetectAppStyle(Application.Current);

// now set the Green accent and dark theme
ThemeManager.ChangeAppStyle(Application.Current,
                            ThemeManager.GetAccent("Green"),
                            ThemeManager.GetAppTheme("BaseDark"));

( Mahapps.Styles for ref )

Or you could use DynamicResource for each Brushes and change the SINGLE ResourceDictionary holding them to change everything in a click :)



回答2:

Alternatively, consider just using the DWM Magnification functionality to make all of windows go into "Night-Mode".

Example:

See the Magnification API sample, with modifications below:

/*************************************************************************************************
*
* File: FullscreenMagnifierSample.cpp
*
* Description: Implements simple UI to control fullscreen magnification, using the 
* Magnification API.
*
*  Copyright (C) Microsoft Corporation.  All rights reserved.
* 
* This source code is intended only as a supplement to Microsoft
* Development Tools and/or on-line documentation.  See these other
* materials for detailed information regarding Microsoft code samples.
* 
* THIS CODE AND INFORMATION ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY
* KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
* PARTICULAR PURPOSE.
* 
*************************************************************************************************/

#include "windows.h"
#include "resource.h"
#include "strsafe.h"
#include "magnification.h"

// Global variables and strings.
const LPWSTR g_pszAppTitle = L"Night Colors";

// Initialize color effect matrices to apply grayscale or restore the colors on the desktop.
MAGCOLOREFFECT g_MagEffectIdentity = { 
    1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
    0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
    0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
    0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
    0.0f, 0.0f, 0.0f, 0.0f, 1.0f };
MAGCOLOREFFECT g_MagEffectWashout = {
    0.5f, 0.0f, 0.0f, 0.0f, 0.0f,
    0.0f, 0.3f, 0.0f, 0.0f, 0.0f,
    0.0f, 0.0f, 0.3f, 0.0f, 0.0f,
    0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
    0.0f, 0.0f, 0.0f, 0.0f, 1.0f };

MAGCOLOREFFECT g_MagEffectWashout2 = {
    0.35f, 0.0f, 0.0f, 0.0f, 0.0f,
    0.0f, 0.2f, 0.0f, 0.0f, 0.0f,
    0.0f, 0.0f, 0.2f, 0.0f, 0.0f,
    0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
    0.0f, 0.0f, 0.0f, 0.0f, 1.0f };

MAGCOLOREFFECT g_MagEffectWashout3 = {
    0.25f, 0.0f, 0.0f, 0.0f, 0.0f,
    0.0f, 0.13f, 0.0f, 0.0f, 0.0f,
    0.0f, 0.0f, 0.13f, 0.0f, 0.0f,
    0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
    0.0f, 0.0f, 0.0f, 0.0f, 1.0f };

/*MAGCOLOREFFECT g_MagEffectGrayscale = {0.3f,  0.3f,  0.3f,  0.0f,  0.0f,
                                       0.6f,  0.6f,  0.6f,  0.0f,  0.0f,
                                       0.1f,  0.1f,  0.1f,  0.0f,  0.0f,
                                       0.0f,  0.0f,  0.0f,  1.0f,  0.0f,
                                       0.0f,  0.0f,  0.0f,  0.0f,  1.0f};*/

MAGCOLOREFFECT g_MagEffectGrayscaleInverted = { 
    -0.3f, 0.0f, 0.0f, 0.0f, 0.0f,
    -0.6f, 0.0f, 0.0f, 0.0f, 0.0f,
    -0.1f, 0.0f, 0.0f, 0.0f, 0.0f,
    0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
    1.0f, 0.0f, 0.0f, 0.0f, 1.0f };

MAGCOLOREFFECT g_MagEffectInverted = { 
    -1.0f,  0.0f,  0.0f, 0.0f, 0.0f,
     0.0f, -1.0f,  0.0f, 0.0f, 0.0f,
     0.0f,  0.0f, -1.0f, 0.0f, 0.0f,
     0.0f,  0.0f,  0.0f, 1.0f, 0.0f,
     1.0f,  1.0f,  1.0f, 0.0f, 1.0f 
};

MAGCOLOREFFECT g_MagEffectGrayscale = {0.3f,  0.0f,  0.0f,  0.0f,  0.0f,
                                       0.6f,  0.0f,  0.0f,  0.0f,  0.0f,
                                       0.1f,  0.0f,  0.0f,  0.0f,  0.0f,
                                       0.0f,  0.0f,  0.0f,  1.0f,  0.0f,
                                       0.0f,  0.0f,  0.0f,  0.0f,  1.0f};

// Forward declarations.
INT_PTR CALLBACK SampleDialogProc(_In_ HWND hwndDlg, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam);
void InitDlg(_In_ HWND hwndDlg);
void HandleCommand(_In_ HWND hwndDlg, _In_ WORD wCtrlId);

//
// FUNCTION: WinMain()
//
// PURPOSE: Entry point for the application.
//
int APIENTRY WinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE /*hPrevInstance*/,
                     _In_ LPSTR     /*lpCmdLine*/,
                     _In_ int       /*nCmdShow*/)
{
    // Initialize the magnification functionality for this process.
    if (MagInitialize())
    {
        // Present a dialog box to allow the user to control fullscreen magnification.
        DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG_FULLSCREENMAGNIFICATIONCONTROL), NULL, SampleDialogProc);

        // Any current magnification and color effects will be turned off as a result of calling MagUninitialize().
        MagUninitialize();
    }
    else
    {
        MessageBox(NULL, L"Failed to initialize magnification.", g_pszAppTitle, MB_OK);
    }

    return 0;
}

//
// FUNCTION: SampleDialogProc()
//
// PURPOSE: Dialog proc for the UI used for controlling fullscreen magnification.
//
INT_PTR CALLBACK SampleDialogProc(
  _In_  HWND   hwndDlg,
  _In_  UINT   uMsg,
  _In_  WPARAM wParam,
  _In_  LPARAM /*lParam*/
)
{
    INT_PTR ipRet = 0;

    switch (uMsg)
    {
    case WM_INITDIALOG:

        InitDlg(hwndDlg);

        ipRet = 0;

        break;

    case WM_COMMAND:

        if(HIWORD(wParam) == BN_CLICKED)
        {
            WORD wCtrlId = LOWORD(wParam);

            HandleCommand(hwndDlg, wCtrlId);

            ipRet = 1;
        }

        break;

    case WM_CLOSE:

        EndDialog(hwndDlg, 0);

        break;
    }

    return ipRet;
}

//
// FUNCTION: InitDlg()
//
// PURPOSE: Initialize the sample dialog box's position and controls.
//
void InitDlg(_In_ HWND hwndDlg)
{
    // Position the dialog box in the center of the primary monitor.
    RECT rcDlg;
    GetWindowRect(hwndDlg, &rcDlg);

    int xDlg = (GetSystemMetrics(SM_CXSCREEN) - (rcDlg.right - rcDlg.left)) / 2;
    int yDlg = (GetSystemMetrics(SM_CYSCREEN) - (rcDlg.bottom - rcDlg.top)) / 2;

    SetWindowPos(hwndDlg, NULL, xDlg, yDlg, 0, 0, SWP_NOZORDER | SWP_NOSIZE);

    // Set the default color and invalidate the status
    DWORD defaultColor = IDC_CHECK_SETRED_INVERTED;
    HWND hwndControl = GetDlgItem(hwndDlg, defaultColor);
    SendMessage(hwndControl, BM_SETCHECK, BST_CHECKED, 0);
    HandleCommand(hwndDlg, defaultColor);
    SetFocus(hwndControl);
}        

//
// FUNCTION: HandleCommand()
//
// PURPOSE: Take action in response to user action at the dialog box's controls.
//
void HandleCommand(_In_ HWND hwndDlg, _In_ WORD wCtrlId)
{
    switch (wCtrlId)
    {
        case IDC_CHECK_SETRED:
            {
                if (IsDlgButtonChecked(hwndDlg, IDC_CHECK_SETRED))
                {
                    MagSetFullscreenColorEffect(&g_MagEffectGrayscale);
                }
            }
            break;
        case IDC_CHECK_SETRED_INVERTED:
            {
                if (IsDlgButtonChecked(hwndDlg, IDC_CHECK_SETRED_INVERTED))
                {
                    MagSetFullscreenColorEffect(&g_MagEffectGrayscaleInverted);
                }
            }
            break;
        case IDC_CHECK_SET_INVERTED:
            {
                if (IsDlgButtonChecked(hwndDlg, IDC_CHECK_SET_INVERTED))
                {
                    MagSetFullscreenColorEffect(&g_MagEffectInverted);
                }
            }
            break;
        case IDC_CHECK_SET_WASHOUT:
        {
            if (IsDlgButtonChecked(hwndDlg, IDC_CHECK_SET_WASHOUT))
            {
                MagSetFullscreenColorEffect(&g_MagEffectWashout);
            }
        }
        break;
        case IDC_CHECK_SET_WASHOUT2:
        {
            if (IsDlgButtonChecked(hwndDlg, IDC_CHECK_SET_WASHOUT2))
            {
                MagSetFullscreenColorEffect(&g_MagEffectWashout2);
            }
        }
        break;
        case IDC_CHECK_SET_WASHOUT3:
        {
            if (IsDlgButtonChecked(hwndDlg, IDC_CHECK_SET_WASHOUT3))
            {
                MagSetFullscreenColorEffect(&g_MagEffectWashout3);
            }
        }
        break;
    }
}

Window Specific

You can also do this on a window by window basis by compiling a HLSL shader and setting it as an effect upon the window. This technique also works if you need to vary the intensity.

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        this.img.Source = new BitmapImage(new Uri("https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png"));
        this.Effect = new ColorComplementEffect();
    }
}

public class ColorComplementEffect : ShaderEffect
{
    public ColorComplementEffect()
    {
        PixelShader = _shader;
        UpdateShaderValue(InputProperty);
    }

    public Brush Input
    {
        get { return (Brush)GetValue(InputProperty); }
        set { SetValue(InputProperty, value); }
    }

    public static readonly DependencyProperty InputProperty = ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(ColorComplementEffect), 0);
    private static PixelShader _shader = new PixelShader() { UriSource = new Uri(@"cc.ps") };
}

Shader:

sampler2D implicitInput : register(s0);

float4 main(float2 uv : TEXCOORD) : COLOR
{
    float4 color = tex2D(implicitInput, uv);

    float4 complement;
    complement.r = (color.a - color.r) * 0.2;
    complement.g = (color.a - color.g) * 0.2;
    complement.b = (color.a - color.b) * 0.2;
    complement.a = color.a;

    return complement;
}


标签: c# wpf vision