Базовая графика в Xamarin.iOS

В этой статье рассматриваются основные платформы iOS для графики. В нем показано, как использовать базовую графику для рисования геометрии, изображений и PDF-файлов.

IOS включает базовую платформу графики для обеспечения низкоуровневой поддержки рисования. Эти платформы обеспечивают широкие графические возможности в UIKit.

Базовая графика — это низкоуровневая 2D-графическая платформа, которая позволяет рисованию независимой графики устройства. Все 2D-рисование в UIKit внутренне использует основную графику.

Базовая графика поддерживает рисование в нескольких сценариях, включая:

Геометрическое пространство

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

Существует несколько преимуществ для этого подхода:

  • Код рисования становится динамическим и может впоследствии изменять графику во время выполнения.
  • Уменьшение необходимости статических образов в пакете приложений может уменьшить размер приложения.
  • Графические элементы становятся более устойчивыми к изменению разрешения на разных устройствах.

Рисование в подклассе UIView

Каждый UIView имеет Draw метод, вызываемый системой, когда он должен быть нарисован. Чтобы добавить код рисования в представление, подкласс UIView и переопределить Draw:

public class TriangleView : UIView
{
    public override void Draw (CGRect rect)
    {
        base.Draw (rect);
    }
}

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

Шаблон для графического кода

Код в Draw реализации должен описать то, что он хочет нарисовать. Код рисования следует шаблону, в котором он задает некоторое состояние рисования и вызывает метод для запроса его рисования. Этот шаблон можно обобщить следующим образом:

  1. Получение контекста графики.

  2. Настройте атрибуты рисования.

  3. Создайте геометрию из примитивов рисования.

  4. Вызов метода Draw или Stroke.

Базовый пример рисования

Для примера рассмотрим следующий фрагмент кода:

//get graphics context
using (CGContext g = UIGraphics.GetCurrentContext ()) {

    //set up drawing attributes
    g.SetLineWidth (10);
    UIColor.Blue.SetFill ();
    UIColor.Red.SetStroke ();

    //create geometry
    var path = new CGPath ();

    path.AddLines (new CGPoint[]{
    new CGPoint (100, 200),
    new CGPoint (160, 100),
    new CGPoint (220, 200)});

    path.CloseSubpath ();

    //add geometry to graphics context and draw it
    g.AddPath (path);
    g.DrawPath (CGPathDrawingMode.FillStroke);
}

Давайте разберем этот код:

using (CGContext g = UIGraphics.GetCurrentContext ()) {
...
}

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

g.SetLineWidth (10);
UIColor.Blue.SetFill ();
UIColor.Red.SetStroke ();

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

Для создания геометрии код использует объект CGPath, который позволяет описывать графический путь из линий и кривых. В этом случае путь добавляет линии, соединяющие массив точек для создания треугольника. Как показано ниже Core Graphics, используется система координат для отображения рисунка, где источник находится в левом верхнем углу, с положительным x-direct направо и положительным направлением y вниз:

var path = new CGPath ();

path.AddLines (new CGPoint[]{
new CGPoint (100, 200),
new CGPoint (160, 100),
new CGPoint (220, 200)});

path.CloseSubpath ();

После создания пути он добавляется в графический контекст, чтобы вызвать AddPath и DrawPath соответственно нарисовать его.

Полученное представление показано ниже:

Пример выходного треугольника

Создание градиентных заливок

Кроме того, доступны более широкие формы рисования. Например, Core Graphics позволяет создавать градиентные заливки и применять пути обрезки. Чтобы нарисовать заливку градиента внутри пути из предыдущего примера, сначала необходимо задать путь в качестве пути вырезки:

// add the path back to the graphics context so that it is the current path
g.AddPath (path);
// set the current path to be the clipping path
g.Clip ();

Задание текущего пути в качестве пути вырезки ограничивает все последующие рисунки в геометрии пути, например следующий код, который рисует линейный градиент:

// the color space determines how Core Graphics interprets color information
    using (CGColorSpace rgb = CGColorSpace.CreateDeviceRGB()) {
        CGGradient gradient = new CGGradient (rgb, new CGColor[] {
        UIColor.Blue.CGColor,
        UIColor.Yellow.CGColor
    });

// draw a linear gradient
    g.DrawLinearGradient (
        gradient,
        new CGPoint (path.BoundingBox.Left, path.BoundingBox.Top),
        new CGPoint (path.BoundingBox.Right, path.BoundingBox.Bottom),
        CGGradientDrawingOptions.DrawsBeforeStartLocation);
    }

Эти изменения создают градиентную заливку, как показано ниже:

