다음을 통해 공유


SkiaSharp 선형 그라데이션

이 클래스는 SKPaint 선을 획하거나 단색으로 영역을 채우는 데 사용되는 속성을 정의 Color 합니다. 또는 선이나 그라데이션으로 영역을 채울 수 있습니다. 이 그라데이션은 색의 점진적 혼합입니다.

선형 그라데이션 샘플

가장 기본적인 그라데이션 형식은 선형 그라데이션입니다. 색의 혼합은 한 지점에서 다른 지점으로 선(그라데이션 선이라고 함)에서 발생합니다. 그라데이션 선에 수직인 선의 색은 동일합니다. 두 정적 SKShader.CreateLinearGradient 메서드 중 하나를 사용하여 선형 그라데이션을 만듭니다. 두 오버로드 간의 차이점은 하나는 행렬 변환을 포함하고 다른 하나는 그렇지 않다는 것입니다.

이러한 메서드는 .의 속성SKPaint으로 설정한 형식 SKShader 의 개체를 Shader 반환합니다. 속성이 Shader null이 아닌 경우 속성을 재정의 Color 합니다. 스트로크된 모든 선 또는 이 SKPaint 개체를 사용하여 채워진 영역은 단색이 아닌 그라데이션을 기반으로 합니다.

참고 항목

Shader 호출에 개체를 포함하면 속성이 SKPaint DrawBitmap 무시됩니다. 속성을 사용하여 Color SKPaint 비트맵을 표시하기 위한 투명도 수준을 설정할 수 있지만(SkiaSharp 비트맵 표시 문서에 설명된 대로) 그라데이션 투명도가 있는 비트맵을 표시하는 데는 이 속성을 사용할 Shader 수 없습니다. 그라데이션 투명도를 사용하여 비트맵을 표시하는 데 사용할 수 있는 다른 기법은 SkiaSharp 원형 그라데이션 및 SkiaSharp 작성 및 혼합 모드 문서에 설명되어 있습니다.

모서리-모서리 그라데이션

선형 그라데이션이 사각형의 한 모서리에서 다른 모서리로 확장되는 경우가 많습니다. 시작점이 사각형의 왼쪽 위 모서리인 경우 그라데이션은 다음을 확장할 수 있습니다.

  • 왼쪽 아래 모서리에 세로로
  • 오른쪽 위 모서리에 가로로
  • 오른쪽 아래 모서리에 대각선으로

대각선 선형 그라데이션은 샘플의 SkiaSharp 셰이더 및 기타 효과 섹션의 첫 번째 페이지에 설명되어 있습니다 . 코너-모퉁이 그라데이션 페이지는 생성자에 생성자를 만듭니다SKCanvasView. PaintSurface 처리기는 문에 개체를 SKPaint using 만든 다음 캔버스 가운데에 있는 300픽셀 사각형을 정의합니다.

public class CornerToCornerGradientPage : ContentPage
{
    ···
    public CornerToCornerGradientPage ()
    {
        Title = "Corner-to-Corner Gradient";

        SKCanvasView canvasView = new SKCanvasView();
        canvasView.PaintSurface += OnCanvasViewPaintSurface;
        Content = canvasView;
        ···
    }

    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        using (SKPaint paint = new SKPaint())
        {
            // Create 300-pixel square centered rectangle
            float x = (info.Width - 300) / 2;
            float y = (info.Height - 300) / 2;
            SKRect rect = new SKRect(x, y, x + 300, y + 300);

            // Create linear gradient from upper-left to lower-right
            paint.Shader = SKShader.CreateLinearGradient(
                                new SKPoint(rect.Left, rect.Top),
                                new SKPoint(rect.Right, rect.Bottom),
                                new SKColor[] { SKColors.Red, SKColors.Blue },
                                new float[] { 0, 1 },
                                SKShaderTileMode.Repeat);

            // Draw the gradient on the rectangle
            canvas.DrawRect(rect, paint);
            ···
        }
    }
}

속성 SKPaintShader 정적 SKShader.CreateLinearGradient 메서드의 SKShader 반환 값이 할당됩니다. 5개의 인수는 다음과 같습니다.

  • 그라데이션의 시작점으로, 사각형의 왼쪽 위 모서리로 설정합니다.
  • 그라데이션의 끝점입니다. 여기서 사각형의 오른쪽 아래 모서리로 설정합니다.
  • 그라데이션에 영향을 주는 두 개 이상의 색 배열
  • 그라데이션 선 내의 색의 상대 위치를 나타내는 값의 배열 float 입니다.
  • 그라데이션이 SKShaderTileMode 그라데이션 선의 끝을 넘어 동작하는 방식을 나타내는 열거형의 멤버입니다.

그라데이션 개체를 만든 DrawRect 후 메서드는 셰이더를 포함하는 개체를 사용하여 SKPaint 300픽셀 사각형 사각형을 그립니다. 여기서는 iOS, Android 및 UWP(유니버설 Windows 플랫폼)에서 실행됩니다.

모서리-모서리 그라데이션

