Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add themed image effect #246

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions src/Gemini/Framework/ShaderEffects/Converters/BindingProxy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;

namespace Gemini.Framework.ShaderEffects.Converters
{
// From http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/
public class BindingProxy : Freezable
{
#region Overrides of Freezable

protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}

#endregion

public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}

// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
}
44 changes: 44 additions & 0 deletions src/Gemini/Framework/ShaderEffects/Converters/IsNullConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;

namespace Gemini.Framework.ShaderEffects.Converters
{
// From https://github.com/MahApps/MahApps.Metro/blob/9c331aa20b2b3fd6a9426e0687cfc535511bf134/MahApps.Metro/Converters/IsNullConverter.cs

/// <summary>
/// Converts the value from true to false and false to true.
/// </summary>
public sealed class IsNullConverter : IValueConverter
{
private static IsNullConverter _instance;

// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static IsNullConverter()
{
}

private IsNullConverter()
{
}

public static IsNullConverter Instance
{
get { return _instance ?? (_instance = new IsNullConverter()); }
}

public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null == value;
}

public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return Binding.DoNothing;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
using System.Windows.Media;

namespace Gemini.Framework.ShaderEffects.Converters
{
public class SolidColorBrushToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
SolidColorBrush brush = value as SolidColorBrush;
if (brush != null)
return brush.Color;

return default(Color?);
}

public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null)
{
Color color = (Color)value;
return new SolidColorBrush(color);
}

return default(SolidColorBrush);
}
}
}
145 changes: 145 additions & 0 deletions src/Gemini/Framework/ShaderEffects/Scripts/ThemedImage.fx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/// <class>ThemedImageEffect</class>
/// <description>Converts an input image into an image that blends in with the target background.</description>

/// <summary>The input image</summary>
sampler2D Input : register(S0);

/// <summary>Background color of the image.</summary>
/// <defaultValue>#FFF6F6F6</defaultValue>
float4 Background : register(C0);

/// <summary>1.0 if the image should be rendered enabled, 0.0 if it should be disabled (grayscaled).</summary>
/// <minValue>0.0</minValue>
/// <maxValue>1.0</maxValue>
/// <defaultValue>0.0</defaultValue>
float IsEnabled : register(C1);

float3 rgb2hsl(in float4 RGB)
{
float r = RGB.r; // Red, range: [0..1]
float g = RGB.g; // Green, range: [0..1]
float b = RGB.b; // Blue, range: [0..1]

float maxChannel = (r > g && r > b) ? r : (g > b) ? g : b;
float minChannel = (r < g && r < b) ? r : (g < b) ? g : b;

float h = (maxChannel + minChannel) / 2.0f; // Hue, range: [0..1]
float s = (maxChannel + minChannel) / 2.0f; // Saturation, range: [0..1]
float l = (maxChannel + minChannel) / 2.0f; // Lightness, range: [0..1]

if (maxChannel == minChannel)
{
h = s = 0.0f;
}
else
{
float d = maxChannel - minChannel;
s = (l > 0.5f) ? d / (2.0f - maxChannel - minChannel) : d / (maxChannel + minChannel);

if (r > g && r > b) // maxChannel == r
h = (g - b) / d + (g < b ? 6.0f : 0.0f);
else if (g > b) // maxChannel == g
h = (b - r) / d + 2.0f;
else // maxChannel = b
h = (r - g) / d + 4.0f;

h /= 6.0f;
}

return float3(h, s, l);
}

float hue2rgb(in float p, in float q, in float t)
{
if (t < 0.0f) t += 1.0f;
if (t > 1.0f) t -= 1.0f;
if (t < 1.0f / 6.0f) return p + (q - p) * 6.0f * t;
if (t < 1.0f / 2.0f) return q;
if (t < 2.0f / 3.0f) return p + (q - p) * (2.0f / 3.0f - t) * 6.0f;
return p;
}

float3 hsl2rgb(in float3 HSL)
{
float h = HSL[0];
float s = HSL[1];
float l = HSL[2];

float q = (l < 0.5f) ? l * (1.0f + s) : l + s - l * s;
float p = 2.0f * l - q;

float r = hue2rgb(p, q, h + 1.0f / 3.0f);
float g = hue2rgb(p, q, h);
float b = hue2rgb(p, q, h - 1.0f / 3.0f);

return float3(r, g, b);
}

