Skip to content

Commit

Permalink
Skia - Only reverse radial gradient stops if we reach cover the whole…
Browse files Browse the repository at this point in the history
… content bounds
  • Loading branch information
Gillibald committed Jan 20, 2025
1 parent 9a94a62 commit 0b46143
Showing 1 changed file with 124 additions and 118 deletions.
242 changes: 124 additions & 118 deletions src/Skia/Avalonia.Skia/DrawingContextImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -886,151 +886,157 @@ private static void ConfigureGradientBrush(ref PaintWrapper paintWrapper, Rect t
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))
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)
{
paintWrapper.Paint.Shader = shader;
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()))
else
{
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 transformOrigin = linearGradient.TransformOrigin.ToPixels(targetRect);
var offset = Matrix.CreateTranslation(transformOrigin);
var transform = (-offset) * linearGradient.Transform.Value * (offset);

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;
using (var shader =
SKShader.CreateLinearGradient(start, end, stopColors, stopOffsets, tileMode, transform.ToSKMatrix()))
{
paintWrapper.Paint.Shader = shader;
}
}

break;
}
else
case IRadialGradientBrush radialGradient:
{
// when the origin is different to the center use a two point ConicalGradient to match the behaviour of D2D
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)
// 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();
transform =
Matrix.CreateTranslation(-centerPoint)
* Matrix.CreateScale(1, radiusY / radiusX)
* Matrix.CreateTranslation(centerPoint);

var endOffset = 0.0;

// and then reverse the reference point of the stops
var reversedStops = new float[stopOffsets.Length];
for (var i = 0; i < stopOffsets.Length; i++)
if (radialGradient.Transform != null)
{
var offset = stopOffsets[i];
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 (endOffset < offset)
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)
)
{
endOffset = offset;
paintWrapper.Paint.Shader = shader;
}

reversedStops[i] = offset;

if (reversedStops[i] > 0 && reversedStops[i] < 1)
}
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 = 0.0;
// and then reverse the reference point of the stops
var reversedStops = new float[stopOffsets.Length];
for (var i = 0; i < stopOffsets.Length; i++)
{
reversedStops[i] = Math.Abs(1 - offset);
var offset = stopOffsets[i];
if (endOffset < offset)
{
endOffset = offset;
}
reversedStops[i] = offset;
if (reversedStops[i] > 0 && reversedStops[i] < 1)
{
reversedStops[i] = Math.Abs(1 - offset);
}
}
}

// 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)

var start = origin;
var radiusStart = 0f;
var end = center;
var radiusEnd = (float)radiusX;
var reverse = MathUtilities.AreClose(1, endOffset);
if (reverse)
{
(start, radiusStart, end, radiusEnd) = (end, radiusEnd, start, radiusStart);
// reverse the order of the stops to match D2D
var reversedColors = new SKColor[stopColors.Length];
Array.Copy(stopColors, reversedColors, stopColors.Length);
Array.Reverse(reversedColors);
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;
)
{
paintWrapper.Paint.Shader = shader;
}
}
}

break;
}
break;
}
case IConicGradientBrush conicGradient:
{
var center = conicGradient.Center.ToPixels(targetRect).ToSKPoint();
{
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);
// 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);
if (conicGradient.Transform is { })
{

rotation = rotation.PreConcat(transform.ToSKMatrix());
}
var transformOrigin = conicGradient.TransformOrigin.ToPixels(targetRect);
var offset = Matrix.CreateTranslation(transformOrigin);
var transform = (-offset) * conicGradient.Transform.Value * (offset);

using (var shader =
SKShader.CreateSweepGradient(center, stopColors, stopOffsets, rotation))
{
paintWrapper.Paint.Shader = shader;
}
rotation = rotation.PreConcat(transform.ToSKMatrix());
}

break;
}
using (var shader =
SKShader.CreateSweepGradient(center, stopColors, stopOffsets, rotation))
{
paintWrapper.Paint.Shader = shader;
}

break;
}
}
}

Expand Down

0 comments on commit 0b46143

Please sign in to comment.