AffineTransform without transforming Stroke?

前端 未结 3 1627
-上瘾入骨i
-上瘾入骨i 2021-02-19 15:20

When using the Graphics2D scale() function with two different parameters (scaling by different ratios in x- and y-direction), everything drawn later on this Graphic

相关标签:
3条回答
  • 2021-02-19 15:23

    Have you just tried to make the int x and int y on the application bigger like int x = 500 int y = 900??? Also my suggestion is that with out rewritten the whole code is to implement where the recs are thicker when the app is closer together more like doubling the rectangle on the top and the bottom but when the app is extended the recs on the top and bottom go back to normal...

    0 讨论(0)
  • 2021-02-19 15:28

    There is a simpler and less 'hacky' solution than the original TransformedStroke answer.

    I got the idea when I read how the rendering pipeline works:

    (from http://docs.oracle.com/javase/7/docs/technotes/guides/2d/spec/j2d-awt.html)

    • If the Shape is to be stroked, the Stroke attribute in the Graphics2D context is used to generate a new Shape that encompasses the stroked path.
    • The coordinates of the Shape’s path are transformed from user space into device space according to the transform attribute in the Graphics2D context.
    • The Shape’s path is clipped using the clip attribute in the Graphics2D context.
    • The remaining Shape, if any, is filled using the Paint and Composite attributes in the Graphics2D context.

    What you, and I, ideally seek is a way to swap the first two steps.

    If you look closely at the second step, TransformedStroke already contains part of the solution.

    Shape sTrans = transform.createTransformedShape(s);

    solution

    In stead of:

    g.scale(...), g.transform(...), whatever,
    g.draw(new Rectangle( 1, 2, 2, 4));

    Or, using TransformedStroke:

    g.setStroke(new TransformedStroke(new BasicStroke(2f), g.getTransform());
    g.draw(new Rectangle( 1, 2, 2, 4));

    I propose you do:

    transform =whatever,
    g.draw(transform.createTransformedShape(new Rectangle( 1, 2, 2, 4));

    Don't transform g anymore. Ever. Transform the shapes instead, using a transform that you make and modify yourself.

    discussion

    TransformedStroke feels more like a 'hack' than a way the authors of Stroke meant the interface to be used. It also requires an extra class.

    This solution keeps a separate Transform around and modifies the Shape instead of transforming the Graphics object. This is however in no way a hack, because I'm not abusing existing functionality but using API functionality exactly how it's meant to be used. I'm just using the more explicit parts of the API instead of the 'shortcut'/'convenience' methods of the API (g.scale() etc.).

    Performance-wise, this solution can only be more efficient. Effectively one step is now skipped. In the original solution, TransformedStroke transforms the shape twice and strokes the shape once. This solution transforms the shape explicitly and the *current* stroke strokes the shape once.

    0 讨论(0)
  • 2021-02-19 15:29

    Turns out my question was not so horrible difficult, and that my two ideas given in the question are actually the same idea. Here is a TransformedStroke class which implements a distorted Stroke by transforming the 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;
        }
    
    }
    

    My paint-method using it then looks like this:

    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));
    }
    

    Then my window looks like this:

    screenshot of undistorted stroke

    I'm quite content with this, but if someone has more ideas, feel free to answer nevertheless.


    Attention: This g.getTransform() is returning the complete transformation of g relative to the device space, not only the transformation applied after the .create(). So, if someone did some scaling before giving the Graphics to my component, this would still draw with a 2-device-pixel width stroke, not 2 pixels of the grapics given to my method. If this would be a problem, use it like this:

    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));
    }
    

    In Swing normally your Graphics given to the paintComponent is only translated (so (0,0) is the upper left corner of your component), not scaled, so there is no difference.

    0 讨论(0)
提交回复
热议问题