diff --git a/src/Gemini/Framework/ShaderEffects/Converters/BindingProxy.cs b/src/Gemini/Framework/ShaderEffects/Converters/BindingProxy.cs
new file mode 100644
index 000000000..4516ba940
--- /dev/null
+++ b/src/Gemini/Framework/ShaderEffects/Converters/BindingProxy.cs
@@ -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));
+ }
+}
diff --git a/src/Gemini/Framework/ShaderEffects/Converters/IsNullConverter.cs b/src/Gemini/Framework/ShaderEffects/Converters/IsNullConverter.cs
new file mode 100644
index 000000000..3faa4a968
--- /dev/null
+++ b/src/Gemini/Framework/ShaderEffects/Converters/IsNullConverter.cs
@@ -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
+
+ ///
+ /// Converts the value from true to false and false to true.
+ ///
+ 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;
+ }
+ }
+}
diff --git a/src/Gemini/Framework/ShaderEffects/Converters/SolidColorBrushToColorConverter.cs b/src/Gemini/Framework/ShaderEffects/Converters/SolidColorBrushToColorConverter.cs
new file mode 100644
index 000000000..fa372e55a
--- /dev/null
+++ b/src/Gemini/Framework/ShaderEffects/Converters/SolidColorBrushToColorConverter.cs
@@ -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);
+ }
+ }
+}
diff --git a/src/Gemini/Framework/ShaderEffects/Scripts/ThemedImage.fx b/src/Gemini/Framework/ShaderEffects/Scripts/ThemedImage.fx
new file mode 100644
index 000000000..f7ab7241e
--- /dev/null
+++ b/src/Gemini/Framework/ShaderEffects/Scripts/ThemedImage.fx
@@ -0,0 +1,145 @@
+/// ThemedImageEffect
+/// Converts an input image into an image that blends in with the target background.
+
+/// The input image
+sampler2D Input : register(S0);
+
+/// Background color of the image.
+/// #FFF6F6F6
+float4 Background : register(C0);
+
+/// 1.0 if the image should be rendered enabled, 0.0 if it should be disabled (grayscaled).
+/// 0.0
+/// 1.0
+/// 0.0
+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;
+}
\ No newline at end of file
diff --git a/src/Gemini/Framework/ShaderEffects/ThemedImageEffect.cs b/src/Gemini/Framework/ShaderEffects/ThemedImageEffect.cs
new file mode 100644
index 000000000..959832f14
--- /dev/null
+++ b/src/Gemini/Framework/ShaderEffects/ThemedImageEffect.cs
@@ -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
+{
+ /// Converts an input image into an image that blends in with the target background.
+ public class ThemedImageEffect : ShaderEffectBase
+ {
+ 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); }
+ }
+
+ /// Background color of the image.
+ public Color Background
+ {
+ get { return ((Color)(GetValue(BackgroundProperty))); }
+ set { SetValue(BackgroundProperty, value); }
+ }
+
+ /// 1.0 if the image should be rendered enabled, 0.0 if it should be disabled (grayscaled).
+ public double IsEnabled
+ {
+ get { return ((double)(this.GetValue(IsEnabledProperty))); }
+ set { this.SetValue(IsEnabledProperty, value); }
+ }
+ }
+}
diff --git a/src/Gemini/Framework/ShaderEffects/ThemedImageEffect.ps b/src/Gemini/Framework/ShaderEffects/ThemedImageEffect.ps
new file mode 100644
index 000000000..00763ab6d
Binary files /dev/null and b/src/Gemini/Framework/ShaderEffects/ThemedImageEffect.ps differ
diff --git a/src/Gemini/Gemini.csproj b/src/Gemini/Gemini.csproj
index cc230fc55..8c17c0f29 100644
--- a/src/Gemini/Gemini.csproj
+++ b/src/Gemini/Gemini.csproj
@@ -31,11 +31,12 @@
+
SettingsSingleFileGenerator
Settings.Designer.cs
-
+
ResXFileCodeGenerator
Resources.de.Designer.cs
diff --git a/src/Gemini/Themes/VS2013/Controls/Menu.xaml b/src/Gemini/Themes/VS2013/Controls/Menu.xaml
index 97c961080..a60e84d32 100644
--- a/src/Gemini/Themes/VS2013/Controls/Menu.xaml
+++ b/src/Gemini/Themes/VS2013/Controls/Menu.xaml
@@ -13,8 +13,12 @@
-->
-
+
+
+
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
@@ -115,7 +119,7 @@
-
+
@@ -147,8 +151,7 @@
Background="Transparent"
Padding="2,0,1,0">
-
+ ContentSource="Icon">
+
+
+
+
@@ -222,7 +231,13 @@
HorizontalAlignment="Center"
VerticalAlignment="Center"
Height="16" Width="16"
- ContentSource="Icon" />
+ ContentSource="Icon">
+
+
+
+
diff --git a/src/Gemini/Themes/VS2013/Controls/Toolbar.xaml b/src/Gemini/Themes/VS2013/Controls/Toolbar.xaml
index 76bb1095f..ec5e7cc2b 100644
--- a/src/Gemini/Themes/VS2013/Controls/Toolbar.xaml
+++ b/src/Gemini/Themes/VS2013/Controls/Toolbar.xaml
@@ -2,7 +2,11 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:Gemini.Modules.ToolBars.Controls"
xmlns:shaderEffects="clr-namespace:Gemini.Framework.ShaderEffects"
+ xmlns:converters="clr-namespace:Gemini.Framework.ShaderEffects.Converters"
xmlns:controls="clr-namespace:Gemini.Framework.Controls">
+
+
+