Students Save 30%! Learn & create with unlimited courses & creative assets Students Save 30%! Save Now
Advertisement
  1. Code
  2. OS X
Code

Cómo crear gráficos vectoriales en iOS

by
Difficulty:IntermediateLength:LongLanguages:

Spanish (Español) translation by Elías Nicolás (you can also view the original English article)

Introducción

Los recursos gráficos en el mundo digital son de dos tipos básicos, raster y vector. Las imágenes de trama son esencialmente una matriz rectangular de intensidades de píxeles. Los gráficos vectoriales, por otro lado, son representaciones matemáticas de formas.

Si bien hay situaciones en las que las imágenes de trama son irremplazables (fotos, por ejemplo), en otros escenarios, los gráficos vectoriales hacen sustitutos capaces. Los gráficos vectoriales hacen que la tarea de crear recursos gráficos para resoluciones de pantalla múltiples sea trivial. Al momento de escribir, hay al menos media docena de resoluciones de pantalla con las que lidiar en la plataforma iOS.

Una de las mejores cosas de los gráficos vectoriales es que pueden procesarse en cualquier resolución sin dejar de ser absolutamente nítidos y suaves. Esta es la razón por la cual las fuentes PostScript y TrueType se ven nítidas en cualquier ampliación. Debido a que las pantallas de los teléfonos inteligentes y las computadoras son rasteras en la naturaleza, en última instancia, la imagen vectorial debe representarse en la pantalla como una imagen raster con la resolución adecuada. Esto generalmente es resuelto por la biblioteca de gráficos de bajo nivel y el programador no tiene que preocuparse por esto.

1. ¿Cuándo usar gráficos vectoriales?

Veamos algunos escenarios en los que deberías considerar el uso de gráficos vectoriales.

Iconos de aplicaciones y menús, elementos de la interfaz de usuario

Hace unos años, Apple evitó el skeuomorphism en la interfaz de usuario de sus aplicaciones y el propio iOS, en favor de diseños audaces y geométricamente precisos. Eche un vistazo a los iconos de la aplicación Cámara o Foto, por ejemplo.

Más probable que no, fueron diseñados utilizando herramientas de gráficos vectoriales. Los desarrolladores tuvieron que seguir su ejemplo y la mayoría de las aplicaciones populares (que no son juegos) se sometieron a una metamorfosis completa para ajustarse a este paradigma de diseño.

Juegos

Los juegos con gráficos simples (piense en asteroides) o temas geométricos (Super Hexagon y Geometry Jump vienen a la mente) pueden tener sus sprites renderizados a partir de vectores. Lo mismo se aplica a los juegos que tienen niveles generados de manera procesal.

Imágenes

Imágenes en las que desea inyectar una pequeña cantidad de aleatoriedad para obtener varias versiones de la misma forma básica.

2. Curvas de Bezier

¿Qué son las curvas de Bezier? Sin profundizar en la teoría matemática, solo hablemos de las características que son de uso práctico para los desarrolladores.

Grados de libertad

Las curvas de Bézier se caracterizan por la cantidad de grados de libertad que tienen. Cuanto más alto sea este grado, más variación puede incorporar la curva (pero también más matemáticamente compleja es).

Beziers grado uno son segmentos de línea recta. Grado dos curvas se llaman curvas cuádruples. Las curvas de grado tres (cúbicas) son aquellas en las que nos centraremos, porque ofrecen un buen compromiso entre flexibilidad y complejidad.

Los Beziers cúbicos pueden representar no solo curvas suaves, sino también bucles y cúspides. Varios segmentos Bezier cúbicos se pueden conectar de extremo a extremo para formar formas más complicadas.

Cúbicos Béziers

Un Bezier cúbico se define por sus dos puntos finales y dos puntos de control adicionales que determinan su forma. En general, un grado n Bezier tiene (n-1) puntos de control, sin contar los puntos finales.

Una característica atractiva de Beziers cúbicos es que estos puntos tienen una interpretación visual significativa. La línea que conecta un punto final a su punto de control adyacente actúa como una tangente a la curva en el punto final. Este hecho es útil para diseñar formas. Vamos a explotar esta propiedad más adelante en el tutorial.

Transformaciones geométricas

Debido a la naturaleza matemática de estas curvas, puede aplicarles transformaciones geométricas fácilmente, como escala, rotación y traslación, sin ninguna pérdida de fidelidad.

La siguiente imagen muestra una muestra de diferentes tipos de formas que puede tomar un solo Bezier cúbico. Observe cómo los segmentos de la línea verde actúan como tangentes a la curva.

Cubic Bezier Shapes