그라데이션 선은 처음 두 인수로 지정된 두 점으로 정의됩니다. 이러한 점은 그라데이션과 함께 표시되는 그래픽 개체가 아니라 캔버스를 기준으로 합니다. 그라데이션 선을 따라 색이 왼쪽 상단의 빨간색에서 오른쪽 아래의 파란색으로 점차 전환됩니다. 그라데이션 선에 수직인 모든 선은 상수 색을 가집니다.

네 번째 인수로 지정된 값 배열 float 에는 색 배열과 일대일 대응이 있습니다. 값은 해당 색이 발생하는 그라데이션 선의 상대 위치를 나타냅니다. 여기서 0은 Red 그라데이션 선의 시작 부분에 발생하는 것을 의미하고, 1은 줄의 끝에서 발생한다는 Blue 것을 의미합니다. 숫자는 오름차순이어야 하며 0에서 1까지의 범위에 있어야 합니다. 해당 범위에 없는 경우 해당 범위에 있도록 조정됩니다.

배열의 두 값은 0과 1이 아닌 값으로 설정할 수 있습니다. 다음을 실행해보세요.

new float[] { 0.25f, 0.75f }

이제 그라데이션 라인의 전체 1 분기는 순수한 빨간색이며, 마지막 분기는 순수한 파란색입니다. 빨간색과 파란색의 혼합은 그라데이션 선의 중앙 절반으로 제한됩니다.

일반적으로 이러한 위치 값의 간격을 0에서 1로 균등하게 지정할 수 있습니다. 이 경우 네 번째 인수CreateLinearGradient로 간단히 제공할 null 수 있습니다.

이 그라데이션은 300픽셀 정사각형 사각형의 두 모서리 사이에 정의되어 있지만 해당 사각형을 채우는 것으로 제한되지는 않습니다. 코너-모서리 그라데이션 페이지에는 페이지의 탭 또는 마우스 클릭에 응답하는 몇 가지 추가 코드가 포함되어 있습니다. drawBackground 필드는 탭할 때마다 전환 true 됩니다false. 값이 truePaintSurface 면 처리기는 동일한 SKPaint 개체를 사용하여 전체 캔버스를 채운 다음 작은 사각형을 나타내는 검은색 사각형을 그립니다.

public class CornerToCornerGradientPage : ContentPage
{
    bool drawBackground;

    public CornerToCornerGradientPage ()
    {
        ···
        TapGestureRecognizer tap = new TapGestureRecognizer();
        tap.Tapped += (sender, args) =>
        {
            drawBackground ^= true;
            canvasView.InvalidateSurface();
        };
        canvasView.GestureRecognizers.Add(tap);
    }

    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        ···
        using (SKPaint paint = new SKPaint())
        {
            ···
            if (drawBackground)
            {
                // Draw the gradient on the whole canvas
                canvas.DrawRect(info.Rect, paint);

                // Outline the smaller rectangle
                paint.Shader = null;
                paint.Style = SKPaintStyle.Stroke;
                paint.Color = SKColors.Black;
                canvas.DrawRect(rect, paint);
            }
        }
    }
}

화면을 탭한 후 표시되는 내용은 다음과 같습니다.

모서리-모서리 그라데이션 전체

그라데이션은 그라데이션 선을 정의하는 점 이외의 동일한 패턴으로 반복됩니다. 이 반복은 마지막 인수 CreateLinearGradient 가 .이므로 SKShaderTileMode.Repeat발생합니다. (곧 다른 옵션이 표시됩니다.)

또한 그라데이션 선을 지정하는 데 사용하는 점이 고유하지 않습니다. 그라데이션 선에 수직인 선의 색은 같으므로 동일한 효과에 대해 지정할 수 있는 그라데이션 선 수가 무한합니다. 예를 들어 사각형을 가로 그라데이션으로 채울 때 왼쪽 위와 오른쪽 위 모서리, 왼쪽 아래 및 오른쪽 아래 모서리 또는 해당 선과 짝수 및 병렬인 두 점을 지정할 수 있습니다.

대화형 실험

대화형 선형 그라데이션 페이지를 사용하여 선형 그라데이션을 대화형으로 실험할 수 있습니다. 이 페이지에서는 세 가지 방법으로 호를 그리는 문서에 소개된 클래스를 사용합니다InteractivePage. InteractivePage 이벤트를 처리 TouchEffect 하여 손가락이나 마우스로 이동할 수 있는 개체 컬렉션을 TouchPoint 기본.

XAML 파일은 부모에 연결 TouchEffect 하며 열거형의 SKCanvasView 세 멤버 SKShaderTileMode 중 하나를 선택할 수 있는 파일도 포함합니다Picker.

