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


Преобразование масштаба

Обнаружение преобразования масштабирования SkiaSharp для масштабирования объектов до различных размеров

Как вы видели в статье "Преобразование перевода", преобразование перевода может перемещать графический объект из одного расположения в другое. В отличие от этого, преобразование масштабирования изменяет размер графического объекта:

Большое число слов, масштабируемых по размеру

Преобразование масштабирования также часто приводит к перемещению графических координат по мере их увеличения.

Ранее вы видели две формулы преобразования, описывающие последствия факторов dx перевода и dy:

x ' = x + dx

y' = y + dy

Коэффициенты масштабирования sx и sy являются мультипликативными, а не аддитивными:

x' = sx · X

y' = sy · Y

Значения по умолчанию для коэффициентов перевода — 0; Значения по умолчанию для коэффициентов масштабирования — 1.

Класс SKCanvas определяет четыре Scale метода. Первый Scale метод предназначен для случаев, когда требуется тот же горизонтальный и вертикальный коэффициент масштабирования:

public void Scale (Single s)

Это называется изотропным масштабированием — масштабирование, одинаковое в обоих направлениях. Изотропное масштабирование сохраняет пропорции объекта.

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

public void Scale (Single sx, Single sy)

Это приводит к анизотропии масштабирования. Scale Третий метод объединяет два фактора масштабирования в одном SKPoint значении:

public void Scale (SKPoint size)

Четвертый Scale метод будет описан в ближайшее время.

На странице "Базовый масштаб" демонстрируется Scale метод. Файл BasicScalePage.xaml содержит два Slider элемента, которые позволяют выбирать горизонтальные и вертикальные коэффициенты масштабирования от 0 до 10. Файл BasicScalePage.xaml.cs кодовой части использует эти значения для вызова Scale перед отображением округленного прямоугольника с тиреной линией и размером, чтобы поместить текст в левом верхнем углу холста:

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

    canvas.Clear(SKColors.SkyBlue);

    using (SKPaint strokePaint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        Color = SKColors.Red,
        StrokeWidth = 3,
        PathEffect = SKPathEffect.CreateDash(new float[] {  7, 7 }, 0)
    })
    using (SKPaint textPaint = new SKPaint
    {
        Style = SKPaintStyle.Fill,
        Color = SKColors.Blue,
        TextSize = 50
    })
    {
        canvas.Scale((float)xScaleSlider.Value,
                     (float)yScaleSlider.Value);

        SKRect textBounds = new SKRect();
        textPaint.MeasureText(Title, ref textBounds);

        float margin = 10;
        SKRect borderRect = SKRect.Create(new SKPoint(margin, margin), textBounds.Size);
        canvas.DrawRoundRect(borderRect, 20, 20, strokePaint);
        canvas.DrawText(Title, margin, -textBounds.Top + margin, textPaint);
    }
}

Вы можете задаться вопросом: как коэффициенты масштабирования влияют на значение, возвращаемое методом MeasureTextSKPaint? Ответ: Нет вообще. Scale — это метод SKCanvas. Он не влияет на то, что вы делаете с SKPaint объектом, пока не будете использовать этот объект для отрисовки что-то на холсте.

Как видно, все, что вырисовывается после Scale вызова, пропорционально увеличивается:

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

Текст, ширина пунктирной линии, длина тире в этой строке, округление углов и 10-пиксельное поле между левыми и верхними краями холста и округленным прямоугольником все подвержены одинаковым коэффициентам масштабирования.

Внимание

Универсальная платформа Windows неправильно отрисовывает анисотропный текст.

Анисотропное масштабирование приводит к тому, что ширина штриха будет отличаться для линий, выровненных по горизонтали и вертикальным осям. (Это также видно из первого изображения на этой странице.) Если вы не хотите, чтобы ширина росчерка влияла на коэффициенты масштабирования, задайте для нее значение 0, и она всегда будет шириной одного пикселя независимо от Scale параметра.

Масштабирование относительно левого верхнего угла холста. Это может быть именно то, что вы хотите, но это может быть не так. Предположим, вы хотите разместить текст и прямоугольник в другом месте на холсте, и вы хотите масштабировать его относительно его центра. В этом случае можно использовать четвертую версию метода, которая включает два дополнительных Scale параметра для указания центра масштабирования:

public void Scale (Single sx, Single sy, Single px, Single py)

Параметры px определяют точку, которая иногда называется центром масштабирования, но в документации SkiaSharp называется точкой сводных данных.py Это точка относительно левого верхнего угла холста, который не влияет на масштабирование. Все масштабирование происходит относительно этого центра.

