domingo, 27 de enero de 2013

Realidad Aumentada - Parte III - Cocos2D

Cocos2D es un framework para realizar juegos y aplicaciones gráficas. Cocos2D tiene algunas características claves como ser facil de usar, rápido, flexible y es open source.

Instalación de Cocos2D
Debemos descargar la versión estable cocos2d-iphone-1.0.1 (ya que el tutorial esta realizado para esta versión) desde  www.cocos2d-iphone.org/download, la desempacamos y en el nuevo directorio encontramos un archivo llamado cocos2D-ios.xcodeproj, abrimos ese proyecto y vemos que tiene como 70 targets, cada uno de ellos para testear una característica diferente.
Podemos seleccionar ParticleTest desde el menú que muestra los Scheme para comprobar que todo funciona correctamente.

Instalación de Templates
Cocos 2D viene con templates para XCode. Hay 3 proyectos principales

 cocos2D stand-alone template
 cocos2D + box2D template
 cocos2D + chipmunk template

Para ejecutar estos scripts abrimos la Terminal y vamos hasta el directorio donde desempaquetamos Cocos 2D. Para instalar los templates ejecutamos los siguientes comandos

cocos2d-iphone-1.0.1$ ./install-templates.sh -u
cocos2d-iphone template installer
Installing Xcode 4 cocos2d iOS template

Crear un proyecto nuevo
 Abrimos XCode, si instalamos correctamente los templates deberiamos ver algo como lo que se muestra en la siguiente figura.



Seleccionamos cocos2D y creamos un nuevo proyecto al cual llamaremos TestCocos2D.
Ejecutamos la aplicación y veremos algo como muestra la siguiente imagen:


Lo que vamos a hacer es convertir este Hello World en un Hello World pero de realidad aumentada.
Como sabemos las aplicaciones de realidad aumentada se realizan teniendo como base la vista de una cámara. En nuestro ejemplo tenemos una pantalla negra como fondo. Esto es lo primero que vamos a reemplazar.
Cocos2D utiliza OpenGL para hacer el render en 2D. Lo que tenemos que hacer es reemplazar lo que llamamos capa de dibujo para reemplazarla con el preview de la cámara. Esta capa implementa el protocolo EAGLDrawable que es el que se utiliza para el render y para mostrar en pantalla el objeto EAGLContext.
Lo que debemos saber por ahora es que no podemos mostrar la cámara con el formato de buffer de 16 bit si no que debemos utilizar el formato color de 32 bits.

Ajustando la vista por defecto

Abrimos AppDelegate.h y Agregamos un UIView a la interface de esta manera
UIView *cameraView;

En  AppDelegate.m lo que debemos hacer es intercambiar el buffer a 32 bits.
Busquemos la linea similar a esta
//EAGLView *glView = [EAGLView viewWithFrame:[window bounds]

//Create the EAGLView manually

//1. Create a RGB565 format. Alternative: RGBA8

//2. depth format of 0 bit. Use 16 or 24 bit for 3d effects, like CCPageTurnTransition

EAGLView *glView = [EAGLView viewWithFrame:[window bounds] pixelFormat:kEAGLColorFormatRGBA8 depthFormat:0];
 //kEAGLColorFormatRGBA8
No podemos usar el formato de 16-bit para la cámara  por eso debemos cambiar el formato kEAGLColorFormatRGB565 por este kEAGLColorFormatRGBA8. Lo próximo que haremos es agregar nuestra UIView a la pantalla en reemplazo del fondo negro. Busquemos este código para poder reemplazarlo:
 // make the View Controller a child of the main window
 [window addSubview: viewController.view];
 
