private SkiaGeometrySource2D GetGeometry(Size finalSize) { var strokeThickness = StrokeThickness; var radiusX = RadiusX; var radiusY = RadiusY; var offset = new Vector2((float)(strokeThickness * 0.5), (float)(strokeThickness * 0.5)); var size = new Vector2((float)finalSize.Width, (float)finalSize.Height); SkiaGeometrySource2D geometry; if (radiusX == 0 || radiusY == 0) { // Simple rectangle geometry = new SkiaGeometrySource2D( CompositionGeometry.BuildRectangleGeometry( offset, size)); } else { // Complex rectangle geometry = new SkiaGeometrySource2D( CompositionGeometry.BuildRoundedRectangleGeometry( offset, size, new Vector2((float)radiusX, (float)radiusY))); } return(geometry); }
private SkiaGeometrySource2D GetGeometry(Size finalSize) { var geometry = new SkiaGeometrySource2D(); geometry.Geometry.AddOval(new SKRect(0, 0, (float)finalSize.Width, (float)finalSize.Height)); return(geometry); }
private SkiaGeometrySource2D GetGeometry(Rect renderingArea) { var geometry = new SkiaGeometrySource2D(); geometry.Geometry.AddOval(new SKRect((float)renderingArea.X, (float)renderingArea.Y, (float)renderingArea.Right, (float)renderingArea.Bottom)); return(geometry); }
private static CompositionPath GetRoundedRect(CornerRadius cornerRadius, CornerRadius innerCornerRadius, Rect area, Rect insetArea) { var geometrySource = new SkiaGeometrySource2D(); GetRoundedPath(cornerRadius, area, geometrySource); GetRoundedPath(innerCornerRadius, insetArea, geometrySource); geometrySource.Geometry.FillType = SKPathFillType.EvenOdd; return(new CompositionPath(geometrySource)); }
private SkiaGeometrySource2D ToGeometrySource2D(PathGeometry geometry) { var skiaGeometry = new SkiaGeometrySource2D(); foreach (PathFigure figure in geometry.Figures) { skiaGeometry.Geometry.MoveTo((float)figure.StartPoint.X, (float)figure.StartPoint.Y); foreach (PathSegment segment in figure.Segments) { if (segment is LineSegment lineSegment) { skiaGeometry.Geometry.LineTo((float)lineSegment.Point.X, (float)lineSegment.Point.Y); } else if (segment is BezierSegment bezierSegment) { skiaGeometry.Geometry.CubicTo( (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 (segment is QuadraticBezierSegment quadraticBezierSegment) { skiaGeometry.Geometry.QuadTo( (float)quadraticBezierSegment.Point1.X, (float)quadraticBezierSegment.Point1.Y, (float)quadraticBezierSegment.Point2.X, (float)quadraticBezierSegment.Point2.Y); } else if (segment is ArcSegment arcSegment) { skiaGeometry.Geometry.ArcTo( (float)arcSegment.Size.Width, (float)arcSegment.Size.Height, (float)arcSegment.RotationAngle, arcSegment.IsLargeArc ? SkiaSharp.SKPathArcSize.Large : SkiaSharp.SKPathArcSize.Small, (arcSegment.SweepDirection == SweepDirection.Clockwise ? SkiaSharp.SKPathDirection.Clockwise : SkiaSharp.SKPathDirection.CounterClockwise), (float)arcSegment.Point.X, (float)arcSegment.Point.Y); } } if (figure.IsClosed) { skiaGeometry.Geometry.Close(); } } skiaGeometry.Geometry.FillType = geometry.FillRule.ToSkiaFillType(); return(skiaGeometry); }
internal override SkiaGeometrySource2D GetGeometry(Size finalSize) { var area = new Rect(0, 0, finalSize.Width, finalSize.Height); switch (Stretch) { default: case Stretch.None: break; case Stretch.Fill: area = new Rect(0, 0, finalSize.Width, finalSize.Height); break; case Stretch.Uniform: area = (area.Height > area.Width) ? (new Rect((float)area.X, (float)area.Y, (float)area.Width, (float)area.Width)) : (new Rect((float)area.X, (float)area.Y, (float)area.Height, (float)area.Height)); break; case Stretch.UniformToFill: area = (area.Height > area.Width) ? (new Rect((float)area.X, (float)area.Y, (float)area.Height, (float)area.Height)) : (new Rect((float)area.X, (float)area.Y, (float)area.Width, (float)area.Width)); break; } var shrinkValue = -ActualStrokeThickness / 2; if (area != Rect.Empty) { area.Inflate(shrinkValue, shrinkValue); } var geometry = new SkiaGeometrySource2D(); if (Math.Max(RadiusX, RadiusY) > 0) { geometry.Geometry.AddRoundRect(area.ToSKRect(), (float)RadiusX, (float)RadiusY); } else { geometry.Geometry.AddRect(area.ToSKRect()); } return(geometry); }
private SkiaGeometrySource2D GetGeometry(Size finalSize) { var area = new Rect(0, 0, finalSize.Width, finalSize.Height); var geometry = new SkiaGeometrySource2D(); if (Math.Max(RadiusX, RadiusY) > 0) { geometry.Geometry.AddRoundRect(area.ToSKRect(), (float)RadiusX, (float)RadiusY); } else { geometry.Geometry.AddRect(area.ToSKRect()); } return(geometry); }
private Rect GetPathBoundingBox(SkiaGeometrySource2D path) => path.Geometry.Bounds.ToRect();
private static IDisposable InnerCreateLayer(UIElement owner, LayoutState state) { var parent = owner.Visual; var compositor = parent.Compositor; var area = owner.LayoutRound(state.Area); var background = state.Background; var borderThickness = owner.LayoutRound(state.BorderThickness); var borderBrush = state.BorderBrush; var cornerRadius = state.CornerRadius; var disposables = new CompositeDisposable(); var sublayers = new List <Visual>(); var heightOffset = ((float)borderThickness.Top / 2) + ((float)borderThickness.Bottom / 2); var widthOffset = ((float)borderThickness.Left / 2) + ((float)borderThickness.Right / 2); var halfWidth = (float)area.Width / 2; var halfHeight = (float)area.Height / 2; var adjustedArea = area; adjustedArea = adjustedArea.DeflateBy(borderThickness); if (cornerRadius != CornerRadius.None) { var maxOuterRadius = Math.Max(0, Math.Min(halfWidth - widthOffset, halfHeight - heightOffset)); var maxInnerRadius = Math.Max(0, Math.Min(halfWidth, halfHeight)); cornerRadius = new CornerRadius( Math.Min(cornerRadius.TopLeft, maxOuterRadius), Math.Min(cornerRadius.TopRight, maxOuterRadius), Math.Min(cornerRadius.BottomRight, maxOuterRadius), Math.Min(cornerRadius.BottomLeft, maxOuterRadius)); var innerCornerRadius = new CornerRadius( Math.Min(cornerRadius.TopLeft, maxInnerRadius), Math.Min(cornerRadius.TopRight, maxInnerRadius), Math.Min(cornerRadius.BottomRight, maxInnerRadius), Math.Min(cornerRadius.BottomLeft, maxInnerRadius)); var borderShape = compositor.CreateSpriteShape(); var backgroundShape = compositor.CreateSpriteShape(); var outerShape = compositor.CreateSpriteShape(); // Border brush Brush.AssignAndObserveBrush(borderBrush, compositor, brush => borderShape.FillBrush = brush) .DisposeWith(disposables); // Background brush if (background is ImageBrush imgBackground) { adjustedArea = CreateImageLayer(compositor, disposables, borderThickness, adjustedArea, backgroundShape, adjustedArea, imgBackground); } else { Brush.AssignAndObserveBrush(background, compositor, brush => backgroundShape.FillBrush = brush) .DisposeWith(disposables); } var borderPath = GetRoundedRect(cornerRadius, innerCornerRadius, area, adjustedArea); var backgroundPath = GetRoundedPath(cornerRadius, adjustedArea); var outerPath = GetRoundedPath(cornerRadius, area); backgroundShape.Geometry = compositor.CreatePathGeometry(backgroundPath); borderShape.Geometry = compositor.CreatePathGeometry(borderPath); outerShape.Geometry = compositor.CreatePathGeometry(outerPath); var borderVisual = compositor.CreateShapeVisual(); var backgroundVisual = compositor.CreateShapeVisual(); backgroundVisual.Shapes.Add(backgroundShape); borderVisual.Shapes.Add(borderShape); sublayers.Add(backgroundVisual); sublayers.Add(borderVisual); parent.Children.InsertAtBottom(backgroundVisual); parent.Children.InsertAtTop(borderVisual); owner.ClippingIsSetByCornerRadius = cornerRadius != CornerRadius.None; if (owner.ClippingIsSetByCornerRadius) { parent.Clip = compositor.CreateGeometricClip(outerShape.Geometry); } } else { var shapeVisual = compositor.CreateShapeVisual(); var backgroundShape = compositor.CreateSpriteShape(); var backgroundArea = area; // Background brush if (background is ImageBrush imgBackground) { backgroundArea = CreateImageLayer(compositor, disposables, borderThickness, adjustedArea, backgroundShape, backgroundArea, imgBackground); } else { Brush.AssignAndObserveBrush(background, compositor, brush => backgroundShape.FillBrush = brush) .DisposeWith(disposables); // This is required because changing the CornerRadius changes the background drawing // implementation and we don't want a rectangular background behind a rounded background. Disposable.Create(() => backgroundShape.FillBrush = null) .DisposeWith(disposables); } var geometrySource = new SkiaGeometrySource2D(); var geometry = geometrySource.Geometry; geometry.AddRect(backgroundArea.ToSKRect()); backgroundShape.Geometry = compositor.CreatePathGeometry(new CompositionPath(geometrySource)); shapeVisual.Shapes.Add(backgroundShape); if (borderThickness != Thickness.Empty) { Action <Action <CompositionSpriteShape, SKPath> > createLayer = builder => { var spriteShape = compositor.CreateSpriteShape(); var geometry = new SkiaGeometrySource2D(); // Border brush Brush.AssignAndObserveBrush(borderBrush, compositor, brush => spriteShape.StrokeBrush = brush) .DisposeWith(disposables); builder(spriteShape, geometry.Geometry); spriteShape.Geometry = compositor.CreatePathGeometry(new CompositionPath(geometry)); shapeVisual.Shapes.Add(spriteShape); }; if (borderThickness.Top != 0) { createLayer((l, path) => { l.StrokeThickness = (float)borderThickness.Top; var StrokeThicknessAdjust = (float)(borderThickness.Top / 2); path.MoveTo((float)(area.X + borderThickness.Left), (float)(area.Y + StrokeThicknessAdjust)); path.LineTo((float)(area.X + area.Width - borderThickness.Right), (float)(area.Y + StrokeThicknessAdjust)); path.Close(); }); } if (borderThickness.Bottom != 0) { createLayer((l, path) => { l.StrokeThickness = (float)borderThickness.Bottom; var StrokeThicknessAdjust = borderThickness.Bottom / 2; path.MoveTo((float)(area.X + (float)borderThickness.Left), (float)(area.Y + area.Height - StrokeThicknessAdjust)); path.LineTo((float)(area.X + area.Width - (float)borderThickness.Right), (float)(area.Y + area.Height - StrokeThicknessAdjust)); path.Close(); }); } if (borderThickness.Left != 0) { createLayer((l, path) => { l.StrokeThickness = (float)borderThickness.Left; var StrokeThicknessAdjust = borderThickness.Left / 2; path.MoveTo((float)(area.X + StrokeThicknessAdjust), (float)area.Y); path.LineTo((float)(area.X + StrokeThicknessAdjust), (float)(area.Y + area.Height)); path.Close(); }); } if (borderThickness.Right != 0) { createLayer((l, path) => { l.StrokeThickness = (float)borderThickness.Right; var StrokeThicknessAdjust = borderThickness.Right / 2; path.MoveTo((float)(area.X + area.Width - StrokeThicknessAdjust), (float)area.Y); path.LineTo((float)(area.X + area.Width - StrokeThicknessAdjust), (float)(area.Y + area.Height)); path.Close(); }); } } sublayers.Add(shapeVisual); // Must be inserted below the other subviews, which may happen when // the current view has subviews. parent.Children.InsertAtBottom(shapeVisual); } disposables.Add(() => { owner.ClippingIsSetByCornerRadius = false; foreach (var sv in sublayers) { parent.Children.Remove(sv); sv.Dispose(); } } ); compositor.InvalidateRender(); return(disposables); }
/// <summary> /// Creates a rounded-rectangle path from the nominated bounds and corner radius. /// </summary> private static CompositionPath GetRoundedPath(CornerRadius cornerRadius, Rect area, SkiaGeometrySource2D geometrySource = null) { geometrySource ??= new SkiaGeometrySource2D(); var geometry = geometrySource.Geometry; // How ArcTo works: // http://www.twistedape.me.uk/blog/2013/09/23/what-arctopointdoes/ geometry.MoveTo((float)area.GetMidX(), (float)area.Y); geometry.ArcTo((float)area.Right, (float)area.Top, (float)area.Right, (float)area.GetMidY(), (float)cornerRadius.TopRight); geometry.ArcTo((float)area.Right, (float)area.Bottom, (float)area.GetMidX(), (float)area.Bottom, (float)cornerRadius.BottomRight); geometry.ArcTo((float)area.Left, (float)area.Bottom, (float)area.Left, (float)area.GetMidY(), (float)cornerRadius.BottomLeft); geometry.ArcTo((float)area.Left, (float)area.Top, (float)area.GetMidX(), (float)area.Top, (float)cornerRadius.TopLeft); geometry.Close(); return(new CompositionPath(geometrySource)); }
private static IDisposable InnerCreateLayer( ContainerVisual parent, Rect area, Brush background, Thickness borderThickness, Brush borderBrush, CornerRadius cornerRadius) { var disposables = new CompositeDisposable(); var subVisuals = new List <Visual>(); var adjustedLineWidth = borderThickness.Top; var adjustedLineWidthOffset = adjustedLineWidth / 2; var adjustedArea = area; adjustedArea.Inflate(-adjustedLineWidthOffset, -adjustedLineWidthOffset); var compositor = parent.Compositor; var shapeVisual = compositor.CreateShapeVisual(); parent.Children.InsertAtBottom(shapeVisual); var spriteShape = compositor.CreateSpriteShape(); SkiaGeometrySource2D BuildGeometry() { var maxRadius = Math.Max(0, Math.Min((float)area.Width / 2 - adjustedLineWidthOffset, (float)area.Height / 2 - adjustedLineWidthOffset)); cornerRadius = new CornerRadius( Math.Min(cornerRadius.TopLeft, maxRadius), Math.Min(cornerRadius.TopRight, maxRadius), Math.Min(cornerRadius.BottomRight, maxRadius), Math.Min(cornerRadius.BottomLeft, maxRadius)); var geometry = new SkiaGeometrySource2D(); Brush.AssignAndObserveBrush(borderBrush, color => spriteShape.StrokeBrush = compositor.CreateColorBrush(color)) .DisposeWith(disposables); geometry.Geometry.MoveTo((float)adjustedArea.GetMidX(), (float)adjustedArea.Y); geometry.Geometry.ArcTo((float)adjustedArea.Right, (float)adjustedArea.Top, (float)adjustedArea.Right, (float)adjustedArea.GetMidY(), (float)cornerRadius.TopRight); geometry.Geometry.ArcTo((float)adjustedArea.Right, (float)adjustedArea.Bottom, (float)adjustedArea.GetMidX(), (float)adjustedArea.Bottom, (float)cornerRadius.BottomRight); geometry.Geometry.ArcTo((float)adjustedArea.Left, (float)adjustedArea.Bottom, (float)adjustedArea.Left, (float)adjustedArea.GetMidY(), (float)cornerRadius.BottomLeft); geometry.Geometry.ArcTo((float)adjustedArea.Left, (float)adjustedArea.Top, (float)adjustedArea.GetMidX(), (float)adjustedArea.Top, (float)cornerRadius.TopLeft); geometry.Geometry.Close(); if (background is LinearGradientBrush lgbBackground) { //var fillMask = new CAShapeLayer() //{ // Path = path, // Frame = area, // // We only use the fill color to create the mask area // FillColor = _Color.White.CGColor, //}; //// We reduce the adjustedArea again so that the gradient is inside the border (like in Windows) //adjustedArea = adjustedArea.Shrink((nfloat)adjustedLineWidthOffset); //CreateLinearGradientBrushLayers(area, adjustedArea, parent, sublayers, ref insertionIndex, lgbBackground, fillMask); } else if (background is SolidColorBrush scbBackground) { Brush.AssignAndObserveBrush(scbBackground, color => spriteShape.FillBrush = compositor.CreateColorBrush(color)) .DisposeWith(disposables); } else if (background is ImageBrush imgBackground) { //var uiImage = imgBackground.ImageSource?.ImageData; //if (uiImage != null && uiImage.Size != CGSize.Empty) //{ // var fillMask = new CAShapeLayer() // { // Path = path, // Frame = area, // // We only use the fill color to create the mask area // FillColor = _Color.White.CGColor, // }; // // We reduce the adjustedArea again so that the image is inside the border (like in Windows) // adjustedArea = adjustedArea.Shrink((nfloat)adjustedLineWidthOffset); // CreateImageBrushLayers(area, adjustedArea, parent, sublayers, ref insertionIndex, imgBackground, fillMask); //} } else { spriteShape.FillBrush = null; } return(geometry); } spriteShape.Geometry = compositor.CreatePathGeometry(new CompositionPath(BuildGeometry())); shapeVisual.Size = new Vector2((float)area.Width, (float)area.Height); shapeVisual.Offset = new Vector3(0, 0, 0); shapeVisual.Shapes.Add(spriteShape); subVisuals.Add(shapeVisual); disposables.Add(() => { foreach (var sv in subVisuals) { parent.Children.Remove(sv); sv.Dispose(); } }); Window.Current.QueueInvalidateRender(); return(disposables); }