19

Images, animations et graphiques

L’environnement iOS est très riche en fonctions graphiques. En s’appuyant sur l’environnement Quartz, on dispose d’un nombre impressionnant de filtres d’images et d’animations faciles à mettre en place. L’API standard OpenGL ES (Embedded Systems pour environnements mobiles) est également disponible et permet d’aller encore plus loin dans le contrôle du coprocesseur graphique, mais au prix d’une complexité accrue.

Core Image

Ce framework facilite la modification des caractéristiques d’une image, comme la teinte et l’exposition. Tous les iPad ont un co-processeur graphique (GPU) PowerVR (de ImgTec) qui rend ces opérations très rapides, y compris sur un flux vidéo.

Les effets, sous forme de filtres, peuvent être combinés et produisent des résultats de très grande qualité.

Classe Description
CIContext Contexte de travail dans lequel l’image est traitée.
CIImage Données de l’image. Par exemple depuis une UIImage ou un fichier.
CIFilter Cette classe est un dictionnaire qui donne la description du filtre correspondant.

L’environnement iOS fournit un très grand nombre de filtres applicables sur des images. Dans l’exemple suivant, nous ne nous concentrerons que sur le filtre CIHueAdjust qui sert à modifier la teinte d’une image.

Exemple de filtre appliqué sur une image

1 Créer un nouveau projet de type Single View Application.

2 Positionner une UIImageView dans le contrôleur de vue et ajouter un fichier image.png (penser à le copier).

3 Connecter l’UIImageView au code (.h et .m).

4 Ajouter le framework intitulé CoreImage.

Création et application d’un filtre à une image

- (void)viewDidLoad

{

[super viewDidLoad] ;

// Départ de l’application.

// image - URL du fichier

NSString *cheminDuFichier =

[[NSBundle mainBundle] pathForResource:@"image" ofType:@"png"];

NSURL *URLDuFichier = [NSURL fileURLWithPath:cheminDuFichier];

// image - Création d’un objet de type CIImage à partir de l’URL

CIImage *imageDeDepart = [CIImage

imageWithContentsOfURL:URLDuFichier];

// image - Création d’un contexte de travail pour l’image

CIContext *contexte = [CIContext contextWithOptions:nil];

// image - Création du filtre et application

CIFilter *teinte = [CIFilter filterWithName:@"CIHueAdjust"

keysAndValues:@"inputImage", imageDeDepart,

@"inputAngle", [NSNumber numberWithFloat:1.8],

nil];

CIImage *imageFinale = [teinte outputImage];

// image - Récupération de l’image du contexte et affichage

CGImageRef cgImageRef =

[contexte createCGImage:imageFinale fromRect:[imageFinale extent]];

UIImage *nouvelleImage = [UIImage imageWithCGImage:cgImageRef];

[self.monImageView setImage:nouvelleImage];

CGImageRelease(cgImageRef);

}

Dans cet exemple simple, on utilise le filtre intitulé CIHueAdjust. Le développeur devra étudier chacun des filtres et les dictionnaires associés. Attention, les filtres présents sous iOS sont moins nombreux que sous Mac OS X.

Pour le filtre CIHueAdjust, les deux paramètres attendus sont les suivants :

Clé Valeur
inputImage Un objet de la classe CIImage.
inputAngle Un élément de classe NSNumber entre -3,14 et 3,14. Ce chiffre représente la teinte (le jaune correspond à peu près à 1.0).

Dans l’étape image, on obtient l’URL de l’image (ce qui sera souvent le cas dans la réalité). Dans l’étape image, on crée la classe CIImage chargée de contenir l’image de départ. Dans l’étape image, on ouvre le contexte pour effectuer le filtrage. Dans l’étape image, on crée le filtre avec ses attributs spécifiques et on l’applique à l’image pour produire une image finale. Dans l’étape image, on récupère l’image traitée et on l’affiche dans l’UIImageView.

image

Figure 19–1 Exemple de filtrage d’une image par CoreImage

Core Graphics

L’environnement iOS possède un framework intitulé Quartz fournissant un environnement de dessin 2D. Cet environnement propose différents effets comme la transparence, la gestion des couleurs et des documents PDF et bien d’autres encore. Sous iOS, Quartz 2D travaille avec les classes issues des environnement Core Animation, OpenGL ES et UIKit.