Debajo de esta linea agregamos el siguiente código
 // Seteamos el color de fondo como clearColor para tener transparencia
 [CCDirector sharedDirector].openGLView.backgroundColor = [UIColor clearColor];
 
 // Nos aseguramos que sea el fondo no sea opaco por la misma razón 
 [CCDirector sharedDirector].openGLView.opaque = NO; 
 
 //Volvemos a asegurarnos la transparencia
 glClearColor(0.0, 0.0, 0.0, 0.0);
 
 // A nuestra nueva vista la estiramos del tamaño   
 cameraView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
 // Hacemos Transparente esta vista
 cameraView.opaque = NO;
 cameraView.backgroundColor=[UIColor clearColor];
 [window addSubview:cameraView];
 
Si ejecutamos el proyecto todavía no vamos a ver alguna diferencia.
Vamos a agregar el siguiente código debajo de lo que agregamos antes para crear el UIImagePicker y mostrar la cámara.
 UIImagePickerController *imagePicker;
    @try {
        imagePicker = [[[UIImagePickerController alloc] init] autorelease]; 
        imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera; 
        imagePicker.showsCameraControls = NO;
        imagePicker.toolbarHidden = YES;
        imagePicker.navigationBarHidden = YES; 
        imagePicker.wantsFullScreenLayout = YES;
    }
    @catch (NSException * e) {
        [imagePicker release]; imagePicker = nil;
    }
    @finally {
        if(imagePicker) {
            [cameraView addSubview:[imagePicker view]]; [cameraView release];
    }
    }
    
    [window_ bringSubviewToFront:viewController.view];
Hay una cosa más por hacer, abrimos HelloWorldLayer.m y cambiamos el texto y el tamaño del label.
// create and initialize a Label
CCLabelTTF *label = [CCLabelTTF labelWithString:@"Hello Augmented World" fontName:@"Marker Felt" fontSize:48];
Ejecutamos el proyecto y vemos que la cámara no ocupa toda la pantalla. En el modo landscape existe una diferencia entre el tamaño de la pantalla y lo que muestra la cámara. La pantalla del iphone tiene un aspect ratio de 3:4 y la cámara de 4:3, por lo tanto debemos escalar la imagen de la cámara para se ajuste.
Para solucionar esto debemos escalar nuestro UIImagePicker así que volvemos a AppDelegate.m y buscamos el siguiente código y agregamos la última linea.
 
@try {

    imagePicker = [[[UIImagePickerController alloc] init] autorelease];      imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera; imagePicker.showsCameraControls = NO;
    imagePicker.toolbarHidden = YES;
    imagePicker.navigationBarHidden = YES; 
    imagePicker.wantsFullScreenLayout = YES; 
    imagePicker.cameraViewTransform =CGAffineTransformScale(imagePicker.cameraViewTransform, 1.0, 1.3);
 }
Ejecutamos nuevamente el proyecto y vemos que ya la cámara ocupa todoa la pantalla.

Touches
Cocos2D consta una clase singleton llamada CCTouchDispatcher la cual se utiliza para que objetos de la misma envien notificaciones de eventos en la pantalla.
Tenemos 2 capas, una en el fondo que es la de la cámara y otra sobre esta para mostrar el contenido en la pantalla.
Para que este disponible los eventos de touch abrimos HelloWorldLayer.m y en el método init hacemos

 self.isTouchEnabled = YES;

 Luego tenemos que registrar a CCTouchDispatcher como la capa que acepta este tipo de eventos. Después del método init agregamos el siguiente mensaje:

 - (void)registerWithTouchDispatcher {
 [[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES]; 
}

Seteamos el delegado como self y hacemos que ninguna otra capa responda ante algún evento salvo que decidamos que si puede manejar el evento.
Para aceptar el evento usamos el método ccTouchBegan. Justo después del método registerWithTouchDispatcher agregamos el siguiente código:
 
- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
    return YES;
} 

- (void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event {
    CGPoint location = [self convertTouchToNodeSpace:touch];
    NSLog(@"Touch %@: ", NSStringFromCGPoint(location));
} 
El primer método devuelve YES al dispatcher para que sepamos que acepta el evento y que el dispatcher no debe buscar otra respuesta. El segundo evento reacciona al touch guardando el lugar donde se produce el touch en la pantalla. Usamos el método NSStringFromCGPoint para castear correctamente un struct de C a un string. Ejecutamos la aplicacion nuevamente en nuestro dispositivo, tocamos al azar la pantalla y deberiamos ver una imagen como la siguiente:


Código la tercera parte Realidad Aumentada Parte 3

sábado, 26 de enero de 2013

Realidad Aumentada - Parte II - Captura de Cámara y Video

Guardando imágenes en diferentes formatos
UIKit incluye algunas funciones de C que nos permiten exportar imágenes a archivos en cualquier dispositivo de iOS. Los formatos más comunes son JPEG y PNG. Abrimos PhotoViewController.m en Xcode. Al final del método didFinishPickingMediaWithInfo agregamos lo siguiente:
// guardamos los archivos
NSString *pngPath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/ConvertedPNG.png"];
NSString *jpgPath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/ConvertedJPEG.jpg"];
[UIImageJPEGRepresentation(image, 1.0) writeToFile:jpgPath atomically:YES]; [UIImagePNGRepresentation(image) writeToFile:pngPath atomically:YES];
// optional (check for files)
//NSError *error;
NSFileManager *fileMgr = [NSFileManager defaultManager];
NSString *documentsDirectory = [NSHomeDirectory()
stringByAppendingPathComponent:@"Documents"];
NSLog(@"Documents: %@", [fileMgr contentsOfDirectoryAtPath:documentsDirectory
error:&error]);
Primero definimos donde esta ubicados los 2 archivos. Luego usamos funciones de C (UIImageJPEGRepresentation and UIImagePNGRepresentation) para escribir los archivos. Como parte de los parametros pasamos la calidad de compresión medida en una escala de 0.0 a 1.0 . Vemos que guardamos las imágenes con la mejor calidad posible y la menor compresión.

Captura de Video
Hay dos formas de hacer una captura en vivo de video. Una de las formas no requiere analizar los frames de video. Esto se ve en las aplicaciones de realidad aumentada en donde se utilizan clases que nos dicen la ubicación y el giroscopio para determinar donde estamos ubicados. En estas apps vemos el video y sobre este información sobre la ubicación quedando todo integrado. La otra forma, como es en el caso del reconocimiento facial, requiere que analicemos cada frame.

Creando una base de video preview
Abrimos en XCode PhotoViewController.m y buscamos el método loadPhotoPicker. Este método nos permite tomar una foto, ahora lo actualizamos con el siguiente código:
 UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init]; 
    imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
    // uncomment for front camera
    //imagePicker.cameraDevice = UIImagePickerControllerCameraDeviceFront;   imagePicker.cameraDevice = UIImagePickerControllerCameraCaptureModeVideo;  imagePicker.showsCameraControls = NO;
    imagePicker.toolbarHidden = YES;
    imagePicker.navigationBarHidden = YES;
    imagePicker.wantsFullScreenLayout = YES;
    imagePicker.delegate = self;
    imagePicker.allowsEditing = NO;
    [self presentModalViewController:imagePicker animated:YES];
Seteamos unas opciones para el objeto UIImagePickerController. Primero configuramos el dispositivo para el tipo de camera de video. Luego renderizamos UIImagePickerController en modo full-screen. Esta interfaz de video provee un background perfecto para crear una aplicación de realidad aumentada.
Si quisieramos analizar estos frames de videos deberiamos setear un NSTimer y capturar y guardar cada preview sin embargo esto no sería lo ideal en tiempo de procesador. Analizaremos mejores formas de captura de video frames.
Si lo ejecutan van a ver una imagen similar a esta:


Este video es un fondo perfecto para realizar una aplicación de realidad aumentada. Como dijimos antes realizar un NSTimer no es la mejor forma de analizar los frames, vamos a ver que forma es la ideal.
Para la captura de frames por sesiones vamos a utilizar una clase llamada AVCapturesSession. Vamos a hacerlo en un tab separado para ver bien las diferencias.
Lo primero que tenemos que hacer es agregar unos frameworks para poder trabajar con la libreria AVFoundation, estos son los que necesitamos:

CoreVideo
CoreMedia
AVFoundation
ImageIO

Creamos un nuevo tab bar item como vemos en la siguiente imagen:

Creamos un nuevo controlodar llamado ARCVideoViewController para este nuevo item que agregamos.
#import 
#import  
#import 

 @interface VideoViewController : UIViewController {
 }

 @property (strong, nonatomic) IBOutlet UIView *videoPreview;
 @end

Luego agregamos dos oulets más, un UIImageView y un UIButton al ARCVideoViewController. Al UIImageView lo llamamos videoImage y conectamos una acción que la llamaremos captureScreen al UIButton.
Para guardar una imagen desde un preview de un video utilizaremos un objeto del tipo AVCaptureStillImageOutput.
Creamos una propiedad para este objeto a la cual llamaremos stillImageOutput.
 
 VideoViewController.h debería quedar de esta manera:
 #import 
 #import  
 #import 
 @interface VideoViewController : UIViewController { }
 @property(nonatomic, retain) AVCaptureStillImageOutput *stillImageOutput;
 @property (strong, nonatomic) IBOutlet UIView *videoPreview;
 @property (strong, nonatomic) IBOutlet UIImageView *videoImage;
- (IBAction)captureScreen:(id)sender;
En VideoViewController.m necesitamos crear un método viewDidAppear para comenzar la sesión preview de la cámara. Creamos el método viewDidAppear
 
 - (void)viewDidAppear:(BOOL)animated {
     AVCaptureSession *session = [[AVCaptureSession alloc] init]; 
     session.sessionPreset = AVCaptureSessionPresetMedium;
     AVCaptureVideoPreviewLayer *captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session];
     captureVideoPreviewLayer.frame = self.videoPreview.bounds; 
     [self.videoPreview.layer addSublayer:captureVideoPreviewLayer];
     AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
     NSError *error = nil;
     AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
 }
 if (!input) {
     NSLog(@"ERROR: trying to open camera: %@", error);
 }
 [session addInput:input]; // Placeholder
 [session startRunning];
 
}
Lo primero que hicimos en este método fue crear una instancia de AVCaptureSession, seteamos la calidad de video a media y creamos una instancia de AVCaptureVideoPreviewLayer. Esto se usara para almacenar en el buffer el preview.
Luego seteamos el frame para rellenar el tamaño del video preview. Luego configuramos AVCaptureDevice y verificamos si esta disponible. Si no enviamos el mensaje de startRunning a AVCaptureSession en nuestra view no aparecera nada.
Si ejecutamos la aplicación no vemos ningún cambio interesante por eso lo que vamos a hacer es agregar un botón que al hacer touch capture una imagen.
Agregamos el siguiente código en el método viewDidAppear VideoViewController.m entre [session addInput:input]; y [session startRunning];
 

stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
NSDictionary *outputSettings = [[NSDictionary alloc] initWithObjectsAndKeys:
AVVideoCodecJPEG, AVVideoCodecKey, nil]; 
[stillImageOutput setOutputSettings:outputSettings];
[session addOutput:stillImageOutput];
Hay dos bloques principales en este método. En el primer bloque chequeamos si hay salida de video y seteamos una referencia local para la instancia de AVCaptureConnection.

Luego capturamos asincronicamente la imagen desde la cámara. Seteamos un buffer para almacenar la imagen. Después de esto usamos el framework ImageIO para capturar los adjuntos EXIF. Esto puede ser util para configurar filtros o almacenar valores acerca de la calidad de la foto.
Finalmente configuramos el oulet UIImageView para mostrar el frame de lo que capturamos. Si ejecutan el proyecto deberian ver algo similar a la siguiente imagen:
Código de esta primera parte Realidad Aumentada Parte 2

