diff --git a/SkiaRenderer/XGraphics.Renderer.Skia/SkiaPainter.cs b/SkiaRenderer/XGraphics.Renderer.Skia/SkiaPainter.cs index 7e5f1af..dee5477 100644 --- a/SkiaRenderer/XGraphics.Renderer.Skia/SkiaPainter.cs +++ b/SkiaRenderer/XGraphics.Renderer.Skia/SkiaPainter.cs @@ -129,6 +129,7 @@ namespace XGraphics.Renderer.Skia return skiaPathFigures; } + private void AddPathSegmentToSkiaPath(SKPath skPath, IPathSegment pathSegment) { if (pathSegment is IBezierSegment bezierSegment) @@ -136,6 +137,29 @@ namespace XGraphics.Renderer.Skia (float)bezierSegment.Point1.X, (float)bezierSegment.Point1.Y, (float)bezierSegment.Point2.X, (float)bezierSegment.Point2.Y, (float)bezierSegment.Point3.X, (float)bezierSegment.Point3.Y); + else if (pathSegment is IPolyBezierSegment polyBezierSegment) + { + List points = new List(); + foreach (Point point in polyBezierSegment.Points) + points.Add(point); + + if (points.Count % 3 != 0) + throw new InvalidOperationException($"IPolyBezerSegment contains {points.Count} points, which isn't a multiple of 3"); + + for (int i = 0; i < points.Count; ) + { + var point1 = points[i + 0]; + var point2 = points[i + 1]; + var point3 = points[i + 2]; + + skPath.CubicTo( + (float)point1.X, (float)point1.Y, + (float)point2.X, (float)point2.Y, + (float)point3.X, (float)point3.Y); + + i += 3; + } + } else if (pathSegment is ILineSegment lineSegment) skPath.LineTo((float) lineSegment.Point.X, (float) lineSegment.Point.Y); else if (pathSegment is IQuadraticBezierSegment quadraticBezierSegment) @@ -203,7 +227,7 @@ namespace XGraphics.Renderer.Skia if (fill != null && allowFill) { using SKPaint paint = new SKPaint { Style = SKPaintStyle.Fill, IsAntialias = true }; - InitSkiaPaintForBrush(paint, fill); + InitSkiaPaintForBrush(paint, fill, shape); skCanvas.DrawPath(skiaPath, paint); } @@ -211,25 +235,66 @@ namespace XGraphics.Renderer.Skia if (stroke != null) { using SKPaint paint = new SKPaint { Style = SKPaintStyle.Stroke, IsAntialias = true }; - InitSkiaPaintForBrush(paint, stroke); + InitSkiaPaintForBrush(paint, stroke, shape); paint.StrokeWidth = (int)shape.StrokeThickness; skCanvas.DrawPath(skiaPath, paint); } } - public static void InitSkiaPaintForBrush(SKPaint paint, IBrush brush) + public static void InitSkiaPaintForBrush(SKPaint paint, IBrush brush, IShape shape) { if (brush is ISolidColorBrush solidColorBrush) paint.Color = ToSkiaColor(solidColorBrush.Color); + else if (brush is IGradientBrush gradientBrush) + paint.Shader = ToSkiaShader(gradientBrush, shape); else throw new InvalidOperationException($"Brush type {brush.GetType()} isn't currently supported"); } - public static SKColor ToSkiaColor(Color color) => - new SKColor(color.R, color.G, color.B, color.A); + public static SKColor ToSkiaColor(Color color) => new SKColor(color.R, color.G, color.B, color.A); - public static SKPoint ToSkiaPoint(Point point) => - new SKPoint((float) point.X, (float) point.Y); + public static SKShader ToSkiaShader(IGradientBrush gradientBrush, IShape shape) + { + SKShaderTileMode tileMode = gradientBrush.SpreadMethod switch + { + GradientSpreadMethod.Pad => SKShaderTileMode.Clamp, + GradientSpreadMethod.Reflect => SKShaderTileMode.Mirror, + GradientSpreadMethod.Repeat => SKShaderTileMode.Repeat, + _ => throw new InvalidOperationException($"Unknown GradientSpreadmethod value {gradientBrush.SpreadMethod}") + }; + + List skiaColors = new List(); + List skiaColorPositions = new List(); + foreach (IGradientStop gradientStop in gradientBrush.GradientStops) + { + skiaColors.Add(ToSkiaColor(gradientStop.Color)); + skiaColorPositions.Add((float)gradientStop.Offset); + } + + if (gradientBrush is ILinearGradientBrush linearGradientBrush) + { + SKPoint skiaStartPoint = new SKPoint( + (float)(shape.Left + linearGradientBrush.StartPoint.X * shape.Width), + (float)(shape.Top + linearGradientBrush.StartPoint.Y * shape.Height)); + SKPoint skiaEndPoint = new SKPoint( + (float)(shape.Left + linearGradientBrush.EndPoint.X * shape.Width), + (float)(shape.Top + linearGradientBrush.EndPoint.Y * shape.Height)); + + return SKShader.CreateLinearGradient(skiaStartPoint, skiaEndPoint, skiaColors.ToArray(), skiaColorPositions.ToArray(), tileMode); + } + else if (gradientBrush is IRadialGradientBrush radialGradientBrush) + { + SKPoint skiaCenterPoint = new SKPoint( + (float)(shape.Left + radialGradientBrush.Center.X * shape.Width), + (float)(shape.Top + radialGradientBrush.Center.Y * shape.Height)); + + float radius = (float)(radialGradientBrush.RadiusX * shape.Width); + return SKShader.CreateRadialGradient(skiaCenterPoint, radius, skiaColors.ToArray(), skiaColorPositions.ToArray(), tileMode); + } + else throw new InvalidOperationException($"GradientBrush type {gradientBrush.GetType()} is unknown"); + } + + public static SKPoint ToSkiaPoint(Point point) => new SKPoint((float) point.X, (float) point.Y); public static void AddSkiaPoints(IEnumerable points, List skiaPoints) { diff --git a/XGraphics.DataModelGenerator/CompilationUnitGenerator.cs b/XGraphics.DataModelGenerator/CompilationUnitGenerator.cs index bc79438..5730066 100644 --- a/XGraphics.DataModelGenerator/CompilationUnitGenerator.cs +++ b/XGraphics.DataModelGenerator/CompilationUnitGenerator.cs @@ -495,7 +495,7 @@ namespace XGraphics.DataModelGenerator private static bool IsEnumType(string typeName) { - return typeName == "SweepDirection" || typeName == "FillRule"; + return typeName == "SweepDirection" || typeName == "FillRule" || typeName == "GradientSpreadMethod"; } private static bool IsPointType(TypeSyntax type) @@ -536,7 +536,23 @@ namespace XGraphics.DataModelGenerator if (firstArgument == null) throw new UserViewableException($"Property {modelProperty.Identifier.Text} should have an argument for the [ModelDefaultValue] attribute"); - return firstArgument.Expression; + ExpressionSyntax defaultExpression = firstArgument.Expression; + if (defaultExpression is LiteralExpressionSyntax literalExpression && literalExpression.Token.IsKind(SyntaxKind.StringLiteralToken)) + { + string literalExpressionString = literalExpression.Token.ToString(); + if (literalExpressionString == "\"0.5,0.5\"") + defaultExpression = + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName("Wrapper"), + IdentifierName("Point")), + IdentifierName("CenterDefault")); + else throw new UserViewableException($"Unknown string literal based default value: {literalExpressionString}"); + } + + return defaultExpression; } } diff --git a/XGraphics.WPF/Brushes/RadialGradientBrush.cs b/XGraphics.WPF/Brushes/RadialGradientBrush.cs index cffa06d..e966ee6 100644 --- a/XGraphics.WPF/Brushes/RadialGradientBrush.cs +++ b/XGraphics.WPF/Brushes/RadialGradientBrush.cs @@ -6,12 +6,10 @@ namespace XGraphics.WPF.Brushes { public class RadialGradientBrush : GradientBrush, IRadialGradientBrush { - public static readonly DependencyProperty CenterProperty = PropertyUtils.Create(nameof(Center), typeof(Wrapper.Point), typeof(RadialGradientBrush), PropertyUtils.DefaultPoint); - public static readonly DependencyProperty GraidentOriginProperty = PropertyUtils.Create(nameof(GraidentOrigin), typeof(Wrapper.Point), typeof(RadialGradientBrush), PropertyUtils.DefaultPoint); + public static readonly DependencyProperty CenterProperty = PropertyUtils.Create(nameof(Center), typeof(Wrapper.Point), typeof(RadialGradientBrush), Wrapper.Point.CenterDefault); + public static readonly DependencyProperty GraidentOriginProperty = PropertyUtils.Create(nameof(GraidentOrigin), typeof(Wrapper.Point), typeof(RadialGradientBrush), Wrapper.Point.CenterDefault); public static readonly DependencyProperty RadiusXProperty = PropertyUtils.Create(nameof(RadiusX), typeof(double), typeof(RadialGradientBrush), 0.5); - public static readonly DependencyProperty RadiusYProperty = PropertyUtils.Create(nameof(RadiusY), typeof(double), typeof(RadialGradientBrush), 0.5); - // The default value is 0.5, 0.5 Point IRadialGradientBrush.Center => Center.WrappedPoint; public Wrapper.Point Center { @@ -19,7 +17,6 @@ namespace XGraphics.WPF.Brushes set => SetValue(CenterProperty, value); } - // The default value is 0.5, 0.5 Point IRadialGradientBrush.GraidentOrigin => GraidentOrigin.WrappedPoint; public Wrapper.Point GraidentOrigin { @@ -32,11 +29,5 @@ namespace XGraphics.WPF.Brushes get => (double)GetValue(RadiusXProperty); set => SetValue(RadiusXProperty, value); } - - public double RadiusY - { - get => (double)GetValue(RadiusYProperty); - set => SetValue(RadiusYProperty, value); - } } } \ No newline at end of file diff --git a/XGraphics.WPF/Converters/ColorTypeConverter.cs b/XGraphics.WPF/Converters/ColorTypeConverter.cs index 64ee011..5ddb05f 100644 --- a/XGraphics.WPF/Converters/ColorTypeConverter.cs +++ b/XGraphics.WPF/Converters/ColorTypeConverter.cs @@ -12,8 +12,7 @@ namespace XGraphics.WPF.Converters return sourceType == typeof(string); } - public override object ConvertFrom(ITypeDescriptorContext context, - CultureInfo culture, object valueObject) + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object valueObject) { if (!(valueObject is string value)) throw new InvalidOperationException($"Cannot convert from type {valueObject.GetType()}"); diff --git a/XGraphics.WPF/Converters/PointTypeConverter.cs b/XGraphics.WPF/Converters/PointTypeConverter.cs new file mode 100644 index 0000000..fb9b3be --- /dev/null +++ b/XGraphics.WPF/Converters/PointTypeConverter.cs @@ -0,0 +1,25 @@ +using System; +using System.ComponentModel; +using System.Globalization; +using XGraphics.Converters; + +namespace XGraphics.WPF.Converters +{ + public class PointTypeConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) => sourceType == typeof(string); + + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object valueObject) + { + if (!(valueObject is string value)) + throw new InvalidOperationException($"Cannot convert from type {valueObject.GetType()}"); + + return new Wrapper.Point(PointConverter.ConvertFromString(value)); + } + + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) => false; + + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) => + throw new InvalidOperationException($"ConvertTo isn't currently supported"); + } +} \ No newline at end of file diff --git a/XGraphics.WPF/Wrapper/Point.cs b/XGraphics.WPF/Wrapper/Point.cs index b31fd13..dbf4d43 100644 --- a/XGraphics.WPF/Wrapper/Point.cs +++ b/XGraphics.WPF/Wrapper/Point.cs @@ -1,11 +1,15 @@ -using XGraphicsPoint = XGraphics.Point; +using System.ComponentModel; +using XGraphics.WPF.Converters; +using XGraphicsPoint = XGraphics.Point; namespace XGraphics.WPF.Wrapper { + [TypeConverter(typeof(PointTypeConverter))] public struct Point { public static readonly Point Default = new Point(XGraphicsPoint.Default); + public static readonly Point CenterDefault = new Point(XGraphicsPoint.CenterDefault); public XGraphicsPoint WrappedPoint { get; } diff --git a/XGraphics.XamarinForms/Brushes/RadialGradientBrush.cs b/XGraphics.XamarinForms/Brushes/RadialGradientBrush.cs index 5b0f701..dfba663 100644 --- a/XGraphics.XamarinForms/Brushes/RadialGradientBrush.cs +++ b/XGraphics.XamarinForms/Brushes/RadialGradientBrush.cs @@ -5,12 +5,10 @@ namespace XGraphics.XamarinForms.Brushes { public class RadialGradientBrush : GradientBrush, IRadialGradientBrush { - public static readonly BindableProperty CenterProperty = PropertyUtils.Create(nameof(Center), typeof(Wrapper.Point), typeof(RadialGradientBrush), PropertyUtils.DefaultPoint); - public static readonly BindableProperty GraidentOriginProperty = PropertyUtils.Create(nameof(GraidentOrigin), typeof(Wrapper.Point), typeof(RadialGradientBrush), PropertyUtils.DefaultPoint); + public static readonly BindableProperty CenterProperty = PropertyUtils.Create(nameof(Center), typeof(Wrapper.Point), typeof(RadialGradientBrush), Wrapper.Point.CenterDefault); + public static readonly BindableProperty GraidentOriginProperty = PropertyUtils.Create(nameof(GraidentOrigin), typeof(Wrapper.Point), typeof(RadialGradientBrush), Wrapper.Point.CenterDefault); public static readonly BindableProperty RadiusXProperty = PropertyUtils.Create(nameof(RadiusX), typeof(double), typeof(RadialGradientBrush), 0.5); - public static readonly BindableProperty RadiusYProperty = PropertyUtils.Create(nameof(RadiusY), typeof(double), typeof(RadialGradientBrush), 0.5); - // The default value is 0.5, 0.5 Point IRadialGradientBrush.Center => Center.WrappedPoint; public Wrapper.Point Center { @@ -18,7 +16,6 @@ namespace XGraphics.XamarinForms.Brushes set => SetValue(CenterProperty, value); } - // The default value is 0.5, 0.5 Point IRadialGradientBrush.GraidentOrigin => GraidentOrigin.WrappedPoint; public Wrapper.Point GraidentOrigin { @@ -31,11 +28,5 @@ namespace XGraphics.XamarinForms.Brushes get => (double)GetValue(RadiusXProperty); set => SetValue(RadiusXProperty, value); } - - public double RadiusY - { - get => (double)GetValue(RadiusYProperty); - set => SetValue(RadiusYProperty, value); - } } } \ No newline at end of file diff --git a/XGraphics/Brushes/IRadialGradientBrush.cs b/XGraphics/Brushes/IRadialGradientBrush.cs index fe80bf8..c4ebe8d 100644 --- a/XGraphics/Brushes/IRadialGradientBrush.cs +++ b/XGraphics/Brushes/IRadialGradientBrush.cs @@ -3,16 +3,13 @@ [GraphicsModelObject] public interface IRadialGradientBrush : IGradientBrush { - // The default value is 0.5, 0.5 + [ModelDefaultValue("0.5,0.5")] Point Center { get; } - // The default value is 0.5, 0.5 + [ModelDefaultValue("0.5,0.5")] Point GraidentOrigin { get; } [ModelDefaultValue(0.5)] double RadiusX { get; } - - [ModelDefaultValue(0.5)] - double RadiusY { get; } } } diff --git a/XGraphics/Converters/PointConverter.cs b/XGraphics/Converters/PointConverter.cs new file mode 100644 index 0000000..94c69b3 --- /dev/null +++ b/XGraphics/Converters/PointConverter.cs @@ -0,0 +1,22 @@ +using System; +using System.Globalization; + +namespace XGraphics.Converters +{ + public static class PointConverter + { + public static Point ConvertFromString(string value) + { + + if (value != null) + { + double x, y; + string[] xy = value.Split(','); + if (xy.Length == 2 && double.TryParse(xy[0], NumberStyles.Number, CultureInfo.InvariantCulture, out x) && double.TryParse(xy[1], NumberStyles.Number, CultureInfo.InvariantCulture, out y)) + return new Point(x, y); + } + + throw new InvalidOperationException(string.Format("Cannot convert \"{0}\" into {1}", value, typeof(Point))); + } + } +} \ No newline at end of file diff --git a/XGraphics/Point.cs b/XGraphics/Point.cs index ff9121d..625cfb5 100644 --- a/XGraphics/Point.cs +++ b/XGraphics/Point.cs @@ -3,6 +3,7 @@ public struct Point { public static readonly Point Default = new Point(0, 0); + public static readonly Point CenterDefault = new Point(0.5, 0.5); public Point(double x, double y)