Поделиться через


Преобразование циклического сдвига

Изучение эффектов и анимаций с помощью преобразования SkiaSharp

При преобразовании поворота графические объекты SkiaSharp освобождаются от ограничения выравнивания с горизонтальными и вертикальными осями:

Текст, повернутый вокруг центра

Для поворота графического объекта вокруг точки (0, 0), SkiaSharp поддерживает RotateDegrees метод и RotateRadians метод:

public void RotateDegrees (Single degrees)

public Void RotateRadians (Single radians)

Круг 360 градусов совпадает с 2π радианами, поэтому легко преобразовать между двумя единицами. Используйте любой удобный вариант. Все тригонометрические функции в классе .NET Math используют единицы радианов.

Поворот по часовой стрелке для увеличения углов. (Хотя поворот в системе координат Декартиана по соглашению против часовой стрелки по соглашению, по часовой стрелке согласуется с координатами Y, увеличиваясь, как в SkiaSharp.) Допустимы отрицательные угол и угол, превышающие 360 градусов.

Формулы преобразования для поворота являются более сложными, чем для перевода и масштабирования. Для угла α формулы преобразования:

x' = x•cos(α) — y•sin(α)

y' = x•sin(α) + y•cos(α)

На странице "Базовый поворот" демонстрируется RotateDegrees метод. Файл BasicRotate.xaml.cs отображает некоторый текст со своим базовым центром на странице и поворачивает его на Slider основе диапазона от –360 до 360. Ниже приведена соответствующая часть обработчика PaintSurface :

using (SKPaint textPaint = new SKPaint
{
    Style = SKPaintStyle.Fill,
    Color = SKColors.Blue,
    TextAlign = SKTextAlign.Center,
    TextSize = 100
})
{
    canvas.RotateDegrees((float)rotateSlider.Value);
    canvas.DrawText(Title, info.Width / 2, info.Height / 2, textPaint);
}

Так как поворот находится в левом верхнем углу холста, для большинства углов, заданных в этой программе, текст поворачивается с экрана:

Тройной снимок экрана страницы

Очень часто требуется повернуть что-то по центру вокруг указанной точки сводных данных с помощью этих версий RotateDegrees и RotateRadians методов:

public void RotateDegrees (Single degrees, Single px, Single py)

public void RotateRadians (Single radians, Single px, Single py)

Страница поворота по центру похожа на базовую поворотную, за исключением того, что развернутая версия RotateDegrees используется для задания центра поворота в ту же точку, которая используется для размещения текста:

using (SKPaint textPaint = new SKPaint
{
    Style = SKPaintStyle.Fill,
    Color = SKColors.Blue,
    TextAlign = SKTextAlign.Center,
    TextSize = 100
})
{
    canvas.RotateDegrees((float)rotateSlider.Value, info.Width / 2, info.Height / 2);
    canvas.DrawText(Title, info.Width / 2, info.Height / 2, textPaint);
}

Теперь текст поворачивается вокруг точки, используемой для размещения текста, который является горизонтальным центром базового плана текста:

Тройной снимок экрана: страница поворота по центру

Как и в центре версии метода, центрированная версия ScaleRotateDegrees вызова является ярлыком. Ниже приведен метод.

RotateDegrees (degrees, px, py);

Этот вызов эквивалентен следующему:

canvas.Translate(px, py);
canvas.RotateDegrees(degrees);
canvas.Translate(-px, -py);

Вы обнаружите, что иногда можно сочетать Translate звонки с Rotate вызовами. Например, ниже приведены RotateDegreesDrawText вызовы и вызовы на странице " Поворот по центру";

canvas.RotateDegrees((float)rotateSlider.Value, info.Width / 2, info.Height / 2);
canvas.DrawText(Title, info.Width / 2, info.Height / 2, textPaint);

Вызов RotateDegrees эквивалентен двум Translate вызовам и нецентрированных RotateDegrees:

canvas.Translate(info.Width / 2, info.Height / 2);
canvas.RotateDegrees((float)rotateSlider.Value);
canvas.Translate(-info.Width / 2, -info.Height / 2);
canvas.DrawText(Title, info.Width / 2, info.Height / 2, textPaint);

Вызов DrawText отображения текста в определенном расположении эквивалентен Translate вызову этого расположения, за которым следует DrawText в точке (0, 0):

canvas.Translate(info.Width / 2, info.Height / 2);
canvas.RotateDegrees((float)rotateSlider.Value);
canvas.Translate(-info.Width / 2, -info.Height / 2);
canvas.Translate(info.Width / 2, info.Height / 2);
canvas.DrawText(Title, 0, 0, textPaint);