viernes, 25 de enero de 2013

Realidad Aumentada - Parte I - Captura de Cámara y Video

El elemento que tienen en común las aplicaciones de realidad aumentada es que son realizadas sobre la captura de video en vivo. Veremos algunos conceptos básicos sobre como usar la cámara, ejemplos de captura de video y luego analizaremos el video frame por frame.

Primero es importante chequear la disponibilidad del sensor o del componente de hardware. Se puede detectar que cámara esta disponible en tu dispositivo usando la clase UIImagePickerController. Existe un método llamado isSourceTypeAvailable que podemos usar para determinar si la cámara que queremos usar esta disponible.

El siguiente código nos muestra un alerta. Lo que hacemos es chequear la existencia de UIImagePickerControllerSourceTypeCamera para saber si esta disponible la cámara. Luego chequeamos si la cámara que esta disponible es la frontal con el parámetro UIImagePickerControllerCameraDeviceFront. Estos ejemplos se deben ejecutar en un dispositivo real ya que el simulador no soporta cámara o video.

BOOL cameraAvailable = [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera];
BOOL frontCameraAvailable = [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerCameraDeviceFront];

if (cameraAvailable) {
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Camara"
                                                    message:@"Camara Disponible" delegate:self
                                          cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
    [alert show];
    [alert release]; 
} else {
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Camera" message:@"La Camara no esta disponible" delegate:self cancelButtonTitle:@"OK"
                                          otherButtonTitles:nil, nil];
                              
                              [alert show];
                              [alert release]; 
}
                              
if (frontCameraAvailable) {
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Camara" message:@"Camara Frontal Disponible"
                                                   delegate:self cancelButtonTitle:@"OK"
                                          otherButtonTitles:nil, nil]; 

          [alert show];
          [alert release];
    } else {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Camara"                                                 message:@"Front Camera NOT Available"
                              [alert show];
                              [alert release]; }
                                                       delegate:self cancelButtonTitle:@"OK"
                                              otherButtonTitles:nil, nil];

    } 
Realizar una captura de una foto en iOS no es complicado. La clase UIImagePickerController tiene métodos para sacar una foto, acceder a la cámara e incluso hacer un preview de las fotos.

Creamos un nuevo proyecto del tipo Tabbed Application como se muestra en la siguiente captura. Llamemoslo AugmentedRealityCamera.



El storyboard deberia verse como la siguiente imagen:



Es conveniente que comencemos el proyecto desde cero, para eso borramos FirstViewController.h, FirstViewController.m, SecondViewController.h, y SecondViewController.m. También borramos los archivos .png asociados a la vista y las interfaces de FirstViewController y SecondViewController en los 2 storyboards (el del ipad y el del iphone). Lo único que debe quedar en el storyboard es el tabBar y en el proyecto deben quedar solo los dos storyboards y el AppDelegate.

Ya teniendo limpio todo el proyecto comenzamos tomando una foto y guardandola en la libreria del dispositivo. Entonces agregamos un view controller para este ejemplo y lo unimos a uno de los tab en el tab bar controller.

Para esto hacemos click derecho sobre la carpeta del proyecto y elegimos New File desde el menú de contexto. Elegimos la subclase UIViewController como template para este nuevo archivo.

Hacemos click en Next. Nos aseguramos que seleccionamos las opciones que aparecen en la siguiente captura.

Seleccionamos un View Controller desde la libreria de objetos y lo arrastramos a la interface. Hacemos click en el nuevo View Controller y cambiamos en el identity inspector la clase por la que creamos recién PhotoViewController. El resultado de estos 3 pasos se muestra en la siguiente figura:


