Преобразование масштаба
Обнаружение преобразования масштабирования 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);
}
}
Вы можете задаться вопросом: как коэффициенты масштабирования влияют на значение, возвращаемое методом MeasureText
SKPaint
? Ответ: Нет вообще. 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, Right
Bottom
а свойства — приблизительно 100, а Width
Height
свойства — приблизительно 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
вызова, эти значения эффективно масштабируются с помощью коэффициентов масштабирования, поэтому они перемещают центр звезды в центр холста:
Другой способ, который можно подумать о Scale
Translate
вызовах, заключается в определении эффекта в обратной последовательности: Translate
вызов сдвигает путь, чтобы он стал полностью видимым, но ориентированным в левом верхнем углу холста. Затем Scale
метод делает такую звезду больше относительно левого верхнего угла.
На самом деле, кажется, что звезда немного больше, чем холст. Проблема заключается в ширине штриха. Свойство Bounds
SKPath
указывает размеры координат, закодированных в пути, и это то, что программа использует для масштабирования. Когда путь отрисовывается с определенной шириной штриха, отрисованный путь больше холста.
Чтобы устранить эту проблему, необходимо компенсировать это. Один простой подход в этой программе заключается в добавлении следующей инструкции прямо перед вызовом 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 % и постепенно изменяя цвет от красного до синего: