AffinetRansform sans transformer un AVC?
-
15-11-2019 - |
Question
Lorsque vous utilisez les graphiques2d scale()
Fonction avec deux paramètres différents (mise à l'échelle par différents rapports dans la direction x et y), tout dessiné plus tard sur cet objet graphique2d est également mis à l'échelle. Cela a l'effet étrange que les lignes tracées dans une direction sont plus épaisses que celles dans une autre direction. Le programme suivant produit cet effet, il montre cette fenêtre:
public class StrokeExample extends JPanel {
public void paintComponent(Graphics context) {
super.paintComponent(context);
Graphics2D g = (Graphics2D)context.create();
g.setStroke(new BasicStroke(0.2f));
int height = getHeight();
int width = getWidth();
g.scale(width/7.0, height/4.0);
g.setColor(Color.BLACK);
g.draw(new Rectangle( 2, 1, 4, 2));
}
public static void main(String[] params) {
EventQueue.invokeLater(new Runnable(){public void run() {
StrokeExample example = new StrokeExample();
JFrame f = new JFrame("StrokeExample");
f.setSize(100, 300);
f.getContentPane().setLayout(new BorderLayout());
f.getContentPane().add(example);
f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
f.setVisible(true);
}});
}
}
J'utilise cette transformation de coordonnées pour éviter d'avoir à transformer manuellement les coordonnées de mon modèle d'application (les (2,1, 2,4) dans cet exemple) pour dépister les coordonnées de pixels (ou composant), mais je ne veux pas de cette distorsion de trait . Autrement dit, Je veux avoir toutes les lignes de la même largeur, indépendamment des facteurs actuels de x et de l'échelle.
Je sais ce qui produit cet effet (l'objet TRAD crée une forme caressée du rectangle à peindre dans les coordonnées de l'utilisateur, qui sont ensuite traduites en coordonnées d'écran), mais je ne sais pas comment résoudre ce problème.
- Dois-je créer une nouvelle implémentation de l'AVC qui caresse forme différemment dans la direction x et y (annulant ainsi la distorsion ici)? (Ou est-ce que quelqu'un connaît déjà une telle implémentation?)
- Dois-je transformer mes formes en coordonnées d'écran et caresser là-bas?
- D'autres (meilleures) idées?
La solution
Il s'avère que ma question n'était pas si horrible difficile, et que mes deux idées données dans la question sont en fait la même idée. Voici une TransformedStroke
classe qui implémente un déformé Stroke
en transformant le Shape
.
import java.awt.*;
import java.awt.geom.*;
/**
* A implementation of {@link Stroke} which transforms another Stroke
* with an {@link AffineTransform} before stroking with it.
*
* This class is immutable as long as the underlying stroke is
* immutable.
*/
public class TransformedStroke
implements Stroke
{
/**
* To make this serializable without problems.
*/
private static final long serialVersionUID = 1;
/**
* the AffineTransform used to transform the shape before stroking.
*/
private AffineTransform transform;
/**
* The inverse of {@link #transform}, used to transform
* back after stroking.
*/
private AffineTransform inverse;
/**
* Our base stroke.
*/
private Stroke stroke;
/**
* Creates a TransformedStroke based on another Stroke
* and an AffineTransform.
*/
public TransformedStroke(Stroke base, AffineTransform at)
throws NoninvertibleTransformException
{
this.transform = new AffineTransform(at);
this.inverse = transform.createInverse();
this.stroke = base;
}
/**
* Strokes the given Shape with this stroke, creating an outline.
*
* This outline is distorted by our AffineTransform relative to the
* outline which would be given by the base stroke, but only in terms
* of scaling (i.e. thickness of the lines), as translation and rotation
* are undone after the stroking.
*/
public Shape createStrokedShape(Shape s) {
Shape sTrans = transform.createTransformedShape(s);
Shape sTransStroked = stroke.createStrokedShape(sTrans);
Shape sStroked = inverse.createTransformedShape(sTransStroked);
return sStroked;
}
}
Ma méthode de peinture qui l'utilise ressemble alors à ceci:
public void paintComponent(Graphics context) {
super.paintComponent(context);
Graphics2D g = (Graphics2D)context.create();
int height = getHeight();
int width = getWidth();
g.scale(width/4.0, height/7.0);
try {
g.setStroke(new TransformedStroke(new BasicStroke(2f),
g.getTransform()));
}
catch(NoninvertibleTransformException ex) {
// should not occur if width and height > 0
ex.printStackTrace();
}
g.setColor(Color.BLACK);
g.draw(new Rectangle( 1, 2, 2, 4));
}
Puis ma fenêtre ressemble à ceci:
Je suis très content de cela, mais si quelqu'un a plus d'idées, n'hésitez pas à répondre néanmoins.
Attention: Cette g.getTransform()
retourne le Achevée transformation de g par rapport à l'espace de l'appareil, pas seulement la transformation appliquée après .create()
. Donc, si quelqu'un faisait une mise à l'échelle avant de donner les graphiques à mon composant, cela dessinerait toujours avec une trait de largeur à 2 pixels à 2-appareils, pas 2 pixels des grapicaux donnés à ma méthode. Si ce serait un problème, utilisez-le comme ceci:
public void paintComponent(Graphics context) {
super.paintComponent(context);
Graphics2D g = (Graphics2D)context.create();
AffineTransform trans = new AffineTransform();
int height = getHeight();
int width = getWidth();
trans.scale(width/4.0, height/7.0);
g.transform(trans);
try {
g.setStroke(new TransformedStroke(new BasicStroke(2f),
trans));
}
catch(NoninvertibleTransformException ex) {
// should not occur if width and height > 0
ex.printStackTrace();
}
g.setColor(Color.BLACK);
g.draw(new Rectangle( 1, 2, 2, 4));
}
En swing normalement vos graphiques donnés au paintComponent
est uniquement traduit (donc (0,0) est le coin supérieur gauche de votre composant), non à l'échelle, il n'y a donc pas de différence.
Autres conseils
Il existe une solution plus simple et moins «hacky» que l'original TransformedStroke
réponse.
J'ai eu l'idée quand j'ai lu comment fonctionne le pipeline de rendu:
(de http://docs.oracle.com/javase/7/docs/technotes/guides/2d/spec/j2d-awt.html)
- Si la
Shape
doit être caressé, leStroke
attribut dans leGraphics2D
le contexte est utilisé pour générer un nouveauShape
qui englobe le chemin caressé.- Les coordonnées du
Shape
Le chemin de la direction est transformé de l'espace utilisateur en espace de l'appareil en fonction de l'attribut de transformation dans leGraphics2D
le contexte.- La
Shape
le chemin deGraphics2D
le contexte.- Le reste
Shape
, le cas échéant, est rempli en utilisant lePaint
etComposite
attributs dans leGraphics2D
le contexte.
Ce que vous et moi, et moi, c'est idéalement, c'est un moyen d'échanger les deux premières étapes.
Si vous regardez attentivement la deuxième étape, TransformedStroke
Contient déjà une partie de la solution.
Shape sTrans = transform.createTransformedShape(s);
la solution
À la place de:
g.scale(
...)
, g.transform(
...)
, peu importe,
g.draw(new Rectangle( 1, 2, 2, 4));
Ou, en utilisant TransformedStroke
:
g.setStroke(new TransformedStroke(new BasicStroke(2f), g.getTransform());
g.draw(new Rectangle( 1, 2, 2, 4));
Je te propose:
transform =
peu importe,
g.draw(transform.createTransformedShape(new Rectangle( 1, 2, 2, 4));
Ne se transforme pas g
plus. Déjà. Transformez les formes à la place, en utilisant une transformation que vous faites et modifiez-vous.
discussion
TransformedStroke
se sent plus comme un «piratage» qu'une façon dont les auteurs Stroke
signifiait l'interface à utiliser. Cela nécessite également une classe supplémentaire.
Cette solution maintient un séparé Transform
autour et modifie le Shape
au lieu de transformer le Graphics
objet. Ce n'est cependant en aucun cas un piratage, car je n'abuse pas de la fonctionnalité existante, mais en utilisant la fonctionnalité API exactement comment elle est censée être utilisée. J'utilise simplement les parties les plus explicites de l'API au lieu des méthodes «raccourci» / «commodité» de l'API (g.scale()
etc.).
En ce qui concerne les performances, cette solution ne peut être plus efficace. Effectivement, une étape est maintenant sautée. Dans la solution originale, TransformedStroke
Transforme la forme deux fois et caresse la forme une fois. Cette solution transforme la forme explicitement et le * Courant * COURVE fait caresser la forme une fois.
Vous venez d'essayer de rendre l'Int x et int y sur l'application plus grande comme int x = 500 int y = 900 ??? Ma suggestion est également qu'avec une réécriture, l'ensemble du code consiste à implémenter où les REC sont plus épais lorsque l'application est plus proche les uns des autres comme doubler le rectangle en haut et en bas, mais lorsque l'application est étendue, les recs en haut et en bas retour à la normale...