<local:InteractivePage xmlns="http://xamarin.com/schemas/2014/forms"
                       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                       xmlns:local="clr-namespace:SkiaSharpFormsDemos"
                       xmlns:skia="clr-namespace:SkiaSharp;assembly=SkiaSharp"
                       xmlns:skiaforms="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
                       xmlns:tt="clr-namespace:TouchTracking"
                       x:Class="SkiaSharpFormsDemos.Effects.InteractiveLinearGradientPage"
                       Title="Interactive Linear Gradient">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <Grid BackgroundColor="White"
              Grid.Row="0">
            <skiaforms:SKCanvasView x:Name="canvasView"
                                    PaintSurface="OnCanvasViewPaintSurface" />
            <Grid.Effects>
                <tt:TouchEffect Capture="True"
                                TouchAction="OnTouchEffectAction" />
            </Grid.Effects>
        </Grid>

        <Picker x:Name="tileModePicker"
                Grid.Row="1"
                Title="Shader Tile Mode"
                Margin="10"
                SelectedIndexChanged="OnPickerSelectedIndexChanged">
            <Picker.ItemsSource>
                <x:Array Type="{x:Type skia:SKShaderTileMode}">
                    <x:Static Member="skia:SKShaderTileMode.Clamp" />
                    <x:Static Member="skia:SKShaderTileMode.Repeat" />
                    <x:Static Member="skia:SKShaderTileMode.Mirror" />
                </x:Array>
            </Picker.ItemsSource>

            <Picker.SelectedIndex>
                0
            </Picker.SelectedIndex>
        </Picker>
    </Grid>
</local:InteractivePage>

코드 숨김 파일의 생성자는 선형 그라데이션의 시작점과 끝점에 대해 두 개의 TouchPoint 개체를 만듭니다. PaintSurface 처리기는 세 가지 색(빨간색에서 녹색에서 파랑으로 그라데이션)의 배열을 정의하고 다음에서 전류 SKShaderTileModePicker가져옵니다.

public partial class InteractiveLinearGradientPage : InteractivePage
{
    public InteractiveLinearGradientPage ()
    {
        InitializeComponent ();

        touchPoints = new TouchPoint[2];

        for (int i = 0; i < 2; i++)
        {
            touchPoints[i] = new TouchPoint
            {
                Center = new SKPoint(100 + i * 200, 100 + i * 200)
            };
        }

        InitializeComponent();
        baseCanvasView = canvasView;
    }

    void OnPickerSelectedIndexChanged(object sender, EventArgs args)
    {
        canvasView.InvalidateSurface();
    }

    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        SKColor[] colors = { SKColors.Red, SKColors.Green, SKColors.Blue };
        SKShaderTileMode tileMode =
            (SKShaderTileMode)(tileModePicker.SelectedIndex == -1 ?
                                        0 : tileModePicker.SelectedItem);

        using (SKPaint paint = new SKPaint())
        {
            paint.Shader = SKShader.CreateLinearGradient(touchPoints[0].Center,
                                                         touchPoints[1].Center,
                                                         colors,
                                                         null,
                                                         tileMode);
            canvas.DrawRect(info.Rect, paint);
        }
        ···
    }
}

PaintSurface 처리기는 모든 정보에서 개체를 만들고 SKShader 전체 캔버스에 색을 지정하는 데 사용합니다. 값 배열 float 이 .로 설정됩니다 null. 그렇지 않으면 세 색의 간격을 동일하게 지정하려면 해당 매개 변수를 값이 0, 0.5 및 1인 배열로 설정합니다.

처리기의 PaintSurface 대부분은 여러 개체를 표시하는 데 전념합니다. 즉, 터치 포인트는 윤곽선으로, 그라데이션 선은 터치 포인트의 그라데이션 선에 수직으로 표시됩니다.

public partial class InteractiveLinearGradientPage : InteractivePage
{
    ···
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        ···
        // Display the touch points here rather than by TouchPoint
        using (SKPaint paint = new SKPaint())
        {
            paint.Style = SKPaintStyle.Stroke;
            paint.Color = SKColors.Black;
            paint.StrokeWidth = 3;

            foreach (TouchPoint touchPoint in touchPoints)
            {
                canvas.DrawCircle(touchPoint.Center, touchPoint.Radius, paint);
            }

            // Draw gradient line connecting touchpoints
            canvas.DrawLine(touchPoints[0].Center, touchPoints[1].Center, paint);

            // Draw lines perpendicular to the gradient line
            SKPoint vector = touchPoints[1].Center - touchPoints[0].Center;
            float length = (float)Math.Sqrt(Math.Pow(vector.X, 2) +
                                            Math.Pow(vector.Y, 2));
            vector.X /= length;
            vector.Y /= length;
            SKPoint rotate90 = new SKPoint(-vector.Y, vector.X);
            rotate90.X *= 200;
            rotate90.Y *= 200;

            canvas.DrawLine(touchPoints[0].Center,
                            touchPoints[0].Center + rotate90,
                            paint);

            canvas.DrawLine(touchPoints[0].Center,
                            touchPoints[0].Center - rotate90,
                            paint);

            canvas.DrawLine(touchPoints[1].Center,
                            touchPoints[1].Center + rotate90,
                            paint);

            canvas.DrawLine(touchPoints[1].Center,
                            touchPoints[1].Center - rotate90,
                            paint);
        }
    }
}

두 터치포인트를 연결하는 그라데이션 선은 그리기 쉽지만 수직 선에는 좀 더 많은 작업이 필요합니다. 그라데이션 선은 벡터로 변환되고, 하나의 단위 길이로 정규화된 다음, 90도 회전됩니다. 그런 다음, 해당 벡터의 길이는 200픽셀로 지정됩니다. 터치 포인트에서 수직으로 확장되는 4개의 선을 그라데이션 선으로 그리는 데 사용됩니다.

