13

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

示例截图

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(从而在此处撤消失真)?(或者是否有人已经知道这样的实现?)
  • 我应该将我的形状转换为屏幕坐标并在那里描边吗?
  • 还有其他(更好的)想法吗?
4

3 回答 3

10

原来我的问题并没有那么难,而且我在问题中给出的两个想法实际上是同一个想法。这是一个通过转换TransformedStroke来实现扭曲的类。StrokeShape

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

}

我使用它的绘画方法看起来像这样:

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

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

不变形笔画截图

我对此很满意,但是如果有人有更多想法,请尽管回答。


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

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 中,您的 Graphics 通常paintComponent只被翻译(所以 (0,0) 是组件的左上角),没有缩放,所以没有区别。

于 2011-02-18T21:23:40.963 回答
5

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

当我阅读渲染管道的工作原理时,我得到了这个想法:

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

  • 如果Shape要描边,Stroke则上下文中的属性Graphics2D用于生成Shape包含描边路径的新属性。
  • 的路径坐标根据上下文Shape中的变换属性从用户空间变换到设备空间。Graphics2D
  • 的路径使用上下文Shape中的剪辑属性进行剪辑。Graphics2D
  • 剩余的Shape,如果有的话,使用上下文中的PaintandComposite属性填充。Graphics2D

您和我理想地寻求的是一种交换前两个步骤的方法。

如果仔细看第二步,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对象。然而,这绝不是一个 hack,因为我没有滥用现有的功能,而是使用 API 功能,这正是它应该被使用的方式。我只是使用 API 中更明确的部分,而不是 API 的“快捷方式”/“便利”方法(g.scale()等)。

在性能方面,这种解决方案只会更有效率。现在实际上跳过了一步。在原始解决方案中,TransformedStroke变换形状两次并描边形状一次。此解决方案显式地转换形状,并且 *current* 笔画一次描边形状。

于 2016-04-08T20:05:29.837 回答
0

您是否刚刚尝试使应用程序上的 int x 和 int y 更大,例如 int x = 500 int y = 900?另外我的建议是,在不重写整个代码的情况下,当应用程序靠得更近时,recs 更厚,就像将顶部和底部的矩形加倍但是当应用程序扩展时,顶部和底部的 recs 去恢复正常...

于 2011-03-05T00:57:00.800 回答