Quartz 2D utilise un modèle appelé Page. Chaque opération produit une couche supplémentaire. On ne peut modifier les éléments d’une page qu’en superposant une nouvelle couche par-dessus. L’ordre dans lequel les couches sont successivement déposées est donc très important.

Le développeur travaille dans un contexte graphique, qui peut finalement être produit sous forme d’une image Bitmap, d’une nouvelle couche ou d’un document PDF par exemple.

Un exemple

Créez un nouveau projet de type Single-View et créez une nouvelle classe intitulée MaView de type UIView.

Associez cette classe à la vue dans le Storyboard et modifiez le fichier d’implémentation en plaçant la méthode drawRect : suivante :

Dessiner un carré bleu

- (void)drawRect:(CGRect)rect

{

// Code de dessin

// Partie CG

// 1 - Ouvrir le contexte graphique et effacer le contexte

CGContextRef contexte = UIGraphicsGetCurrentContext();

CGContextClearRect(contexte, rect);

// 2 - Dessiner un carré bleu

CGContextSetRGBFillColor(contexte, 0, 0, 255, 1);

CGContextFillRect(contexte, CGRectMake(50, 50, 50, 50));

// 3 - Fin de contexte

UIGraphicsEndImageContext();

}

La première chose qui saute aux yeux est que nous travaillons en C. Nous nous trouvons face à des fonctions ayant un aspect traditionnel de type fonction(type nom de variable).

Dans cet exemple, on commence par récupérer le contexte graphique et on efface la vue (ce qui produit un fond noir). Puis, on choisit une couleur de type RGBA (Rouge, Vert, Bleu, Alpha) et on appelle la fonction CGContectFillRect() pour créer le carré.

image

Figure 19–2 Exemple d’affichage d’un graphique

En fin d’affichage, on ferme le contexte.

À l’aide des fonctions suivantes et en suivant le même mécanisme, on peut créer toutes sortes d’objets graphiques à l’écran.

Fonction Description
CGContextFillRect() Construction d’un rectangle (ou d’un carré).
CGContextFillEllipseInRect() Construction d’une ellipse (ou d’un cercle).
CGContextBeginPath() Construction d’un polygone avec un éventuel remplissage.
[Image drawInRect:] Ajout d’une image avec certains effets possibles.

Notez qu’il est possible de créer des courbes de Bézier ou d’ajouter du texte dans un contexte purement graphique.

Core Animations

L’environnement CoreAnimation fournit des classes s’occupant du rendu graphique, des mécanismes de projection et des animations. L’environnement iOS aide à créer des animations très simples.

Animations simples

Dans l’exemple suivant, on va déplacer un label à l’écran.

Déplacement d’un label

- (IBAction)deplacerLeLabel:(id)sender

{

// image – Début du bloc d’animation

[UIView beginAnimations:nil context:nil];

// image – Durée de l’animation

[UIView setAnimationDuration:2.0];

// image – Accélération de l’animation

[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];

// image – Action réellement effectuée

self.monLabel.center = CGPointMake(200,300);

// image – Validation de l’animation

[UIView commitAnimations];

}

Le bloc d’animation est décomposé ainsi :

image Déclaration du bloc d’animation.

image Durée de l’animation, en secondes.

image Progression de l’animation (elle peut être linéaire, accélérer ou décélérer).

image Les instructions qui se trouvent dans cette section du bloc d’animation indiquent la position finale de l’élément graphique. L’animation s’effectuera par le chemin « de moindre coût » depuis la position de départ vers la position d’arrivée.

image Validation et départ de l’animation.

L’inconvénient de ce type d’animation est que l’ensemble des opérations est effectué en utilisant la règle de moindre coût. Ainsi, une rotation de 360° ne produira rien à l’écran, au lieu de faire une rotation qui finalement ramènera l’objet à sa disposition d’origine !

Pour des animations plus complexes et plus de flexibilité, il faut passer dans le framework QuartzCore, qui fournit des classes supplémentaires CA* (CoreAnimation).