Repetimos este proceso en el storyboard del ipad si queremos que también funcione en el mismo. Luego arrastramos un UIButton al PhotoViewController en el interface builder. Nos aseguramos que el editor asistente este activo y este presente el PhotoViewController.h, lo que hacemos es ctrl+arrastrar desde el botón hasta el archivo .h para crear la acción con este oulet.

A la acción la llamamos loadPhotoPicker. Si queremos que la aplicación sea universal debemos hacer este paso nuevamente para el ipad.
Ahora vamos a completar el método loadPhotoPicker:
 
- (IBAction)loadPhotoPicker:(id)sender {
 UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init]; imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
 // uncomment for front camera
 // imagePicker.cameraDevice = UIImagePickerControllerCameraDeviceFront; 
imagePicker.delegate = self;
 imagePicker.allowsEditing = NO;
 [self presentModalViewController:imagePicker animated:YES];
 }


Este método crea una instancia de la clase UIImagePickerController. Lo que hace esta clase es abrir la interface de la cámara que utilizamos en iOS. Seteamos para utilizar la cámara trasera. Si queremos utilizar la delantera primero deberia mos ver si esta disponible. Luego seteamos el delegate to self, deshabilitamos la edición del image picker y presentamos el UIImagePickerController como modal. Si lo ejecutamos se mostrara la cámara pero no se guardara nada. Métodos para guardar una imagen:
- (void) imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    UIImage *image = [info objectForKey:@"UIImagePickerControllerOriginalImage"];
    UIImageWriteToSavedPhotosAlbum(image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
}


- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
{
    UIAlertView *alert; if (error) {
    alert = [[UIAlertView alloc] initWithTitle:@"Error"
                                       message:@"No se puede guardar la imagen en el album de fotos." 
                                      delegate:self cancelButtonTitle:@"Ok" 
                             otherButtonTitles:nil];
    } else {
        alert = [[UIAlertView alloc] initWithTitle:@"Success"
                                           message:@"Imagen Guardada." 
                                          delegate:self 
                                 cancelButtonTitle:@"Ok"
                                 otherButtonTitles:nil];
    }
    [alert show];
    [self dismissModalViewControllerAnimated:YES];
}           
El primer método es un método delegado de la clase UIImagePickerController que se dispara cuando se selecciona una imagen desde el controlador. Hacemos un alloc de este objeto para la imagen y lo enviamos al segundo método que se motro anteriormente. El segundo método que se establece como el selector que se llama después de guardar el archivo chequea se existe algún error y muestra el mensaje de alerta. Recordemos que solo podemos testear este ejemplo en un dispositivo real ya que el simulador no nos permite tomar fotos.

Código de esta primera parte: Realidad Aumentada - Parte 1

jueves, 24 de enero de 2013

Botones expandibles

DDExpandableButton permite crear botones como los del flash de la cámara.
Se puede cambiar los colores, cambiar el label por una imagen y los bordes.


Podemos bajar el código desde aquí

miércoles, 23 de enero de 2013

Librería para realizar bordes redondeados en UIViews

Esta librería llamada TKRoundedView y realizada por Tomek Kuzma nos permite customizar los bordes de un UIView

Podemos bajarla desde aquí

jueves, 17 de enero de 2013

Librería para crear automaticamente un ABM

SDScaffoldKit es una librería realizada por Steve Derico que nos permite automáticamente crear una serie de vistas de un ABM utilizando Core Data.

Podemos bajarlo desde aquí


Librería para determinar la performance de un método

Esta librería llamada NanoProfiler nos permite medir la performance de un método. Es muy facil de usar, se setea para que corra un método específico y se obtiene el resultado.
//MyClass.m
#import "NanoProfiler.h"

+(void) initialize {
AddProfiler([MyClass class], @selector(foo));
}

-(void) foo {
...
}
y el resultado
*** Timer MyClass_foo started. ***
*** Timer MyClass_foo stopped. runtime: 1.633041 milliseconds ***
Se puede bajar desde aquí