Два последовательных Translate вызова отменяют друг друга:

canvas.Translate(info.Width / 2, info.Height / 2);
canvas.RotateDegrees((float)rotateSlider.Value);
canvas.DrawText(Title, 0, 0, textPaint);

Концептуально два преобразования применяются в порядке, противоположном тому, как они отображаются в коде. Вызов DrawText отображает текст в левом верхнем углу холста. Вызов RotateDegrees поворачивает этот текст относительно левого верхнего угла. Translate Затем вызов перемещает текст в центр холста.

Обычно существует несколько способов объединения поворота и перевода. Страница "Повернутый текст" создает следующее отображение:

Тройной снимок экрана страницы

PaintSurface Вот обработчик RotatedTextPage класса:

static readonly string text = "    ROTATE";
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();

    using (SKPaint textPaint = new SKPaint
    {
        Color = SKColors.Black,
        TextSize = 72
    })
    {
        float xCenter = info.Width / 2;
        float yCenter = info.Height / 2;

        SKRect textBounds = new SKRect();
        textPaint.MeasureText(text, ref textBounds);
        float yText = yCenter - textBounds.Height / 2 - textBounds.Top;

        for (int degrees = 0; degrees < 360; degrees += 30)
        {
            canvas.Save();
            canvas.RotateDegrees(degrees, xCenter, yCenter);
            canvas.DrawText(text, xCenter, yText, textPaint);
            canvas.Restore();
        }
    }
}

И xCenteryCenter значения указывают центр холста. Это yText значение немного смещается от этого. Это значение является координатой Y, необходимой для размещения текста таким образом, чтобы он действительно по вертикали по центру на странице. Затем цикл for задает поворот на основе центра холста. Поворот увеличивается на 30 градусов. Текст рисуется с помощью yText значения. Число пустых перед словом "ROTATE" в text значении было определено эмпирически, чтобы сделать соединение между этими 12 текстовыми строками, как представляется, dodecagon.

Один из способов упрощения этого кода заключается в том, чтобы увеличить угол поворота на 30 градусов каждый раз через цикл после DrawText вызова. Это устраняет потребность в Save вызовах и Restore. Обратите внимание, что degrees переменная больше не используется в теле for блока:

for (int degrees = 0; degrees < 360; degrees += 30)
{
    canvas.DrawText(text, xCenter, yText, textPaint);
    canvas.RotateDegrees(30, xCenter, yCenter);
}

Кроме того, можно использовать простую форму RotateDegrees , префиксируя цикл вызовом для Translate перемещения всего в центр холста:

float yText = -textBounds.Height / 2 - textBounds.Top;

canvas.Translate(xCenter, yCenter);

for (int degrees = 0; degrees < 360; degrees += 30)
{
    canvas.DrawText(text, 0, yText, textPaint);
    canvas.RotateDegrees(30);
}

Измененное yText вычисление больше не включает yCenter. Теперь вызывайте DrawText текст по вертикали в верхней части холста.

Поскольку преобразования концептуально применяются напротив того, как они отображаются в коде, часто можно начать с более глобальных преобразований, а затем более локальных преобразований. Это часто самый простой способ объединить поворот и перевод.

Например, предположим, что вы хотите нарисовать графический объект, который поворачивается вокруг своего центра так же, как планета, вращающаяся на ее оси. Но вы также хотите, чтобы этот объект вращался вокруг центра экрана так же, как планета, вращающаяся вокруг солнца.

Это можно сделать, разместив объект в левом верхнем углу холста, а затем с помощью анимации, чтобы повернуть его вокруг этого угла. Затем преобразуйте объект по горизонтали, как орбитальный радиус. Теперь примените второй анимированный поворот, а также вокруг источника. Это делает объект вращающимся вокруг угла. Теперь преобразуем в центр холста.

Ниже приведен PaintSurface обработчик, содержащий эти вызовы преобразования в обратном порядке:

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

    canvas.Clear();

    using (SKPaint fillPaint = new SKPaint
    {
        Style = SKPaintStyle.Fill,
        Color = SKColors.Red
    })
    {
        // Translate to center of canvas
        canvas.Translate(info.Width / 2, info.Height / 2);

        // Rotate around center of canvas
        canvas.RotateDegrees(revolveDegrees);

        // Translate horizontally
        float radius = Math.Min(info.Width, info.Height) / 3;
        canvas.Translate(radius, 0);

        // Rotate around center of object
        canvas.RotateDegrees(rotateDegrees);

        // Draw a square
        canvas.DrawRect(new SKRect(-50, -50, 50, 50), fillPaint);
    }
}

