Teaching - 2i002 - (TME: sujets)


2i002 : Introduction à la programmation Objet

TME dessin

Voici un dernier TME assez ambitieux où plusieurs aspects sont abordés de front (héritage, introduction basique aux interfaces graphiques...)

Notes préliminaires:

// import nécessaires
import java.awt.Color;
import java.awt.Graphics;

// Pour les couleurs de base:
Color noir = Color.black; // récupération dans la classe Color
// toutes les couleurs de base existent:
Color.red; Color.blue; // ...

// Graphics = pinceau pour le dessin
// Si vous disposez d'une variable Graphics g, vous pouvez faire les opérations suivantes
g.setColor(Color.blue); // le pinceau dessinera en bleu
g.drawLine(A.x, A.y, B.x, B.y); // trace la ligne de A à B
// pour plus de détails => cf javadoc

Hierarchie de classes

Classe Figure

Cette classe abstraite dispose des attributs/méthodes suivants:

  • Color couleur
  • int id (à gérer avec un compteur static)
  • constructeur prenant une couleur
  • accesseur sur id
  • méthode void move(int deltaX, int deltaY) (abstraite)
  • méthode de dessin suivante:
 public void draw(Graphics g){ // permet de choisir un pinceau de la bonne couleur avant le dessin
        g.setColor(couleur);
 }

Note: le dessin en lui-même sera géré au niveau des classes filles

Vous devez donc insérer les imports suivants:

import java.awt.Color;
import java.awt.Graphics;

Classes filles

  • Point (attributs int x,y), accesseurs/setter, 2 constructeurs (resp. 2 et 3 arguments, couleurs à spécifier ou noire par défaut),
    • Méthode de dessin suivante:
 public void draw(Graphics g) {
        // une ligne à ajouter ici pour dessiner de la bonne couleur
        ...

        g.drawLine(x, y, x, y);
 }
  • Classe Polygone basée sur une ArrayList de Point.
    • attribut boolean closed pour savoir si le Polygone est fermé (ie il s'agit vraiment d'un polygone) ou pas (ie, il s'agit d'une ligne brisée)
    • Constructeur à deux arguments (couleur + fermeture)
    • A vous de réfléchir à la position de l'ArrayList (héritage ou attribut -surtout pas les deux!-)
    • méthode add (pour ajouter des points)
    • méthode draw reliant les points avec drawline
  • D'autres classes filles pourront être envisagées plus tard (Carre, Cercle, Ellipse...).

Groupe de Figure (intro au Design Pattern Composite)

Organisation générale

1) DP Composite: un GroupeDeFigure EST UNE Figure. La classe GroupeDeFigure a un attribut ArrayList de Figure.

2) Cette classe aura aussi une méthode makeImage pour sauver une image correspondant à un ensemble de Figure

Il y a besoin des imports suivants:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
  • attribut int dim1, dim2 pour le carré englobant du groupe (ces coordonnées serviront à déterminer la taille de l'image associée au Groupe). Ces attributs sont initialisés à l'aide des arguments du constructeur
  • méthode add pour composer l'objet
  • méthode Figure getFigure(int id) retournant la Figure id si elle est dans le groupe et null sinon
  • méthode public BufferedImage makeImage() créant une BufferedImage et dessinant toutes les Figure du groupe dedans.
BufferedImage im = new BufferedImage(larg, haut, BufferedImage.TYPE_INT_ARGB);
Graphics g = im.getGraphics();
for(Figure f:figs)
  ...

Héritage de Figure

 GroupeDeFigure hérite de Figure.

1) Cela permet de pour pouvoir créer des structures d'arbres (un groupe est composé de plusieurs groupes... Figure complexe!)

2) Cela ajoute des contraintes de développement (move, draw...)

Programme de test

  1. Générer quelques figures,
  2. mettre les figures dans un GroupeDeFigure
  3. Générer et sauver l'image associée
  4. Vérifier le résultat

Quelques lignes du main:

import java.io.*;
import javax.imageio.*;

...

int dim1=400,dim2=400;
GroupeFigures dessin = new GroupeFigures(dim1, dim2);

... // création + ajout des figures dans le groupe

BufferedImage im = dessin.makeImage();

String filename = "test.png";
try {
     ImageIO.write(im, "png", new File(filename));
} catch (IOException e) {
     e.printStackTrace();
}

Note : Comme on ne fait rien de particulier avec l'exception, on peut aussi la déclarer dans la signature du main et ne pas faire le try...catch.

