1. Code
  2. Mobile Development
  3. iOS Development

Construir una aplicación de fotos con GPUImage

Scroll to top

Spanish (Español) translation by Manuel (you can also view the original English article)

Este tutorial te enseñará a aplicar filtros y efectos especiales similares a los de Instagram a las imágenes con el increíblemente potente proyecto GPUImage. En el camino, aprenderás cómo construir una aplicación de cámara simple capaz de tomar nuevas fotos o acceder a las imágenes existentes desde el álbum de fotos.


Demostración del proyecto

Final EffectFinal EffectFinal Effect

La imagen de arriba es un collage de filtros de imagen aplicados con la aplicación que este tutorial te enseñará a construir. La imagen de origen es de ep.Sos.de en Flickr.


Paso 1: Inicia un nuevo proyecto de Xcode

Inicia Xcode y crea una nueva aplicación utilizando la plantilla de vista única.

Figure 1: Choosing a Project TemplateFigure 1: Choosing a Project TemplateFigure 1: Choosing a Project Template

Para este tutorial, utilizaremos tanto Storyboards como el recuento automático de referencias, así que asegúrete de seleccionar ambas casillas. Nombra el proyecto "PhotoFX" y proporciona un identificador único de la empresa para la prueba del dispositivo.

Figure 2: Project Setup ScreenFigure 2: Project Setup ScreenFigure 2: Project Setup Screen

Paso 2: Crea la interfaz de la aplicación

La interfaz de la aplicación consistirá en una UINavigationBar para el título de la aplicación en la UIView y el botón de guardar, una UIToolbar para los botones de utilidad del álbum, la cámara y el filtro, y una UIImageView ajustada al relleno de aspecto para ver y modificar las imágenes seleccionadas.

Figure 3: App Interface Images

Abre el archivo MainStoryboard.storyboard. Selecciona el UIView en la pantalla y luego selecciona Editor > Embed In > Navigation Controller.

Figure 4: App Interface ImagesFigure 4: App Interface ImagesFigure 4: App Interface Images

Selecciona el UINavigationController y luego ve al Inspector de Atributos en el panel de utilidades. Establece el desplegable "Barra superior" en "Barra de navegación negra" y la "Barra de estado" en "Ninguna". Para finalizar la eliminación de la barra de estado, ve al archivo PhotoFX-Info.plist y añade una nueva clave con el texto "La barra de estado está inicialmente oculta". Establece el valor en "SÍ".

Como acabamos de convertir nuestra aplicación en una plantilla UINavigationController, ahora debes ir a ViewController.h y añadir la siguiente declaración de delegado:

1
#import <UIKit/UIKit.h>

2
3
@interface ViewController : UIViewController <UINavigationControllerDelegate>
4
5
@end

Lo necesitaremos más tarde.

Ahora vuelve al archivo del Storyboard y haz doble clic en el título en el centro del UINavigationItem y reemplaza el texto por defecto con "PhotoFX".

Arrastra un UIBarButtonItem desde la Biblioteca de Objetos al UINavigationItem. Con el nuevo elemento de botón seleccionado, ve a la pestaña Inspector de Atributos del panel de Utilidades y establece la propiedad Identificador del botón como "Guardar". A continuación, con el botón "Guardar" aún seleccionado, desmarca la casilla "Activado" de este botón. Esto evitará que el usuario intente guardar una imagen antes de cargar una del álbum de fotos o tomar una foto con la cámara (lo habilitaremos con código de nuevo más adelante).

Figure 5: App Interface ImagesFigure 5: App Interface ImagesFigure 5: App Interface Images

Vuelve a la biblioteca de objetos y arrastra una UIToolbar a la UIView principal. A continuación añade un total de tres objetos UIBarButtonItem en la barra de herramientas. Cambia el texto del título del primer botón a "Álbum", establece la propiedad Identificador del segundo a "Cámara", y establece el texto del título del tercer botón a "Filtro". El botón "Filtro" debería estar desactivado por defecto, al igual que el botón "Guardar" de arriba.

Para pulir el diseño de la barra de herramientas, necesitamos alinear a la derecha el botón de filter en la barra de herramientas. Puedes conseguir este efecto utilizando un elemento de botón de barra espaciadora flexible, que puedes arrastrar simplemente a la barra de herramientas desde la biblioteca de objetos.

Figure 6: App Interface ImagesFigure 6: App Interface ImagesFigure 6: App Interface Images

¿Te has dado cuenta de lo deslumbrante que es el UIView blanco en contraste con la barra de navegación y la barra de herramientas negras y brillantes? Hagamos algo al respecto. Selecciona el UIView y establece el color de fondo en "tungsteno".