На странице "Центрированные масштабы " показано, как это работает. Обработчик PaintSurface аналогичен программе "Базовый масштаб" , за исключением того, что margin значение вычисляется по центру текста по горизонтали, что означает, что программа лучше всего работает в книжном режиме:

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

    canvas.Clear(SKColors.SkyBlue);

    using (SKPaint strokePaint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        Color = SKColors.Red,
        StrokeWidth = 3,
        PathEffect = SKPathEffect.CreateDash(new float[] { 7, 7 }, 0)
    })
    using (SKPaint textPaint = new SKPaint
    {
        Style = SKPaintStyle.Fill,
        Color = SKColors.Blue,
        TextSize = 50
    })
    {
        SKRect textBounds = new SKRect();
        textPaint.MeasureText(Title, ref textBounds);
        float margin = (info.Width - textBounds.Width) / 2;

        float sx = (float)xScaleSlider.Value;
        float sy = (float)yScaleSlider.Value;
        float px = margin + textBounds.Width / 2;
        float py = margin + textBounds.Height / 2;

        canvas.Scale(sx, sy, px, py);

        SKRect borderRect = SKRect.Create(new SKPoint(margin, margin), textBounds.Size);
        canvas.DrawRoundRect(borderRect, 20, 20, strokePaint);
        canvas.DrawText(Title, margin, -textBounds.Top + margin, textPaint);
    }
}

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

Тройной снимок экрана: центрированная страница масштабирования

Элементы Slider в этой программе имеют диапазон от –10 до 10. Как видно, отрицательные значения вертикального масштабирования (например, на экране Android в центре) вызывают переворачивание объектов вокруг горизонтальной оси, которая проходит через центр масштабирования. Отрицательные значения горизонтального масштабирования (например, на экране UWP справа) вызывают переворачивание объектов вокруг вертикальной оси, которая проходит через центр масштабирования.

Версия Scale метода с точками с точками сводных точек — это ярлык для ряда из трех Translate и Scale вызовов. Возможно, вы хотите узнать, как это работает, заменив Scale метод на странице "Центрированные масштабы " следующим образом:

canvas.Translate(-px, -py);

Это отрицательные координаты точки сводных точек.

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

Теперь добавьте базовый Scale вызов (без центра масштабирования) перед этим Translate вызовом:

canvas.Scale(sx, sy);
canvas.Translate(–px, –py);

Если вы знакомы с этим упражнением в других графических системах программирования, вы можете подумать, что это неправильно, но это не так. Skia обрабатывает последовательные вызовы преобразования немного отличается от того, с чем вы можете ознакомиться.

При последующих Scale вызовах Translate центр округленного прямоугольника по-прежнему находится в левом верхнем углу, но теперь его можно масштабировать относительно левого верхнего угла холста, который также является центром округленного прямоугольника.

Теперь перед этим Scale вызовом добавьте еще один Translate вызов со значениями центра:

canvas.Translate(px, py);
canvas.Scale(sx, sy);
canvas.Translate(–px, –py);

Это перемещает масштабируемый результат обратно в исходную позицию. Эти три вызова эквивалентны следующим:

canvas.Scale(sx, sy, px, py);

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

x' = sx · (x – px) + px

y' = sy · (y – py) + py

Помните, что значения sx по умолчанию и sy имеют значение 1. Легко убедить себя, что точка сводных данных (px, py) не преобразуется этими формулами. Он остается в том же расположении относительно холста.

При объединении Translate и Scale вызове порядок имеет значение. Translate Если после этого Scaleфакторы перевода эффективно масштабируются с помощью коэффициентов масштабирования. Если дело Translate доходит до этого Scale, факторы перевода не масштабируются. Этот процесс становится несколько более понятным (хотя и более математическим) при появлении темы матриц преобразования.

Класс SKPath определяет свойство только Bounds для чтения, которое возвращает SKRect определение степени координат в пути. Например, если Bounds свойство получено из пути hendecagram, созданного ранее, Left и Top свойства прямоугольника приблизительно –100, свойства примерно 100, RightBottom а свойства — приблизительно 100, а WidthHeight свойства — приблизительно 200. (Большинство фактических значений немного меньше, потому что точки звезд определяются кругом с радиусом 100, но только верхняя точка параллельна с горизонтальными или вертикальными осями.)

Доступность этой информации подразумевает, что можно наследовать масштаб и преобразовывать факторы, подходящие для масштабирования пути к размеру холста. Страница анисотропного масштабирования демонстрирует это с 11-законечной звездой. Анисотропная шкала означает, что она неравна в горизонтальных и вертикальных направлениях, что означает, что звезда не будет сохранять свое исходное соотношение пропорций. Ниже приведен соответствующий код в обработчике PaintSurface :

SKPath path = HendecagramPage.HendecagramPath;
SKRect pathBounds = path.Bounds;

