您有一组 2D 点,并希望将它们绘制成 JPanel。目标是确保点集在面板中“正确”呈现。在这里我假设“正确”意味着所有的点都应该是可见的,并且应该被渲染,以便它们基本上填满可用的空间。
对此有不同的方法。
最基本的方法将涉及以下步骤(暂时忽略一些实现细节):
- 你必须计算点的边界框
- 您必须计算必须应用于点的变换,以便它们填满屏幕。这个变换可以从边界框计算出来
- 你必须变换点
- 您必须绘制转换后的点
在下面的示例代码中,这些步骤在ScaledPaintingPlainPanel 类的convertToPoints、computeBoundingBox、computeTransform、transformPoints 和drawPolygon 方法中实现。
在您的情况下,这些点实际上是 多边形 的点 - 也就是说,它们与线相连。这可以使事情变得更简单:您可以将点转换为java.awt.Shape,这样您就不必手动计算边界框。形状也可以更容易地转换和绘制。这显示在 ScaledPaintingPlainPanelSimpler 类中。
两种情况的结果都是一样的:
代码在这里:
import java.awt.BasicStroke;
import java.awt.Color;
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.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class ScaledPaintingPlain
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> createAndShowGui());
}
private static void createAndShowGui()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().setLayout(new GridLayout(1,2));
ScaledPaintingPlainPanel p0 =
new ScaledPaintingPlainPanel();
f.getContentPane().add(p0);
ScaledPaintingPlainPanelSimpler p1 =
new ScaledPaintingPlainPanelSimpler();
f.getContentPane().add(p1);
f.setSize(1200, 800);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class ScaledPaintingPlainPanel extends JPanel
{
double[][] subjPoints =
{
{ 38.81904602050781, -71.00624084472656 },
{ 38.81904602050781, -70.29379272460938 },
{ 37.95466232299805, -70.35797882080078 },
{ 37.9495735168457, -71.03191375732422 }
};
double[][] clipPoints =
{
{ 38.62575820040764, -70.84753473092672 },
{ 38.418853759765625, -71.02689361572266 },
{ 38.21194931912361, -71.2057395294625 },
{ 37.931301169971185, -70.67791159484983 },
{ 38.1382056106132, -70.49975311140243 },
{ 38.34511005125521, -70.32108875708619 }
};
@Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setStroke(new BasicStroke(3));
g2.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
// Convert the arrays into lists of points
List<Point2D> sPoints = convertToPoints(subjPoints);
List<Point2D> cPoints = convertToPoints(clipPoints);
// Compute the bounding boxes of the point lists
Rectangle2D sBounds = computeBoundingBox(sPoints);
Rectangle2D cBounds = computeBoundingBox(cPoints);
Rectangle2D bounds = new Rectangle2D.Double();
Rectangle2D.union(sBounds, cBounds, bounds);
AffineTransform affineTransform = computeTransform(bounds);
// Transform the points
transformPoints(sPoints, affineTransform);
transformPoints(cPoints, affineTransform);
// Draw the point lists as polygons
drawPolygon(g2, sPoints, Color.BLUE);
drawPolygon(g2, cPoints, Color.RED);
}
private static List<Point2D> convertToPoints(double array[][])
{
List<Point2D> points = new ArrayList<Point2D>();
for (double a[] : array)
{
points.add(new Point2D.Double(a[0], a[1]));
}
return points;
}
private static Rectangle2D computeBoundingBox(
Iterable<? extends Point2D> points)
{
double minX = Double.MAX_VALUE;
double minY = Double.MAX_VALUE;
double maxX = -Double.MAX_VALUE;
double maxY = -Double.MAX_VALUE;
for (Point2D p : points)
{
minX = Math.min(minX, p.getX());
minY = Math.min(minY, p.getY());
maxX = Math.max(maxX, p.getX());
maxY = Math.max(maxY, p.getY());
}
return new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY);
}
private AffineTransform computeTransform(Rectangle2D bounds)
{
// Compute the scaling factor that has to be applied to the
// points so that the painting fills the screen
double scaleX = getWidth() / bounds.getWidth();
double scaleY = getHeight() / bounds.getHeight();
double scale = Math.min(scaleX, scaleY);
// Compute the transform that has to be applied to the
// points so that they fill the screen
AffineTransform affineTransform = new AffineTransform();
affineTransform.scale(scale, scale);
affineTransform.translate(-bounds.getX(), -bounds.getY());
return affineTransform;
}
private static void transformPoints(
Iterable<? extends Point2D> points, AffineTransform affineTransform)
{
for (Point2D point : points)
{
affineTransform.transform(point, point);
}
}
private void drawPolygon(Graphics2D g2, List<Point2D> points, Color color)
{
g2.setColor(color);
int len = points.size();
Line2D line = new Line2D.Double();
for (int i = 0; i < len; i++)
{
Point2D p1 = points.get(i);
Point2D p2 = points.get((i + 1) % len);
line.setLine(p1.getX(), p1.getY(), p2.getX(), p2.getY());
g2.draw(line);
}
}
}
class ScaledPaintingPlainPanelSimpler extends JPanel
{
double[][] subjPoints =
{
{ 38.81904602050781, -71.00624084472656 },
{ 38.81904602050781, -70.29379272460938 },
{ 37.95466232299805, -70.35797882080078 },
{ 37.9495735168457, -71.03191375732422 }
};
double[][] clipPoints =
{
{ 38.62575820040764, -70.84753473092672 },
{ 38.418853759765625, -71.02689361572266 },
{ 38.21194931912361, -71.2057395294625 },
{ 37.931301169971185, -70.67791159484983 },
{ 38.1382056106132, -70.49975311140243 },
{ 38.34511005125521, -70.32108875708619 }
};
@Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setStroke(new BasicStroke(3));
g2.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
// Convert the arrays into shapes
Shape sShape = convertToShape(subjPoints);
Shape cShape = convertToShape(clipPoints);
// Compute the bounding boxes of the shapes
Rectangle2D sBounds = sShape.getBounds2D();
Rectangle2D cBounds = cShape.getBounds2D();
Rectangle2D bounds = new Rectangle2D.Double();
Rectangle2D.union(sBounds, cBounds, bounds);
AffineTransform affineTransform = computeTransform(bounds);
g2.setColor(Color.BLUE);
g2.draw(affineTransform.createTransformedShape(sShape));
g2.setColor(Color.RED);
g2.draw(affineTransform.createTransformedShape(cShape));
}
private static Shape convertToShape(double array[][])
{
Path2D path = new Path2D.Double();
for (int i=0; i<array.length; i++)
{
double a[] = array[i];
double x = a[0];
double y = a[1];
if (i == 0)
{
path.moveTo(x, y);
}
else
{
path.lineTo(x, y);
}
}
path.closePath();
return path;
}
private AffineTransform computeTransform(Rectangle2D bounds)
{
// Compute the scaling factor that has to be applied to the
// points so that the painting fills the screen
double scaleX = getWidth() / bounds.getWidth();
double scaleY = getHeight() / bounds.getHeight();
double scale = Math.min(scaleX, scaleY);
// Compute the transform that has to be applied to the
// points so that they fill the screen
AffineTransform affineTransform = new AffineTransform();
affineTransform.scale(scale, scale);
affineTransform.translate(-bounds.getX(), -bounds.getY());
return affineTransform;
}
}
为了完整起见:这是一个类似的例子,使用我的Viewer library
在这种情况下,您还可以使用鼠标平移和缩放渲染的表示。
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import de.javagl.viewer.Painter;
import de.javagl.viewer.Viewer;
public class ScaledPaintingViewer
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> createAndShowGUI());
}
private static void createAndShowGUI()
{
JFrame f = new JFrame("Viewer");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().setLayout(new BorderLayout());
f.getContentPane().add(
new JLabel("<html>"
+ "Right mouse drags: Translate<br> "
+ "Left mouse drags: Rotate<br>"
+ "Mouse wheel: Zoom uniformly<br>"
+ " +shift: zoom along x<br>"
+ " +ctrl: zoom along y<br>"
+ "</html>"),
BorderLayout.NORTH);
Viewer viewer = new Viewer();
ScaledPaintingPainter painter = new ScaledPaintingPainter();
viewer.addPainter(painter);
viewer.setDisplayedWorldArea(painter.computeBounds());
f.getContentPane().add(viewer, BorderLayout.CENTER);
f.setSize(800,800);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class ScaledPaintingPainter implements Painter
{
double[][] subjPoints =
{
{ 38.81904602050781, -71.00624084472656 },
{ 38.81904602050781, -70.29379272460938 },
{ 37.95466232299805, -70.35797882080078 },
{ 37.9495735168457, -71.03191375732422 }
};
double[][] clipPoints =
{
{ 38.62575820040764, -70.84753473092672 },
{ 38.418853759765625, -71.02689361572266 },
{ 38.21194931912361, -71.2057395294625 },
{ 37.931301169971185, -70.67791159484983 },
{ 38.1382056106132, -70.49975311140243 },
{ 38.34511005125521, -70.32108875708619 }
};
@Override
public void paint(
Graphics2D g, AffineTransform worldToScreen, double w, double h)
{
// Convert the arrays into shapes
Shape sShape = convertToShape(subjPoints);
Shape cShape = convertToShape(clipPoints);
g.setColor(Color.BLUE);
g.draw(worldToScreen.createTransformedShape(sShape));
g.setColor(Color.RED);
g.draw(worldToScreen.createTransformedShape(cShape));
}
Rectangle2D computeBounds()
{
// Convert the arrays into shapes
Shape sShape = convertToShape(subjPoints);
Shape cShape = convertToShape(clipPoints);
// Compute the bounding boxes of the shapes
Rectangle2D sBounds = sShape.getBounds2D();
Rectangle2D cBounds = cShape.getBounds2D();
Rectangle2D bounds = new Rectangle2D.Double();
Rectangle2D.union(sBounds, cBounds, bounds);
return bounds;
}
private static Shape convertToShape(double array[][])
{
Path2D path = new Path2D.Double();
for (int i=0; i<array.length; i++)
{
double a[] = array[i];
double x = a[0];
double y = a[1];
if (i == 0)
{
path.moveTo(x, y);
}
else
{
path.lineTo(x, y);
}
}
path.closePath();
return path;
}
}