La única subvista que queda por añadir es la UIImageView principal utilizada para mostrar la imagen del usuario. Arrastra un UIImageView desde la Biblioteca de Objetos y céntralo entre el UINavigationItem y el UIToolbar. Abre el Inspector de Atributos y selecciona "Aspect Fit" como modo UIImageView en la subsección del Inspector "View".

¡Todos los componentes principales de la interfaz están ahora en su lugar! El siguiente paso es conectar estos elementos desde el Storyboard a la clase ViewController.

Abre el archivo ViewController.m. Añade las siguientes propiedades IBOutlet y métodos IBAction en la extensión de la clase ViewController:

1
@interface ViewController ()
2
3
@property(nonatomic, weak) IBOutlet UIImageView *selectedImageView;
4
@property(nonatomic, weak) IBOutlet UIBarButtonItem *filterButton;
5
@property(nonatomic, weak) IBOutlet UIBarButtonItem *saveButton;
6
7
- (IBAction)photoFromAlbum;
8
- (IBAction)photoFromCamera;
9
- (IBAction)applyImageFilter:(id)sender;
10
- (IBAction)saveImageToAlbum;
11
12
@end

¿Entonces, por qué crear propiedades IBOutlet para la vista de imagen, el botón de filtro y el botón de guardar, pero no para los demás componentes de Interface Builder? La respuesta es que estos son los únicos objetos a los que necesitaremos acceder mediante programación. Se accederá a la vista de imágenes para fijar las imágenes seleccionadas por el usuario, mientras que se accederá a los botones de filtro y guardar para cambiar el estado de desactivado ha activado después de que el usuario seleccione una imagen o tome una foto.

Los métodos IBAction deberían explicarse por sí mismos y se conectarán directamente al selector UIBarButtonItem implícito en cada nombre.

Después de crear las propiedades IBOutlet, debes sintetizarlas añadiendo la siguiente línea de código a la clase @implementation:

1
@implementation ViewController
2
3
@synthesize selectedImageView, filterButton, saveButton;

Para completar la configuración de Interface Builder, asigna cada uno de los objetos IBOutlet y métodos IBAction declarados a los componentes adecuados de Interface Builder (¿Necesitas ayuda para hacerlo? Deja una pregunta en la sección de comentarios más abajo). Asegúrate de guardar los cambios antes de continuar.

Una vez creada la interfaz de la aplicación, estamos listos para empezar a codificar la funcionalidad.


Paso 3: Seleccionando las fotos del álbum

Este tutorial utilizará la clase UIImagePickerController para acceder directamente a las imágenes dentro del álbum de fotos del usuario. El uso de esta clase superpondrá un navegador de galería de vista modal en la parte superior de nuestra interfaz existente. Cuando un usuario selecciona la imagen que desea, el selector utilizará la delegación para notificar a nuestra clase ViewController que se ha realizado una selección. Si eres nuevo en el desarrollo de iOS, no te preocupes, esto es mucho más fácil de lo que parece.

En el archivo ViewController.m, añade la siguiente implementación para el método photoFromAlbum:

1
@synthesize selectedImageView, filterButton, saveButton;
2
3
- (IBAction)photoFromAlbum
4
{
5
    UIImagePickerController *photoPicker = [[UIImagePickerController alloc] init];
6
    photoPicker.delegate = self;
7
    photoPicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
8
    
9
    [self presentViewController:photoPicker animated:YES completion:NULL];
10
11
}

¿Sencillo, verdad? Ahora solo tenemos que implementar el protocolo delegado UIImagePickerController para responder a las selecciones de imágenes. Esto lo harás en el paso 5 de este tutorial.


Paso 4: Haciendo fotos con la cámara

Hay dos enfoques principales para tomar fotos con la cámara del dispositivo. Puedes utilizar el UIImagePickerController para acceder a la implementación de la cámara por defecto de Apple, o puedes crear una experiencia completamente personalizada con el framework AVFoundation. GPUImage en realidad se basa en la funcionalidad proporcionada por AVFoundation para proporcionar una clase específicamente con este propósito en mente. Sin embargo, para este tutorial, vamos a utilizar UIImagePickerController para la selección de fotos exclusivamente. En un futuro tutorial sobre GPUImage (que probablemente se publicará en las próximas 1-3 semanas), te mostraré cómo utilizar las clases más avanzadas de GPUImage para lograr esto.

El código para tomar fotos en este tutorial es el siguiente:

