Core Graphics en Xamarin.iOS

En este artículo se describen los marcos de Core Graphics en iOS. Se muestra cómo usar Core Graphics para dibujar geometría, imágenes y archivos PDF.

iOS incluye el marco de Core Graphics para proporcionar compatibilidad con dibujos de bajo nivel. Estos marcos son lo que habilita las funcionalidades gráficas enriquecidas dentro de UIKit.

Core Graphics es un marco de gráficos 2D de bajo nivel que permite dibujar gráficos independientes del dispositivo. Todo el dibujo 2D de UIKit usa Core Graphics internamente.

Core Graphics admite el dibujo en varios escenarios, entre los que se incluyen:

Espacio geométrico

Independientemente del escenario, todo el dibujo realizado con Core Graphics se realiza en espacio geométrico, lo que significa que funciona en puntos abstractos en lugar de píxeles. Describa lo que desea dibujar en términos de geometría y estado de dibujo, como colores, estilos de línea, etc. y Core Graphics se ocupa de traducir todo en píxeles. Este estado se agrega a un contexto gráfico, que se puede considerar como el lienzo de un pintor.

Hay algunas ventajas para este enfoque:

  • El código de dibujo se convierte en dinámico y posteriormente puede modificar gráficos en tiempo de ejecución.
  • Reducir la necesidad de imágenes estáticas en el conjunto de aplicaciones puede reducir el tamaño de la aplicación.
  • Los gráficos se vuelven más resistentes a los cambios de resolución en todos los dispositivos.

Dibujar en una subclase UIView

Cada UIView tiene un método Draw al que llama el sistema cuando debe dibujarse. Para agregar código de dibujo a una vista, subclase UIView e invalidar Draw:

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

Dibujar nunca debe llamarse directamente. El sistema lo llama durante el procesamiento del bucle de ejecución. La primera vez a través del bucle de ejecución después de agregar una vista a la jerarquía de vistas, se llama a su método Draw. Las llamadas posteriores a Draw se producen cuando la vista está marcada como necesaria para dibujarse llamando a SetNeedsDisplay o SetNeedsDisplayInRect en la vista.

Patrón para el código gráfico

El código de la implementación de Draw debe describir lo que quiere dibujar. El código de dibujo sigue un patrón en el que establece algún estado de dibujo y llama a un método para solicitar que se dibuje. Este patrón se puede generalizar de la siguiente manera:

  1. Obtener un contexto de gráficos.

  2. Configurar atributos de dibujo.

  3. Cree alguna geometría a partir de los primitivos de dibujo.

  4. Llame a un método Dibujar o Trazar.

Ejemplo de dibujo básico

Por ejemplo, observe el siguiente fragmento de código:

//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);
}

Vamos a desglosar este código:

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

Con esta línea, primero obtiene el contexto gráfico actual que se va a usar para dibujar. Puede pensar en un contexto de gráficos como el lienzo en el que se produce el dibujo, que contiene todo el estado sobre el dibujo, como los colores de trazo y relleno, así como la geometría que se va a dibujar.

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

Después de obtener un contexto de gráficos, el código configura algunos atributos que se usarán al dibujar, mostrados anteriormente. En este caso, se establecen los colores de ancho de línea, trazo y relleno. A continuación, cualquier dibujo posterior usará estos atributos porque se mantienen en el estado del contexto de gráficos.

Para crear geometría, el código usa un CGPath, que permite describir una ruta de acceso gráfica a partir de líneas y curvas. En este caso, la ruta de acceso agrega líneas que conectan una matriz de puntos para crear un triángulo. Como se muestra a continuación, Core Graphics usa un sistema de coordenadas para el dibujo de vistas, donde el origen está en la parte superior izquierda, con x-direct positivo a la derecha y la dirección positiva-y hacia abajo:

var path = new CGPath ();

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

path.CloseSubpath ();

Una vez creada la ruta de acceso, se agrega al contexto de gráficos para que la llamada a AddPath y DrawPath, respectivamente, pueda dibujarla.

A continuación se muestra la vista resultante:

Triángulo de salida de ejemplo

Crear rellenos degradados

También hay disponibles formas más enriquecidas de dibujo. Por ejemplo, Core Graphics permite crear rellenos degradados y aplicar rutas de recorte. Para dibujar un relleno degradado dentro de la ruta de acceso del ejemplo anterior, primero debe establecerse la ruta de acceso como ruta de recorte:

// 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 ();

Al establecer la ruta de acceso actual como ruta de recorte, se restringe todo el dibujo posterior dentro de la geometría de la ruta de acceso, como el código siguiente, que dibuja un degradado lineal:

// 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);
    }

Estos cambios producen un relleno degradado como se muestra a continuación:

Ejemplo con relleno degradado

Modificar patrones de línea

Los atributos de dibujo de líneas también se pueden modificar con Core Graphics. Esto incluye cambiar el ancho de línea y el color del trazo, así como el propio patrón de línea, como se muestra en el código siguiente:

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

Agregar este código antes de que las operaciones de dibujo produzcan trazos discontinuos de 10 unidades de longitud, con 4 unidades de espaciado entre guiones, como se muestra a continuación:

Adición de este código antes de que las operaciones de dibujo produzcan trazos discontinuos

Tenga en cuenta que, al usar la Unified API en Xamarin.iOS, el tipo de matriz debe ser un nfloat y también debe convertirse explícitamente a Math.PI.

Dibujar de imágenes y texto

