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:

example screenshot

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?
Était-ce utile?

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:

screenshot of undistorted stroke

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é, le Stroke attribut dans le Graphics2D le contexte est utilisé pour générer un nouveau Shape qui englobe le chemin caressé.
  • Les coordonnées du ShapeLe chemin de la direction est transformé de l'espace utilisateur en espace de l'appareil en fonction de l'attribut de transformation dans le Graphics2D le contexte.
  • La Shapele chemin de Graphics2D le contexte.
  • Le reste Shape, le cas échéant, est rempli en utilisant le Paint et Composite attributs dans le Graphics2D 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...

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top