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 14, 2025
1 parent 03f91a2 commit e29a256
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 73 deletions.
173 changes: 100 additions & 73 deletions src/Skia/Avalonia.Skia/DrawingContextImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -915,90 +915,117 @@ private static void ConfigureGradientBrush(ref PaintWrapper paintWrapper, Rect t
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)
)
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)
{
paintWrapper.Paint.Shader = shader;
var transformOrigin = radialGradient.TransformOrigin.ToPixels(targetRect);
var offset = Matrix.CreateTranslation(transformOrigin);
var brushTransform = (-offset) * radialGradient.Transform.Value * (offset);
transform = transform.HasValue ? transform * brushTransform : brushTransform;
}
}
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();

// reverse the order of the stops to match D2D
var reversedColors = new SKColor[stopColors.Length];
Array.Copy(stopColors, reversedColors, stopColors.Length);
Array.Reverse(reversedColors);

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

// 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(reversedColors[0]),
transform.HasValue
? SKShader.CreateTwoPointConicalGradient(center, (float)radiusX, origin, 0,
reversedColors, reversedStops, tileMode, transform.Value.ToSKMatrix())
: SKShader.CreateTwoPointConicalGradient(center, (float)radiusX, origin, 0,
reversedColors, reversedStops, tileMode)

)
)
else
{
paintWrapper.Paint.Shader = shader;
// 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++)
{
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);
}
}

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;
}
}
}

break;
}
break;
}
case IConicGradientBrush conicGradient:
{
var center = conicGradient.Center.ToPixels(targetRect).ToSKPoint();
Expand Down
26 changes: 26 additions & 0 deletions tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,32 @@ public RadialGradientBrushTests() : base(@"Media\RadialGradientBrush")
{
}

[Fact]
public async Task RadialGradientBrush_Partial_Cover()
{
Decorator target = new Decorator
{
Padding = new Thickness(8),
Width = 200,
Height = 200,
Child = new Border
{
Background = new RadialGradientBrush
{
GradientStops =
{
new GradientStop { Color = Colors.White, Offset = 0 },
new GradientStop { Color = Color.Parse("#00DD00"), Offset = 0.7 }
},
GradientOrigin = new RelativePoint(0.7, 0.15, RelativeUnit.Relative)
}
}
};

await RenderToFile(target);
CompareImages();
}

[Fact]
public async Task RadialGradientBrush_RedBlue()
{
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit e29a256

Please sign in to comment.