수직 선은 그라데이션의 시작과 끝과 일치합니다. 이러한 줄을 벗어나는 작업은 열거형의 설정에 SKShaderTileMode 따라 달라집니다.

대화형 선형 그라데이션

세 개의 스크린샷은 세 가지 값 SKShaderTileMode의 결과를 보여 줍니다. iOS 스크린샷은 그라데이션 테두리의 색만 확장하는 것을 보여 SKShaderTileMode.Clamp줍니다. Android 스크린샷의 옵션은 SKShaderTileMode.Repeat 그라데이션 패턴이 반복되는 방식을 보여 줍니다. SKShaderTileMode.Mirror UWP 스크린샷의 옵션도 패턴을 반복하지만 패턴은 매번 반전되어 색 불연속성이 없습니다.

그라데이션의 그라데이션

클래스는 SKShader public 속성 또는 메서드를 제외하고 Dispose정의하지 않습니다. SKShader 따라서 정적 메서드에서 만든 개체는 변경할 수 없습니다. 서로 다른 두 개체에 동일한 그라데이션을 사용하더라도 그라데이션을 약간 변경할 수 있습니다. 이렇게 하려면 새 SKShader 개체를 만들어야 합니다.

그라데이션 텍스트 페이지에는 유사한 그라데이션으로 색이 지정된 텍스트와 브랙그라운드가 표시됩니다.

그라데이션 텍스트

그라데이션의 유일한 차이점은 시작점과 끝점입니다. 텍스트를 표시하는 데 사용되는 그라데이션은 텍스트의 경계 사각형 모서리에 있는 두 점을 기반으로 합니다. 배경의 경우 두 점은 전체 캔버스를 기반으로합니다. 코드는 다음과 같습니다.

public class GradientTextPage : ContentPage
{
    const string TEXT = "GRADIENT";

    public GradientTextPage ()
    {
        Title = "Gradient Text";

        SKCanvasView canvasView = new SKCanvasView();
        canvasView.PaintSurface += OnCanvasViewPaintSurface;
        Content = canvasView;
    }

    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        using (SKPaint paint = new SKPaint())
        {
            // Create gradient for background
            paint.Shader = SKShader.CreateLinearGradient(
                                new SKPoint(0, 0),
                                new SKPoint(info.Width, info.Height),
                                new SKColor[] { new SKColor(0x40, 0x40, 0x40),
                                                new SKColor(0xC0, 0xC0, 0xC0) },
                                null,
                                SKShaderTileMode.Clamp);

            // Draw background
            canvas.DrawRect(info.Rect, paint);

            // Set TextSize to fill 90% of width
            paint.TextSize = 100;
            float width = paint.MeasureText(TEXT);
            float scale = 0.9f * info.Width / width;
            paint.TextSize *= scale;

            // Get text bounds
            SKRect textBounds = new SKRect();
            paint.MeasureText(TEXT, ref textBounds);

            // Calculate offsets to center the text on the screen
            float xText = info.Width / 2 - textBounds.MidX;
            float yText = info.Height / 2 - textBounds.MidY;

            // Shift textBounds by that amount
            textBounds.Offset(xText, yText);

            // Create gradient for text
            paint.Shader = SKShader.CreateLinearGradient(
                                new SKPoint(textBounds.Left, textBounds.Top),
                                new SKPoint(textBounds.Right, textBounds.Bottom),
                                new SKColor[] { new SKColor(0x40, 0x40, 0x40),
                                                new SKColor(0xC0, 0xC0, 0xC0) },
                                null,
                                SKShaderTileMode.Clamp);

            // Draw text
            canvas.DrawText(TEXT, xText, yText, paint);
        }
    }
}

Shader 개체의 SKPaint 속성은 배경을 덮는 그라데이션을 표시하기 위해 먼저 설정됩니다. 그라데이션 점은 캔버스의 왼쪽 위와 오른쪽 아래 모서리로 설정됩니다.

이 코드는 텍스트가 TextSize 캔버스 너비의 SKPaint 90%에 표시되도록 개체의 속성을 설정합니다. 텍스트 범위는 계산 xText 에 사용되며 yText 값을 메서드에 DrawText 전달하여 텍스트를 가운데에 배치합니다.

그러나 두 번째 CreateLinearGradient 호출의 그라데이션 점은 캔버스가 표시될 때 캔버스를 기준으로 텍스트의 왼쪽 위와 오른쪽 아래 모서리를 참조해야 합니다. 이 작업은 직사각형을 textBounds 동일한 xText 값으로 yText 이동하여 수행합니다.

textBounds.Offset(xText, yText);

이제 사각형의 왼쪽 위와 오른쪽 아래 모서리를 사용하여 그라데이션의 시작점과 끝점을 설정할 수 있습니다.

그라데이션 애니메이션

그라데이션에 애니메이션 효과를 적용하는 방법에는 여러 가지가 있습니다. 한 가지 방법은 시작점과 끝점에 애니메이션을 적용하는 것입니다. 그라데이션 애니메이션 페이지는 캔버스 가운데에 있는 원에서 두 점을 이동합니다. 이 원의 반경은 캔버스의 너비 또는 높이의 절반이며, 그 중에서 더 작습니다. 시작점과 끝점은 이 원에서 서로 반대이며, 그라데이션은 타일 모드를 사용하여 Mirror 흰색에서 검은색으로 바꿉니다.

