Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Conversion monde réel / Java2D

5 réponses
Avatar
ToOmS
Bonjour =E0 tous,

Pour un projet graphique, j'ai cr=E9=E9 une classe utilitaire permettant
la conversion de coordonn=E9es 2D r=E9elles (double x, double y) vers les
coordonn=E9es de trac=E9 AWT (int X, int Y). Le probl=E8me =E9tant que l'ax=
e X
se trouve en AWT en haut du composant, tandis qu'en g=E9n=E9ral on pense
les coordonn=E9es avec un axe (X=3D0) "en bas de la page", comme =E0
l'=E9cole :

/**
* Retourne la position X sur l'=E9cran d'un point d'abscisse r=E9elle x.
*/
public int toXcoord(double x) {
return (int) Math.rint(x / uniteX) - X0;
}
/**
* Retourne la position Y sur l'=E9cran d'un point d'abscisse r=E9elle y.
*/
public int toYcoord(double y) {
return YMAX - (int) Math.rint(y / uniteY) - Y0;
// inversion de l'axe des ordonn=E9es (origine =E9cran en haut =E0
gauche)
}

Maintenant, je passe mon logiciel sous Java2D (Graphics -> Graphics2D)
et j'aimerais bien utiliser une ou plusieurs transformations affines
pour atteindre le m=EAme r=E9sultat... sans avoir =E0 convertir mes
coordonn=E9es r=E9elles =E0 chaque fois que je veux tracer qq chose ; du
genre

public void set(Graphics2D g) {
AffineTransform tx =3D new AffineTransform();
tx.scale(1.0d / uniteX, -1.0d / uniteY);
tx.translate(-X0, -Y0);
g.setTransform(tx);
}
public void reset(Graphics2D g) {
AffineTransform tx =3D new AffineTransform();
tx.scale(1.0d, 1.0d);
tx.translate(0.0d, 0.0d);
g.setTransform(tx);
}

Quelqu'un peut-il me donner une id=E9e ou me renvoyer vers un bon
exemple, SVP ?

5 réponses

Avatar
ToOmS
Après avoir simplifié le pb (même déformation pour les deux axes, p as
de décalage de l'origine) :

/**
* Retourne la position X sur l'écran d'un point d'abscisse réelle x.
*/
public int toXcoord(double x) {
return (int) Math.rint(x / unite);
}
/**
* Retourne la position Y sur l'écran d'un point d'abscisse réelle y.
*/
public int toYcoord(double y) {
return YMAX - (int) Math.rint(y / unite); // inversion de l'axe des
ordonnées (origine écran en haut à gauche)
}

/** configure le contexte graphique pour qu'il convertisse directement
les coordonnées réelles */
public void set(Graphics2D g) {
g.getTransform().setToTranslation(0.0d, YMAX); // décalage écran
g.getTransform().scale(1.0d / unite, -1.0d / unite); // inversion Y
et zoom
}
/** remet le contexte graphique dans son état par défaut (pas de
transformations) */
public void reset(Graphics2D g) {
g.getTransform().setToIdentity();
}
Avatar
ToOmS
Je m'apperçois, avec stupéfaction, que g.getTransform() renvoie une
COPIE de la transformation courante. Comme il est indiqué dans la doc
de ne (je cite) JAMAIS faire de g.setTransform()... Je me demande bien
comment on peut faire.
Ah, si on a translate/scale/rotate sur Graphics2D, exit donc le
g.getTransform().
Avatar
Xavier Tarrago
Il y a deux choses.
- 1 : setTransform est problématique car il peut y avoir une
transformation courante et elle est ignorée. Il est préférable d'utiliser
transform qui applique une transformation supplémentaire, ce qui est en
général le comportement voulu.

- 2 : Le même Graphics2D est utilisé pour tous les dessins des différents
objets et il ne faut pas le modifier dans la méthode paint ou
paintComponent. Il faut rendre le Graphics2D dans l'état ou on l'a trouvé.
Donc soit on mémorise ce qu'on change pour remettre les originaux, soit on
copie.

private AffineTransform tr;

public void defineUserCoords() {
tr = new AffineTransform(...);
// set up your affine transform.
}

// Solution 1 - restaurer
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D)g;
AffineTransform orig = g2d.getTransform();
g2d.transform(tr);
// Draw in user coords
g2d.setTransform(orig);
}