Dans l’environnement iOS, l’élément de travail est la couche (appelée layer), qui est de la classe CALayer et obéit au design pattern MVC. L’animation est réalisée par l’UIView à laquelle la CALayer appartient. La CALayer n’intervient pas dans l’affichage, elle ne fait que représenter les propriétés de la vue que vous voulez animer.

ATTENTION

Certaines propriétés graphiques ne peuvent pas être animées de cette façon. On trouve une liste des propriétés pouvant être animés à la fin du Core Animation Programming Guide.

Apple fournit un certain nombre de sous-classes de CALayer facilitant la manipulation des animations, comme CAScrollLayer, CAGradientLayer, etc.

Par exemple, pour effectuer une rotation de 360°, codez les opérations suivantes :

1 Ajoutez le framework QuartzCore dans votre projet.

2 Ajoutez un bouton avec l’action suivante associée :

Rotation de 360°

-(IBAction)faireTournerLeLabel:(id)sender {

// Utilisation du Framework Quartz pour faire un tour de 360°

// 1 seul tour

// 3 – Déclarer une animation de type transform.rotation

CABasicAnimation *rotation360;

rotation360 = [CABasicAnimation

animationWithKeyPath:@"transform.rotation"];

// 4 - Propriétés

rotation360.fromValue = [NSNumber numberWithFloat:0];

rotation360.toValue = [NSNumber numberWithFloat:((360*M_PI)/180)];

rotation360.duration = 6;

rotation360.repeatCount = 1;

// 5 – Associer l’animation à l’élément

[self.monLabel.layer addAnimation:rotation360 forKey:@"360"];

}

3 Créez une nouvelle animation de la classe CABasicAnimation et modifiez dans son dictionnaire associé la clé intitulée transform.rotation. On peut modifier de nombreux paramètres (opacity, position, etc.).

4 En fonction du type d’animation demandé, modifiez les paramètres correspondants. Ici, de 0 à 360° pendant 6 secondes et pour une seule animation (un seul tour).

5 Associez l’animation à la couche (layer) de l’élément concerné et déclenchez-la.

Grouper les animations

On peut grouper les animations au niveau de la couche (layer) en utilisant le framework QuartzCore comme précédemment.

Pour cela, on utilise tout simplement la classe CAAnimationGroup.

Par exemple, le code suivant combine une animation d’opacité avec une animation de position.

Groupement d’animations

-(CABasicAnimation *)effetDeTransparence { image

CABasicAnimation *animation = [CABasicAnimation

animationWithKeyPath:@"opacity"];

[animation setFromValue:[NSNumber numberWithFloat:1.0f]];

[animation setToValue: [NSNumber numberWithFloat:0.2f]];

return animation;

}

- (CABasicAnimation *)effetDeTranslation{ image

CABasicAnimation *animation = [CABasicAnimation

animationWithKeyPath:@"position"];

[animation setFromValue:[NSValue

valueWithCGPoint:CGPointMake(100.0, 100.0)]];

animation.toValue = [NSValue

valueWithCGPoint:CGPointMake(100.0, 250.0)];

return animation;

}

-(IBAction)combinaison:(id)sender { image

CAAnimationGroup *anim = [CAAnimationGroup animation];

[anim setAnimations:[NSArray

arrayWithObjects:[self effetDeTransparence],

[self effetDeTranslation],

nil]];

[anim setDuration:3.0];

[anim setRemovedOnCompletion:NO];

[anim setFillMode:kCAFillModeForwards];

[self.monLabel.layer addAnimation:anim forKey:nil];

}

image Déclaration de la première animation.

image Déclaration de la seconde animation.

image Regroupement des animations dans un tableau et association à la couche (layer) de notre label.

Image animée

On ne peut pas directement jouer de GIF animé sous iOS (en fait, la première image sera affichée sans l’animation correspondante). On peut cependant produire le même effet en affichant un tableau d’images pendant une durée donnée et en répétant la séquence un certain nombre de fois, à l’aide de la propriété animationImages de la classe UIImageView.

Image animée. Dans le fichier ViewController1.h

#import <UIKit/UIKit.h>