그라데이션 애니메이션

생성자는 .를 SKCanvasView만듭니다. 및 OnDisappearing 메서드는 OnAppearing 애니메이션 논리를 처리합니다.

public class GradientAnimationPage : ContentPage
{
    SKCanvasView canvasView;
    bool isAnimating;
    double angle;
    Stopwatch stopwatch = new Stopwatch();

    public GradientAnimationPage()
    {
        Title = "Gradient Animation";

        canvasView = new SKCanvasView();
        canvasView.PaintSurface += OnCanvasViewPaintSurface;
        Content = canvasView;
    }

    protected override void OnAppearing()
    {
        base.OnAppearing();

        isAnimating = true;
        stopwatch.Start();
        Device.StartTimer(TimeSpan.FromMilliseconds(16), OnTimerTick);
    }

    protected override void OnDisappearing()
    {
        base.OnDisappearing();

        stopwatch.Stop();
        isAnimating = false;
    }

    bool OnTimerTick()
    {
        const int duration = 3000;
        angle = 2 * Math.PI * (stopwatch.ElapsedMilliseconds % duration) / duration;
        canvasView.InvalidateSurface();

        return isAnimating;
    }
    ···
}

이 메서드는 OnTimerTick 3초마다 0에서 2π까지 애니메이션 효과를 주는 값을 계산합니다 angle .

다음은 두 그라데이션 점을 계산하는 한 가지 방법입니다. 명명된 SKPoint vector 값은 캔버스의 중심에서 원 반경의 지점까지 확장되도록 계산됩니다. 이 벡터의 방향은 각도의 사인 및 코사인 값을 기반으로 합니다. 그런 다음 두 개의 반대 그라데이션 점이 계산됩니다. 한 지점은 중심점에서 해당 벡터를 빼서 계산되고 다른 점은 중심점에 벡터를 추가하여 계산됩니다.

public class GradientAnimationPage : ContentPage
{
    ···
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        using (SKPaint paint = new SKPaint())
        {
            SKPoint center = new SKPoint(info.Rect.MidX, info.Rect.MidY);
            int radius = Math.Min(info.Width, info.Height) / 2;
            SKPoint vector = new SKPoint((float)(radius * Math.Cos(angle)),
                                         (float)(radius * Math.Sin(angle)));

            paint.Shader = SKShader.CreateLinearGradient(
                                center - vector,
                                center + vector,
                                new SKColor[] { SKColors.White, SKColors.Black },
                                null,
                                SKShaderTileMode.Mirror);

            canvas.DrawRect(info.Rect, paint);
        }
    }
}

다소 다른 접근 방식에는 코드가 더 적게 필요합니다. 이 방법은 행렬 변환과 SKShader.CreateLinearGradient 함께 오버로드 메서드를 마지막 인수로 사용합니다. 이 방법은 샘플의 버전입니다.

public class GradientAnimationPage : ContentPage
{
    ···
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        using (SKPaint paint = new SKPaint())
        {
            paint.Shader = SKShader.CreateLinearGradient(
                                new SKPoint(0, 0),
                                info.Width < info.Height ? new SKPoint(info.Width, 0) :
                                                           new SKPoint(0, info.Height),
                                new SKColor[] { SKColors.White, SKColors.Black },
                                new float[] { 0, 1 },
                                SKShaderTileMode.Mirror,
                                SKMatrix.MakeRotation((float)angle, info.Rect.MidX, info.Rect.MidY));

            canvas.DrawRect(info.Rect, paint);
        }
    }
}

캔버스의 너비가 높이보다 작으면 두 그라데이션 점이 (0, 0) 및 (info.Width, 0)로 설정됩니다. 마지막 인수 CreateLinearGradient 로 전달된 회전 변환은 화면 중앙을 중심으로 두 점을 효과적으로 회전합니다.

각도가 0이면 회전이 없고 두 그라데이션 점은 캔버스의 왼쪽 위와 오른쪽 위 모서리입니다. 이러한 점은 이전 CreateLinearGradient 호출에 표시된 것과 동일한 그라데이션 포인트가 아닙니다. 그러나 이러한 점들은 캔버스의 중심을 양분하는 가로 그라데이션 선과 평행 하며 동일한 그라데이션을 생성합니다.

레인보우 그라데이션

레인보우 그라데이션 페이지는 캔버스의 왼쪽 위 모서리에서 오른쪽 아래 모서리까지 무지개를 그립니다. 그러나이 무지개 그라데이션은 실제 무지개처럼되지 않습니다. 곡선이 아닌 직선이지만 0에서 360까지의 색조 값을 순환하여 결정되는 8개의 HSL(색조 채도-광도) 색을 기반으로 합니다.

SKColor[] colors = new SKColor[8];

for (int i = 0; i < colors.Length; i++)
{
    colors[i] = SKColor.FromHsl(i * 360f / (colors.Length - 1), 100, 50);
}

이 코드는 아래에 표시된 처리기의 PaintSurface 일부입니다. 처리기는 캔버스의 왼쪽 위 모서리에서 오른쪽 아래 모서리까지 확장하는 6면 다각형을 정의하는 경로를 만드는 것으로 시작합니다.