3. Core Graphics y la clase UIBezierPath

En iOS y OS X, los gráficos vectoriales se implementan utilizando la biblioteca Core Graphics basada en C. Construido sobre esto está UIKit / Cocoa, que agrega una apariencia de orientación a objetos. El caballo de batalla es la clase UIBezierPath (NSBezierPath en OS X), una implementación de una curva Bezier matemática.

La clase UIBezierPath admite curvas Bezier de grado uno (segmentos de línea recta), dos (curvas cuádruples) y tres (curvas cúbicas).

Programáticamente, un objeto UIBezierPath puede construirse pieza por pieza agregándole nuevos componentes (subpaths). Para facilitar esto, el objeto UIBezierPath realiza un seguimiento de la propiedad currentPoint. Cada vez que agregue un nuevo segmento de ruta, el último punto del segmento agregado se convertirá en el punto actual. Cualquier dibujo adicional que hagas generalmente comienza en este punto. Puede mover este punto explícitamente a una ubicación deseada.

La clase tiene métodos de conveniencia para hacer formas de uso común, como arcos y círculos, rectángulos (redondeados), etc. Internamente, estas formas se han construido mediante la conexión de varias rutas secundarias.

El camino general puede ser una forma abierta o cerrada. Incluso puede ser de auto-intersección o tener múltiples componentes cerrados.

4. Empezando

Este tutorial está destinado a servir como una mirada más allá de lo básico en la generación de gráficos vectoriales. Pero incluso si eres un desarrollador experimentado que no ha usado Core Graphics o UIBezierPath antes, deberías poder seguirlo. Si eres nuevo en esto, te recomendamos que hojees la referencia de clase UIBezierPath (y las funciones subyacentes de Core Graphics) si aún no estás familiarizado. Solo podemos ejercer un número limitado de funciones de la API en un solo tutorial.

Basta de hablar. Vamos a empezar a codificar. En el resto de este tutorial, presentaré dos escenarios donde los gráficos vectoriales son la herramienta ideal para usar.

Arranque Xcode, cree un nuevo patio de recreo y configure la plataforma en iOS. Por cierto, los patios de juego de Xcode son otra razón por la que ahora es divertido trabajar con gráficos vectoriales. Puede modificar su código y obtener retroalimentación visual instantánea. Tenga en cuenta que debe usar la última versión estable de Xcode, que es 7.2 en el momento de escribir este artículo.

Escenario 1: Haciendo formas de nubes

Nos gustaría generar imágenes de nubes que se adhieran a una forma básica de la nube mientras tenemos algo de aleatoriedad para que cada nube se vea diferente. El diseño básico en el que me he conformado es una forma compuesta, definida por varios círculos de radios aleatorios centrados a lo largo de una trayectoria elíptica de tamaño aleatorio (dentro de los rangos apropiados).

Para aclarar, esto es lo que parece el objeto general si acariciamos la ruta del vector en lugar de rellenarla.

Anatomy of a Cloud

Si su geometría está un poco oxidada, entonces esta imagen de Wikipedia muestra cómo se ve una elipse.

Ellipse

Algunas funciones de utilidad

Empecemos por escribir un par de funciones de ayuda.

La función random(lower:upper:) utiliza la función incorporada arc4random_uniform() para generar números aleatorios en el rango lower y (upper-1). La función de circle(at:center:) genera una ruta Bezier, que representa un círculo con un center y un radius dados.

Generando puntos y caminos

Centrémonos ahora en generar los puntos a lo largo del camino elíptico. Una elipse centrada en el origen del sistema de coordenadas con sus ejes alineados a lo largo de los ejes de coordenadas tiene una forma matemática particularmente simple que se ve así.

Asignamos valores aleatorios para las longitudes de sus ejes mayor y menor, de modo que la forma se vea como una nube, más alargada horizontalmente que verticalmente.

Usamos la función stride() para generar ángulos espaciados regularmente alrededor del círculo, y luego usamos map() para generar puntos regularmente espaciados en la elipse usando la expresión matemática anterior.

Generamos la "masa" central de la nube uniendo los puntos a lo largo del camino elíptico. Si no lo hacemos, obtendremos un gran vacío en el centro.

Tenga en cuenta que la ruta exacta no importa, porque estaremos llenando la ruta, no acariciándola. Esto significa que no se podrá distinguir de los círculos.

Para generar los círculos, primero elegimos heurísticamente un rango para los radios de círculo aleatorio. El hecho de que estemos desarrollando esto en un patio de recreo me ayudó a jugar con los valores hasta que obtuve un resultado con el que estuve satisfecho.

Vista previa del resultado