1
- (IBAction)photoFromCamera
2
{
3
    UIImagePickerController *photoPicker = [[UIImagePickerController alloc] init];
4
5
    photoPicker.delegate = self;
6
    photoPicker.sourceType = UIImagePickerControllerSourceTypeCamera;
7
    
8
    [self presentViewController:photoPicker animated:YES completion:NULL];
9
10
}

Si comparas el método anterior con el método photoFromAlbum del paso 3, verás que la única diferencia es si sourceType se establece como UIImagePickerControllerSourceTypePhotoLibrary o UIImagePickerControllerSourceTypeCamera. Debido a esto, podría combinar fácilmente estos dos métodos en uno solo. Sin embargo, he decidido dejar photoFromCamera como un método separado, ya que lo refactorizaré para usar AVFoundation en un futuro tutorial y la lógica tendrá que ser separada.


Paso 5: Codificar el Delegado del Recogedor de Fotos

El usuario puede ahora navegar por la biblioteca del dispositivo o utilizar la cámara del dispositivo para seleccionar una imagen con UIImagePickerController. Independientemente de cómo el usuario seleccione una imagen, el siguiente paso es implementar el método delegado que se encargará de colocar esa imagen en la pantalla.

Primero, ve a ViewController.h y declara que esta clase se ajustará a UIImagePickerControllerDelegate:

1
#import <UIKit/UIKit.h>

2
3
@interface ViewController : UIViewController <UINavigationControllerDelegate, UIImagePickerControllerDelegate>
4
5
@end

Ahora cambia a ViewController.m e implementa el método delegado imagePickerController:didFinishPickingMediaWithInfo: llamado por el selector de fotos al ser seleccionado:

1
- (void)imagePickerController:(UIImagePickerController *)photoPicker didFinishPickingMediaWithInfo:(NSDictionary *)info
2
{
3
    self.saveButton.enabled = YES;
4
    self.filterButton.enabled = YES;
5
    
6
    UIImage *selectedImage = [info valueForKey:UIImagePickerControllerOriginalImage];
7
    
8
    [self.selectedImageView setImage:selectedImage];
9
    
10
    [photoPicker dismissModalViewControllerAnimated:YES];
11
}

En las líneas 3-4 anteriores, los botones de guardar y filtrar están habilitados porque ahora tenemos una imagen sobre la que se pueden realizar esas acciones.

La línea 6 crea un objeto UIImage con la foto seleccionada por el usuario, y la línea 8 establece la propiedad image del UIImageViewController a la imagen elegida que la mostrará en la pantalla.

Por último, la línea 10 desestima la vista modal utilizada para seleccionar la foto.

El código anterior debería funcionar bien, pero hay una mejora necesaria. En lugar de simplemente almacenar la imagen seleccionada en el selectedImageView, también deberíamos retener una copia en un miembro de datos UIImage interno. Esto permitirá que la aplicación aplique cada filtro seleccionado directamente a la imagen original en lugar de superponer los efectos de forma iterativa. También permitirá al usuario volver fácilmente a la imagen original desde una perspectiva filtrada. Para ello, primero añade un objeto UIImage a la extensión de la clase en la parte superior de ViewController.m:

1
#import "ViewController.h"

2
#import "GPUImage.h"

3
4
@interface ViewController ()
5
{
6
    UIImage *originalImage;
7
}
8
9
@property(nonatomic, weak) IBOutlet UIImageView *selectedImageView;
10
@property(nonatomic, weak) IBOutlet UIBarButtonItem *filterButton;

A continuación, modifica el método imagePickerController:didFinishPickingMediaWithInfo: como viene ahora:

1
- (void)imagePickerController:(UIImagePickerController *)photoPicker didFinishPickingMediaWithInfo:(NSDictionary *)info
2
{
3
    self.saveButton.enabled = YES;
4
    self.filterButton.enabled = YES;
5
    
6
    originalImage = [info valueForKey:UIImagePickerControllerOriginalImage];
7
    
8
    [self.selectedImageView setImage:originalImage];
9
    
10
    [photoPicker dismissModalViewControllerAnimated:YES];
11
}

Si construyes y ejecutas el proyecto ahora, deberías poder seleccionar las fotos directamente desde el álbum del dispositivo.


Paso 6: Guardando la imagen seleccionada 

La última cosa que tenemos que hacer antes de abordar GPUImage es permitir a los usuarios guardar las fotos que toman con la cámara del dispositivo. Puedes hacer esto con una sola línea de código dentro del método saveImageToAlbum:

