Paŭ*_*ann 12 java awt java-2d affinetransform stroke
当使用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);
}});
}
}
Run Code Online (Sandbox Code Playgroud)
我正在使用此坐标转换以避免必须手动将我的应用程序模型坐标(在此示例中为(2,1,2,4))转换为屏幕(或组件)像素坐标,但我不希望此笔画失真.换句话说,我希望所有线都具有相同的宽度,与当前的x和y尺度因子无关.
我知道是什么产生了这种效果(Stroke对象创建了一个在用户坐标中绘制的矩形的描边形状,然后被转换为屏幕坐标),但我不确定如何解决这个问题.
事实证明我的问题并不是那么可怕,而且我在问题中给出的两个想法实际上是同一个想法.这是一个通过转换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;
}
}
Run Code Online (Sandbox Code Playgroud)
我使用它的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));
}
Run Code Online (Sandbox Code Playgroud)
然后我的窗口看起来像这样:
我对此非常满意,但如果有人有更多想法,请随时回答.
注意:这g.getTransform()
是返回g相对于设备空间的完全转换,而不仅仅是在应用之后应用的转换.因此,如果有人在将图形提供给我的组件之前进行了一些缩放,那么仍然会使用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));
}
Run Code Online (Sandbox Code Playgroud)
在Swing中,通常你的图形paintComponent
只被翻译(所以(0,0)是你组件的左上角),没有缩放,所以没有区别.
有一个比原始TransformedStroke
答案更简单、更少“hacky”的解决方案。
当我阅读渲染管道的工作原理时,我有了这个想法:
(来自http://docs.oracle.com/javase/7/docs/technotes/guides/2d/spec/j2d-awt.html)
- 如果
Shape
要描边,则上下文中的Stroke
属性Graphics2D
用于生成Shape
包含描边路径的新属性。Shape
的路径坐标根据Graphics2D
上下文中的transform属性从用户空间转换到设备空间。- 所述
Shape
的路径是使用在夹子夹住属性Graphics2D
上下文。- 剩余的
Shape
,如果有,使用上下文中的Paint
和Composite
属性填充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
对象。然而,这绝不是一个黑客,因为我没有滥用现有的功能,而是使用 API 功能正是它的用途。我只是使用 API 中更明确的部分,而不是 API 的“快捷方式”/“便利”方法(g.scale()
等)。
在性能方面,此解决方案只会更高效。现在实际上跳过了一个步骤。在原来的解决方案中,TransformedStroke
变换形状两次,描边一次。此解决方案显式转换形状,*当前* 笔划对形状进行一次描边。