float3 rgb2gray(in float3 RGB)
{
// https://en.wikipedia.org/wiki/Relative_luminance
// https://en.wikipedia.org/wiki/SRGB
// Convert the gamma compressed RGB values to linear RGB
RGB.r = (RGB.r <= 0.04045f) ? RGB.r / 12.92f : pow((RGB.r + 0.055f) / 1.055f, 2.4f);
RGB.g = (RGB.g <= 0.04045f) ? RGB.g / 12.92f : pow((RGB.g + 0.055f) / 1.055f, 2.4f);
RGB.b = (RGB.b <= 0.04045f) ? RGB.b / 12.92f : pow((RGB.b + 0.055f) / 1.055f, 2.4f);
float y = dot(float3(0.2126f, 0.7152f, 0.0722f), RGB.rgb);
return y <= 0.0031308f ? 12.92f * y : 1.055f * pow(y, 1.0f/2.4f) - 0.055f;
}

float4 main(float2 uv : TEXCOORD) : COLOR
{
// This performs two conversions.
// 1. The lightness of the image is transformed so that the constant "halo" lightness blends in with the background.
// - This has the effect of eliminating the halo visually.
// - The "halo" lightness is an immutable constant, and is not calculated from the input image.
// 2. The image is converted to grayscale if the IsEnabled parameter is 0.

// Refer to http://stackoverflow.com/questions/36778989/vs2015-icon-guide-color-inversion

// First, loopk up original image color
float4 pxRGBA = tex2D(Input, uv.xy);

// For performance reason, the WPF multiplies each color channel
// by the alpha channel before sending the Input into HLSL shader.
// So let's convert the color to be non-premultiplied.
if (pxRGBA.a > 0.0) pxRGBA.rgb /= pxRGBA.a;

// Convert the color space from RGB to HSL.
float3 pxHSL = rgb2hsl(pxRGBA);

// Define lightness of default outlined color(#F6F6F6)
float haloHSL2 = 0.96470588235294f;

// Lightness of Background color is bgHSL[2]
float3 bgHSL = rgb2hsl(Background);

if (bgHSL[2] < 0.5f)
{
haloHSL2 = 1.0f - haloHSL2;
pxHSL[2] = 1.0f - pxHSL[2];
}

if(pxHSL[2] <= haloHSL2)
{
pxHSL[2] = bgHSL[2] / haloHSL2 * pxHSL[2];
}
else
{
pxHSL[2] = (1.0f - bgHSL[2]) / (1.0f - haloHSL2) * (pxHSL[2] - 1.0f) + 1.0f;
}

// Convert the color space from HSL to RGB.
pxRGBA.rgb = hsl2rgb(pxHSL);

// if !IsEnabled, convert to grayscale.
if (IsEnabled == 0.0f)
{
pxRGBA.rgb = rgb2gray(pxRGBA.rgb);
}

// Convert the color to a PreMultiplied color.
pxRGBA.rgb *= pxRGBA.a;

return pxRGBA;
}
47 changes: 47 additions & 0 deletions src/Gemini/Framework/ShaderEffects/ThemedImageEffect.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;

