Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 77 additions & 36 deletions src/ShimSkiaSharp/SKPath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,6 @@ public SKPath()
Commands = new List<PathCommand>();
}

private void ComputePointBounds(float x, float y, ref SKRect bounds)
{
bounds.Left = Math.Min(x, bounds.Left);
bounds.Right = Math.Max(x, bounds.Right);
bounds.Top = Math.Min(y, bounds.Top);
bounds.Bottom = Math.Max(y, bounds.Bottom);
}

private SKRect GetBounds()
{
Expand All @@ -61,6 +54,9 @@ private SKRect GetBounds()

var bounds = new SKRect(float.MaxValue, float.MaxValue, float.MinValue, float.MinValue);

var last = new SKPoint();
var haveLast = false;

foreach (var pathCommand in Commands)
{
switch (pathCommand)
Expand All @@ -69,76 +65,116 @@ private SKRect GetBounds()
{
var x = moveToPathCommand.X;
var y = moveToPathCommand.Y;
ComputePointBounds(x, y, ref bounds);
SKPathBoundsHelper.ComputePointBounds(x, y, ref bounds);
last = new SKPoint(x, y);
haveLast = true;
}
break;
case LineToPathCommand lineToPathCommand:
{
var x = lineToPathCommand.X;
var y = lineToPathCommand.Y;
ComputePointBounds(x, y, ref bounds);
if (haveLast)
{
SKPathBoundsHelper.AddLineBounds(last.X, last.Y, x, y, ref bounds);
}
else
{
SKPathBoundsHelper.ComputePointBounds(x, y, ref bounds);
}
last = new SKPoint(x, y);
haveLast = true;
}
break;
case ArcToPathCommand arcToPathCommand:
{
var x = arcToPathCommand.X;
var y = arcToPathCommand.Y;
ComputePointBounds(x, y, ref bounds);
var end = new SKPoint(arcToPathCommand.X, arcToPathCommand.Y);
if (haveLast)
{
SKPathBoundsHelper.AddArcBounds(last, end, arcToPathCommand.Rx, arcToPathCommand.Ry, arcToPathCommand.XAxisRotate, arcToPathCommand.LargeArc, arcToPathCommand.Sweep, ref bounds);
}
else
{
SKPathBoundsHelper.ComputePointBounds(end.X, end.Y, ref bounds);
}
last = end;
haveLast = true;
}
break;
case QuadToPathCommand quadToPathCommand:
{
var x0 = quadToPathCommand.X0;
var y0 = quadToPathCommand.Y0;
var x1 = quadToPathCommand.X1;
var y1 = quadToPathCommand.Y1;
ComputePointBounds(x0, y0, ref bounds);
ComputePointBounds(x1, y1, ref bounds);
var p1 = new SKPoint(quadToPathCommand.X0, quadToPathCommand.Y0);
var p2 = new SKPoint(quadToPathCommand.X1, quadToPathCommand.Y1);
if (haveLast)
{
SKPathBoundsHelper.AddQuadBounds(last, p1, p2, ref bounds);
}
else
{
SKPathBoundsHelper.ComputePointBounds(p1.X, p1.Y, ref bounds);
SKPathBoundsHelper.ComputePointBounds(p2.X, p2.Y, ref bounds);
}
last = p2;
haveLast = true;
}
break;
case CubicToPathCommand cubicToPathCommand:
{
var x0 = cubicToPathCommand.X0;
var y0 = cubicToPathCommand.Y0;
var x1 = cubicToPathCommand.X1;
var y1 = cubicToPathCommand.Y1;
var x2 = cubicToPathCommand.X2;
var y2 = cubicToPathCommand.Y2;
ComputePointBounds(x0, y0, ref bounds);
ComputePointBounds(x1, y1, ref bounds);
ComputePointBounds(x2, y2, ref bounds);
var p1 = new SKPoint(cubicToPathCommand.X0, cubicToPathCommand.Y0);
var p2 = new SKPoint(cubicToPathCommand.X1, cubicToPathCommand.Y1);
var p3 = new SKPoint(cubicToPathCommand.X2, cubicToPathCommand.Y2);
if (haveLast)
{
SKPathBoundsHelper.AddCubicBounds(last, p1, p2, p3, ref bounds);
}
else
{
SKPathBoundsHelper.ComputePointBounds(p1.X, p1.Y, ref bounds);
SKPathBoundsHelper.ComputePointBounds(p2.X, p2.Y, ref bounds);
SKPathBoundsHelper.ComputePointBounds(p3.X, p3.Y, ref bounds);
}
last = p3;
haveLast = true;
}
break;
case ClosePathCommand _:
break;
case AddRectPathCommand addRectPathCommand:
{
var rect = addRectPathCommand.Rect;
ComputePointBounds(rect.Left, rect.Top, ref bounds);
ComputePointBounds(rect.Right, rect.Bottom, ref bounds);
SKPathBoundsHelper.ComputePointBounds(rect.Left, rect.Top, ref bounds);
SKPathBoundsHelper.ComputePointBounds(rect.Right, rect.Bottom, ref bounds);
last = rect.BottomRight;
haveLast = true;
}
break;
case AddRoundRectPathCommand addRoundRectPathCommand:
{
var rect = addRoundRectPathCommand.Rect;
ComputePointBounds(rect.Left, rect.Top, ref bounds);
ComputePointBounds(rect.Right, rect.Bottom, ref bounds);
SKPathBoundsHelper.ComputePointBounds(rect.Left, rect.Top, ref bounds);
SKPathBoundsHelper.ComputePointBounds(rect.Right, rect.Bottom, ref bounds);
last = rect.BottomRight;
haveLast = true;
}
break;
case AddOvalPathCommand addOvalPathCommand:
{
var rect = addOvalPathCommand.Rect;
ComputePointBounds(rect.Left, rect.Top, ref bounds);
ComputePointBounds(rect.Right, rect.Bottom, ref bounds);
SKPathBoundsHelper.ComputePointBounds(rect.Left, rect.Top, ref bounds);
SKPathBoundsHelper.ComputePointBounds(rect.Right, rect.Bottom, ref bounds);
last = rect.BottomRight;
haveLast = true;
}
break;
case AddCirclePathCommand addCirclePathCommand:
{
var x = addCirclePathCommand.X;
var y = addCirclePathCommand.Y;
var radius = addCirclePathCommand.Radius;
ComputePointBounds(x - radius, y - radius, ref bounds);
ComputePointBounds(x + radius, y + radius, ref bounds);
SKPathBoundsHelper.ComputePointBounds(x - radius, y - radius, ref bounds);
SKPathBoundsHelper.ComputePointBounds(x + radius, y + radius, ref bounds);
last = new SKPoint(x + radius, y + radius);
haveLast = true;
}
break;
case AddPolyPathCommand addPolyPathCommand:
Expand All @@ -148,7 +184,12 @@ private SKRect GetBounds()
var points = addPolyPathCommand.Points;
foreach (var point in points)
{
ComputePointBounds(point.X, point.Y, ref bounds);
SKPathBoundsHelper.ComputePointBounds(point.X, point.Y, ref bounds);
}
if (points.Count > 0)
{
last = points[points.Count - 1];
haveLast = true;
}
}
}
Expand Down
206 changes: 206 additions & 0 deletions src/ShimSkiaSharp/SKPathBoundsHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
// Copyright (c) Wiesław Šoltés. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
using System;
using System.Collections.Generic;

