在 Java2D AWT Frame 中使用世界坐标
Using world coordinates in Java2D AWT Frame
我在尝试实现世界坐标到设备坐标的转换时卡住了。
基本上我想在以下世界坐标中绘制:
// --- World Coordinates
//
// (xmax,ymax)
// ┌────────────────┐
// │ │
// │ │
// │ │
// │ │
// └────────────────┘
// (xmin,ymin)
//
// (xmin,ymin) = (0, 100)
// (xmax,ymax) = (1.5, 2.5)
阅读本书(第 31 页)后,我正在尝试实现从世界坐标视口移动到设备坐标视口所需的仿射变换。
// Introduction to Computer Graphics Using Java 2D and 3D
// Frank Klawonn, Ed. Springer 2008
// Page 31
我准备了一个测试class,其中包含两个测试,第一个(刚设置int test = 1
)测试前两个变换翻转Y轴(屏幕左上角的原点)角,而不是左下角)。
测试工作正常,绘制了一个矩形和一条线来显示这一点。
但是,在切换到包含所有预期转换的测试 2 时,结果是一个空白屏幕:
// --- Affine Transform 1 and 2
//
// T(0, h) ◦ S(1,−1)
//
// --- Affine Transform 3, 4 and 5
//
// ╭ umax − umin vmax − vmin ╮
// T(umin, vmin) ◦ S | ----------- , ----------- | ◦ T(-xmin, -ymin)
// ╰ xmax − xmin ymax − ymin ╯
//
包含测试 class 的全部源代码:
package com.example.test2;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
public class Test2 extends Frame {
Graphics2D g2d;
Insets insFrame;
Dimension sizeFrame;
public Test2() {
this.setSize(660,540);
this.setUndecorated(false);
this.setVisible(true);
this.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e) {
dispose();
}
});
}
@Override
public void paint(Graphics g) {
g2d = (Graphics2D) g;
insFrame = this.getInsets();
sizeFrame = this.getSize();
//int test = 1; // Change to test 2 to test the whole transformation
int test = 2;
if ( test == 1 ) {
// AT1 & AT2 Test
this.setScale(1);
g2d.setColor(Color.ORANGE);
Line2D.Double line = new Line2D.Double(0, 0, sizeFrame.width-insFrame.left-insFrame.right-1, sizeFrame.height-insFrame.top-insFrame.bottom);
g2d.draw(line);
g2d.setColor(Color.RED);
g2d.drawRect(0, 0, sizeFrame.width-insFrame.left-insFrame.right-1, sizeFrame.height-insFrame.top-insFrame.bottom-1);
} else if (test == 2) {
// AT1, AT2, AT3, AT4 & AT5 Test
this.setScale(2);
g2d.setColor(Color.ORANGE);
Line2D.Double line = new Line2D.Double(0, 1.5, 100, 2.5);
g2d.draw(line);
g2d.setColor(Color.RED);
Rectangle2D.Double rectangle = new Rectangle2D.Double(0, 1.5, 100, 2.5);
g2d.draw(rectangle);
}
};
// Required affine transforms to move from
// World Coordinates Viewport to
// Screen Pixel Coordinates Viewport
//
// --- Reference textbook:
//
// Introduction to Computer Graphics Using Java 2D and 3D
// Frank Klawonn, Ed. Springer 2008
// Page 31
//
// --- Viewports
//
// World Coordinates Viewport (xmin,ymin) - (xmax,ymax)
// Screen Pixel Coordinates Viewport (umin, vmin) - (umax, vmax)
//
// --- World Coordinates
//
// (xmax,ymax)
// ┌────────────────┐
// │ │
// │ │
// │ │
// │ │
// └────────────────┘
// (xmin,ymin)
//
// (xmin,ymin) = (0, 100)
// (xmax,ymax) = (1.5, 2.5)
//
// --- User coordinates
//
// (umax,vmax)
// ┌────────────────┐
// │ │
// │ │
// │ │
// │ │
// └────────────────┘
// (umin,vmin)
//
// (umin,vmin) = (inset.left, heightFrame - inset.bottom)
// (umax,vmax) = (widthFrame - inset.right, inset.top)
//
// --- Affine Transform 1 and 2
//
// T(0, h) ◦ S(1,−1)
//
// --- Affine Transform 3, 4 and 5
//
// ╭ umax − umin vmax − vmin ╮
// T(umin, vmin) ◦ S | ----------- , ----------- | ◦ T(-xmin, -ymin)
// ╰ xmax − xmin ymax − ymin ╯
//
private void setScale(int test) {
// World Coordinates
// (xmin,ymin) = (0, 1.5)
// (xmax,ymax) = (100, 2.5)
Double xmin = 0.0;
Double ymin = 1.5;
Double xmax = 100.0;
Double ymax = 2.5;
// User Coordinates
// (umin,vmin) = (inset.left, heightFrame - inset.bottom)
// (umax,vmax) = (widthFrame - inset.right, inset.top)
int umin = insFrame.left;
int vmin = (int) (sizeFrame.getHeight() - insFrame.bottom);
int umax = (int) (sizeFrame.getWidth() - insFrame.right);
int vmax = insFrame.top;
if (test == 1) {
// Affine Transformation 1 and 2
// T(0, h) ◦ S(1,−1)
AffineTransform at1 = new AffineTransform();
at1.setToScale(1,-1);
AffineTransform at2 = new AffineTransform();
at2.setToTranslation(insFrame.left, sizeFrame.getHeight() - insFrame.bottom - 1);
at1.preConcatenate(at2);
g2d.transform(at1);
} else if (test == 2) {
// Affine Transformation 1 and 2
// T(0, h) ◦ S(1,−1)
AffineTransform at1 = new AffineTransform();
at1.setToScale(1,-1);
AffineTransform at2 = new AffineTransform();
at2.setToTranslation(insFrame.left, sizeFrame.getHeight() - insFrame.bottom - 1);
// Affine Transformation 3, 4 and 5
// ╭ umax − umin vmax − vmin ╮
// T(umin, vmin) ◦ S | ----------- , ----------- | ◦ T(-xmin, -ymin)
// ╰ xmax − xmin ymax − ymin ╯
AffineTransform at3 = new AffineTransform();
at3.setToTranslation(umin, vmin);
AffineTransform at4 = new AffineTransform();
at4.setToScale(1.0*(umax-umin)/(xmax-xmin), 1.0*(vmax-vmin)/(ymax-ymin));
AffineTransform at5 = new AffineTransform();
at5.setToTranslation(-xmin,-ymin);
at4.preConcatenate(at5);
at3.preConcatenate(at4);
at2.preConcatenate(at3);
at1.preConcatenate(at2);
g2d.transform(at1);
}
}
public static void main( String[] args ) {
Test2 window = new Test2();
}
}
我查看了这本书,发现 AffineTransform.preConcatenate()
的用法令人困惑。我更喜欢使用 AffineTransform.concatenate()
,因为对我来说这提供了更自然的流程。
让我们看第一个例子(变换,然后交换y方向)。
这是您的代码:
AffineTransform at1 = new AffineTransform();
at1.setToScale(1,-1);
AffineTransform at2 = new AffineTransform();
at2.setToTranslation(insFrame.left, sizeFrame.getHeight() - insFrame.bottom - 1);
at1.preConcatenate(at2);
使用concatenate
,你可以写成:
AffineTransform at1 = new AffineTransform();
at1.setToTranslation(insFrame.left, sizeFrame.getHeight() - insFrame.bottom - 1);
AffineTransform at2 = new AffineTransform();
at2.setToScale(1,-1);
at1.concatenate(at2);
或者,更短:
AffineTransform at = new AffineTransform();
at.translate(insFrame.left, sizeFrame.getHeight() - insFrame.bottom - 1);
at.scale(1,-1);
您可以看到代码中直接表示的流程"translate, then scale"。
对于第二个测试,您可以应用相同的方法:
// Affine Transformation 3, 4 and 5
// ╭ umax − umin vmax − vmin ╮
// T(umin, vmin) ◦ S | ----------- , ----------- | ◦ T(-xmin, -ymin)
// ╰ xmax − xmin ymax − ymin ╯
AffineTransform at = new AffineTransform();
at.translate(umin, vmin);
at.scale((umax-umin)/(xmax-xmin), (vmax-vmin)/(ymax-ymin));
at.translate(-xmin, -ymin);
请注意:umin, vmin
已经表示屏幕左下坐标,umax, vmax
屏幕右上坐标,因此不需要额外的平移或 y 轴翻转!
一些结束语:
- Java2D 中默认的线条宽度是一个缩放单位。选择
ymin
和 ymax
,一个缩放单位填满 window 的完整高度。为了不只得到一个填充的矩形,您应该在 g2d.draw()
调用之前使用 g2d.setStroke(new BasicStroke(0.0f));
将线宽设置为可能的最小值。
Rectangle2D.Double()
的参数是 x, y, w, h
,因此您在第二个示例中创建矩形对象的代码应该是 Rectangle2D.Double rectangle = new Rectangle2D.Double(0, 1.5, 100, 1);
(上边框的高度为 2.5不显示在屏幕上)。
我在尝试实现世界坐标到设备坐标的转换时卡住了。
基本上我想在以下世界坐标中绘制:
// --- World Coordinates
//
// (xmax,ymax)
// ┌────────────────┐
// │ │
// │ │
// │ │
// │ │
// └────────────────┘
// (xmin,ymin)
//
// (xmin,ymin) = (0, 100)
// (xmax,ymax) = (1.5, 2.5)
阅读本书(第 31 页)后,我正在尝试实现从世界坐标视口移动到设备坐标视口所需的仿射变换。
// Introduction to Computer Graphics Using Java 2D and 3D
// Frank Klawonn, Ed. Springer 2008
// Page 31
我准备了一个测试class,其中包含两个测试,第一个(刚设置int test = 1
)测试前两个变换翻转Y轴(屏幕左上角的原点)角,而不是左下角)。
测试工作正常,绘制了一个矩形和一条线来显示这一点。
但是,在切换到包含所有预期转换的测试 2 时,结果是一个空白屏幕:
// --- Affine Transform 1 and 2
//
// T(0, h) ◦ S(1,−1)
//
// --- Affine Transform 3, 4 and 5
//
// ╭ umax − umin vmax − vmin ╮
// T(umin, vmin) ◦ S | ----------- , ----------- | ◦ T(-xmin, -ymin)
// ╰ xmax − xmin ymax − ymin ╯
//
包含测试 class 的全部源代码:
package com.example.test2;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
public class Test2 extends Frame {
Graphics2D g2d;
Insets insFrame;
Dimension sizeFrame;
public Test2() {
this.setSize(660,540);
this.setUndecorated(false);
this.setVisible(true);
this.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e) {
dispose();
}
});
}
@Override
public void paint(Graphics g) {
g2d = (Graphics2D) g;
insFrame = this.getInsets();
sizeFrame = this.getSize();
//int test = 1; // Change to test 2 to test the whole transformation
int test = 2;
if ( test == 1 ) {
// AT1 & AT2 Test
this.setScale(1);
g2d.setColor(Color.ORANGE);
Line2D.Double line = new Line2D.Double(0, 0, sizeFrame.width-insFrame.left-insFrame.right-1, sizeFrame.height-insFrame.top-insFrame.bottom);
g2d.draw(line);
g2d.setColor(Color.RED);
g2d.drawRect(0, 0, sizeFrame.width-insFrame.left-insFrame.right-1, sizeFrame.height-insFrame.top-insFrame.bottom-1);
} else if (test == 2) {
// AT1, AT2, AT3, AT4 & AT5 Test
this.setScale(2);
g2d.setColor(Color.ORANGE);
Line2D.Double line = new Line2D.Double(0, 1.5, 100, 2.5);
g2d.draw(line);
g2d.setColor(Color.RED);
Rectangle2D.Double rectangle = new Rectangle2D.Double(0, 1.5, 100, 2.5);
g2d.draw(rectangle);
}
};
// Required affine transforms to move from
// World Coordinates Viewport to
// Screen Pixel Coordinates Viewport
//
// --- Reference textbook:
//
// Introduction to Computer Graphics Using Java 2D and 3D
// Frank Klawonn, Ed. Springer 2008
// Page 31
//
// --- Viewports
//
// World Coordinates Viewport (xmin,ymin) - (xmax,ymax)
// Screen Pixel Coordinates Viewport (umin, vmin) - (umax, vmax)
//
// --- World Coordinates
//
// (xmax,ymax)
// ┌────────────────┐
// │ │
// │ │
// │ │
// │ │
// └────────────────┘
// (xmin,ymin)
//
// (xmin,ymin) = (0, 100)
// (xmax,ymax) = (1.5, 2.5)
//
// --- User coordinates
//
// (umax,vmax)
// ┌────────────────┐
// │ │
// │ │
// │ │
// │ │
// └────────────────┘
// (umin,vmin)
//
// (umin,vmin) = (inset.left, heightFrame - inset.bottom)
// (umax,vmax) = (widthFrame - inset.right, inset.top)
//
// --- Affine Transform 1 and 2
//
// T(0, h) ◦ S(1,−1)
//
// --- Affine Transform 3, 4 and 5
//
// ╭ umax − umin vmax − vmin ╮
// T(umin, vmin) ◦ S | ----------- , ----------- | ◦ T(-xmin, -ymin)
// ╰ xmax − xmin ymax − ymin ╯
//
private void setScale(int test) {
// World Coordinates
// (xmin,ymin) = (0, 1.5)
// (xmax,ymax) = (100, 2.5)
Double xmin = 0.0;
Double ymin = 1.5;
Double xmax = 100.0;
Double ymax = 2.5;
// User Coordinates
// (umin,vmin) = (inset.left, heightFrame - inset.bottom)
// (umax,vmax) = (widthFrame - inset.right, inset.top)
int umin = insFrame.left;
int vmin = (int) (sizeFrame.getHeight() - insFrame.bottom);
int umax = (int) (sizeFrame.getWidth() - insFrame.right);
int vmax = insFrame.top;
if (test == 1) {
// Affine Transformation 1 and 2
// T(0, h) ◦ S(1,−1)
AffineTransform at1 = new AffineTransform();
at1.setToScale(1,-1);
AffineTransform at2 = new AffineTransform();
at2.setToTranslation(insFrame.left, sizeFrame.getHeight() - insFrame.bottom - 1);
at1.preConcatenate(at2);
g2d.transform(at1);
} else if (test == 2) {
// Affine Transformation 1 and 2
// T(0, h) ◦ S(1,−1)
AffineTransform at1 = new AffineTransform();
at1.setToScale(1,-1);
AffineTransform at2 = new AffineTransform();
at2.setToTranslation(insFrame.left, sizeFrame.getHeight() - insFrame.bottom - 1);
// Affine Transformation 3, 4 and 5
// ╭ umax − umin vmax − vmin ╮
// T(umin, vmin) ◦ S | ----------- , ----------- | ◦ T(-xmin, -ymin)
// ╰ xmax − xmin ymax − ymin ╯
AffineTransform at3 = new AffineTransform();
at3.setToTranslation(umin, vmin);
AffineTransform at4 = new AffineTransform();
at4.setToScale(1.0*(umax-umin)/(xmax-xmin), 1.0*(vmax-vmin)/(ymax-ymin));
AffineTransform at5 = new AffineTransform();
at5.setToTranslation(-xmin,-ymin);
at4.preConcatenate(at5);
at3.preConcatenate(at4);
at2.preConcatenate(at3);
at1.preConcatenate(at2);
g2d.transform(at1);
}
}
public static void main( String[] args ) {
Test2 window = new Test2();
}
}
我查看了这本书,发现 AffineTransform.preConcatenate()
的用法令人困惑。我更喜欢使用 AffineTransform.concatenate()
,因为对我来说这提供了更自然的流程。
让我们看第一个例子(变换,然后交换y方向)。
这是您的代码:
AffineTransform at1 = new AffineTransform();
at1.setToScale(1,-1);
AffineTransform at2 = new AffineTransform();
at2.setToTranslation(insFrame.left, sizeFrame.getHeight() - insFrame.bottom - 1);
at1.preConcatenate(at2);
使用concatenate
,你可以写成:
AffineTransform at1 = new AffineTransform();
at1.setToTranslation(insFrame.left, sizeFrame.getHeight() - insFrame.bottom - 1);
AffineTransform at2 = new AffineTransform();
at2.setToScale(1,-1);
at1.concatenate(at2);
或者,更短:
AffineTransform at = new AffineTransform();
at.translate(insFrame.left, sizeFrame.getHeight() - insFrame.bottom - 1);
at.scale(1,-1);
您可以看到代码中直接表示的流程"translate, then scale"。
对于第二个测试,您可以应用相同的方法:
// Affine Transformation 3, 4 and 5
// ╭ umax − umin vmax − vmin ╮
// T(umin, vmin) ◦ S | ----------- , ----------- | ◦ T(-xmin, -ymin)
// ╰ xmax − xmin ymax − ymin ╯
AffineTransform at = new AffineTransform();
at.translate(umin, vmin);
at.scale((umax-umin)/(xmax-xmin), (vmax-vmin)/(ymax-ymin));
at.translate(-xmin, -ymin);
请注意:umin, vmin
已经表示屏幕左下坐标,umax, vmax
屏幕右上坐标,因此不需要额外的平移或 y 轴翻转!
一些结束语:
- Java2D 中默认的线条宽度是一个缩放单位。选择
ymin
和ymax
,一个缩放单位填满 window 的完整高度。为了不只得到一个填充的矩形,您应该在g2d.draw()
调用之前使用g2d.setStroke(new BasicStroke(0.0f));
将线宽设置为可能的最小值。 Rectangle2D.Double()
的参数是x, y, w, h
,因此您在第二个示例中创建矩形对象的代码应该是Rectangle2D.Double rectangle = new Rectangle2D.Double(0, 1.5, 100, 1);
(上边框的高度为 2.5不显示在屏幕上)。