Série 4 – Animations

Introduction

Le but de cette série, la dernière du mini-projet images continues, est 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 3, 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 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 classe implémentant l'interface ci-dessus :

public final class ConstantAnimation<T> implements Animation<T> {
    private final Image<T> image;

    public ConstantAnimation(Image<T> image) {
        this.image = image;
    }

    @Override
    public Image<T> imageAt(double time) {
        return image;
    }
}

Pour visualiser de telles animations, nous vous fournissons dans une archive Zip la classe AnimationViewer (accompagnée de Animation et ConstantAnimation), 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. Pour une raison qui sera expliquée plus tard au cours, cette variable doit être marquée final.

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

Définissez une classe d'animation nommée RainbowAnimation 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 classe, il faut définir au préalable une classe d'image monochrome, que vous pourrez nommer ConstantImage.

Exercice 2

Définissez une classe AnimationCompositor qui soit le pendant, pour les animations, de la classe ImageCompositor.

Tout comme celui de ImageCompositor, le constructeur de AnimationCompositor prend trois arguments, mais ce sont trois animations : les deux premières produisent une image couleur, tandis que la troisième produit un masque. AnimationCompositor compose les images produites par ces animations exactement comme ImageCompositor, et il est d'ailleurs conseillé d'utiliser ImageCompositor dans la mise en œuvre.

Une fois cette classe définie, définissez une classe animation DissolveMask qui implémente Animation<Double> et dont le constructeur prend en argument deux instants \(t_1\) et \(t_2\) tels que \(0 \le t_1 < t_2\). Cette classe produit un masque uniforme dont la valeur vaut :

  • 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\).

A noter que pour mettre en œuvre DissolveMask, vous devrez au préalable écrire une classe de masque constant, un peu comme à l'exercice 1.

Comme son nom l'indique, DissolveMask permet, en combinaison avec AnimationCompositor, 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 classes 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 classes 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 classe nommée AnimationLooper, dont le constructeur prend en arguments deux instants \(t_1\) et \(t_2\) et une animation, et qui fait tourner cette dernière en boucle entre les instants \(t_1\) et \(t_2\).

Exercice 4

Une fois encore, si vous avez terminé les exercices ci-dessus plus tôt que prévu, laissez libre cours à votre imagination pour inventer de nouveaux types d'animations, de transitions (en utilisant AnimationCompositor avec d'autres masques qu'un masque constant, p.ex. un cercle qui s'agrandit au cours du temps) ou même d'images.