namespace Gemini.Framework.ShaderEffects
{
/// <summary>Converts an input image into an image that blends in with the target background.</summary>
public class ThemedImageEffect : ShaderEffectBase<ThemedImageEffect>
{
public static readonly DependencyProperty InputProperty = RegisterPixelShaderSamplerProperty("Input", typeof(ThemedImageEffect), 0);

public static readonly DependencyProperty BackgroundProperty = DependencyProperty.Register("Background", typeof(Color), typeof(ThemedImageEffect), new UIPropertyMetadata(Color.FromArgb(255, 246, 246, 246), PixelShaderConstantCallback(0)));

public static readonly DependencyProperty IsEnabledProperty = DependencyProperty.Register("IsEnabled", typeof(double), typeof(ThemedImageEffect), new UIPropertyMetadata(((double)(1D)), PixelShaderConstantCallback(1)));

public ThemedImageEffect()
{
UpdateShaderValue(InputProperty);
UpdateShaderValue(BackgroundProperty);
UpdateShaderValue(IsEnabledProperty);
}

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

/// <summary>Background color of the image.</summary>
public Color Background
{
get { return ((Color)(GetValue(BackgroundProperty))); }
set { SetValue(BackgroundProperty, value); }
}

/// <summary>1.0 if the image should be rendered enabled, 0.0 if it should be disabled (grayscaled).</summary>
public double IsEnabled
{
get { return ((double)(this.GetValue(IsEnabledProperty))); }
set { this.SetValue(IsEnabledProperty, value); }
}
}
}
Binary file not shown.
3 changes: 2 additions & 1 deletion src/Gemini/Gemini.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@
</Compile>
<DesignData Include="Modules\Settings\SampleData\SettingsViewModelSampleData.xaml" />
<Resource Include="Framework\ShaderEffects\GrayscaleEffect.ps" />
<Resource Include="Framework\ShaderEffects\ThemedImageEffect.ps" />
<None Update="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<Resource Include="Resources\Icons\*.png" />
<Resource Include="Resources\Icons\*.png" />
<EmbeddedResource Update="Properties\Resources.de.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.de.Designer.cs</LastGenOutput>
Expand Down
27 changes: 21 additions & 6 deletions src/Gemini/Themes/VS2013/Controls/Menu.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@
-->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:shaderEffects="clr-namespace:Gemini.Framework.ShaderEffects"
xmlns:converters="clr-namespace:Gemini.Framework.ShaderEffects.Converters"
xmlns:xcad="http://schemas.xceed.com/wpf/xaml/avalondock">

<converters:BindingProxy x:Key="DefaultBackground" Data="{DynamicResource MenuDefaultBackground}"/>
<converters:SolidColorBrushToColorConverter x:Key="SolidColorBrushToColorConverter"/>

<Geometry x:Key="Checkmark">
M 0,5.1 L 1.7,5.2 L 3.4,7.1 L 8,0.4 L 9.2,0 L 3.3,10.8 Z
</Geometry>
Expand Down Expand Up @@ -115,7 +119,7 @@
<ControlTemplate x:Key="{ComponentResourceKey ResourceId=TopLevelItemTemplateKey}" TargetType="MenuItem">
<Border Name="Border">
<Grid>
<ContentPresenter Margin="6,3,6,3" ContentSource="Header" RecognizesAccessKey="True" />
<ContentPresenter Margin="8,3,8,3" ContentSource="Header" RecognizesAccessKey="True" />
</Grid>
</Border>
<ControlTemplate.Triggers>
Expand Down Expand Up @@ -147,8 +151,7 @@
Background="Transparent"
Padding="2,0,1,0">
<Grid>
<Border x:Name="Check"
Margin="3"
<Border x:Name="Check"
VerticalAlignment="Center" HorizontalAlignment="Center"
Width="16" Height="16"
Visibility="Collapsed"
Expand All @@ -164,7 +167,13 @@
HorizontalAlignment="Center"
VerticalAlignment="Center"
Height="16" Width="16"
ContentSource="Icon" />
ContentSource="Icon">
<ContentPresenter.Effect>
<shaderEffects:ThemedImageEffect Background="{Binding Converter={StaticResource SolidColorBrushToColorConverter}, Mode=OneWay,
Source={StaticResource DefaultBackground}, Path=Data}"
IsEnabled="{Binding Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}, Path=IsEnabled}"/>
</ContentPresenter.Effect>
</ContentPresenter>
</Border>
</Grid>
</Border>
Expand Down Expand Up @@ -222,7 +231,13 @@
HorizontalAlignment="Center"
VerticalAlignment="Center"
Height="16" Width="16"
ContentSource="Icon" />
ContentSource="Icon">
<ContentPresenter.Effect>
<shaderEffects:ThemedImageEffect Background="{Binding Converter={StaticResource SolidColorBrushToColorConverter}, Mode=OneWay,
Source={StaticResource DefaultBackground}, Path=Data}"
IsEnabled="{Binding Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}, Path=IsEnabled}"/>
</ContentPresenter.Effect>
</ContentPresenter>
</Grid>
</Border>
<Border Grid.Column="1" Padding="6 2 0 2">
Expand Down
Loading