public class RainbowGradientPage : ContentPage
{
    public RainbowGradientPage ()
    {
        Title = "Rainbow Gradient";

        SKCanvasView canvasView = new SKCanvasView();
        canvasView.PaintSurface += OnCanvasViewPaintSurface;
        Content = canvasView;
    }

    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        using (SKPath path = new SKPath())
        {
            float rainbowWidth = Math.Min(info.Width, info.Height) / 2f;

            // Create path from upper-left to lower-right corner
            path.MoveTo(0, 0);
            path.LineTo(rainbowWidth / 2, 0);
            path.LineTo(info.Width, info.Height - rainbowWidth / 2);
            path.LineTo(info.Width, info.Height);
            path.LineTo(info.Width - rainbowWidth / 2, info.Height);
            path.LineTo(0, rainbowWidth / 2);
            path.Close();

            using (SKPaint paint = new SKPaint())
            {
                SKColor[] colors = new SKColor[8];

                for (int i = 0; i < colors.Length; i++)
                {
                    colors[i] = SKColor.FromHsl(i * 360f / (colors.Length - 1), 100, 50);
                }

                paint.Shader = SKShader.CreateLinearGradient(
                                    new SKPoint(0, rainbowWidth / 2),
                                    new SKPoint(rainbowWidth / 2, 0),
                                    colors,
                                    null,
                                    SKShaderTileMode.Repeat);

                canvas.DrawPath(path, paint);
            }
        }
    }
}

메서드의 CreateLinearGradient 두 그라데이션 점은 이 경로를 정의하는 두 점을 기반으로 합니다. 두 점 모두 왼쪽 위 모서리에 가깝습니다. 첫 번째는 캔버스의 위쪽 가장자리에 있고 두 번째는 캔버스의 왼쪽 가장자리에 있습니다. 결과:

레인보우 그라데이션 결함

이것은 흥미로운 이미지이지만 의도는 아닙니다. 문제는 선형 그라데이션을 만들 때 상수 색의 선이 그라데이션 선에 수직적이라는 것입니다. 그라데이션 선은 그림이 위쪽과 왼쪽에 닿는 점을 기반으로 하며, 이 선은 일반적으로 오른쪽 아래 모서리까지 확장되는 그림의 가장자리에 수직이 아닙니다. 이 방법은 캔버스가 정사각형인 경우에만 작동합니다.

적절한 무지개 그라데이션을 만들려면 그라데이션 선이 무지개 가장자리에 수직이어야 합니다. 이는 더 관련된 계산입니다. 그림의 긴 면과 평행한 벡터를 정의해야 합니다. 벡터는 90도 회전되므로 해당 쪽에 수직으로 회전합니다. 그런 다음 을 곱 rainbowWidth하여 그림의 너비로 길어집니다. 두 그라데이션 점은 그림 측면의 점과 해당 점 및 벡터를 기준으로 계산됩니다. 샘플의 레인보우 그라데이션 페이지에 표시되는 코드는 다음과 같습니다.

public class RainbowGradientPage : ContentPage
{
    ···
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        ···
        using (SKPath path = new SKPath())
        {
            ···
            using (SKPaint paint = new SKPaint())
            {
                ···
                // Vector on lower-left edge, from top to bottom
                SKPoint edgeVector = new SKPoint(info.Width - rainbowWidth / 2, info.Height) -
                                     new SKPoint(0, rainbowWidth / 2);

                // Rotate 90 degrees counter-clockwise:
                SKPoint gradientVector = new SKPoint(edgeVector.Y, -edgeVector.X);

                // Normalize
                float length = (float)Math.Sqrt(Math.Pow(gradientVector.X, 2) +
                                                Math.Pow(gradientVector.Y, 2));
                gradientVector.X /= length;
                gradientVector.Y /= length;

                // Make it the width of the rainbow
                gradientVector.X *= rainbowWidth;
                gradientVector.Y *= rainbowWidth;

                // Calculate the two points
                SKPoint point1 = new SKPoint(0, rainbowWidth / 2);
                SKPoint point2 = point1 + gradientVector;

                paint.Shader = SKShader.CreateLinearGradient(point1,
                                                             point2,
                                                             colors,
                                                             null,
                                                             SKShaderTileMode.Repeat);

                canvas.DrawPath(path, paint);
            }
        }
    }
}

이제 무지개 색이 그림에 맞춰집니다.

레인보우 그라데이션

무한대 색

무지개 그라데이션은 무한대 색 페이지에서도 사용됩니다. 이 페이지는 세 가지 유형의 베지어 곡선 문서에 설명된 경로 개체를 사용하여 무한대 기호를 그립니다. 그런 다음 이미지 전체에 지속적으로 스윕되는 애니메이션 레인보우 그라데이션으로 이미지가 색이 지정됩니다.

생성자는 무한대 기호를 설명하는 개체를 만듭니다 SKPath . 경로를 만든 후 생성자는 경로의 사각형 범위를 가져올 수도 있습니다. 그런 다음 , 라는 gradientCycleLength값을 계산합니다. 그라데이션이 사각형의 왼쪽 위와 오른쪽 아래 모서리를 pathBounds 기반으로 하는 경우 이 gradientCycleLength 값은 그라데이션 패턴의 총 가로 너비입니다.