Пример с заливкой градиента

Изменение шаблонов строк

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

//use a dashed line
g.SetLineDash (0, new nfloat[] { 10, 4 * (nfloat)Math.PI });

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

Добавление этого кода перед любыми операциями рисования приводит к дефисным штрихам

Обратите внимание, что при использовании единого API в Xamarin.iOS тип массива должен быть nfloatявным образом приведение к Math.PI.

Рисование изображений и текста

Помимо путей рисования в графическом контексте представления, Core Graphics также поддерживает рисование изображений и текста. Чтобы нарисовать изображение, просто создайте CGImage и передайте его вызову DrawImage :

public override void Draw (CGRect rect)
{
    base.Draw (rect);

    using(CGContext g = UIGraphics.GetCurrentContext ()){
        g.DrawImage (rect, UIImage.FromFile ("MyImage.png").CGImage);
    }
}

Однако это создает изображение, нарисованное вверх ногами, как показано ниже:

Изображение, нарисованное вверх снизу

Причина этого — основной источник графики для рисунка в левом нижнем углу, а представление имеет свое происхождение в верхнем левом углу. Поэтому для правильного отображения изображения необходимо изменить источник, который можно выполнить, изменив матрицу текущего преобразования (CTM). CTM определяет, где живут точки, также известные как пространство пользователя. Перевернутый CTM в направлении y и сдвиг его высотой границ в отрицательном направлении y может перевернуть изображение.

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

