在最通用的形式中,可以将其视为图形编程的经典问题,即从世界坐标到屏幕坐标的转换。您的世界坐标系中有一个大小为“1.0 x 1.0”的对象(无论它具有哪个 unit)。并且这个对象应该被绘制,使其在屏幕上具有例如“600 像素 * 600 像素”的大小。
一般来说,在 Swing 中至少有三个选项可以实现这一点:
- 您可以绘制到图像中,然后绘制图像的缩放版本
- 您可以绘制成缩放的
Graphics2D 对象
- 您可以绘制缩放对象
每一个都有可能的优点和缺点,以及隐藏的警告。
绘制成图像,并绘制图像的缩放版本:
这可能看起来像一个简单的解决方案,但有一个潜在的缺点:图像本身具有一定的分辨率(大小)。如果图像太小,并且您将其放大以填满屏幕,它可能会出现块状。如果图像太大,并且您将其缩小以适应屏幕,则图像的像素可能会丢失。
在这两种情况下,缩放图像的过程都有几个调整参数。事实上,缩放图像比乍一看要复杂得多。详情可参考 Chris Campbell 的文章The Perils of Image.getScaledInstance()。
绘制成缩放的Graphics2D 对象
Graphics2D class 已经提供了创建世界坐标系和屏幕坐标系之间转换所需的全部功能。这是由Graphics2D 类通过在内部存储描述此转换的AffineTransform 来完成的。这个AffineTransform可以直接通过Graphics2D对象修改:
void paintSomething(Graphics2D g) {
...
g.draw(someShape);
// Everything that is painted after this line will
// be painted 3 times as large:
g.scale(3.0, 3.0);
g.draw(someShape); // Will be drawn larger
}
必须注意正确管理存储在Graphics2D 对象中的转换。通常,应在应用其他转换之前创建原始 AffineTransform 的备份,然后再恢复此原始转换:
// Create a backup of the original transform
AffineTransform oldAT = g.getTransform();
// Apply some transformations
g.scale(3.0, 4.0);
g.translate(10.0, 20.0);
// Do custom painting the the transformed graphics
paintSomething(g):
// Restore the original transformation
g.setTransform(oldAT);
(对最后一种方法的另一个建议:Graphics2D#setTransform 方法应该永远用于在现有变换之上应用新的坐标变换。它仅用于恢复“旧”转换,如本示例所示(以及此方法的文档中))。
使用Graphics2D 类进行缩放的一个潜在缺点是之后,所有内容都会被缩放。特别是,这种缩放也会影响线宽(即Stroke 的宽度)。例如,考虑这样的一系列调用:
// By default, this will paint a line with a width (stroke) of 1.0:
g.draw(someLine);
// Apply some scaling...
g.scale(10.0, 10.0);
// Now, this will paint the same line, but with a width of 10.
g.draw(someLine);
第二次调用将导致绘制一条 10 像素宽的线。在许多情况下,这可能不是所希望的。第三种选择可以避免这种影响:
绘制缩放对象
世界坐标系和屏幕坐标系之间的转换也可以手动维护。将其表示为AffineTransform 很方便。 AffineTransform 类可用于创建Shape 对象的转换版本,然后可以将其直接绘制到(un-transformed)Graphics2D 对象中。这是通过AffineTransform#createTransformedShape 方法完成的:
void paintSomething(Graphics2D g) {
...
// Draw some shape in its normal size
g.draw(someShape);
// Create a scaling transform
AffineTransform at = AffineTransform.getScaleInstance(3.0, 3.0);
// Create a scaled version of the shape
Shape transformedShape = at.createTransformedShape(someShape);
// Draw the scaled shape
g.draw(transformedShape);
}
这可能是最通用的方法。唯一的潜在缺点是,当绘制 许多 个小而简单的形状时,这将导致创建许多小的临时变换形状,这可能会导致性能下降。 (有一些方法可以缓解这个问题,但详细的性能考虑和优化超出了这个答案的范围)。
总结
下图显示了所有方法的比较。绘制了一些示例对象(表示为Shape 对象)。每行比较上述三种不同的缩放方法。使用它们的“默认”大小,对象填充世界坐标中大小为 100x100 的矩形。在前两行中,它们被放大以填充屏幕上 190x190 像素的区域。在最后两行中,它们被缩小以填充屏幕上 60x60 像素的区域。 (选择这些大小是为了获得一些“奇数”的缩放因子 1.9 和 0.6。例如,当缩放因子为整数或恰好为 0.5 时,某些效果(伪影)可能不会出现)。
对于放大和缩小,还对“标准”绘画方式和“高质量”绘画(由每个面板标题中的“(HQ)”表示)进行了比较。这里的“高质量”只是意味着渲染提示
KEY_ANTIALIAS = VALUE_ANTIALIAS_ON
KEY_RENDERING = VALUE_RENDER_QUALITY
已设置:
这里是对应的程序,作为MCVE:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class ScalingMethodComparison
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().setLayout(new GridLayout(0,1));
Dimension larger = new Dimension(190,190);
Dimension smaller = new Dimension(60,60);
f.getContentPane().add(createPanel(larger, false));
f.getContentPane().add(createPanel(larger, true));
f.getContentPane().add(createPanel(smaller, false));
f.getContentPane().add(createPanel(smaller, true));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private static JPanel createPanel(Dimension d, boolean highQuality)
{
JPanel p = new JPanel(new GridLayout(1,3));
for (ScalingMethodComparisonPanel.ScalingMethod scalingMethod :
ScalingMethodComparisonPanel.ScalingMethod.values())
{
p.add(createPanel(d, scalingMethod, highQuality));
}
return p;
}
private static JPanel createPanel(
Dimension d, ScalingMethodComparisonPanel.ScalingMethod scalingMethod,
boolean highQuality)
{
JPanel p = new JPanel(new GridLayout(1,1));
p.setBorder(BorderFactory.createTitledBorder(
scalingMethod.toString()+(highQuality?" (HQ)":"")));
JPanel scalingMethodComparisonPanel =
new ScalingMethodComparisonPanel(
createObjects(), d, scalingMethod, highQuality);
p.add(scalingMethodComparisonPanel);
return p;
}
// Returns a list of objects that should be drawn,
// occupying a rectangle of 100x100 in WORLD COORDINATES
private static List<Shape> createObjects()
{
List<Shape> objects = new ArrayList<Shape>();
objects.add(new Ellipse2D.Double(10,10,80,80));
objects.add(new Rectangle2D.Double(20,20,60,60));
objects.add(new Line2D.Double(30,30,70,70));
return objects;
}
}
class ScalingMethodComparisonPanel extends JPanel
{
private static final Color COLORS[] = {
Color.RED, Color.GREEN, Color.BLUE,
};
enum ScalingMethod
{
SCALING_IMAGE,
SCALING_GRAPHICS,
SCALING_SHAPES,
}
private final List<Shape> objects;
private final ScalingMethod scalingMethod;
private final boolean highQuality;
private final Dimension originalSize = new Dimension(100,100);
private final Dimension scaledSize;
private BufferedImage image;
public ScalingMethodComparisonPanel(
List<Shape> objects,
Dimension scaledSize,
ScalingMethod scalingMethod,
boolean highQuality)
{
this.objects = objects;
this.scaledSize = new Dimension(scaledSize);
this.scalingMethod = scalingMethod;
this.highQuality = highQuality;
}
@Override
public Dimension getPreferredSize()
{
return new Dimension(scaledSize);
}
@Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setColor(Color.WHITE);
g.fillRect(0,0,getWidth(), getHeight());
if (highQuality)
{
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(
RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
}
if (scalingMethod == ScalingMethod.SCALING_IMAGE)
{
paintByScalingImage(g);
}
else if (scalingMethod == ScalingMethod.SCALING_GRAPHICS)
{
paintByScalingGraphics(g);
}
else if (scalingMethod == ScalingMethod.SCALING_SHAPES)
{
paintByScalingShapes(g);
}
}
private void paintByScalingImage(Graphics2D g)
{
if (image == null)
{
image = new BufferedImage(
originalSize.width, originalSize.height,
BufferedImage.TYPE_INT_ARGB);
}
Graphics2D ig = image.createGraphics();
paintObjects(ig, null);
ig.dispose();
g.drawImage(image, 0, 0, scaledSize.width, scaledSize.height, null);
}
private void paintByScalingGraphics(Graphics2D g)
{
AffineTransform oldAT = g.getTransform();
double scaleX = (double)scaledSize.width / originalSize.width;
double scaleY = (double)scaledSize.height / originalSize.height;
g.scale(scaleX, scaleY);
paintObjects(g, null);
g.setTransform(oldAT);
}
private void paintByScalingShapes(Graphics2D g)
{
double scaleX = (double)scaledSize.width / originalSize.width;
double scaleY = (double)scaledSize.height / originalSize.height;
AffineTransform at =
AffineTransform.getScaleInstance(scaleX, scaleY);
paintObjects(g, at);
}
private void paintObjects(Graphics2D g, AffineTransform at)
{
for (int i=0; i<objects.size(); i++)
{
Shape shape = objects.get(i);
g.setColor(COLORS[i%COLORS.length]);
if (at == null)
{
g.draw(shape);
}
else
{
g.draw(at.createTransformedShape(shape));
}
}
}
}