in src/Skia/Avalonia.Skia/DrawingContextImpl.cs [885:1057]
private static void ConfigureGradientBrush(ref PaintWrapper paintWrapper, Rect targetRect, IGradientBrush gradientBrush)
{
var tileMode = gradientBrush.SpreadMethod.ToSKShaderTileMode();
var stopColors = gradientBrush.GradientStops.Select(s => s.Color.ToSKColor()).ToArray();
var stopOffsets = gradientBrush.GradientStops.Select(s => (float)s.Offset).ToArray();
switch (gradientBrush)
{
case ILinearGradientBrush linearGradient:
{
var start = linearGradient.StartPoint.ToPixels(targetRect).ToSKPoint();
var end = linearGradient.EndPoint.ToPixels(targetRect).ToSKPoint();
// would be nice to cache these shaders possibly?
if (linearGradient.Transform is null)
{
using (var shader =
SKShader.CreateLinearGradient(start, end, stopColors, stopOffsets, tileMode))
{
paintWrapper.Paint.Shader = shader;
}
}
else
{
var transformOrigin = linearGradient.TransformOrigin.ToPixels(targetRect);
var offset = Matrix.CreateTranslation(transformOrigin);
var transform = (-offset) * linearGradient.Transform.Value * (offset);
using (var shader =
SKShader.CreateLinearGradient(start, end, stopColors, stopOffsets, tileMode, transform.ToSKMatrix()))
{
paintWrapper.Paint.Shader = shader;
}
}
break;
}
case IRadialGradientBrush radialGradient:
{
var centerPoint = radialGradient.Center.ToPixels(targetRect);
var center = centerPoint.ToSKPoint();
var radiusX = (radialGradient.RadiusX.ToValue(targetRect.Width));
var radiusY = (radialGradient.RadiusY.ToValue(targetRect.Height));
var originPoint = radialGradient.GradientOrigin.ToPixels(targetRect);
Matrix? transform = null;
if (radiusX != radiusY)
transform =
Matrix.CreateTranslation(-centerPoint)
* Matrix.CreateScale(1, radiusY / radiusX)
* Matrix.CreateTranslation(centerPoint);
if (radialGradient.Transform != null)
{
var transformOrigin = radialGradient.TransformOrigin.ToPixels(targetRect);
var offset = Matrix.CreateTranslation(transformOrigin);
var brushTransform = (-offset) * radialGradient.Transform.Value * (offset);
transform = transform.HasValue ? transform * brushTransform : brushTransform;
}
if (originPoint.Equals(centerPoint))
{
// when the origin is the same as the center the Skia RadialGradient acts the same as D2D
using (var shader =
transform.HasValue
? SKShader.CreateRadialGradient(center, (float)radiusX, stopColors, stopOffsets, tileMode,
transform.Value.ToSKMatrix())
: SKShader.CreateRadialGradient(center, (float)radiusX, stopColors, stopOffsets, tileMode)
)
{
paintWrapper.Paint.Shader = shader;
}
}
else
{
// when the origin is different to the center use a two point ConicalGradient to match the behaviour of D2D
if (radiusX != radiusY)
// Adjust the origin point for radiusX/Y transformation by reversing it
originPoint = originPoint.WithY(
(originPoint.Y - centerPoint.Y) * radiusX / radiusY + centerPoint.Y);
var origin = originPoint.ToSKPoint();
var endOffset = stopOffsets[stopOffsets.Length - 1];
var start = origin;
var radiusStart = 0f;
var end = center;
var radiusEnd = (float)radiusX;
var reverse = (centerPoint.X != originPoint.X || centerPoint.Y != originPoint.Y) && endOffset == 1;
if (reverse)
{
// reverse the order of the stops to match D2D
(start, radiusStart, end, radiusEnd) = (end, radiusEnd, start, radiusStart);
var count = stopOffsets.Length;
var reversedColors = new SKColor[stopColors.Length];
// and then reverse the reference point of the stops
var reversedStops = new float[count];
for (var i = 0; i < count; i++)
{
var offset = radialGradient.GradientStops[i].Offset;
offset = 1 - offset;
if (MathUtilities.IsZero(offset))
{
offset = 0;
}
var reversedIndex = count - 1 - i;
reversedStops[reversedIndex] = (float)offset;
reversedColors[reversedIndex] = stopColors[i];
}
stopColors = reversedColors;
stopOffsets = reversedStops;
}
// compose with a background colour of the final stop to match D2D's behaviour of filling with the final color
using (var shader = SKShader.CreateCompose(
SKShader.CreateColor(stopColors[0]),
transform.HasValue
? SKShader.CreateTwoPointConicalGradient(start, radiusStart, end, radiusEnd,
stopColors, stopOffsets, tileMode, transform.Value.ToSKMatrix())
: SKShader.CreateTwoPointConicalGradient(start, radiusStart, end, radiusEnd,
stopColors, stopOffsets, tileMode)
)
)
{
paintWrapper.Paint.Shader = shader;
}
}
break;
}
case IConicGradientBrush conicGradient:
{
var center = conicGradient.Center.ToPixels(targetRect).ToSKPoint();
// Skia's default is that angle 0 is from the right hand side of the center point
// but we are matching CSS where the vertical point above the center is 0.
var angle = (float)(conicGradient.Angle - 90);
var rotation = SKMatrix.CreateRotationDegrees(angle, center.X, center.Y);
if (conicGradient.Transform is { })
{
var transformOrigin = conicGradient.TransformOrigin.ToPixels(targetRect);
var offset = Matrix.CreateTranslation(transformOrigin);
var transform = (-offset) * conicGradient.Transform.Value * (offset);
rotation = rotation.PreConcat(transform.ToSKMatrix());
}
using (var shader =
SKShader.CreateSweepGradient(center, stopColors, stopOffsets, rotation))
{
paintWrapper.Paint.Shader = shader;
}
break;
}
}
}