diff --git a/CUE.NET.csproj b/CUE.NET.csproj index 2950544..a122d31 100644 --- a/CUE.NET.csproj +++ b/CUE.NET.csproj @@ -45,6 +45,7 @@ + diff --git a/Devices/Keyboard/Brushes/AbstractBrush.cs b/Devices/Keyboard/Brushes/AbstractBrush.cs new file mode 100644 index 0000000..40daba7 --- /dev/null +++ b/Devices/Keyboard/Brushes/AbstractBrush.cs @@ -0,0 +1,41 @@ +using System.Drawing; +using CUE.NET.Helper; + +namespace CUE.NET.Devices.Keyboard.Brushes +{ + public abstract class AbstractBrush : IBrush + { + #region Properties & Fields + + public float Brightness { get; set; } + public float Opacity { get; set; } + + #endregion + + #region Constructors + + protected AbstractBrush(float brightness = 1f, float opacity = 1f) + { + this.Brightness = brightness; + this.Opacity = opacity; + } + + #endregion + + #region Methods + + public abstract Color GetColorAtPoint(RectangleF rectangle, PointF point); + + protected virtual Color FinalizeColor(Color color) + { + // Since we use HSV to calculate there is no way to make a color 'brighter' than 100% + // Be carefull with the naming: Since we use HSV the correct term is 'value' but outside we call it 'brightness' + // THIS IS NOT A HSB CALCULATION!!! + float finalBrightness = color.GetHSVValue() * (Brightness < 0 ? 0 : (Brightness > 1f ? 1f : Brightness)); + byte finalAlpha = (byte)(color.A * (Opacity < 0 ? 0 : (Opacity > 1f ? 1f : Opacity))); + return ColorHelper.ColorFromHSV(color.GetHue(), color.GetHSVSaturation(), finalBrightness, finalAlpha); + } + + #endregion + } +} diff --git a/Devices/Keyboard/Brushes/IBrush.cs b/Devices/Keyboard/Brushes/IBrush.cs index 7e23773..de0bcd4 100644 --- a/Devices/Keyboard/Brushes/IBrush.cs +++ b/Devices/Keyboard/Brushes/IBrush.cs @@ -4,6 +4,9 @@ namespace CUE.NET.Devices.Keyboard.Brushes { public interface IBrush { + float Brightness { get; set; } + float Opacity { get; set; } + Color GetColorAtPoint(RectangleF rectangle, PointF point); } } \ No newline at end of file diff --git a/Devices/Keyboard/Brushes/LinearGradientBrush.cs b/Devices/Keyboard/Brushes/LinearGradientBrush.cs index 7ab8482..172db0d 100644 --- a/Devices/Keyboard/Brushes/LinearGradientBrush.cs +++ b/Devices/Keyboard/Brushes/LinearGradientBrush.cs @@ -2,7 +2,6 @@ // ReSharper disable MemberCanBePrivate.Global // ReSharper disable ReturnTypeCanBeEnumerable.Global -using System; using System.Collections.Generic; using System.Drawing; using System.Linq; @@ -10,7 +9,7 @@ using CUE.NET.Helper; namespace CUE.NET.Devices.Keyboard.Brushes { - public class LinearGradientBrush : IBrush + public class LinearGradientBrush : AbstractBrush { #region Properties & Fields @@ -44,7 +43,7 @@ namespace CUE.NET.Devices.Keyboard.Brushes #region Methods - public Color GetColorAtPoint(RectangleF rectangle, PointF point) + public override Color GetColorAtPoint(RectangleF rectangle, PointF point) { if (!GradientStops.Any()) return Color.Transparent; if (GradientStops.Count == 1) return GradientStops.First().Color; @@ -75,7 +74,8 @@ namespace CUE.NET.Devices.Keyboard.Brushes byte colR = (byte)((gsAfter.Color.R - gsBefore.Color.R) * blendFactor + gsBefore.Color.R); byte colG = (byte)((gsAfter.Color.G - gsBefore.Color.G) * blendFactor + gsBefore.Color.G); byte colB = (byte)((gsAfter.Color.B - gsBefore.Color.B) * blendFactor + gsBefore.Color.B); - return Color.FromArgb(colA, colR, colG, colB); + + return FinalizeColor(Color.FromArgb(colA, colR, colG, colB)); } #endregion diff --git a/Devices/Keyboard/Brushes/RainbowBrush.cs b/Devices/Keyboard/Brushes/RainbowBrush.cs index 011408d..813d443 100644 --- a/Devices/Keyboard/Brushes/RainbowBrush.cs +++ b/Devices/Keyboard/Brushes/RainbowBrush.cs @@ -1,10 +1,11 @@ -using System; +// ReSharper disable MemberCanBePrivate.Global + using System.Drawing; using CUE.NET.Helper; namespace CUE.NET.Devices.Keyboard.Brushes { - public class RainbowBrush : IBrush + public class RainbowBrush : AbstractBrush { #region Properties & Fields @@ -12,61 +13,39 @@ namespace CUE.NET.Devices.Keyboard.Brushes public PointF EndPoint { get; set; } = new PointF(1f, 0.5f); public float StartHue { get; set; } public float EndHue { get; set; } - public int Alpha { get; set; } = 255; #endregion #region Constructors - public RainbowBrush(float startHue = 0f, float endHue = 360f, int alpha = 255) + public RainbowBrush(float startHue = 0f, float endHue = 360f) { this.StartHue = startHue; this.EndHue = endHue; - this.Alpha = alpha; } - public RainbowBrush(PointF startPoint, PointF endPoint, float startHue = 0f, float endHue = 360f, int alpha = 255) + public RainbowBrush(PointF startPoint, PointF endPoint, float startHue = 0f, float endHue = 360f) { this.StartPoint = startPoint; this.EndPoint = endPoint; this.StartHue = startHue; this.EndHue = endHue; - this.Alpha = alpha; } #endregion #region Methods - public Color GetColorAtPoint(RectangleF rectangle, PointF point) + public override Color GetColorAtPoint(RectangleF rectangle, PointF point) { PointF startPoint = new PointF(StartPoint.X * rectangle.Width, StartPoint.Y * rectangle.Height); PointF endPoint = new PointF(EndPoint.X * rectangle.Width, EndPoint.Y * rectangle.Height); float offset = GradientHelper.CalculateGradientOffset(startPoint, endPoint, point); float range = EndHue - StartHue; - float progress = (StartHue + (range * offset)) / 360f; - float div = (Math.Abs(progress % 1) * 6); - int value = (int)((div % 1) * 255); - - switch ((int)div) - { - case 0: - return Color.FromArgb(Alpha, 255, value, 0); - case 1: - return Color.FromArgb(Alpha, 255 - value, 255, 0); - case 2: - return Color.FromArgb(Alpha, 0, 255, value); - case 3: - return Color.FromArgb(Alpha, 0, 255 - value, 255); - case 4: - return Color.FromArgb(Alpha, value, 0, 255); - case 5: - return Color.FromArgb(Alpha, 255, 0, 255 - value); - default: - return Color.Transparent; - } + float hue = (StartHue + (range * offset)) % 360f; + return FinalizeColor(ColorHelper.ColorFromHSV(hue, 1f, 1f)); } #endregion diff --git a/Devices/Keyboard/Brushes/SolidColorBrush.cs b/Devices/Keyboard/Brushes/SolidColorBrush.cs index 148f949..79899b3 100644 --- a/Devices/Keyboard/Brushes/SolidColorBrush.cs +++ b/Devices/Keyboard/Brushes/SolidColorBrush.cs @@ -1,8 +1,10 @@ +// ReSharper disable MemberCanBePrivate.Global + using System.Drawing; namespace CUE.NET.Devices.Keyboard.Brushes { - public class SolidColorBrush : IBrush + public class SolidColorBrush : AbstractBrush { #region Properties & Fields @@ -21,9 +23,9 @@ namespace CUE.NET.Devices.Keyboard.Brushes #region Methods - public Color GetColorAtPoint(RectangleF rectangle, PointF point) + public override Color GetColorAtPoint(RectangleF rectangle, PointF point) { - return Color; // A solid color brush returns the same color no matter the point + return FinalizeColor(Color); } #endregion diff --git a/Helper/ColorHelper.cs b/Helper/ColorHelper.cs index 16be988..8f3df61 100644 --- a/Helper/ColorHelper.cs +++ b/Helper/ColorHelper.cs @@ -7,6 +7,8 @@ namespace CUE.NET.Helper { public static class ColorHelper { + #region byte/float conversion + public static float GetFloatA(this Color color) { return color.A / 255f; @@ -27,21 +29,6 @@ namespace CUE.NET.Helper return color.B / 255f; } - public static Color Blend(this Color bg, Color fg) - { - if (fg.A == 255) - return fg; - - if (fg.A == 0) - return bg; - - float resultA = (1 - (1 - fg.GetFloatA()) * (1 - bg.GetFloatA())); - float resultR = (fg.GetFloatR() * fg.GetFloatA() / resultA + bg.GetFloatR() * bg.GetFloatA() * (1 - fg.GetFloatA()) / resultA); - float resultG = (fg.GetFloatG() * fg.GetFloatA() / resultA + bg.GetFloatG() * bg.GetFloatA() * (1 - fg.GetFloatA()) / resultA); - float resultB = (fg.GetFloatB() * fg.GetFloatA() / resultA + bg.GetFloatB() * bg.GetFloatA() * (1 - fg.GetFloatA()) / resultA); - return CreateColorFromFloat(resultA, resultR, resultG, resultB); - } - public static Color CreateColorFromFloat(float a, float r, float g, float b) { return Color.FromArgb(GetIntColorFromFloat(a), GetIntColorFromFloat(r), GetIntColorFromFloat(g), GetIntColorFromFloat(b)); @@ -50,7 +37,79 @@ namespace CUE.NET.Helper private static byte GetIntColorFromFloat(float f) { float calcF = (float)Math.Max(0f, Math.Min(1f, f)); - return (byte)(calcF.Equals(1f) ? 255 : calcF * 256.0); + return (byte)(calcF.Equals(1f) ? 255 : calcF * 256f); } + + #endregion + + #region Blending + + public static Color Blend(this Color bg, Color fg) + { + if (fg.A == 255) + return fg; + + if (fg.A == 0) + return bg; + + float resultA = (1f - (1f - fg.GetFloatA()) * (1f - bg.GetFloatA())); + float resultR = (fg.GetFloatR() * fg.GetFloatA() / resultA + bg.GetFloatR() * bg.GetFloatA() * (1f - fg.GetFloatA()) / resultA); + float resultG = (fg.GetFloatG() * fg.GetFloatA() / resultA + bg.GetFloatG() * bg.GetFloatA() * (1f - fg.GetFloatA()) / resultA); + float resultB = (fg.GetFloatB() * fg.GetFloatA() / resultA + bg.GetFloatB() * bg.GetFloatA() * (1f - fg.GetFloatA()) / resultA); + return CreateColorFromFloat(resultA, resultR, resultG, resultB); + } + + #endregion + + #region RGB/HSV conversion + // https://en.wikipedia.org/wiki/HSL_and_HSV + + public static float GetHSVSaturation(this Color color) + { + int max = Math.Max(color.R, Math.Max(color.G, color.B)); + int min = Math.Min(color.R, Math.Min(color.G, color.B)); + + return (max == 0) ? 0 : 1f - ((float)min / (float)max); + } + + public static float GetHSVValue(this Color color) + { + return Math.Max(color.R, Math.Max(color.G, color.B)) / 255f; + } + + // Based on http://stackoverflow.com/questions/3018313/algorithm-to-convert-rgb-to-hsv-and-hsv-to-rgb-in-range-0-255-for-both/6930407#6930407 as of 27.09.2015 + public static Color ColorFromHSV(float hue, float saturation, float value, byte alpha = 255) + { + if (saturation <= 0.0) + { + int val = GetIntColorFromFloat(value); + return Color.FromArgb(alpha, val, val, val); + } + + float hh = (hue % 360f) / 60f; + int i = (int)hh; + float ff = hh - i; + float p = value * (1f - saturation); + float q = value * (1f - (saturation * ff)); + float t = value * (1f - (saturation * (1f - ff))); + + switch (i) + { + case 0: + return Color.FromArgb(alpha, GetIntColorFromFloat(value), GetIntColorFromFloat(t), GetIntColorFromFloat(p)); + case 1: + return Color.FromArgb(alpha, GetIntColorFromFloat(q), GetIntColorFromFloat(value), GetIntColorFromFloat(p)); + case 2: + return Color.FromArgb(alpha, GetIntColorFromFloat(p), GetIntColorFromFloat(value), GetIntColorFromFloat(t)); + case 3: + return Color.FromArgb(alpha, GetIntColorFromFloat(p), GetIntColorFromFloat(q), GetIntColorFromFloat(value)); + case 4: + return Color.FromArgb(alpha, GetIntColorFromFloat(t), GetIntColorFromFloat(p), GetIntColorFromFloat(value)); + default: + return Color.FromArgb(alpha, GetIntColorFromFloat(value), GetIntColorFromFloat(p), GetIntColorFromFloat(q)); + } + } + + #endregion } }