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


Пиксели и аппаратно-независимые единицы

Изучение различий между координатами и Xamarin.Forms координатами SkiaSharp

В этой статье рассматриваются различия в системе координат, используемой в SkiaSharp и Xamarin.Forms. Вы можете получить сведения для преобразования между двумя системами координат, а также рисования графики, заполняющей определенную область:

Овал, заполняющий экран

Если вы в течение некоторого времени программировали Xamarin.Forms , возможно, у вас есть ощущение Xamarin.Forms координат и размеров. Круги, нарисованные в двух предыдущих статьях, могут показаться немного маленькими для вас.

Эти круги небольшие по сравнению с Xamarin.Forms размерами. По умолчанию SkiaSharp рисует единицы пикселей, а Xamarin.Forms базы координаты и размеры на устройстве независимо от устройства единиц, установленных базовой платформой. (Дополнительные сведения о системе Xamarin.Forms координат можно найти в главе 5. Работа с размерами книги "Создание мобильных приложений с Xamarin.Formsпомощью .)

Страница в примере программы Surface Size использует текстовые выходные данные SkiaSharp для отображения размера поверхности отображения из трех разных источников:

  • Обычные Xamarin.FormsWidth и Height свойства SKCanvasView объекта.
  • Свойство CanvasSize объекта SKCanvasView.
  • Size Свойство SKImageInfo значения, которое соответствует Width свойствам, Height используемым на двух предыдущих страницах.

В SurfaceSizePage классе показано, как отобразить эти значения. Конструктор сохраняет SKCanvasView объект в виде поля, чтобы его можно было получить в обработчике PaintSurface событий:

SKCanvasView canvasView;

public SurfaceSizePage()
{
    Title = "Surface Size";

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

SKCanvas включает шесть различных DrawText методов, но этот DrawText метод является самым простым:

public void DrawText (String text, Single x, Single y, SKPaint paint)

Вы указываете текстовую строку, координаты X и Y, где начинается текст, и SKPaint объект. Координата X указывает расположение левой стороны текста, но следите за тем, чтобы координата Y указывала позицию базового плана текста. Если вы когда-либо написали вручную на листе бумаги, базовый план — это линия, на которой сидеть символы, и под которыми нисходящие (например, на буквах g, p, q и y) убыть.

Объект SKPaint позволяет указать цвет текста, семейство шрифтов и размер текста. По умолчанию TextSize свойство имеет значение 12, что приводит к крошечному тексту на устройствах с высоким разрешением, таких как телефоны. В чем-либо, кроме самых простых приложений, вам также потребуется некоторая информация о размере отображаемого текста. Класс SKPaint определяет FontMetrics свойство и несколько MeasureText методов, FontSpacing но для менее необычных потребностей свойство предоставляет рекомендуемое значение для интервала последовательных строк текста.

PaintSurface Следующий обработчик создает SKPaint объект для TextSize 40 пикселей, который является требуемой вертикальной высотой текста от верхней части возрастания до нижней части спуска. Значение FontSpacing , возвращаемое SKPaint объектом, немного больше, чем это, около 47 пикселей.

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

    canvas.Clear();

    SKPaint paint = new SKPaint
    {
        Color = SKColors.Black,
        TextSize = 40
    };

    float fontSpacing = paint.FontSpacing;
    float x = 20;               // left margin
    float y = fontSpacing;      // first baseline
    float indent = 100;

    canvas.DrawText("SKCanvasView Height and Width:", x, y, paint);
    y += fontSpacing;
    canvas.DrawText(String.Format("{0:F2} x {1:F2}",
                                  canvasView.Width, canvasView.Height),
                    x + indent, y, paint);
    y += fontSpacing * 2;
    canvas.DrawText("SKCanvasView CanvasSize:", x, y, paint);
    y += fontSpacing;
    canvas.DrawText(canvasView.CanvasSize.ToString(), x + indent, y, paint);
    y += fontSpacing * 2;
    canvas.DrawText("SKImageInfo Size:", x, y, paint);
    y += fontSpacing;
    canvas.DrawText(info.Size.ToString(), x + indent, y, paint);
}

Метод начинает первую строку текста с координатой X 20 (для небольшого поля слева) и координатой fontSpacingY, которая немного превышает то, что необходимо для отображения полной высоты первой строки текста в верхней части поверхности отображения. После каждого вызова DrawTextкоордината Y увеличивается на один или два приращения fontSpacing.

Вот работающая программа:

Снимок экрана: приложение Surface Size, работающее на двух мобильных устройствах.

Как видно, CanvasSize свойство и Size свойство SKCanvasView SKImageInfo значения согласованы в отчетах о измерениях пикселей. Свойства Height являются Width Xamarin.Forms свойствами и сообщают о размере представления в независимых SKCanvasView от устройства единицах, определенных платформой.

Семь симуляторов iOS слева имеют два пикселя на устройство независимо от устройства, и Android Nexus 5 в центре имеет три пикселя на единицу. Вот почему простой круг, показанный ранее, имеет разные размеры на разных платформах.

Если вы предпочитаете полностью работать в единицах, независимых от устройств, это можно сделать, задав IgnorePixelScaling для свойства объекта trueSKCanvasView значение . Однако результаты могут не понравиться. SkiaSharp отрисовывает графику на меньшей поверхности устройства с размером пикселя, равным размеру представления в независимых от устройства единицах. (Например, SkiaSharp будет использовать поверхность дисплея 360 x 512 пикселей на Nexus 5.) Затем он масштабирует это изображение в размере, что приводит к заметным растровым изображениям jaggies.

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

DrawCircle Помимо метода, также определяет два DrawOval метода, SKCanvas которые рисуют многоточие. Многоточие определяется двумя радиями, а не одним радиусом. Они называются основным радиусом и дополнительным радиусом. Метод DrawOval рисует многоточие с двумя радиями параллельно с осями X и Y. (Если вам нужно нарисовать многоточие с осями, не параллельными осям X и Y, можно использовать преобразование поворота, как описано в статье Преобразование поворота или графический путь, как описано в статье "Три способа рисования дуги". Эта перегрузка DrawOval метода называет два параметра rx радии и ry указывает, что они параллельны осям X и Y:

public void DrawOval (Single cx, Single cy, Single rx, Single ry, SKPaint paint)

Можно ли нарисовать многоточие, заполняющее поверхность дисплея? На странице Ellipse Fill показано, как. Обработчик PaintSurface событий в классе EllipseFillPage.xaml.cs вычитает половину ширины штриха из xRadius yRadius и значений, чтобы соответствовать всему многоточию и ее контуру в области отображения:

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

    canvas.Clear();

    float strokeWidth = 50;
    float xRadius = (info.Width - strokeWidth) / 2;
    float yRadius = (info.Height - strokeWidth) / 2;

    SKPaint paint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        Color = SKColors.Blue,
        StrokeWidth = strokeWidth
    };
    canvas.DrawOval(info.Width / 2, info.Height / 2, xRadius, yRadius, paint);
}

Здесь выполняется:

Снимок экрана: приложение Ellipse Fill, работающее на двух мобильных устройствах.

Другой DrawOval метод имеет SKRect аргумент, который представляет собой прямоугольник, определенный с точки зрения координат X и Y в левом верхнем углу и правом нижнем углу. Овал заполняет прямоугольник, который предполагает, что его можно использовать на странице Ellipse Fill , как показано ниже:

SKRect rect = new SKRect(0, 0, info.Width, info.Height);
canvas.DrawOval(rect, paint);

Тем не менее, это усечение всех краев контура эллипса на четырех сторонах. Чтобы сделать эту работу правильной SKRect , необходимо настроить все аргументы конструктора strokeWidth :

SKRect rect = new SKRect(strokeWidth / 2,
                         strokeWidth / 2,
                         info.Width - strokeWidth / 2,
                         info.Height - strokeWidth / 2);
canvas.DrawOval(rect, paint);