Exemple d'image issue de la création de 20 polygones rectangles de tailles variables:

Sauvegarde/Chargement de figure

Dans le cadre du développement d'un logiciel de dessin, il faut évidemment être capable de sauvegarder et charger un espace de travail...

Solution 1 [A FAIRE A LA MAISON]

A l'aide du cours et en choisissant vous même le format de fichier que vous souhaitez, essayez de:

  1. Créer un groupe de figure
  2. Le sauver dans un fichier en définissant vous-même un format raisonnable
  3. Dans un second main (indépendant), Charger ce fichier et sauver l'image associée pour vérifier le bon fonctionnement du système

Solution 2 : la version élégante avec Serialisation

L'interface Serializable permet de sauver/charger un objet automatiquement.

Question: quel objet doit être Serializable pour que la propriété de sauvegarde s'applique immédiatement à toutes les Figure?

Ajouter la commande:

import java.io.Serializable;

public class XXX implements Serializable{
...
}

Procédures standard de sauvegarde/chargement:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

...
// sauvegarde:
FileOutputStream fos = new FileOutputStream ("monFichier.dat");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(dessin); // n'importe quel objet Serializable
fos.close();


// chargement:
FileInputStream fin = new FileInputStream("monFichier.dat");
ObjectInputStream oos = new ObjectInputStream(fin);
dessin = ( GroupeFigures ) oos.readObject(); // cast délicat (il faut être sûr de ce qu'il y a dans le fichier)
// + possibilité de lever une ClassNotFoundException
fin.close ();

Proposition de canevas de test

A l'aide d'un if, distinguons deux cas au début du programme:

1) Création d'une figure + sauvegarde dans un fichier

2) Chargement de la figure à partir du fichier

Ensuite, dans les deux cas, on fabrique une image et on la sauvegarde respectivement dans solution1.png ou solution2.png. On vérifie que les images sont bien identiques.

Introduction aux interfaces + petite animation

Solution gif animé

Il faut d'abord récupérer la classe GifSequenceWriter, qui permet d'ajouter successivement des images dans un flux.

Le canevas de code ci-dessous illustre le fonctionnement de la classe (sans tenir compte des exceptions):

import javax.imageio.stream.FileImageOutputStream;
import javax.imageio.stream.ImageOutputStream;
...

ImageOutputStream output;
output = new FileImageOutputStream(new File("mongif.gif"));
// create a gif sequence with the type of the first image, 1 second
// between frames, which loops continuously
GifSequenceWriter writer = new GifSequenceWriter(output, BufferedImage.TYPE_4BYTE_ABGR, 1, false);

// Boucle de mouvement pour générer des images différentes
for(int iter=0; iter<nIter; iter++){
     // determination d'un mouvement à appliquer sur toutes les figures
     ...
     for(Figure f:dessin.getFigs()){
          f.move(...);
     }
     BufferedImage im = dessin.makeImage();
     writer.writeToSequence(im); // ajout d'une frame
}

writer.close();
output.close();
 

Solution 'à la main'

De votre coté, il s'agit d'une simple modification du main pour faire les opérations suivantes:

  1. Générer le groupe de figure (comme précédemment)
  2. Entrer dans une boucle
  3. Faire appel à la méthode move avec des paramètres aléatoires
    1. soit directement sur le groupe (mouvement uniforme)
    2. soit sur les figures qui composent le groupes (déformation sur le groupe)

Le problème est: comment afficher cette animation???

Réponse: en sauvant régulièrement les images et en introduisant une temporisation. C'est un autre programme qui va faire l'affichage en relisant périodiquement l'image sur le disque.

...
// Squelette du programme
        int nIter = 300;
        int amplitude = 6;

        for(int iter=0; iter<nIter; iter++){
            // appel à move

            BufferedImage im = dessin.makeImage();

            String filename = "test.png";
            try { // sauvegarde de l'image
                ImageIO.write(im, "png", new File(filename));
            } catch (IOException e) {
                e.printStackTrace();
            }
            try { // temporisation
                Thread.sleep(500); // 500 ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
...

Le programme d'affichage est disponible ici, vous le sauvegardez dans votre répertoire de code et il se lance avec la commande suivante:

  $ java -jar dessin.jar [nomdelimage]

Pour information, ce programme est constitué des deux fichiers suivants:

Vous pouvez aussi faire un gif animé en utilisant des outils en ligne comme: lien

Javadoc

Aller chercher dans la javadoc les informations nécessaires pour jouer sur l'épaisseur et la nature des traits.