Série 8 – Jokers et animations

Introduction

Le but de cette série est double : premièrement, de vous permettre de vous entraîner à utiliser les jokers (wildcards) vus dans le cours sur la généricité avancée ; et deuxièmement, de terminer le miniprojet images continues en animant celles-ci.

Partie 1 : jokers

Introduction

Comme vu au cours, les jokers (ou wildcards) permettent, entre autres, de généraliser le type des paramètres de certaines méthode génériques. Ainsi, une méthode addAll qui ajoute à la liste à laquelle on l'applique tous les éléments de la liste reçue en argument peut être définie ainsi :

interface List<E> {
    // ...
    void addAll(List<? extends E> other);
}

Comparée à une version prenant une liste de type List<E> en argument, celle-ci est plus générale puisqu'elle permet, p.ex., l'appel suivant :

List<Number> l = new LinkedList<>();
List<Integer> li = new LinkedList<>();
li.add(1);
l.addAll(li);

Cet appel serait invalide si addAll prenait un argument de type List<E>. Pour plus de détails, voir le cours.

Exercice

Afin de vous entraîner à utiliser les jokers, nous vous fournissons une archive Zip contenant un fichier Lists.java définissant une classe Lists (notez le s à la fin !) qui offre trois méthodes statiques travaillant sur les listes Java :

  1. addMany, qui ajoute un certain nombre d'occurrences d'un élément à la fin d'une liste,
  2. copy, qui copie le contenu d'une première liste à la fin d'une seconde liste,
  3. max, qui retourne le plus grand élément d'une liste selon un comparateur reçu en argument.

Nous vous fournissons de plus un test JUnit pour cette classe dans le fichier ListsTest.java. Ce test comporte 13 appels aux trois méthodes susmentionnées, numérotés par des commentaires. Dans la version que nous vous fournissons, tous ces appels sont signalés comme erronés par Eclipse.

Examinez chacun de ces 13 appels et déterminez s'il pourrait être valide ou non. Si oui, généralisez le type de la fonction appelée (dans la classe Lists) au moyen de jokers. Si non, mettez l'appel en commentaire et justifiez votre choix. Attention, vous n'avez pas le droit de modifier le code des fonctions de Lists, seulement le type de leurs arguments et variables locales.

Partie 2 : animations

Introduction

Pour terminer le miniprojet images continues, nous vous proposons d'incorporer une notion de temps au système, afin de pouvoir décrire et afficher des animations. Votre point de départ sera donc soit votre propre version de la série 6, soit le code de notre corrigé.

Tout comme une image couleur peut être vue comme une fonction qui, étant donné un point, produit une couleur, une animation peut être vue comme une fonction qui, étant donné un instant dans le temps, produit une image. En Java, et en utilisant une idée très similaire à celle utilisée pour l'interface Image, on peut modéliser une animation au moyen de l'interface fonctionnelle suivante :

public interface Animation<T> {
    public Image<T> imageAt(double time);
}

La méthode imageAt reçoit en paramètre le temps depuis le début de l'animation, exprimé en secondes, et retourne l'image correspondant à cet instant.

L'animation la plus simple qui soit est l'animation constante, qui produit la même image à chaque instant. Elle s'exprime facilement au moyen d'une méthode statique de l'interface Animation :

public static <T> Animation<T> constant(Image<T> image) {
    return t -> image;
}

Pour visualiser de telles animations, nous vous fournissons dans une archive Zip la classe AnimationViewer (accompagnée d'un squelette de Animation), très similaire à ImageViewer si ce n'est qu'elle ne cesse d'obtenir la prochaine image de l'animation et l'affiche. La variable animation contient l'animation affichée, tout comme la variable image contenait l'image affichée dans le cas d'ImageViewer.

Comme précédemment, il n'est pas nécessaire de comprendre la classe AnimationViewer, d'autant qu'elle utilise des notions que nous n'avons pas encore vues au cours.

Exercice 1

Pour commencer, ajoutez à l'interface Animation une méthode statique nommée rainbow qui produit une image monochrome dont la couleur change au cours du temps. Chacune des composantes rouge, verte et bleue de la couleur est déterminée par une sinusoïdes oscillant entre 0 et 1 et dont la fréquence (en Hz) et la phase sont passées au constructeur de la classe. Par exemple, la composante rouge est déterminée par la formule suivante :

\[ r(t) = \frac{1}{2} \sin(2\pi\,f_r\,t + \phi_r) + \frac{1}{2} \]

où \(f_r\) et \(\phi_r\) sont respectivement la fréquence et la phase d'oscillation de la composante rouge. Les autres composantes sont calculées de manière similaire.

Notez que pour pouvoir définir cette méthode, il faut ajouter au préalable une méthode permettant de produire une image monochrome à l'interface Image, que vous pourrez nommer constant.

Exercice 2

Ajoutez une méthode statique à Animation, nommée compose et qui soit le pendant, pour les animations, de la méthode compose de l'interface Image.

Tout comme la méthode compose de l'interface Image, celle de Animation prend trois arguments, mais ce sont trois animations : les deux premières produisent une image couleur, tandis que la troisième produit un masque. La méthode compose de Animation compose les images produites par ces animations exactement comme celle de Image, et il est d'ailleurs conseillé d'utiliser cette dernière dans la mise en œuvre de la première.

Une fois cette méthode ajoutée, définissez une méthode statique nommée dissolveMask qui prend en argument deux instants \(t_1\) et \(t_2\) tels que \(0 \le t_1 < t_2\) et qui retourne une animation de type Animation<Double>. Cette animation produit un masque uniforme dont la valeur est :

  • 0 à tous les instants antérieurs à \(t_1\),
  • 1 à tous les instants postérieurs à \(t_2\),
  • une valeur variant linéairement de 0 à 1 pour tous les instants compris entre \(t_1\) et \(t_2\).

Comme son nom l'indique, dissolveMask permet, en combinaison avec compose, de faire un fondu enchaîné entre deux images. Une fois les classes ci-dessus écrite, combinez-les afin de faire un tel fondu entre deux images de votre choix.

Exercice 3

Au même titre qu'il est possible de définir des méthodes transformatrices pour les images, qui transforment les coordonnées avant de les passer à l'image qu'elles transforment, il est possible de définir des méthodes transformatrices pour les animations, qui transforment le temps avant de le passer à l'animation qu'elles transforment.

Utilisez cette idée pour définir une méthode par défaut nommée loop, prenant en arguments deux instants \(t_1\) et \(t_2\) tels que \(0\le t_1 < t_2\) et une animation, et qui fait tourner cette dernière en boucle entre les instants \(t_1\) et \(t_2\).