private static void ConfigureGradientBrush()

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