Puede ver el resultado haciendo clic en el icono "ojo" en el panel de resultados a la derecha, en la misma línea que la declaración de "ruta".

Quick look

Toques finales

¿Cómo rasterizamos esto para obtener el resultado final? Necesitamos lo que se conoce como un "contexto gráfico" en el que dibujar las rutas. En nuestro caso, dibujaremos en una imagen (una instancia de UIImage). Es en este punto que necesita establecer varios parámetros que especifiquen cómo se representará la ruta final, como los colores y los anchos de trazo. Finalmente, acaricias o rellenas tu camino (o ambos). En nuestro caso, queremos que nuestras nubes sean blancas, y solo queremos llenarlas.

Empaquetemos este código en una función para que podamos generar tantas nubes como deseamos. Y mientras estamos en eso, escribiremos un código para dibujar algunas nubes al azar sobre un fondo azul (que representa el cielo) y dibujar todo esto en la vista en vivo del patio de recreo.

Aquí está el código final:

Y así es como se ve el resultado final:

Final clouds image

Las siluetas de las nubes aparecen un poco borrosas en la imagen anterior, pero esto es simplemente un artefacto de cambio de tamaño. La verdadera imagen de salida es nítida.

Para verlo en su propio patio de juegos, asegúrese de que el Asistente Editor esté abierto. Seleccione Mostrar asistente del editor en el menú Ver.

Escenario 2: Generando piezas de rompecabezas

Las piezas del rompecabezas generalmente tienen un “marco” cuadrado, con cada borde plano, con una pestaña redondeada que sobresale hacia afuera, o una ranura de la misma forma para tesellate con una pestaña de una pieza adyacente. Aquí hay una sección de un rompecabezas típico.

Jigsaw piece puzzle prototype

Variaciones acogedoras con gráficos vectoriales

Si estuvieras desarrollando una aplicación de rompecabezas, querrías usar una máscara con forma de pieza del rompecabezas para segmentar la imagen que representa el rompecabezas. Podría optar por las máscaras ráster pregeneradas que envió con la aplicación, pero tendría que incluir varias variaciones para acomodar todas las posibles variaciones de forma de los cuatro bordes.

Con los gráficos vectoriales, puede generar la máscara para cualquier tipo de pieza sobre la marcha. Además, sería más fácil acomodar otras variaciones, como si quisieras piezas rectangulares u oblicuas (en lugar de piezas cuadradas).

Diseñando el límite de la pieza de rompecabezas

¿Cómo diseñamos realmente la pieza del rompecabezas, es decir, cómo encontramos la forma de colocar nuestros puntos de control para generar un camino más bonito que se parece a la pestaña curva?

Recordemos la útil propiedad tangencial de los Beziers cúbicos que mencioné anteriormente. Puede comenzar dibujando una aproximación a la forma deseada, dividiéndola en segmentos estimando cuántos segmentos cúbicos necesitará (sabiendo los tipos de formas que puede acomodar un solo segmento cúbico) y luego dibujando tangentes a estos segmentos para averiguar Donde puedes colocar tus puntos de control. Aquí hay un diagrama que explica de qué estoy hablando.

Bezier path for outward tab

Relacionar la forma con los puntos de control de la curva Bezier

Determiné que para representar la forma de la pestaña, cuatro segmentos Bezier harían muy bien:

  • dos que representan los segmentos de línea recta en cada extremo de la forma
  • dos que representan los segmentos en forma de S que representan la pestaña en el centro

Observe que los segmentos de línea discontinua verde y amarilla actúan como tangentes a los segmentos en forma de S, lo que me ayudó a estimar dónde colocar los puntos de control. Tenga en cuenta también que visualicé la pieza con una longitud de una unidad, por lo que todas las coordenadas son fracciones de una. Fácilmente podría haber hecho que mi curva tuviera, digamos, 100 puntos de largo (escalando los puntos de control por un factor de 100). La independencia de resolución de los gráficos vectoriales significa que esto no es un problema.

Por último, usé Béziers cúbicos incluso para los segmentos de línea recta simplemente por conveniencia, de modo que el código se pudiera escribir de manera más concisa y uniforme.

Me salté dibujando los puntos de control de los segmentos rectos en el diagrama para evitar el desorden. Por supuesto, un Bezier cúbico que representa una línea simplemente tiene los puntos finales y los puntos de control que se encuentran a lo largo de la misma línea.

El hecho de que estés desarrollando esto en un patio de recreo significa que puedes "reajustar" fácilmente los valores de los puntos de control para encontrar una forma que te guste y obtener retroalimentación instantánea.

Empezando