1
- (IBAction)saveImageToAlbum
2
{
3
    UIImageWriteToSavedPhotosAlbum(self.selectedImageView.image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
4
}

La línea de código anterior intentará guardar la imagen en el álbum de fotos, pero tendrás que implementar el selector especificado para responder al éxito o al fracaso:

1
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
2
{
3
    NSString *alertTitle;
4
    NSString *alertMessage;
5
    
6
    if(!error)
7
    {
8
        alertTitle   = @"Image Saved";
9
        alertMessage = @"Image saved to photo album successfully.";
10
    }
11
    else
12
    {
13
        alertTitle   = @"Error";
14
        alertMessage = @"Unable to save to photo album.";
15
    }
16
17
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:alertTitle
18
                                                    message:alertMessage
19
                                                   delegate:self
20
                                          cancelButtonTitle:@"Okay"
21
                                          otherButtonTitles:nil];
22
    [alert show];
23
}

Las líneas de código anteriores son bastante sencillas y simplemente muestran un mensaje UIAlertView que notifica al usuario si la imagen se ha guardado correctamente o no.


Paso 7: Añadir GPUImage a tu proyecto

Añadir GPUImage a tu proyecto es un poco más complicado de lo que podrías esperar, pero siguiendo este paso sólo deberías tardar unos minutos en ponerte en marcha.

En primer lugar, tienes que descargar una copia de GPUImage desde el proyecto oficial GitHub. Desarchiva el archivo descargado y abre la carpeta "framework". Estos son los archivos esenciales necesarios para importar GPUImage en tu proyecto. En lugar de copiar todo esto en tu proyecto directamente, utiliza el Finder para ir a la ubicación en la que guardó tu proyecto Xcode en el Paso 1 (para mí es ~/Desktop/PhotoFX).Crea una nueva carpeta llamada "Submodules" con una carpeta hija llamada "GPUImage". Ahora copia la carpeta "framework" descargada de GitHub y pégala en la carpeta "GPUImage". A continuación, abre la carpeta "framework" y selecciona el archivo GPUImage.xcodeproj. Tu pantalla debería ser algo así:

Figure 7: Adding GPUImage to the ProjectFigure 7: Adding GPUImage to the ProjectFigure 7: Adding GPUImage to the Project

Ahora arrastra el archivo GPUImage.xcodeproj al navegador de proyectos de Xcode. Si has hecho esto con éxito, deberías ver algo como lo siguiente:

Figure 8: GPUImage in Xcode

Con el proyecto añadido con éxito, tendrás que añadir GPUImage como una dependencia en la configuración de construcción de tu aplicación. Selecciona "PhotoFX" en el navegador del proyecto, selecciona el objetivo "PhotoFX" y luego ve a la pestaña "Fases de construcción".  Expande el menú desplegable "Dependencias de destino" y haz clic en el icono "+". Selecciona "GPUImage" de la lista que aparece. Echa un vistazo a la siguiente imagen para hacerte una idea de cómo se hace esto:

Figure 9: GPUImage DependencyFigure 9: GPUImage DependencyFigure 9: GPUImage Dependency

Ahora tienes que arrastrar el archivo libGPUImage.a (que se encuentra en el navegador de proyectos de Xcode en GPUImage.xcodeproj > Productos) al menú desplegable "Enlazar binario con bibliotecas". Una vez completado esto, deberías ver algo como lo siguiente:

Figure 10: GPUImage FrameworkFigure 10: GPUImage FrameworkFigure 10: GPUImage Framework

Mientras te centras en el desplegable "Vincular binarios con bibliotecas", sigue adelante y añade los siguientes frameworks necesarios haciendo clic en el botón "+" de la esquina inferior izquierda:

  • CoreMedia
  • CoreVideo
  • OpenGLES
  • AVFoundation
  • QuartzCore

¡Ya casi está hecho! El siguiente paso es seleccionar el proyecto PhotoFX e ir a "Build Settings". Busca "Header Search Paths" (es posible que tengas que seleccionar el botón "All" en lugar de "Basic" para que aparezca esta opción), y luego haz doble clic para añadir Submodules/GPUImage/framework en el cuadro de diálogo emergente que aparecerá. Haz clic en la casilla junto a la entrada para indicar que esta ruta debe buscarse de forma recursiva. Si has hecho esto correctamente, deberías ver algo como lo siguiente:

Figure 11: Adding GPUImage FrameworkFigure 11: Adding GPUImage FrameworkFigure 11: Adding GPUImage Framework

El último paso es volver a ViewController.m y añadir la siguiente línea en la parte superior:

1
#import "ViewController.h"

2
#import "GPUImage.h"

Ahora deberías poder compilar y ejecutar el proyecto sin problemas. ¿Tienes problemas? Deja un comentario a continuación.


Paso 8: Mostrar una lista de filtros