// Solution 2 - copier le Graphics2D
protected void paintComponent(Graphics g) {
Graphics2D copie = (Graphics2D)g.create();
try{
copie.transform(tr);
// Draw in user coords
} finally {
copie.release();
}
}

Perso, je préfère la solution 2 car au fur et à mesure qu'on change des
paramètres de rendu (couleur, type de trait, ...) la solution 1 devient de
plus en plus lourde. Par ailleurs, je n'ai jamais constaté que le cout en
perf de 1 soit pénalisant.

"ToOmS" a écrit dans le message de news:

Je m'apperçois, avec stupéfaction, que g.getTransform() renvoie une
COPIE de la transformation courante. Comme il est indiqué dans la doc
de ne (je cite) JAMAIS faire de g.setTransform()... Je me demande bien
comment on peut faire.
Ah, si on a translate/scale/rotate sur Graphics2D, exit donc le
g.getTransform().
Avatar
ToOmS
Merci, dans les deux cas, on effectue une copie. Tout dépend de
laquelle sera la plus coûteuse (transform ou graphics).
Je suppose que selon l'application, les préférences varieront.
Dans mon cas, seule la transformation de coordonnées est appliquée,
les objets se transforment eux-même avant d'être tracés. Il me para ît
donc plus économique de garder une copie du transform au début de la
"transaction" et de tout remettre en place avant de rendre la main.
Avatar
ToOmS
Voici le source de la classe finale (a priori testée), pour ceux qui
ont suivi ce fil :

