AffineTransform没有改变中风?

当使用带有两个不同参数的Graphics2D scale()函数(在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(从而在这里消除失真)? (或者是否有人已经知道这样的实现?)
  • 我应该将形状转换为屏幕坐标和笔划吗?
  • 还有其他(更好)的想法吗?

结果我的问题并不那么可怕,而且我在问题中给出的两个想法实际上是同一个想法。 这是一个TransformedStroke类,它通过转换Shape来实现扭曲的Stroke

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

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

无失真笔画的截图

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


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

 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)是组件的左上角),而不是缩放,所以没有区别。

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

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

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

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

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

如果仔细观察第二步, 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对象。 然而,这绝不是一个黑客行为,因为我并没有滥用现有的function,而是使用API​​function,而不是它的使用方式。 我只是使用API​​的更明确的部分而不是API的“快捷”/“便利”方法( g.scale()等)。

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

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