在最通用的形式中,人们可以将其视为图形编程的经典问题,即从世界坐标到屏幕坐标的转换.在世界坐标系中有一个大小为“1.0 x 1.0”的对象(无论它具有哪个单位).并且应该对该对象进行绘制,使其在屏幕上具有例如“600像素×600像素”的大小.
从广义上讲,在Swing中至少有三种方法可以实现这一目标:
>您可以绘制图像,然后绘制图像的缩放版本
>您可以绘制成缩放的Graphics2D对象
>您可以绘制缩放的对象
每一个都有可能的优点和缺点,以及隐藏的警告.
绘制图像,并绘制图像的缩放版本:
这可能看起来像一个简单的解决方案,但有一个潜在的缺点:图像本身具有一定的分辨率(大小).如果图像太小,并且您要将其缩放以填充屏幕,则可能看起来很块.如果图像太大,并且您将其缩小以适合屏幕,则图像的像素可能会丢失.
在这两种情况下,缩放图像的过程都有几个调整参数.实际上,缩放图像远比第一眼看上去要复杂得多.有关详细信息,请参阅Chris Campbell撰写的第The Perils of Image.getScaledInstance()条.
绘制成缩放的Graphics2D对象
Graphics2D class已经提供了在世界坐标系和屏幕坐标系之间创建转换所需的全部功能.这是由Graphics2D类通过内部存储AffineTransform来完成的,它描述了这种转换.可以通过Graphics2D对象直接修改此AffineTransform:
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对象的转换版本,然后可以将其直接绘制到(未转换的)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对象).每行比较上面提到的三种不同的缩放方法.使用“默认”大小,对象将填充大小为100×100的世界坐标中的矩形.在前两行中,它们按比例放大以填充屏幕上190×190像素的区域.在最后两行中,它们按比例缩小以填充屏幕上60×60像素的区域. (选择这些尺寸是为了使一些“奇数”缩放因子为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 createObjects()
{
List objects = new ArrayList();
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 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 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
{
Shape shape = objects.get(i);
g.setColor(COLORS[i%COLORS.length]);
if (at == null)
{
g.draw(shape);
}
else
{
g.draw(at.createTransformedShape(shape));
}
}
}
}