Además de dibujar rutas de acceso en el contexto de gráficos de una vista, Core Graphics también admite imágenes de dibujo y texto. Para dibujar una imagen, simplemente cree un CGImage y páselo a una llamada DrawImage:

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

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

Sin embargo, esto genera una imagen dibujada al revés, como se muestra a continuación:

Imagen dibujada al revés

El motivo de esto es el origen de Core Graphics para el dibujo de imágenes está en la parte inferior izquierda, mientras que la vista tiene su origen en la parte superior izquierda. Por lo tanto, para mostrar la imagen correctamente, el origen debe modificarse, lo que se puede lograr modificando la Matriz de transformación actual(CTM). El CTM define dónde residen los puntos, también conocidos como espacio de usuario. Invertir el CTM en la dirección y, así como desplazarlo por el alto de los límites en la dirección negativa y, puede voltear la imagen.

El contexto de gráficos tiene métodos auxiliares para transformar el CTM. En este caso, ScaleCTM "voltea" el dibujo y TranslateCTM lo desplaza a la parte superior izquierda, como se muestra a continuación:

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);
}

A continuación, la imagen resultante se muestra verticalmente:

Imagen de ejemplo que se muestra en posición vertical

Importante

Los cambios en el contexto de gráficos se aplican a todas las operaciones de dibujo posteriores. Por lo tanto, cuando se transforma el CTM, afectará a cualquier dibujo adicional. Por ejemplo, si dibujaste el triángulo después de la transformación CTM, aparecería al revés.

Agregar texto a la imagen

Al igual que con las rutas de acceso y las imágenes, el dibujo de texto con Core Graphics implica el mismo patrón básico de configuración de algunos estados gráficos y la llamada a un método para dibujar. En el caso del texto, el método para mostrar texto es ShowText. Cuando se agrega al ejemplo de dibujo de imagen, el código siguiente dibuja texto con Core Graphics:

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");
}

Como puede ver, establecer el estado de gráficos para el dibujo de texto es similar a la geometría de dibujo. Sin embargo, para el dibujo de texto, también se aplican el modo de dibujo de texto y la fuente. En este caso, también se aplica una sombra, aunque aplicar sombras funciona igual para el dibujo de trazado.

El texto resultante se muestra con la imagen como se muestra a continuación:

El texto resultante se muestra con la imagen

Imágenes respaldadas por memoria

Además de dibujar en el contexto gráfico de una vista, Core Graphics admite dibujar imágenes respaldadas por memoria, también conocidas como dibujar fuera de pantalla. Para ello, es necesario:

  • Creación de un contexto de gráficos respaldado por un mapa de bits en memoria
  • Establecer el estado de dibujo y emitir comandos de dibujo
  • Obtención de la imagen desde el contexto
  • Quitar el contexto

A diferencia del método Draw, donde la vista proporciona el contexto, en este caso se crea el contexto de una de estas dos maneras:

  1. Llamando a UIGraphics.BeginImageContext (o BeginImageContextWithOptions)

  2. Mediante la creación de un nuevo CGBitmapContextInstance

CGBitmapContextInstance resulta útil cuando se trabaja directamente con los bits de imagen, como en los casos en los que se usa un algoritmo de manipulación de imágenes personalizado. En todos los demás casos, debe usar BeginImageContext o BeginImageContextWithOptions.

Una vez que tenga un contexto de imagen, agregar código de dibujo es igual que en una subclase de UIView. Por ejemplo, el ejemplo de código usado anteriormente para dibujar un triángulo se puede usar para dibujar en una imagen en la memoria en lugar de en un UIView, como se muestra a continuación:

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;
}

Un uso común de dibujar a un mapa de bits respaldado por memoria es capturar una imagen de cualquier UIView. Por ejemplo, el código siguiente representa la capa de una vista en un contexto de mapa de bits y crea un UIImage a partir de ella:

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 ();

Dibujar archivos PDF

Además de las imágenes, Core Graphics admite dibujar en formato PDF. Al igual que las imágenes, puede representar un PDF en memoria, así como leer un PDF para su representación en un UIView.

PDF en un UIView

Core Graphics también admite la lectura de un PDF desde un archivo y representarlo en una vista mediante la clase CGPDFDocument. La clase CGPDFDocument representa un PDF en el código y se puede usar para leer y dibujar páginas.

Por ejemplo, el código siguiente de una subclase UIView lee un PDF de un archivo en un 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)
    {
        ...
    }
}

Después, el método Draw puede usar el CGPDFDocument para leer una página en CGPDFPage y representarla llamando a DrawPDFPage, como se muestra a continuación:

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 respaldado por memoria

Para un PDF en memoria, debe crear un contexto PDF llamando a BeginPDFContext. Dibujar en formato PDF es granular a las páginas. Cada página se inicia llamando a BeginPDFPage y se completa mediante una llamada a EndPDFContent, con el código gráfico entre. Además, al igual que con el dibujo de imágenes, el dibujo de PDF respaldado por memoria usa un origen en la parte inferior izquierda, que se puede tener en cuenta modificando el CTM igual que con las imágenes.

En el código siguiente se muestra cómo dibujar texto en un 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 ();

El texto resultante se dibuja en el PDF, que se incluye en una NSData que se puede guardar, cargar, enviar por correo electrónico, etc.

Resumen

En este artículo hemos examinado las funcionalidades de gráficos proporcionadas a través del marco Core Graphics. Hemos visto cómo usar Core Graphics para dibujar geometría, imágenes y ARCHIVOS PDF en el contexto de un UIView,, así como para contextos de gráficos respaldados por memoria.