public override void Draw (CGRect rect)
{
    base.Draw (rect);

    using (CGContext g = UIGraphics.GetCurrentContext ()) {

        // scale and translate the CTM so the image appears upright
        g.ScaleCTM (1, -1);
        g.TranslateCTM (0, -Bounds.Height);
        g.DrawImage (rect, UIImage.FromFile ("MyImage.png").CGImage);
}

Затем результирующее изображение отображается в вертикальном порядке:

Изображение, отображаемое в вертикальном порядке

Внимание

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

Добавление текста в изображение

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

public override void Draw (RectangleF rect)
{
    base.Draw (rect);

    // image drawing code omitted for brevity ...

    // translate the CTM by the font size so it displays on screen
    float fontSize = 35f;
    g.TranslateCTM (0, fontSize);

    // set general-purpose graphics state
    g.SetLineWidth (1.0f);
    g.SetStrokeColor (UIColor.Yellow.CGColor);
    g.SetFillColor (UIColor.Red.CGColor);
    g.SetShadow (new CGSize (5, 5), 0, UIColor.Blue.CGColor);

    // set text specific graphics state
    g.SetTextDrawingMode (CGTextDrawingMode.FillStroke);
    g.SelectFont ("Helvetica", fontSize, CGTextEncoding.MacRoman);

    // show the text
    g.ShowText ("Hello Core Graphics");
}

Как видно, настройка состояния графики для текста похожа на геометрию рисования. Однако для текста применяется режим рисования текста и шрифт. В этом случае также применяется тень, хотя применение теней работает одинаково для рисования пути.

Полученный текст отображается с изображением, как показано ниже:

Результирующий текст отображается с изображением

Образы с поддержкой памяти

Помимо рисунка в графическом контексте представления, Core Graphics поддерживает изображения, поддерживаемые памятью, также известные как рисование вне экрана. Для этого требуется:

  • Создание графического контекста, поддерживаемого растровым изображением в памяти
  • Настройка состояния рисования и выдача команд рисования
  • Получение изображения из контекста
  • Удаление контекста

Draw В отличие от метода, в котором контекст предоставляется представлением, в этом случае вы создаете контекст одним из двух способов:

  1. UIGraphics.BeginImageContext Вызов (илиBeginImageContextWithOptions)

  2. Создание нового CGBitmapContextInstance

CGBitmapContextInstance полезно при работе непосредственно с битами изображений, например в случаях использования пользовательского алгоритма обработки изображений. Во всех остальных случаях следует использовать BeginImageContext или BeginImageContextWithOptions.

После создания контекста изображения добавление кода рисования точно так же, как и в подклассе UIView . Например, пример кода, используемый ранее для рисования треугольника, можно использовать для рисования изображения в памяти, а не в следующем UIView:

UIImage DrawTriangle ()
{
    UIImage triangleImage;

    //push a memory backed bitmap context on the context stack
    UIGraphics.BeginImageContext (new CGSize (200.0f, 200.0f));

    //get graphics context
    using(CGContext g = UIGraphics.GetCurrentContext ()){

        //set up drawing attributes
        g.SetLineWidth(4);
        UIColor.Purple.SetFill ();
        UIColor.Black.SetStroke ();

        //create geometry
        path = new CGPath ();

        path.AddLines(new CGPoint[]{
            new CGPoint(100,200),
            new CGPoint(160,100),
            new CGPoint(220,200)});

        path.CloseSubpath();

        //add geometry to graphics context and draw it
        g.AddPath(path);
        g.DrawPath(CGPathDrawingMode.FillStroke);

        //get a UIImage from the context
        triangleImage = UIGraphics.GetImageFromCurrentImageContext ();
    }

    return triangleImage;
}

Распространенный способ рисования для растрового изображения с поддержкой памяти — запись изображения из любого UIView. Например, следующий код отрисовывает слой представления в контекст растрового изображения и создает из него следующий UIImage код:

UIGraphics.BeginImageContext (cellView.Frame.Size);

//render the view's layer in the current context
anyView.Layer.RenderInContext (UIGraphics.GetCurrentContext ());

//get a UIImage from the context
UIImage anyViewImage = UIGraphics.GetImageFromCurrentImageContext ();
UIGraphics.EndImageContext ();

Рисование PDF-файлов

Помимо изображений, Core Graphics поддерживает документ PDF. Как и изображения, вы можете отобразить PDF-файл в памяти, а также прочитать PDF-файл для отрисовки в .UIView

PDF в UIView

Core Graphics также поддерживает чтение PDF-файла из файла и отрисовку его в представлении CGPDFDocument с помощью класса. Класс CGPDFDocument представляет PDF-файл в коде и может использоваться для чтения и рисования страниц.

Например, следующий код в подклассе считывает PDF-файл UIView из файла в CGPDFDocument:

public class PDFView : UIView
{
    CGPDFDocument pdfDoc;

    public PDFView ()
    {
        //create a CGPDFDocument from file.pdf included in the main bundle
        pdfDoc = CGPDFDocument.FromFile ("file.pdf");
    }

     public override void Draw (Rectangle rect)
    {
        ...
    }
}

Затем Draw метод может использовать CGPDFDocument для чтения страницы CGPDFPage и отрисовки его путем вызова DrawPDFPage, как показано ниже:

public override void Draw (CGRect rect)
{
    base.Draw (rect);

    //flip the CTM so the PDF will be drawn upright
    using (CGContext g = UIGraphics.GetCurrentContext ()) {
        g.TranslateCTM (0, Bounds.Height);
        g.ScaleCTM (1, -1);

        // render the first page of the PDF
        using (CGPDFPage pdfPage = pdfDoc.GetPage (1)) {

        //get the affine transform that defines where the PDF is drawn
        CGAffineTransform t = pdfPage.GetDrawingTransform (CGPDFBox.Crop, rect, 0, true);

        //concatenate the pdf transform with the CTM for display in the view
        g.ConcatCTM (t);

        //draw the pdf page
        g.DrawPDFPage (pdfPage);
        }
    }
}

PDF-файл с поддержкой памяти

Для PDF-файла в памяти необходимо создать контекст PDF путем вызова BeginPDFContext. Рисование в ФОРМАТЕ PDF детализировано на страницы. Каждая страница запускается путем BeginPDFPage вызова и завершения путем вызова EndPDFContentс графическим кодом между ними. Кроме того, как и при рисовании изображений, в левом нижнем левом нижнем слева используется резервная копия PDF-документа, которая может быть связана с изменением CTM так же, как и с изображениями.

В следующем коде показано, как нарисовать текст в ФОРМАТЕ PDF:

//data buffer to hold the PDF
NSMutableData data = new NSMutableData ();

//create a PDF with empty rectangle, which will configure it for 8.5x11 inches
UIGraphics.BeginPDFContext (data, CGRect.Empty, null);

//start a PDF page
UIGraphics.BeginPDFPage ();

using (CGContext g = UIGraphics.GetCurrentContext ()) {
    g.ScaleCTM (1, -1);
    g.TranslateCTM (0, -25);
    g.SelectFont ("Helvetica", 25, CGTextEncoding.MacRoman);
    g.ShowText ("Hello Core Graphics");
    }

//complete a PDF page
UIGraphics.EndPDFContent ();

Полученный текст рисуется в PDF-файл, который затем содержится в NSData том, который можно сохранить, отправить, отправить по электронной почте и т. д.

Итоги

В этой статье мы рассмотрели возможности графики, предоставляемые с помощью основной графической платформы . Мы узнали, как использовать базовую графику для рисования геометрии, изображений и PDF-файлов в контексте UIView, , а также для контекстов графики с поддержкой памяти.