GPUImage viene con un impresionante número de filtros para su uso dentro de sus aplicaciones. Para este tutorial, he seleccionado la siguiente muestra para mojarnos los pies:

  • GPUImageGrayscaleFilter
  • GPUImageSepiaFilter
  • GPUImageSketchFilter
  • GPUImagePixellateFilter
  • GPUImageColorInvertFilter
  • GPUImageToonFilter
  • GPUImagePinchDistortionFilter

Para crear tu propia lista o simplemente ver todos los filtros que ofrece GPUImage, consulta la documentación oficial en GitHub.

Para presentar la lista de filtros anterior al usuario, utilizaremos una simple UIActionSheet. Implementa el método applyImageFilter: como sigue:

1
- (IBAction)applyImageFilter:(id)sender
2
{
3
    UIActionSheet *filterActionSheet = [[UIActionSheet alloc] initWithTitle:@"Select Filter"
4
                                                                   delegate:self
5
                                                          cancelButtonTitle:@"Cancel"
6
                                                     destructiveButtonTitle:nil
7
                                                          otherButtonTitles:@"Grayscale", @"Sepia", @"Sketch", @"Pixellate", @"Color Invert", @"Toon", @"Pinch Distort", @"None", nil];
8
9
    [filterActionSheet showFromBarButtonItem:sender animated:YES];
10
}

Paso 9: Aplicar la selección de filtros

Para responder a la selección del filtro, tendremos que implementar el UIActionSheetDelegate. Ve a ViewController.h y declara que la clase se ajustará a este delegado de la siguiente manera:

1
#import <UIKit/UIKit.h>

2
3
@interface ViewController : UIViewController <UINavigationControllerDelegate, UIImagePickerControllerDelegate, UIActionSheetDelegate>
4
5
@end

Ahora vuelve a ViewController.m y añade el siguiente método:

1
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
2
{
3
    GPUImageFilter *selectedFilter;
4
    
5
    switch (buttonIndex) {
6
        case 0:
7
            selectedFilter = [[GPUImageGrayscaleFilter alloc] init];
8
            break;
9
        case 1:
10
            selectedFilter = [[GPUImageSepiaFilter alloc] init];
11
            break;
12
        case 2:
13
            selectedFilter = [[GPUImageSketchFilter alloc] init];
14
            break;
15
        case 3:
16
            selectedFilter = [[GPUImagePixellateFilter alloc] init];
17
            break;
18
        case 4:
19
            selectedFilter = [[GPUImageColorInvertFilter alloc] init];
20
            break;
21
        case 5:
22
            selectedFilter = [[GPUImageToonFilter alloc] init];
23
            break;
24
        case 6:
25
            selectedFilter = [[GPUImagePinchDistortionFilter alloc] init];
26
            break;
27
        case 7:
28
            selectedFilter = [[GPUImageFilter alloc] init];
29
            break;
30
        default:
31
            break;
32
    }
33
    
34
    UIImage *filteredImage = [selectedFilter imageByFilteringImage:originalImage];
35
    [self.selectedImageView setImage:filteredImage];
36
}

¡Pum! Tu aplicación debería funcionar ahora como se desea. Como puedes ver en lo anterior, aplicar filtros a una imagen existente con GPUImage no podría ser más sencillo. Simplemente tienes que instanciar un GPUImageFilter y luego llamar al método imageByFilteringImage:originalImage.


Paso 10: Añadir un icono de la aplicación

Esta aplicación sólo necesita una última cosa: un buen icono para el dock. Gracias a nuestro sitio hermano Psdtuts+, pude encontrar justo lo que estaba buscando:

Lo anterior es simplemente un recorte de 57x57 (no retina) y 114x114 (retina) píxeles del efecto final enseñado en Cómo dibujar una cámara Leica en Photoshop por Mohammad Jeprie.

Para introducirlos en tu aplicación, sólo tienes que arrastrarlos al navegador de proyectos de Xcode.


Resumen

Este tutorial apenas ha arañado la superficie de lo que es posible con GPUImage. Si has disfrutado de este tutorial o crees que te beneficiarás de la potencia de GPUImage en el futuro, busca a @bradlarson y dale las gracias por crear un proyecto de código abierto tan impresionante.


¿Más contenido de GPUImage?

¿Quieres ver más contenido sobre GPUImage y el procesamiento de imágenes? Si es así, ¡házmelo saber! Puedes dejar tu opinión en la sección de comentarios de abajo (preferiblemente) o simplemente enviarme un mensaje en Twitter (@markhammonds).

ACTUALIZACIÓN: He publicado un segundo tutorial que detalla cómo utilizar la cámara GPUImage y mostrar las fotos en una galería.Mejora de una aplicación fotográfica con GPUImage e iCarousel.