namespace ShimSkiaSharp;

internal static class SKPathBoundsHelper
{
public static void ComputePointBounds(float x, float y, ref SKRect bounds)
{
bounds.Left = Math.Min(x, bounds.Left);
bounds.Right = Math.Max(x, bounds.Right);
bounds.Top = Math.Min(y, bounds.Top);
bounds.Bottom = Math.Max(y, bounds.Bottom);
}

public static void AddLineBounds(float x0, float y0, float x1, float y1, ref SKRect bounds)
{
if (x0 < x1)
{
bounds.Left = Math.Min(x0, bounds.Left);
bounds.Right = Math.Max(x1, bounds.Right);
}
else
{
bounds.Left = Math.Min(x1, bounds.Left);
bounds.Right = Math.Max(x0, bounds.Right);
}

if (y0 < y1)
{
bounds.Top = Math.Min(y0, bounds.Top);
bounds.Bottom = Math.Max(y1, bounds.Bottom);
}
else
{
bounds.Top = Math.Min(y1, bounds.Top);
bounds.Bottom = Math.Max(y0, bounds.Bottom);
}
}

private static float Quad(float a, float b, float c, float t)
{
var mt = 1f - t;
return mt * mt * a + 2f * mt * t * b + t * t * c;
}

public static void AddQuadBounds(SKPoint p0, SKPoint p1, SKPoint p2, ref SKRect bounds)
{
ComputePointBounds(p0.X, p0.Y, ref bounds);
ComputePointBounds(p2.X, p2.Y, ref bounds);

var denomX = p0.X - 2f * p1.X + p2.X;
if (Math.Abs(denomX) > float.Epsilon)
{
var t = (p0.X - p1.X) / denomX;
if (t > 0f && t < 1f)
{
var x = Quad(p0.X, p1.X, p2.X, t);
var y = Quad(p0.Y, p1.Y, p2.Y, t);
ComputePointBounds(x, y, ref bounds);
}
}

var denomY = p0.Y - 2f * p1.Y + p2.Y;
if (Math.Abs(denomY) > float.Epsilon)
{
var t = (p0.Y - p1.Y) / denomY;
if (t > 0f && t < 1f)
{
var x = Quad(p0.X, p1.X, p2.X, t);
var y = Quad(p0.Y, p1.Y, p2.Y, t);
ComputePointBounds(x, y, ref bounds);
}
}
}

private static float Cubic(float a, float b, float c, float d, float t)
{
var mt = 1f - t;
return mt * mt * mt * a + 3f * mt * mt * t * b + 3f * mt * t * t * c + t * t * t * d;
}

private static IEnumerable<float> SolveCubicDerivative(float a, float b, float c, float d)
{
var A = -a + 3f * b - 3f * c + d;
var B = 2f * (a - 2f * b + c);
var C = b - a;

if (Math.Abs(A) < float.Epsilon)
{
if (Math.Abs(B) < float.Epsilon)
yield break;

var t = -C / B;
if (t > 0f && t < 1f)
yield return t;
yield break;
}

var discriminant = B * B - 4f * A * C;
if (discriminant < 0f)
yield break;

var sqrt = (float)Math.Sqrt(discriminant);
var q = -B / (2f * A);
var r = sqrt / (2f * A);

var t1 = q + r;
if (t1 > 0f && t1 < 1f)
yield return t1;

var t2 = q - r;
if (t2 > 0f && t2 < 1f)
yield return t2;
}

public static void AddCubicBounds(SKPoint p0, SKPoint p1, SKPoint p2, SKPoint p3, ref SKRect bounds)
{
ComputePointBounds(p0.X, p0.Y, ref bounds);
ComputePointBounds(p3.X, p3.Y, ref bounds);

foreach (var t in SolveCubicDerivative(p0.X, p1.X, p2.X, p3.X))
{
var x = Cubic(p0.X, p1.X, p2.X, p3.X, t);
var y = Cubic(p0.Y, p1.Y, p2.Y, p3.Y, t);
ComputePointBounds(x, y, ref bounds);
}

foreach (var t in SolveCubicDerivative(p0.Y, p1.Y, p2.Y, p3.Y))
{
var x = Cubic(p0.X, p1.X, p2.X, p3.X, t);
var y = Cubic(p0.Y, p1.Y, p2.Y, p3.Y, t);
ComputePointBounds(x, y, ref bounds);
}
}

public static void AddArcBounds(SKPoint p0, SKPoint p1, float rx, float ry, float angle, SKPathArcSize largeArc, SKPathDirection sweep, ref SKRect bounds)
{
if (rx <= 0f || ry <= 0f)
{
ComputePointBounds(p0.X, p0.Y, ref bounds);
ComputePointBounds(p1.X, p1.Y, ref bounds);
return;
}

var phi = angle * (float)Math.PI / 180f;
var cosPhi = (float)Math.Cos(phi);
var sinPhi = (float)Math.Sin(phi);

var dx2 = (p0.X - p1.X) / 2f;
var dy2 = (p0.Y - p1.Y) / 2f;

var x1p = cosPhi * dx2 + sinPhi * dy2;
var y1p = -sinPhi * dx2 + cosPhi * dy2;

rx = Math.Abs(rx);
ry = Math.Abs(ry);

var rxsq = rx * rx;
var rysq = ry * ry;
var x1psq = x1p * x1p;
var y1psq = y1p * y1p;

var lambda = x1psq / rxsq + y1psq / rysq;
if (lambda > 1f)
{
var factor = (float)Math.Sqrt(lambda);
rx *= factor;
ry *= factor;
rxsq = rx * rx;
rysq = ry * ry;
}

var sign = (largeArc == SKPathArcSize.Large) == (sweep == SKPathDirection.Clockwise) ? -1f : 1f;
var sq = (rxsq * rysq - rxsq * y1psq - rysq * x1psq) / (rxsq * y1psq + rysq * x1psq);
sq = Math.Max(sq, 0f);
var coef = sign * (float)Math.Sqrt(sq);
var cxp = coef * (rx * y1p / ry);
var cyp = coef * (-ry * x1p / rx);

var cx = cosPhi * cxp - sinPhi * cyp + (p0.X + p1.X) / 2f;
var cy = sinPhi * cxp + cosPhi * cyp + (p0.Y + p1.Y) / 2f;

var startAngle = (float)Math.Atan2((y1p - cyp) / ry, (x1p - cxp) / rx);
var endAngle = (float)Math.Atan2((-y1p - cyp) / ry, (-x1p - cxp) / rx);
var sweepFlag = sweep == SKPathDirection.Clockwise;
var deltaAngle = endAngle - startAngle;
if (!sweepFlag && deltaAngle > 0)
deltaAngle -= 2f * (float)Math.PI;
else if (sweepFlag && deltaAngle < 0)
deltaAngle += 2f * (float)Math.PI;

const int segments = 20;
for (var i = 0; i <= segments; i++)
{
var theta = startAngle + deltaAngle * i / segments;
var cosTheta = (float)Math.Cos(theta);
var sinTheta = (float)Math.Sin(theta);
var x = cosPhi * rx * cosTheta - sinPhi * ry * sinTheta + cx;
var y = sinPhi * rx * cosTheta + cosPhi * ry * sinTheta + cy;
ComputePointBounds(x, y, ref bounds);
}
}
}
Loading
Loading