rotateDegrees Анимированные revolveDegrees поля. Эта программа использует другой метод анимации на Xamarin.FormsAnimation основе класса. (Этот класс описан в главе 22 Бесплатная загрузка PDF-файла создания мобильных приложений сXamarin.Forms помощью OnAppearing функции переопределения создает два Animation объекта с методами обратного вызова, а затем вызывает Commit их в течение длительности анимации:

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

    new Animation((value) => revolveDegrees = 360 * (float)value).
        Commit(this, "revolveAnimation", length: 10000, repeat: () => true);

    new Animation((value) =>
    {
        rotateDegrees = 360 * (float)value;
        canvasView.InvalidateSurface();
    }).Commit(this, "rotateAnimation", length: 1000, repeat: () => true);
}

Первый Animation объект анимирует revolveDegrees от 0 до 360 градусов в течение 10 секунд. Второй один анимирует rotateDegrees от 0 до 360 градусов каждые 1 секунду, а также делает поверхность недопустимой для создания другого вызова обработчика PaintSurface . Переопределение OnDisappearing отменяет эти две анимации:

protected override void OnDisappearing()
{
    base.OnDisappearing();
    this.AbortAnimation("revolveAnimation");
    this.AbortAnimation("rotateAnimation");
}

Уродливые аналоговые часы (так называемые, потому что более привлекательные аналоговые часы будут описаны в более поздней статье) использует поворот для рисования минут и часовых меток часов и поворота рук. Программа рисует часы с помощью произвольной системы координат на основе круга, который находится в центре точки (0, 0) с радиусом 100. Он использует преобразование и масштабирование для расширения и центра этого круга на странице.

Scale Вызовы Translate применяются глобально к часам, поэтому они являются первыми, которые должны вызываться после инициализации SKPaint объектов:

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

    canvas.Clear();

    using (SKPaint strokePaint = new SKPaint())
    using (SKPaint fillPaint = new SKPaint())
    {
        strokePaint.Style = SKPaintStyle.Stroke;
        strokePaint.Color = SKColors.Black;
        strokePaint.StrokeCap = SKStrokeCap.Round;

        fillPaint.Style = SKPaintStyle.Fill;
        fillPaint.Color = SKColors.Gray;

        // Transform for 100-radius circle centered at origin
        canvas.Translate(info.Width / 2f, info.Height / 2f);
        canvas.Scale(Math.Min(info.Width / 200f, info.Height / 200f));
        ...
    }
}

Существует 60 меток двух разных размеров, которые должны быть нарисованы в кругу круглосуточно. Вызов DrawCircle рисует этот круг в точке (0, –90), что относительно центра часов соответствует 12:00. Вызов RotateDegrees увеличивает угол поворота на 6 градусов после каждой галочки. Переменная angle используется исключительно для определения того, рисуется ли большой круг или небольшой круг:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    ...
        // Hour and minute marks
        for (int angle = 0; angle < 360; angle += 6)
        {
            canvas.DrawCircle(0, -90, angle % 30 == 0 ? 4 : 2, fillPaint);
            canvas.RotateDegrees(6);
        }
    ...
    }
}

Наконец, PaintSurface обработчик получает текущее время и вычисляет градусы поворота в течение часа, минуты и секунды. Каждая рука рисуется в позиции 12:00, чтобы угол поворота был относительно этого:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    ...
        DateTime dateTime = DateTime.Now;

        // Hour hand
        strokePaint.StrokeWidth = 20;
        canvas.Save();
        canvas.RotateDegrees(30 * dateTime.Hour + dateTime.Minute / 2f);
        canvas.DrawLine(0, 0, 0, -50, strokePaint);
        canvas.Restore();

        // Minute hand
        strokePaint.StrokeWidth = 10;
        canvas.Save();
        canvas.RotateDegrees(6 * dateTime.Minute + dateTime.Second / 10f);
        canvas.DrawLine(0, 0, 0, -70, strokePaint);
        canvas.Restore();

        // Second hand
        strokePaint.StrokeWidth = 2;
        canvas.Save();
        canvas.RotateDegrees(6 * dateTime.Second);
        canvas.DrawLine(0, 10, 0, -80, strokePaint);
        canvas.Restore();
    }
}

Часы, безусловно, функциональны, хотя руки довольно сырые:

Тройной снимок экрана страницы

Более привлекательные часы см. в статье SVG Path Data in SkiaSharp.