using (SKPaint fillPaint = new SKPaint
{
    Style = SKPaintStyle.Fill,
    Color = SKColors.Pink
})
using (SKPaint strokePaint = new SKPaint
{
    Style = SKPaintStyle.Stroke,
    Color = SKColors.Blue,
    StrokeWidth = 3,
    StrokeJoin = SKStrokeJoin.Round
})
{
    canvas.Scale(info.Width / pathBounds.Width,
                 info.Height / pathBounds.Height);
    canvas.Translate(-pathBounds.Left, -pathBounds.Top);

    canvas.DrawPath(path, fillPaint);
    canvas.DrawPath(path, strokePaint);
}

Прямоугольник pathBounds получается в верхней части этого кода, а затем используется позже с шириной и высотой холста в вызове Scale . Этот вызов сам по себе масштабирует координаты пути, когда он отображается DrawPath вызовом, но звезда будет центрирована в правом верхнем углу холста. Он должен быть смещен вниз и влево. Это задание Translate вызова. Эти два свойства pathBounds примерно –100, поэтому факторы перевода около 100. Translate Так как вызов после Scale вызова, эти значения эффективно масштабируются с помощью коэффициентов масштабирования, поэтому они перемещают центр звезды в центр холста:

Тройной снимок экрана страницы анисотропного масштабирования

Другой способ, который можно подумать о ScaleTranslate вызовах, заключается в определении эффекта в обратной последовательности: Translate вызов сдвигает путь, чтобы он стал полностью видимым, но ориентированным в левом верхнем углу холста. Затем Scale метод делает такую звезду больше относительно левого верхнего угла.

На самом деле, кажется, что звезда немного больше, чем холст. Проблема заключается в ширине штриха. Свойство BoundsSKPath указывает размеры координат, закодированных в пути, и это то, что программа использует для масштабирования. Когда путь отрисовывается с определенной шириной штриха, отрисованный путь больше холста.

Чтобы устранить эту проблему, необходимо компенсировать это. Один простой подход в этой программе заключается в добавлении следующей инструкции прямо перед вызовом Scale :

pathBounds.Inflate(strokePaint.StrokeWidth / 2,
                   strokePaint.StrokeWidth / 2);

Это увеличивает pathBounds прямоугольник на 1,5 единиц на всех четырех сторонах. Это разумное решение только в том случае, если соединение штриха округляется. Соединение митер может быть длиннее и трудно вычислить.

Вы также можете использовать аналогичный метод с текстом, как показано на странице "Анисотропный текст ". Ниже приведена соответствующая часть обработчика PaintSurface из AnisotropicTextPage класса:

using (SKPaint textPaint = new SKPaint
{
    Style = SKPaintStyle.Stroke,
    Color = SKColors.Blue,
    StrokeWidth = 0.1f,
    StrokeJoin = SKStrokeJoin.Round
})
{
    SKRect textBounds = new SKRect();
    textPaint.MeasureText("HELLO", ref textBounds);

    // Inflate bounds by the stroke width
    textBounds.Inflate(textPaint.StrokeWidth / 2,
                       textPaint.StrokeWidth / 2);

    canvas.Scale(info.Width / textBounds.Width,
                 info.Height / textBounds.Height);
    canvas.Translate(-textBounds.Left, -textBounds.Top);

    canvas.DrawText("HELLO", 0, 0, textPaint);
}

Это аналогичная логика, и текст расширяется до размера страницы на основе прямоугольника границ текста, возвращаемого ( MeasureText который немного больше фактического текста):

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

Если необходимо сохранить пропорции графических объектов, необходимо использовать isotropic масштабирование. Страница "Isotropic Scaling" демонстрирует это для 11-законечной звезды. По сути, действия по отображению графического объекта в центре страницы с изотропным масштабированием:

  • Переведите центр графического объекта в левый верхний угол.
  • Масштабируйте объект на основе минимального размера горизонтальной и вертикальной страницы, разделенных измерениями графических объектов.
  • Перевод центра масштабируемого объекта в центр страницы.

Эти IsotropicScalingPage действия выполняются в обратном порядке перед отображением звезды:

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

    canvas.Clear();

    SKPath path = HendecagramArrayPage.HendecagramPath;
    SKRect pathBounds = path.Bounds;

    using (SKPaint fillPaint = new SKPaint())
    {
        fillPaint.Style = SKPaintStyle.Fill;

        float scale = Math.Min(info.Width / pathBounds.Width,
                               info.Height / pathBounds.Height);

        for (int i = 0; i <= 10; i++)
        {
            fillPaint.Color = new SKColor((byte)(255 * (10 - i) / 10),
                                          0,
                                          (byte)(255 * i / 10));
            canvas.Save();
            canvas.Translate(info.Width / 2, info.Height / 2);
            canvas.Scale(scale);
            canvas.Translate(-pathBounds.MidX, -pathBounds.MidY);
            canvas.DrawPath(path, fillPaint);
            canvas.Restore();

            scale *= 0.9f;
        }
    }
}

Код также отображает звезду более 10 раз, каждый раз уменьшая коэффициент масштабирования на 10 % и постепенно изменяя цвет от красного до синего:

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