public class InfinityColorsPage : ContentPage
{
    ···
    SKCanvasView canvasView;

    // Path information
    SKPath infinityPath;
    SKRect pathBounds;
    float gradientCycleLength;

    // Gradient information
    SKColor[] colors = new SKColor[8];
    ···

    public InfinityColorsPage ()
    {
        Title = "Infinity Colors";

        // Create path for infinity sign
        infinityPath = new SKPath();
        infinityPath.MoveTo(0, 0);                                  // Center
        infinityPath.CubicTo(  50,  -50,   95, -100,  150, -100);   // To top of right loop
        infinityPath.CubicTo( 205, -100,  250,  -55,  250,    0);   // To far right of right loop
        infinityPath.CubicTo( 250,   55,  205,  100,  150,  100);   // To bottom of right loop
        infinityPath.CubicTo(  95,  100,   50,   50,    0,    0);   // Back to center  
        infinityPath.CubicTo( -50,  -50,  -95, -100, -150, -100);   // To top of left loop
        infinityPath.CubicTo(-205, -100, -250,  -55, -250,    0);   // To far left of left loop
        infinityPath.CubicTo(-250,   55, -205,  100, -150,  100);   // To bottom of left loop
        infinityPath.CubicTo( -95,  100, - 50,   50,    0,    0);   // Back to center
        infinityPath.Close();

        // Calculate path information
        pathBounds = infinityPath.Bounds;
        gradientCycleLength = pathBounds.Width +
            pathBounds.Height * pathBounds.Height / pathBounds.Width;

        // Create SKColor array for gradient
        for (int i = 0; i < colors.Length; i++)
        {
            colors[i] = SKColor.FromHsl(i * 360f / (colors.Length - 1), 100, 50);
        }

        canvasView = new SKCanvasView();
        canvasView.PaintSurface += OnCanvasViewPaintSurface;
        Content = canvasView;
    }
    ···
}

또한 생성자는 무지개 및 개체에 대한 배열을 SKCanvasView 만듭니다colors.

재정의 및 OnDisappearing 메서드는 OnAppearing 애니메이션에 대한 오버헤드를 수행합니다. 이 메서드는 OnTimerTick 필드에 0에서 2초마다 애니메이션 효과를 gradientCycleLength 봅니 offset 다.

public class InfinityColorsPage : ContentPage
{
    ···
    // For animation
    bool isAnimating;
    float offset;
    Stopwatch stopwatch = new Stopwatch();
    ···

    protected override void OnAppearing()
    {
        base.OnAppearing();

        isAnimating = true;
        stopwatch.Start();
        Device.StartTimer(TimeSpan.FromMilliseconds(16), OnTimerTick);
    }

    protected override void OnDisappearing()
    {
        base.OnDisappearing();

        stopwatch.Stop();
        isAnimating = false;
    }

    bool OnTimerTick()
    {
        const int duration = 2;     // seconds
        double progress = stopwatch.Elapsed.TotalSeconds % duration / duration;
        offset = (float)(gradientCycleLength * progress);
        canvasView.InvalidateSurface();

        return isAnimating;
    }
    ···
}

마지막으로 처리기는 PaintSurface 무한대 기호를 렌더링합니다. 경로는 중심점(0, 0) Translate 을 둘러싼 음수 및 양수 좌표를 포함하므로 캔버스의 변환을 사용하여 가운데로 이동합니다. 변환 변환 뒤에는 캔버스 너비와 높이의 95% 이내를 유지하면서 무한대 기호를 최대한 크게 만드는 배율 인수를 적용하는 변환이 뒤따 Scale 릅니다.

STROKE_WIDTH 상수는 경로 경계 사각형의 너비와 높이에 추가됩니다. 경로는 이 너비의 선으로 스트로크되므로 렌더링된 무한대 크기의 크기가 네 면 모두에 있는 너비의 절반으로 증가합니다.

public class InfinityColorsPage : ContentPage
{
    const int STROKE_WIDTH = 50;
    ···
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        // Set transforms to shift path to center and scale to canvas size
        canvas.Translate(info.Width / 2, info.Height / 2);
        canvas.Scale(0.95f *
            Math.Min(info.Width / (pathBounds.Width + STROKE_WIDTH),
                     info.Height / (pathBounds.Height + STROKE_WIDTH)));

        using (SKPaint paint = new SKPaint())
        {
            paint.Style = SKPaintStyle.Stroke;
            paint.StrokeWidth = STROKE_WIDTH;
            paint.Shader = SKShader.CreateLinearGradient(
                                new SKPoint(pathBounds.Left, pathBounds.Top),
                                new SKPoint(pathBounds.Right, pathBounds.Bottom),
                                colors,
                                null,
                                SKShaderTileMode.Repeat,
                                SKMatrix.MakeTranslation(offset, 0));

            canvas.DrawPath(infinityPath, paint);
        }
    }
}

의 처음 두 인수로 전달된 점을 확인합니다 SKShader.CreateLinearGradient. 이러한 점은 원래 경로 경계 사각형을 기반으로 합니다. 첫 번째 점은 (-250, –100)이고 두 번째는 (250, 100)입니다. SkiaSharp의 내부 요소에는 현재 캔버스 변환이 적용되므로 표시된 무한대 기호에 올바르게 맞춥니다.

