diff --git a/RGB.NET.Brushes/Brushes/ConicalGradientBrush.cs b/RGB.NET.Brushes/Brushes/ConicalGradientBrush.cs new file mode 100644 index 0000000..c391f3c --- /dev/null +++ b/RGB.NET.Brushes/Brushes/ConicalGradientBrush.cs @@ -0,0 +1,97 @@ +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable MemberCanBeProtected.Global +// ReSharper disable ReturnTypeCanBeEnumerable.Global +// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global +// ReSharper disable UnusedMember.Global + +using System; +using RGB.NET.Brushes.Gradients; +using RGB.NET.Core; + +namespace RGB.NET.Brushes +{ + /// + /// Represents a brush drawing a conical gradient. + /// + public class ConicalGradientBrush : AbstractBrush, IGradientBrush + { + #region Properties & Fields + + /// + /// Gets or sets the origin (radian-angle) this is drawn to. (default: -π/2) + /// + public float Origin { get; set; } = (float)Math.Atan2(-1, 0); + + /// + /// Gets or sets the center (as percentage in the range [0..1]) of the drawn by this . (default: 0.5, 0.5) + /// + public Point Center { get; set; } = new Point(0.5, 0.5); + + /// + /// Gets or sets the gradient drawn by the brush. If null it will default to full transparent. + /// + public IGradient Gradient { get; set; } + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + public ConicalGradientBrush() + { } + + /// + /// Initializes a new instance of the class. + /// + /// The drawn by this . + public ConicalGradientBrush(IGradient gradient) + { + this.Gradient = gradient; + } + + /// + /// Initializes a new instance of the class. + /// + /// The center (as percentage in the range [0..1]). + /// The drawn by this . + public ConicalGradientBrush(Point center, IGradient gradient) + { + this.Center = center; + this.Gradient = gradient; + } + + /// + /// Initializes a new instance of the class. + /// + /// The center (as percentage in the range [0..1]). + /// The origin (radian-angle) the is drawn to. + /// The drawn by this . + public ConicalGradientBrush(Point center, float origin, IGradient gradient) + { + this.Center = center; + this.Origin = origin; + this.Gradient = gradient; + } + + #endregion + + #region Methods + + /// + protected override Color GetColorAtPoint(Rectangle rectangle, BrushRenderTarget renderTarget) + { + double centerX = rectangle.Size.Width * Center.X; + double centerY = rectangle.Size.Height * Center.Y; + + double angle = Math.Atan2(renderTarget.Point.Y - centerY, renderTarget.Point.X - centerX) - Origin; + if (angle < 0) angle += Math.PI * 2; + double offset = angle / (Math.PI * 2); + + return Gradient.GetColor(offset); + } + + #endregion + } +} diff --git a/RGB.NET.Brushes/Brushes/IGradientBrush.cs b/RGB.NET.Brushes/Brushes/IGradientBrush.cs new file mode 100644 index 0000000..5e111c1 --- /dev/null +++ b/RGB.NET.Brushes/Brushes/IGradientBrush.cs @@ -0,0 +1,16 @@ +using RGB.NET.Brushes.Gradients; +using RGB.NET.Core; + +namespace RGB.NET.Brushes +{ + /// + /// Represents a basic gradient-brush. + /// + public interface IGradientBrush : IBrush + { + /// + /// Gets the used by this . + /// + IGradient Gradient { get; } + } +} diff --git a/RGB.NET.Brushes/Brushes/LinearGradientBrush.cs b/RGB.NET.Brushes/Brushes/LinearGradientBrush.cs new file mode 100644 index 0000000..1b178c0 --- /dev/null +++ b/RGB.NET.Brushes/Brushes/LinearGradientBrush.cs @@ -0,0 +1,88 @@ +// ReSharper disable CollectionNeverUpdated.Global +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable MemberCanBeProtected.Global +// ReSharper disable ReturnTypeCanBeEnumerable.Global +// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global +// ReSharper disable UnusedMember.Global + +using RGB.NET.Brushes.Gradients; +using RGB.NET.Brushes.Helper; +using RGB.NET.Core; + +namespace RGB.NET.Brushes +{ + /// + /// Represents a brush drawing a linear gradient. + /// + public class LinearGradientBrush : AbstractBrush, IGradientBrush + { + #region Properties & Fields + + /// + /// Gets or sets the start (as percentage in the range [0..1]) of the drawn by this . (default: 0.0, 0.5) + /// + public Point StartPoint { get; set; } = new Point(0, 0.5); + + /// + /// Gets or sets the end (as percentage in the range [0..1]) of the drawn by this . (default: 1.0, 0.5) + /// + public Point EndPoint { get; set; } = new Point(1, 0.5); + + /// + public IGradient Gradient { get; set; } + + #endregion + + #region Constructor + + /// + /// Initializes a new instance of the class. + /// + public LinearGradientBrush() + { } + + /// + /// Initializes a new instance of the class. + /// + /// The drawn by this . + public LinearGradientBrush(IGradient gradient) + { + this.Gradient = gradient; + } + /// + /// Initializes a new instance of the class. + /// + /// The start (as percentage in the range [0..1]). + /// The end (as percentage in the range [0..1]). + /// The drawn by this . + public LinearGradientBrush(Point startPoint, Point endPoint, IGradient gradient) + { + this.StartPoint = startPoint; + this.EndPoint = endPoint; + this.Gradient = gradient; + } + + #endregion + + #region Methods + + /// + /// Gets the color at an specific point assuming the brush is drawn into the given rectangle. + /// + /// The rectangle in which the brush should be drawn. + /// The target (key/point) from which the color should be taken. + /// The color at the specified point. + protected override Color GetColorAtPoint(Rectangle rectangle, BrushRenderTarget renderTarget) + { + if (Gradient == null) return Color.Transparent; + + Point startPoint = new Point(StartPoint.X * rectangle.Size.Width, StartPoint.Y * rectangle.Size.Height); + Point endPoint = new Point(EndPoint.X * rectangle.Size.Width, EndPoint.Y * rectangle.Size.Height); + + double offset = GradientHelper.CalculateLinearGradientOffset(startPoint, endPoint, renderTarget.Point); + return Gradient.GetColor(offset); + } + + #endregion + } +} diff --git a/RGB.NET.Brushes/Brushes/RadialGradientBrush.cs b/RGB.NET.Brushes/Brushes/RadialGradientBrush.cs new file mode 100644 index 0000000..6812134 --- /dev/null +++ b/RGB.NET.Brushes/Brushes/RadialGradientBrush.cs @@ -0,0 +1,82 @@ +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global +// ReSharper disable UnusedMember.Global + +using System; +using RGB.NET.Brushes.Gradients; +using RGB.NET.Brushes.Helper; +using RGB.NET.Core; + +namespace RGB.NET.Brushes +{ + /// + /// Represents a brush drawing a radial gradient around a center point. + /// + public class RadialGradientBrush : AbstractBrush, IGradientBrush + { + #region Properties & Fields + + /// + /// Gets or sets the center (as percentage in the range [0..1]) around which the should be drawn. (default: 0.5, 0.5) + /// + public Point Center { get; set; } = new Point(0.5, 0.5); + + /// + public IGradient Gradient { get; set; } + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + public RadialGradientBrush() + { } + + /// + /// Initializes a new instance of the class. + /// + /// The gradient drawn by the brush. + public RadialGradientBrush(IGradient gradient) + { + this.Gradient = gradient; + } + + /// + /// Initializes a new instance of the class. + /// + /// The center point (as percentage in the range [0..1]). + /// The gradient drawn by the brush. + public RadialGradientBrush(Point center, IGradient gradient) + { + this.Center = center; + this.Gradient = gradient; + } + + #endregion + + #region Methods + + /// + protected override Color GetColorAtPoint(Rectangle rectangle, BrushRenderTarget renderTarget) + { + if (Gradient == null) return Color.Transparent; + + Point centerPoint = new Point(rectangle.Location.X + (rectangle.Size.Width * Center.X), rectangle.Location.Y + (rectangle.Size.Height * Center.Y)); + + // Calculate the distance to the farthest point from the center as reference (this has to be a corner) + // ReSharper disable once RedundantCast - never trust this ... + double refDistance = Math.Max(Math.Max(Math.Max(GradientHelper.CalculateDistance(rectangle.Location, centerPoint), + GradientHelper.CalculateDistance(new Point(rectangle.Location.X + rectangle.Size.Width, rectangle.Location.Y), centerPoint)), + GradientHelper.CalculateDistance(new Point(rectangle.Location.X, rectangle.Location.Y + rectangle.Size.Height), centerPoint)), + GradientHelper.CalculateDistance(new Point(rectangle.Location.X + rectangle.Size.Width, rectangle.Location.Y + rectangle.Size.Height), centerPoint)); + + double distance = GradientHelper.CalculateDistance(renderTarget.Point, centerPoint); + double offset = distance / refDistance; + return Gradient.GetColor(offset); + } + + #endregion + } +} diff --git a/RGB.NET.Brushes/Brushes/SolidColorBrush.cs b/RGB.NET.Brushes/Brushes/SolidColorBrush.cs new file mode 100644 index 0000000..03952d7 --- /dev/null +++ b/RGB.NET.Brushes/Brushes/SolidColorBrush.cs @@ -0,0 +1,67 @@ +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global + +using RGB.NET.Core; + +namespace RGB.NET.Brushes +{ + /// + /// Represents a brush drawing only a single color. + /// + public class SolidColorBrush : AbstractBrush + { + #region Properties & Fields + + /// + /// Gets or sets the drawn by this . + /// + public Color Color { get; set; } + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The drawn by this . + public SolidColorBrush(Color color) + { + this.Color = color; + } + + #endregion + + #region Methods + + /// + protected override Color GetColorAtPoint(Rectangle rectangle, BrushRenderTarget renderTarget) + { + return Color; + } + + #endregion + + #region Operators + + /// + /// Converts a to a . + /// + /// The to convert. + public static explicit operator SolidColorBrush(Color color) + { + return new SolidColorBrush(color); + } + + /// + /// Converts a to a . + /// + /// The to convert. + public static implicit operator Color(SolidColorBrush brush) + { + return brush.Color; + } + + #endregion + } +} \ No newline at end of file diff --git a/RGB.NET.Brushes/Gradients/AbstractGradient.cs b/RGB.NET.Brushes/Gradients/AbstractGradient.cs new file mode 100644 index 0000000..b4dc152 --- /dev/null +++ b/RGB.NET.Brushes/Gradients/AbstractGradient.cs @@ -0,0 +1,87 @@ +// ReSharper disable MemberCanBeProtected.Global +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global + +using System.Collections.Generic; +using System.Linq; +using RGB.NET.Core; + +namespace RGB.NET.Brushes.Gradients +{ + /// + /// Represents a basic gradient. + /// + public abstract class AbstractGradient : IGradient + { + #region Properties & Fields + + /// + /// Gets a list of the stops used by this . + /// + public IList GradientStops { get; } = new List(); + + /// + /// Gets or sets if the Gradient wraps around if there isn't a second stop to take. + /// Example: There is a stop at offset 0.0, 0.5 and 0.75. + /// Without wrapping offset 1.0 will be calculated the same as 0.75; with wrapping it would be the same as 0.0. + /// + public bool WrapGradient { get; set; } + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + protected AbstractGradient() + { } + + /// + /// Initializes a new instance of the class. + /// + /// The stops with which the gradient should be initialized. + protected AbstractGradient(params GradientStop[] gradientStops) + { + foreach (GradientStop gradientStop in gradientStops) + GradientStops.Add(gradientStop); + } + + /// + /// Initializes a new instance of the class. + /// + /// Specifies whether the gradient should wrapp or not (see for an example of what this means). + /// The stops with which the gradient should be initialized. + protected AbstractGradient(bool wrapGradient, params GradientStop[] gradientStops) + { + this.WrapGradient = wrapGradient; + + foreach (GradientStop gradientStop in gradientStops) + GradientStops.Add(gradientStop); + } + + #endregion + + #region Methods + + /// + /// Clips the offset and ensures, that it is inside the bounds of the stop list. + /// + /// + /// + protected double ClipOffset(double offset) + { + double max = GradientStops.Max(n => n.Offset); + if (offset > max) + return max; + + double min = GradientStops.Min(n => n.Offset); + return offset < min ? min : offset; + } + + /// + public abstract Color GetColor(double offset); + + #endregion + } +} diff --git a/RGB.NET.Brushes/Gradients/GradientStop.cs b/RGB.NET.Brushes/Gradients/GradientStop.cs new file mode 100644 index 0000000..e27776a --- /dev/null +++ b/RGB.NET.Brushes/Gradients/GradientStop.cs @@ -0,0 +1,42 @@ +// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global +// ReSharper disable MemberCanBePrivate.Global + +using RGB.NET.Core; + +namespace RGB.NET.Brushes.Gradients +{ + /// + /// Represents a stop on a gradient. + /// + public class GradientStop + { + #region Properties & Fields + + /// + /// Gets or sets the percentage offset to place this . This should be inside the range of [0..1] but it's not necessary. + /// + public double Offset { get; set; } + + /// + /// Gets or sets the of this . + /// + public Color Color { get; set; } + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The percentage offset to place this . + /// The of the . + public GradientStop(double offset, Color color) + { + this.Offset = offset; + this.Color = color; + } + + #endregion + } +} diff --git a/RGB.NET.Brushes/Gradients/IGradient.cs b/RGB.NET.Brushes/Gradients/IGradient.cs new file mode 100644 index 0000000..e8ffebc --- /dev/null +++ b/RGB.NET.Brushes/Gradients/IGradient.cs @@ -0,0 +1,17 @@ +using RGB.NET.Core; + +namespace RGB.NET.Brushes.Gradients +{ + /// + /// Represents a basic gradient. + /// + public interface IGradient + { + /// + /// Gets the of the on the specified offset. + /// + /// The percentage offset to take the from. + /// The at the specific offset. + Color GetColor(double offset); + } +} diff --git a/RGB.NET.Brushes/Gradients/LinearGradient.cs b/RGB.NET.Brushes/Gradients/LinearGradient.cs new file mode 100644 index 0000000..cbe66d8 --- /dev/null +++ b/RGB.NET.Brushes/Gradients/LinearGradient.cs @@ -0,0 +1,95 @@ +// ReSharper disable UnusedMember.Global + +using System.Collections.Generic; +using System.Linq; +using RGB.NET.Core; + +namespace RGB.NET.Brushes.Gradients +{ + /// + /// Represents a linear interpolated gradient with n stops. + /// + public class LinearGradient : AbstractGradient + { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + public LinearGradient() + { } + + /// + /// Initializes a new instance of the class. + /// + /// The stops with which the gradient should be initialized. + public LinearGradient(params GradientStop[] gradientStops) + : base(gradientStops) + { } + + /// + /// Initializes a new instance of the class. + /// + /// Specifies whether the gradient should wrapp or not (see for an example of what this means). + /// The stops with which the gradient should be initialized. + public LinearGradient(bool wrapGradient, params GradientStop[] gradientStops) + : base(wrapGradient, gradientStops) + { } + + #endregion + + #region Methods + + /// + /// Gets the linear interpolated at the given offset. + /// + /// The percentage offset to take the color from. + /// The at the specific offset. + public override Color GetColor(double offset) + { + if (GradientStops.Count == 0) return Color.Transparent; + if (GradientStops.Count == 1) return new Color(GradientStops.First().Color); + + GradientStop gsBefore; + GradientStop gsAfter; + + IList orderedStops = GradientStops.OrderBy(x => x.Offset).ToList(); + if (WrapGradient) + { + gsBefore = orderedStops.LastOrDefault(n => n.Offset <= offset); + if (gsBefore == null) + { + GradientStop lastStop = orderedStops[orderedStops.Count - 1]; + gsBefore = new GradientStop(lastStop.Offset - 1, lastStop.Color); + } + + gsAfter = orderedStops.FirstOrDefault(n => n.Offset >= offset); + if (gsAfter == null) + { + GradientStop firstStop = orderedStops[0]; + gsAfter = new GradientStop(firstStop.Offset + 1, firstStop.Color); + } + } + else + { + offset = ClipOffset(offset); + + gsBefore = orderedStops.Last(n => n.Offset <= offset); + gsAfter = orderedStops.First(n => n.Offset >= offset); + } + + double blendFactor = 0; + if (!gsBefore.Offset.Equals(gsAfter.Offset)) + blendFactor = ((offset - gsBefore.Offset) / (gsAfter.Offset - gsBefore.Offset)); + + byte colA = (byte)(((gsAfter.Color.A - gsBefore.Color.A) * blendFactor) + gsBefore.Color.A); + 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 new Color(colA, colR, colG, colB); + } + + #endregion + } +} diff --git a/RGB.NET.Brushes/Gradients/RainbowGradient.cs b/RGB.NET.Brushes/Gradients/RainbowGradient.cs new file mode 100644 index 0000000..66deb4b --- /dev/null +++ b/RGB.NET.Brushes/Gradients/RainbowGradient.cs @@ -0,0 +1,61 @@ +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global + +using RGB.NET.Core; + +namespace RGB.NET.Brushes.Gradients +{ + /// + /// Represents a rainbow gradient which circles through all colors of the HUE-color-space.
+ /// See as reference. + ///
+ public class RainbowGradient : IGradient + { + #region Properties & Fields + + /// + /// Gets or sets the hue (in degrees) to start from. + /// + public double StartHue { get; set; } + + /// + /// Gets or sets the hue (in degrees) to end the with. + /// + public double EndHue { get; set; } + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The hue (in degrees) to start from (default: 0) + /// The hue (in degrees) to end with (default: 360) + public RainbowGradient(double startHue = 0, double endHue = 360) + { + this.StartHue = startHue; + this.EndHue = endHue; + } + + #endregion + + #region Methods + + /// + /// Gets the color on the rainbow at the given offset. + /// + /// The percentage offset to take the color from. + /// The color at the specific offset. + public Color GetColor(double offset) + { + double range = EndHue - StartHue; + double hue = (StartHue + (range * offset)) % 360f; + if (hue < 0) + hue += 360; + return new Color(hue, 1f, 1f); + } + + #endregion + } +} diff --git a/RGB.NET.Brushes/Helper/GradientHelper.cs b/RGB.NET.Brushes/Helper/GradientHelper.cs new file mode 100644 index 0000000..be99a1c --- /dev/null +++ b/RGB.NET.Brushes/Helper/GradientHelper.cs @@ -0,0 +1,80 @@ +// ReSharper disable MemberCanBePrivate.Global + +using System; +using RGB.NET.Core; + +namespace RGB.NET.Brushes.Helper +{ + /// + /// Offers some extensions and helper-methods for gradient related things. + /// + public static class GradientHelper + { + // Based on https://web.archive.org/web/20170125201230/https://dotupdate.wordpress.com/2008/01/28/find-the-color-of-a-point-in-a-lineargradientbrush/ + /// + /// Calculates the offset of an given on an gradient. + /// + /// The start of the gradient. + /// The end of the gradient. + /// The on the gradient to which the offset is calculated. + /// The offset of the on the gradient. + public static double CalculateLinearGradientOffset(Point startPoint, Point endPoint, Point point) + { + Point intersectingPoint; + if (startPoint.Y.Equals(endPoint.Y)) // Horizontal case + intersectingPoint = new Point(point.X, startPoint.Y); + + else if (startPoint.X.Equals(endPoint.X)) // Vertical case + intersectingPoint = new Point(startPoint.X, point.Y); + + else // Diagonal case + { + double slope = (endPoint.Y - startPoint.Y) / (endPoint.X - startPoint.X); + double orthogonalSlope = -1 / slope; + + double startYIntercept = startPoint.Y - (slope * startPoint.X); + double pointYIntercept = point.Y - (orthogonalSlope * point.X); + + double intersectingPointX = (pointYIntercept - startYIntercept) / (slope - orthogonalSlope); + double intersectingPointY = (slope * intersectingPointX) + startYIntercept; + intersectingPoint = new Point(intersectingPointX, intersectingPointY); + } + + // Calculate distances relative to the vector start + double intersectDistance = CalculateDistance(intersectingPoint, startPoint, endPoint); + double gradientLength = CalculateDistance(endPoint, startPoint, endPoint); + + return intersectDistance / gradientLength; + } + + // Based on https://web.archive.org/web/20170125201230/https://dotupdate.wordpress.com/2008/01/28/find-the-color-of-a-point-in-a-lineargradientbrush/ + /// + /// Returns the signed magnitude of a on a vector. + /// + /// The on the vector of which the magnitude should be calculated. + /// The origin of the vector. + /// The direction of the vector. + /// The signed magnitude of a on a vector. + public static double CalculateDistance(Point point, Point origin, Point direction) + { + double distance = CalculateDistance(point, origin); + + return (((point.Y < origin.Y) && (direction.Y > origin.Y)) || + ((point.Y > origin.Y) && (direction.Y < origin.Y)) || + ((point.Y.Equals(origin.Y)) && (point.X < origin.X) && (direction.X > origin.X)) || + ((point.Y.Equals(origin.Y)) && (point.X > origin.X) && (direction.X < origin.X))) + ? -distance : distance; + } + + /// + /// Calculated the distance between two . + /// + /// The first . + /// The second . + /// The distance between the two . + public static double CalculateDistance(Point point1, Point point2) + { + return Math.Sqrt(((point1.Y - point2.Y) * (point1.Y - point2.Y)) + ((point1.X - point2.X) * (point1.X - point2.X))); + } + } +} diff --git a/RGB.NET.Brushes/RGB.NET.Brushes.csproj b/RGB.NET.Brushes/RGB.NET.Brushes.csproj index 779d7b5..d817436 100644 --- a/RGB.NET.Brushes/RGB.NET.Brushes.csproj +++ b/RGB.NET.Brushes/RGB.NET.Brushes.csproj @@ -31,6 +31,10 @@ 4 + + ..\packages\RGB.NET.Core.1.0.0\lib\net45\RGB.NET.Core.dll + True + @@ -41,8 +45,23 @@ + + + + + + + + + + + + + + +