<code>
/**
* Définit un repère orthogonal 2D "abstrait" que l'on peut
représenter sur un
* Graphics2D (fenêtre d'affichage).
* Ce repère est du type "mathématique", l'origine est "en bas à
gauche",
* l'axe X positif va vers la droite, l'axe Y positif va vers le haut.
*
* Le point de coordonnées réelles (x, y) a pour position dans la
fenêtre d'affichage
* (X, Y) où les entiers X varient de 0 à XMAX et Y de 0 à YMAX (vers
le bas !).
*
* @author Thomas Escolan
* @version 20070710
*/
@XStreamAlias("repere_geometrique")
public final class RepereGeo implements Serializable {
/**
* Variable activant l'affichage des noms des figures (cf.
ObjetGeo.draw()).
*/
public static final boolean DEBUG = false;

/**
* Abscisse et Ordonnée maximale du repère dans la fenêtre
d'affichage.
*/
@XStreamAsAttribute
private int XMAX, YMAX;
/**
* Nombre d'unités (cm, mètre, etc.) dans un pixel pour l'axe des
abscisses et celui des ordonnées.
* Exemple : 1 pxl = N cm.
* unitex = unitey pour un repère orthonormal.
*/
@XStreamAsAttribute
private float scale;

/
* //////////////////////////////////////////////////////////////////////////
*/

/** Construit le repère orthogonal */
public RepereGeo(int XMAX, int YMAX, float unite) {
this.setPxlDimensions(XMAX, YMAX);
this.setScale(unite);
}

/
* //////////////////////////////////////////////////////////////////////////
*/

/**
* Renvoie la largeur (en pixels) du repère orthonogonal ;
* C'est-à-dire la partie "visible" de l'axe des abscisses.
*/
public final int getPxlWidth() {
return XMAX;
}
/**
* Renvoie longueur (en pixels) du repère orthonogonal ;
* C'est-à-dire la partie "visible" de l'axe des ordonnées.
*/
public final int getPxlHeight() {
return YMAX;
}

/** conversion de la largeur de l'écran en coordonnées réelles */
public final int getWidth() {
return (int) this.fromXcoord(XMAX);
}
/** conversion de la hauteur de l'écran en coordonnées réelles */
public final int getHeight() {
return (int) this.fromYcoord(-YMAX);
}

/**
* Renvoie les dimensions (en pixels) du repère orthonogonal.
*/
public final Dimension getPxlDimensions() {
return new Dimension(XMAX, YMAX);
}
/**
* Mise à jour des dimensions (en pixels) du repère orthogonal.
*/
public final void setPxlDimensions(int XMAX, int YMAX) {
this.XMAX = XMAX;
this.YMAX = YMAX;
}

/
* //////////////////////////////////////////////////////////////////////////
*/

public final float getScale() {
return scale;
}
/**
* Mise à jour des unités (réelles) du repère orthogonal.
* unitex = unitey pour un repère orthonormal.
*/
public final void setScale(float unite) {
this.scale = unite;
}

/
* //////////////////////////////////////////////////////////////////////////
*/

/**
* configure un contexte graphique pour qu'il convertisse directement
les coordonnées réelles
* @see http://www.unix.org.ua/orelly/java-ent/jfc/ch04_03.htm
* @param g contexte graphique à transformer pour convertir les
coordonnées réelles
* @return la transformation d'origine du paramètre g, pour
restauration
*/
public AffineTransform set(Graphics2D g) {
final AffineTransform old = g.getTransform(); // copie
g.translate(0.0d, YMAX); // décalage écran
g.scale(1.0d / scale, -1.0d / scale); // inversion Y et zoom
return old;
}
/**
* restaure le contexte graphique dans un état (a priori) antérieur
* @param g contexte graphique transformé pour convertir les
coordonnées réelles
* @param old la transformation d'origine, typiquement retournée par
#set
* @deprecated pas vraiment fortiche ; utiliser Graphics#create ?
*/
public void reset(Graphics2D g, AffineTransform old) {
g.setTransform(old);
}

/* //////////////////// */

/**
* Retourne l'abscisse réelle x d'un point placé en position (X, Y)
sur l'écran.
*/
public double fromXcoord(int X) {
return X * scale;
}
/**
* Retourne l'ordonnée réelle y d'un point placé en position (X, Y)
sur l'écran.
*/
public double fromYcoord(int Y) {
return (double) (YMAX - Y) * scale; // inversion de l'axe des
ordonnées (origine écran en haut à gauche)
}

/*
AFFICHAGE ////////////////////////////////////////////////////////////////
*/

/**
* Trace les axes du repère orthogonal.
* @param g contexte graphique
* @param step le pas souhaité pour la graduation, en pixels
* @param unity l'unité retenue pour l'échelle finale (step * scale)
* @param showScale pour afficher l'échelle courante prêt de l'origine
du repère
* NB : ne fonctionne que si aucune transformation (cf. #set) n'a été
appliquée.
*/
public void draw(Graphics2D g, final int step, String unity, boolean
showScale) {
final int block = 2;

final FontMetrics fm = g.getFontMetrics();
// axe des abscisses
g.draw(new Line2D.Float(0.0f, YMAX, XMAX, YMAX));
int i = 0;
for (float UX = step; UX <= XMAX; UX += step) {
if (i++ > 0 && i % 5 == 0) {
String text = String.valueOf(UX * this.scale) + unity;
// texte "posé" sur le taquet
Rectangle2D bnd = fm.getStringBounds(text, g);
int stringHeight = (int) bnd.getHeight() - fm.getAscent();
g.drawString(text, (int) UX, YMAX - (block * 2) - stringHeight);

g.setColor(Color.RED);
} else {
g.setColor(Color.BLACK);
}
g.draw(new Line2D.Float(UX, YMAX + block, UX, YMAX - block)); //
graduations
}

// axe des ordonnées, on inverse les graduations par rapport aux axes
de g
g.draw(new Line2D.Float(0.0f, 0.0f, 0.0f, YMAX));
int j = 0;
for (float UY = YMAX - step; UY >= 0; UY -= step) {
if (j++ > 0 && j % 5 == 0) {
String text = String.valueOf((YMAX - UY) * this.scale) + unity;
// texte "posé" sur le taquet
Rectangle2D bnd = fm.getStringBounds(text, g);
int stringHeight = (int) bnd.getHeight() / 2 - fm.getAscent();
g.drawString(text, block * 3, (int) UY - stringHeight);

g.setColor(Color.RED);
} else {
g.setColor(Color.BLACK);
}
g.draw(new Line2D.Float(-block, UY, block, UY)); // graduations
}

// échelle (en bas à gauche)
if (showScale) {
final int yMax = YMAX - step;
// unité
g.draw(new Line2D.Float(step, yMax, step * 2, yMax));
// taquets
g.draw(new Line2D.Float(step, yMax, step, yMax -block));
g.draw(new Line2D.Float(step * 2, yMax, step * 2, yMax -block));
// légende
g.drawString(String.valueOf(step * this.scale) + unity, step, yMax
- block);
}
}

/**
* Trace un cadre délimitant la fenêtre d'affichage du repère
orthogonal.
* NB : ne fonctionne que si aucune transformation (cf. #set) n'a été
appliquée.
*/
public void drawFrame(Graphics2D g) {
g.setColor(Color.BLACK);
g.draw(new Rectangle2D.Float(0.0f, 0.0f, XMAX, YMAX));
}
}
</code>