AffineTransform没有改变中风?

时间:2011-02-18 20:17:11

标签: java awt java-2d affinetransform stroke

当使用带有两个不同参数的Graphics2D scale()函数(在x和y方向上按不同比例缩放)时,稍后在此Graphics2D对象上绘制的所有内容也会缩放。这具有奇怪的效果,即在一个方向上绘制的线比在另一个方向上绘制的线更粗。以下程序产生此效果,它显示以下窗口:

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

    }

}

我正在使用此坐标转换以避免必须手动将我的应用程序模型坐标(在此示例中为(2,1,2,4))转换为屏幕(或组件)像素坐标,但我不想要这个中风失真。换句话说,我希望所有行都具有相同的宽度,与当前的x和y比例因子无关。

我知道是什么产生了这种效果(Stroke对象创建了一个在用户坐标中绘制的矩形的描边形状,然后被转换为屏幕坐标),但我不确定如何解决这个问题。

  • 我应该创建一个新的Stroke实现,它在X和Y方向上以不同的方式描绘Shapes(从而在这里撤消失真)? (或者是否有人已经知道这样的实现?)
  • 我应该将形状转换为屏幕坐标并在那里划线吗?
  • 任何其他(更好)的想法?

3 个答案:

答案 0 :(得分:8)

原来我的问题并不那么可怕,而且我在问题中给出的两个想法实际上是同一个想法。这是一个TransformedStroke类,它通过转换Stroke来实现扭曲的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;
    }

}

我使用它的paint-method看起来像这样:

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

然后我的窗口看起来像这样:

screenshot of undistorted stroke

我对此非常满意,但如果有人有更多想法,请随时回答。


注意:g.getTransform()正在返回 g 相对于设备空间的完整转换,而不仅仅是应用了转换在.create()之后。因此,如果有人在将图形提供给我的组件之前进行了一些缩放,那么仍然会使用2个设备像素的宽度笔划绘制,而不是为我的方法提供2个像素的图形。如果这是一个问题,请使用它:

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

在Swing中,通常只会翻译给paintComponent的图形(因此(0,0)是组件的左上角),而不是缩放,所以没有区别。

答案 1 :(得分:3)

与原始的TransformedStroke答案相比,有一种更简单且更少'hacky'的解决方案。

当我读到渲染管道如何工作时,我明白了这个想法:

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

  
      
  • 如果要追踪ShapeStroke上下文中的Graphics2D属性将用于生成包含描边路径的新Shape
  •   
  • 根据Shape上下文中的transform属性,Graphics2D路径的坐标将从用户空间转换为设备空间。
  •   
  • 使用Shape上下文中的剪辑属性剪切Graphics2D的路径。
  •   
  • 剩余的Shape(如果有)使用Paint上下文中的CompositeGraphics2D属性填充。
  •   

你和我理想寻求的是交换前两步的方法。

如果仔细观察第二步,TransformedStroke已包含部分解决方案。

  

Shape sTrans = transform.createTransformedShape(s);

溶液

代替:

g.scale( ... )g.transform( ... ),无论如何,
g.draw(new Rectangle( 1, 2, 2, 4));

或者,使用TransformedStroke

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

我建议你这样做:

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

不要再转换g了。永远。转换形状,使用您自己制作的变换并自行修改。

讨论

TransformedStroke感觉更像是'黑客',而不是Stroke的作者意味着使用界面的方式。它还需要额外的课程。

此解决方案保持单独的Transform并修改Shape,而不是转换Graphics对象。然而,这绝不是一个黑客攻击,因为我并没有滥用现有的功能,而是使用API​​功能的确切方式。我只是使用API​​的更明确的部分而不是API的{快捷方式'/'方便'方法(g.scale()等。)。

在性能方面,此解决方案只能更高效。现在有效地跳过了一步。在原始解决方案中,TransformedStroke将形状转换两次并将形状描绘一次。此解决方案显式转换形状,* current *笔划一次描边形状。

答案 2 :(得分:0)

您是否曾尝试使应用程序中的int x和int y更大,如int x = 500 int y = 900 ???另外我的建议是,在没有重写的情况下,整个代码是实现当应用程序更靠近在一起时recs更厚的地方更像是在顶部和底部加倍矩形但是当应用程序被扩展时顶部和底部的recs去恢复正常......