@interface ViewController1 : UIViewController {

@property (nonatomic,strong) IBOutlet UIImageView *monImageView;

Image animée. Dans le fichier ViewController1.m, par exemple au démarrage de la vue

- (void)viewDidLoad

{

[super viewDidLoad];

// Départ.

self.monImageView.animationImages = [NSArray arrayWithObjects:

[UIImage imageNamed:@"singeanime1.png"],

[UIImage imageNamed:@"singeanime2.png"],

[UIImage imageNamed:@"singeanime3.png"],

[UIImage imageNamed:@"singeanime4.png"],

[UIImage imageNamed:@"singeanime5.png"],

[UIImage imageNamed:@"singeanime6.png"],

[UIImage imageNamed:@"singeanime7.png"], nil];

[self.monImageView setAnimationRepeatCount:10]; // Nombre de boucles

self.monImageView.animationDuration = 1.0f; // Durée de la boucle

[self.monImageView startAnimating];

}

On alimente la propriété animationImages de notre UIImageView avec un tableau d’images.

On indique le nombre d’itérations à l’aide de AnimationRepeatCount et la durée de la boucle, qui sera implicitement divisée par le nombre d’éléments du tableau, lequel aura donc la même durée d’exposition à l’écran (ici 1s/7 = 0,14 s).

OpenGL ES

OpenGL ES (Embedded Systems) est une variante de la célèbre API graphique 3D OpenGL, adaptée aux terminaux mobiles et systèmes embarqués. C’est naturellement dans les jeux vidéo qu’on l’utilise le plus, mais elle trouve également sa place dans les applications plus classiques, où elle sert à implémenter des effets graphiques saisissants renforçant l’immersion de l’utilisateur : un effet de page de livre qui se tourne, ou encore le célèbre effet de Cover Flow (défilement des pochettes de disques, en relief), qui a fait son apparition sur l’iPod il y a plusieurs années.

L’utilisation d’OpenGL nécessite normalement une compréhension de plusieurs fondamentaux mathématiques, comme les matrices et l’algèbre linéaire. Cependant, dans l’exemple que nous allons donner ci-après, cette compréhension ne sera pas vraiment nécessaire, car nous expliquerons chaque étape. Néanmoins, le lecteur qui souhaiterait aller plus loin avec OpenGL devra se préparer à travailler sur ces concepts théoriques. Courage, la puissance d’OpenGL n’en deviendra que plus évidente !

La troisième dimension

Les points se trouvant dans un espace à 3 dimensions sont définis par x, y et z. x est la position sur l’axe des abscisses et les valeurs positives vont vers la droite par rapport à l’observateur ; y est la position sur l’axe des ordonnées, avec les valeurs positives allant vers le haut. z est la profondeur, avec les valeurs positives se rapprochant de l’observateur.

Sous OpenGL, on utilise des points de type (x, y, z) appelés sommets ou vertex (vertices au pluriel).

On utilise des mécanismes de calcul matriciel pour transformer les objets (pivoter, agrandir, déplacer, etc.).

Les objets, définis par des combinaisons de sommets, sont tous positionnés indépendamment dans l’espace.

Par ailleurs, il est possible de choisir la façon dont on souhaite gérer la perspective : soit les objets deviennent plus petits en fonction de leur éloignement en profondeur (selon l’axe z), soit ils ne changent pas de taille (on parle alors de projection orthographique). Le moteur OpenGL se chargera également de tous les effets de textures ou de lumières que vous mettrez en place.

Un projet

Commençons par créer un nouveau projet de type Empty Application et par y inclure les éléments nécessaires pour OpenGL.

1 Créez un nouveau projet OpenGL avec ARC sans CoreData et intitulez-le NIV2OpenGLSimple.

2 Tout d’abord, ajoutez les frameworks nécessaires : QuartzCore, OpenGL et GLKit.

3 Créez une scène vide.

On importe le header GLKit dans l’AppDelegate.h et on utilise ses protocoles de la façon suivante.

Dans le fichier AppDelegate.h

#import <UIKit/UIKit.h>

#import <GLKit/GLKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate, GLKViewDelegate, GLKViewControllerDelegate>

@property (strong, nonatomic) UIWindow *window;

@end

Dans le fichier Appdelegate.m, modification de la méthode application:didFinishLaunchingWithOptions

- (BOOL)application:(UIApplication *)application

didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

{

// image - Contexte OpenGL

EAGLContext *contexte = [[EAGLContext alloc]

initWithAPI:kEAGLRenderingAPIOpenGLES2];

[EAGLContext setCurrentContext:contexte];

// image – Création de la vue

GLKView *view = [[GLKView alloc]

initWithFrame:[[UIScreen mainScreen] bounds] context:contexte];

view.delegate = self;

// image – Création du View Controller spécifique OpenGL

GLKViewController *controller = [[GLKViewController alloc] init];

controller.delegate = self;

controller.view = view;

// image – Démarrage de la fenêtre et de l’application

self.window = [[UIWindow alloc]

initWithFrame:[[UIScreen mainScreen] bounds]];

self.window.rootViewController = controller;

[self.window makeKeyAndVisible];

return YES;

}

image Création du contexte OpenGL

image Création de la vue qui effectue le rendu de la scène. Le délégué va en réalité appeler la méthode glkView:drawInRect: pour chaque image (au sens frame dans OpenGL).

image De la même façon, allocation d’un contrôleur de vue spécifique pour OpenGL.

image Démarrage habituel de la fenêtre et de l’application.

À ce stade, on peut déjà compiler en ignorant les avertissements et vérifier qu’on obtient une scène (fond mauve).

On implémente alors les méthodes déléguées suivantes :

Méthodes déléguées OpenGL

#pragma mark - Méthodes delegate OpenGL

- (void)glkViewControllerUpdate:(GLKViewController *)controller {

// NSLog(@"Entrée dans la fonction %s", __FUNCTION__);

}

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {

// NSLog(@"Entrée dans la fonction %s", __FUNCTION__);

glClearColor(0.5, 0.2, 0.2, 0.5);

glClear(GL_COLOR_BUFFER_BIT);

}

On implémente deux méthodes, car, à l’image du design pattern MVC, on sépare l’état du modèle de la présentation. L’état (animations, simulation des éléments physiques, etc.) se place dans la méthode glkViewControllerUpdate: et l’affichage réel de votre scène doit être placé dans la méthode glkView:drawInRect:.

Construction d’un tétraèdre

OpenGL ES ne sait manipuler que les triangles. Pour faire un carré, on met deux triangles côte à côte.

Pour construire un tétraèdre, la plus petite forme 3D, on place 4 triangles. On déclare d’abord la liste des sommets des triangles :

Les sommets du tétraèdre

GLKVector3 vertices[] = {

GLKVector3Make(0.0, 1.0, 0.0), //

GLKVector3Make( -0.75, 0.0, 0.5), //

GLKVector3Make( 0.0, 0.0, -0.5), //

GLKVector3Make(0.5, 0.5, 0.5), //

};

Puis on construit le tétraèdre à partir des triangles dont les sommets viennent d’être définis.

Juxtaposition de triangles

GLKVector3 triangleVertices[] = {

//

vertices[0], vertices[2], vertices[3],

vertices[2], vertices[1], vertices[0],

//

vertices[1], vertices[3], vertices[0],

vertices[3], vertices[1], vertices[2],

//

};

L’ordre dans lequel les sommets sont désignés est important. En effet, chaque triangle a une face avant et une face arrière. Seule la face avant nous intéresse. On la détermine en décrivant les sommets dans le sens trigonométrique.

Une fois la structure du tétraèdre déterminée, il faut un rendu de la figure. Il existe des rendus simples comme dans le code suivant :

Rendu du tétraèdre

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {

glClearColor(0.5, 0.2, 0.2, 0.5);

glClear(GL_COLOR_BUFFER_BIT);

// Les sommets du tétraèdre

GLKVector3 vertices[] = {

GLKVector3Make(0.0, 1.0, 0.0), //

GLKVector3Make( -0.75, 0.0, 0.5), //

GLKVector3Make( 0.0, 0.0, -0.5), //

GLKVector3Make(0.5, 0.5, 0.5), //

};

GLKVector3 triangleVertices[] = {

vertices[0], vertices[2], vertices[3],

vertices[2], vertices[1], vertices[0],

vertices[1], vertices[3], vertices[0],

vertices[3], vertices[1], vertices[2],

};

GLKBaseEffect *effect = [[GLKBaseEffect alloc] init] image

[effect prepareToDraw];

glEnable(GL_DEPTH_TEST); image

glEnable(GL_CULL_FACE); image

glEnableVertexAttribArray(GLKVertexAttribPosition); image

glVertexAttribPointer(GLKVertexAttribPosition, 3,

GL_FLOAT, GL_FALSE, 0, triangleVertices);

glDrawArrays(GL_TRIANGLES, 0, 4); image

glDisableVertexAttribArray(GLKVertexAttribPosition); image

}

En plus de l’initialisation de la couleur et des sommets et des triangles, il faut :

image Créer un effet simple.

image et image Demander à ne pas afficher la face arrière des triangles.

image Afficher le tableau de sommets avec 3 sommets par figure, pour chaque triangle.

image Afficher les quatre triangles.

image Terminer l’affichage.

image

Figure 19–3 Premier affichage OpenGL du tétraèdre

On peut modifier la matrice de projection qui définit la manière dont sont projetées les coordonnées des objets, comme évoqué plus haut. Pour que le rendu tienne compte des proportions de l’iPhone, on ajoute l’instruction suivante :

Projection de la forme dans l’espace OpenGL

GLKBaseEffect *effect = [[GLKBaseEffect alloc] init];

effect.transform.projectionMatrix =

GLKMatrix4MakeOrtho(-1, 1, -1.5, 1.5, -1, 1);

[effect prepareToDraw];

image

Figure 19–4 Modification de la projection

On ajoute ensuite les couleurs :

Ajout de couleurs

GLKVector4 colors[] = {

GLKVector4Make(1.0, 0.0, 0.0, 1.0), // Rouge

GLKVector4Make(0.0, 1.0, 0.0, 1.0), // Vert

GLKVector4Make(0.0, 0.0, 1.0, 1.0), // Bleu

GLKVector4Make(0.0, 0.0, 0.0, 1.0), // Noir

GLKVector4Make(0.0, 0.0, 1.0, 1.0), // Bleu

GLKVector4Make(0.0, 0.0, 0.0, 1.0), // Noir

GLKVector4Make(1.0, 0.0, 0.0, 1.0), // Rouge

GLKVector4Make(0.0, 1.0, 0.0, 1.0), // Vert

};

GLKVector4 colorVertices[] = {

colors[0], colors[1], colors[2],

colors[0], colors[2], colors[3],

colors[1], colors[4], colors[5],

colors[7], colors[6], colors[8],

};

// Les couleurs

glEnableVertexAttribArray(GLKVertexAttribColor);

glVertexAttribPointer(GLKVertexAttribColor, 4,

GL_FLOAT, GL_FALSE, 0, colorVertices);

image

Figure 19–5 Ajout de couleur

On ajoute alors un effet de rotation pour voir l’effet 3D produit :

Ajout d’un effet de rotation

GLKBaseEffect *effect = [[GLKBaseEffect alloc] init];

effect.transform.modelviewMatrix =

GLKMatrix4MakeYRotation(1.0/8.0*(2*M_PI));

effect.transform.projectionMatrix =

GLKMatrix4MakeOrtho(-1, 1, -1.5, 1.5, -1, 1);

[effect prepareToDraw];

On peut également apporter une source lumineuse et régler son intensité et sa provenance. OpenGL ES gère jusqu’à 8 sources lumineuses simultanées. Le code devient progressivement assez complexe est il est alors nécessaire de travailler avec des bibliothèques ou des structures qui simplifient le codage.

Conclusion

Dans ce chapitre, nous avons présenté les différents mécanismes de manipulation des éléments graphiques à l’écran, des plus simples comme Core Image ou les animations de base, jusqu’aux plus complexes comme OpenGL ES qui nécessite des connaissances plus approfondies et pour lequel on aura recours à des ouvrages dédiés.

Les animations de type 2D que QuartzCore vous apporte sont déjà suffisantes pour de nombreuses applications. Vous pourrez à titre d’exercice les utiliser pour programmer un petit jeu où des objets animés selon un modèle physique simpliste (gravité) rentreraient en collision avec d’autres objets. Les possibilités sont infinies !

Propriété de Marie Richie <FB1245387-C111754-prod@customers.feedbooks.com>