Empecemos. Puedes usar el mismo campo de juego que antes agregando una nueva página. Seleccione New > Playground Page en el menú File o cree un nuevo playground si lo prefiere.

Reemplace cualquier código en la nueva página con lo siguiente:

Generando los cuatro lados usando transformaciones geométricas

Tenga en cuenta que decidimos hacer que nuestro camino fuera de 100 puntos aplicando una transformación de escala a los puntos.

Vemos el siguiente resultado usando la función "Vista rápida":

Quick Look

Hasta ahora tan bueno. ¿Cómo generamos cuatro lados de la pieza de rompecabezas? La respuesta es (como puedes adivinar), usando transformaciones geométricas. Al aplicar una rotación de 90 grados seguida de una traducción apropiada a path anterior, podemos generar fácilmente el resto de los lados.

Advertencia: un problema con el relleno interior

Hay una advertencia aquí, por desgracia. La transformación no se unirá automáticamente a segmentos individuales. A pesar de que la silueta de nuestra pieza de rompecabezas se ve bien, su interior no se llenará y enfrentaremos problemas al usarlo como máscara. Podemos observar esto en el patio de recreo. Agregue el siguiente código:

Quick Look nos muestra lo siguiente:

Improperly-filled jigsaw piece

Observe que el interior de la pieza no está sombreado, lo que indica que no se ha llenado.

Puede averiguar los comandos de dibujo utilizados para construir un UIBezierPath complejo examinando su propiedad debugDescription en el playground.

Resolviendo el problema de llenado

Las transformaciones geométricas en UIBezierPath funcionan bastante bien para el caso de uso común, es decir, cuando ya tiene una forma cerrada o la forma que está transformando es intrínsecamente abierta, y desea generar versiones de ellas geométricamente transformadas. Nuestro caso de uso es diferente. El camino actúa como un subpath en una forma más grande que estamos construyendo y cuyo interior pretendemos rellenar. Esto es un poco más complicado.

Un enfoque sería entrometerse con las partes internas de la ruta (utilizando la función CGPathApply() de la Core Graphics API) y unir manualmente los segmentos para obtener una forma única, cerrada y con el relleno adecuado.

Pero esa opción se siente un poco pirateada y es por eso que opté por un enfoque diferente. Primero aplicamos las transformaciones geométricas a los puntos mismos, a través de la función CGPointApplyAffineTransform(), aplicando exactamente la misma transformación que intentamos usar hace un momento. Luego usamos los puntos transformados para crear la ruta secundaria, que se adjunta a la forma general. Al final del tutorial, veremos un ejemplo en el que podemos aplicar correctamente una transformación geométrica a la ruta Bezier.

Generando las variaciones del borde de la pieza

¿Cómo generamos la pestaña "innie"? Podríamos aplicar una transformada geométrica de nuevo, con un factor de escala negativo en la dirección y (invirtiendo su forma), pero opté por hacerlo manualmente simplemente invirtiendo las coordenadas y de los puntos en outie_points.

En cuanto a la pestaña de bordes planos, si bien podría haber utilizado un segmento de línea recta para representarlo, para evitar tener que especializar el código para casos distintos, simplemente puse la coordenada y de cada punto en outie_points a cero . Esto nos da:

Como ejercicio, puede generar curvas de Bézier a partir de estos bordes y verlos utilizando la Quick Look.

Ahora sabes lo suficiente para que te bombardee con todo el código, que une todo en una sola función.

Reemplace todo el contenido de la página de playground con lo siguiente:

Solo hay algunas cosas más interesantes en el código que me gustaría aclarar:

  • Utilizamos una enum para definir las diferentes formas de borde. Almacenamos los puntos en un diccionario que usa los valores de enumeración como claves.
  • Reunimos las rutas secundarias (que consisten en cada borde de la forma de pieza de cuatro lados del rompecabezas) en la función incrementalPathBuilder(_), definida internamente para la función jigsawPieceMaker(size:edges:).
  • Ahora que la pieza de rompecabezas se rellena correctamente, como podemos ver en la salida de Vista rápida, podemos usar de forma segura el método applyTransform(_:) para aplicar una transformación geométrica a la forma. Como ejemplo, he aplicado una rotación de 60 grados a la segunda pieza.
Examples of jigsaw puzzle pieces

Conclusión

Espero haberte convencido de que la capacidad de generar gráficos vectoriales mediante programación puede ser una habilidad útil para tener en tu arsenal. Con suerte, se sentirá inspirado para pensar (y codificar) otras aplicaciones interesantes para gráficos vectoriales que puede incorporar en sus propias aplicaciones.

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.