마지막 인수 CreateLinearGradient가 없으면 무한대 기호의 왼쪽 위에서 오른쪽 아래까지 확장되는 무지개 그라데이션이 표시됩니다. (실제로 그라데이션은 왼쪽 위 모서리에서 경계 사각형의 오른쪽 아래 모서리까지 확장됩니다. 렌더링된 무한대 기호는 경계 사각형보다 모든 면의 값의 STROKE_WIDTH 절반보다 큽니다. 그라데이션은 시작과 끝 모두에서 빨간색이고 그라데이션이 생성 SKShaderTileMode.Repeat되므로 차이가 눈에 띄지 않습니다.)

마지막 인수를 사용하면 CreateLinearGradient그라데이션 패턴이 이미지 전체에서 계속 스윕됩니다.

무한대 색

투명도 및 그라데이션

그라데이션에 영향을 주는 색은 투명도를 통합할 수 있습니다. 그라데이션은 한 색에서 다른 색으로 페이드되는 그라데이션 대신 색에서 투명으로 페이드할 수 있습니다.

몇 가지 흥미로운 효과에 대 한이 기술을 사용할 수 있습니다. 클래식 예제 중 하나는 리플렉션이 있는 그래픽 개체를 보여 줍니다.

리플렉션 그라데이션

거꾸로 된 텍스트는 위쪽에 50% 투명하고 아래쪽에 완전히 투명하게 그라데이션으로 색이 지정됩니다. 이러한 수준의 투명도는 0x80 및 0의 알파 값과 연결됩니다.

PaintSurface 리플렉션 그라데이션 페이지의 처리기는 텍스트 크기를 캔버스 너비의 90%로 조정합니다. 그런 다음, 텍스트를 가로 가운데에 배치하지만 페이지의 세로 가운데에 해당하는 기준선에 배치하는 값을 계산 xText 합니다 yText .

public class ReflectionGradientPage : ContentPage
{
    const string TEXT = "Reflection";

    public ReflectionGradientPage ()
    {
        Title = "Reflection Gradient";

        SKCanvasView canvasView = new SKCanvasView();
        canvasView.PaintSurface += OnCanvasViewPaintSurface;
        Content = canvasView;
    }

    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        using (SKPaint paint = new SKPaint())
        {
            // Set text color to blue
            paint.Color = SKColors.Blue;

            // Set text size to fill 90% of width
            paint.TextSize = 100;
            float width = paint.MeasureText(TEXT);
            float scale = 0.9f * info.Width / width;
            paint.TextSize *= scale;

            // Get text bounds
            SKRect textBounds = new SKRect();
            paint.MeasureText(TEXT, ref textBounds);

            // Calculate offsets to position text above center
            float xText = info.Width / 2 - textBounds.MidX;
            float yText = info.Height / 2;

            // Draw unreflected text
            canvas.DrawText(TEXT, xText, yText, paint);

            // Shift textBounds to match displayed text
            textBounds.Offset(xText, yText);

            // Use those offsets to create a gradient for the reflected text
            paint.Shader = SKShader.CreateLinearGradient(
                                new SKPoint(0, textBounds.Top),
                                new SKPoint(0, textBounds.Bottom),
                                new SKColor[] { paint.Color.WithAlpha(0),
                                                paint.Color.WithAlpha(0x80) },
                                null,
                                SKShaderTileMode.Clamp);

            // Scale the canvas to flip upside-down around the vertical center
            canvas.Scale(1, -1, 0, yText);

            // Draw reflected text
            canvas.DrawText(TEXT, xText, yText, paint);
        }
    }
}

이러한 xText 값과 값은 처리기 맨 아래에 PaintSurface 있는 호출에 DrawText 반영된 텍스트를 표시하는 데 사용되는 값과 yText 동일합니다. 그러나 해당 코드 바로 앞에는 메서드에 대한 호출이 Scale SKCanvas표시됩니다. 이 Scale 메서드는 가로로 1(아무 작업도 수행하지 않음)으로 확장되지만 세로로 –1로 조정되어 모든 항목을 거꾸로 효과적으로 대칭 이동합니다. 회전 중심은 캔버스의 세로 중심인 점(0 yText)으로 설정되며 yText , 원래는 2로 나눈 값으로 info.Height 계산됩니다.

Skia는 캔버스 변환 전에 그라데이션을 사용하여 그래픽 개체에 색을 지정합니다. 반사되지 않은 텍스트가 그려지면 textBounds 표시된 텍스트에 해당하도록 사각형이 이동합니다.

textBounds.Offset(xText, yText);

호출은 CreateLinearGradient 해당 사각형의 위쪽에서 아래쪽으로 그라데이션을 정의합니다. 그라데이션은 완전히 투명한 파란색(paint.Color.WithAlpha(0))에서 50% 투명 파랑(paint.Color.WithAlpha(0x80))입니다. 캔버스 변환은 텍스트를 거꾸로 대칭 이동하므로 50% 투명한 파란색이 기준선에서 시작되고 텍스트 맨 위에서 투명해집니다.