gpt4 book ai didi

Java2D 相对于鼠标位置的缩放和滚动

转载 作者:太空宇宙 更新时间:2023-11-04 13:03:46 24 4
gpt4 key购买 nike

我正在尝试使用 swing 和 java2D 创建一个简单的绘图应用程序。目的是实现始终相对于鼠标光标点的平滑缩放。该应用程序由两个类组成:CanvasPane 和 Canvas。

  • CanvasPane 类是一个简单的容器,中间有 BorderLayout 和 JScrollPane。
  • Canvas类是CanvasPane中添加到JScrollPane中的绘图组件。Canvas 绘制一个简单的矩形[800x600],并调度它的鼠标事件(滚轮和拖动)。

当矩形小于visibleRect时, Canvas 大小等于visibleRect,我调用AffineTransform.translate来跟随鼠标(感谢this question)

当矩形变得比 Canvas 更大时, Canvas 尺寸也会增大并且变得可滚动。然后我在它上面调用scrollRectToVisible来跟随鼠标。

问题是:如何一起使用translate和scrollRectToVisible,以平滑缩放而没有图形跳跃。可能有一些已知的决定?

我想要的在YED Graph Editor完美实现,但它的代码是封闭的。我尝试过很多例子,但只有缩放或滚动,没有复杂的使用它们。

完整代码如下。

CanvasPane 类:

import javax.swing.*;
import java.awt.*;


public class CanvasPane extends JPanel {

private Canvas canvas;

public CanvasPane(boolean isDoubleBuffered) {
super(isDoubleBuffered);
setLayout(new BorderLayout());
canvas = new Canvas(1.0);
JScrollPane pane = new JScrollPane(canvas);
pane.getViewport().setBackground(Color.DARK_GRAY);
add(pane, BorderLayout.CENTER);
}

public static void main(String[] args) {
JFrame frame = new JFrame("Test Graphics");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new CanvasPane(true), BorderLayout.CENTER);
frame.setSize(new Dimension(1000, 800));
frame.setVisible(true);
}
}

类 Canvas :

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;


public class Canvas extends JComponent implements MouseWheelListener, MouseMotionListener, MouseListener {
private double zoom = 1.0;
public static final double SCALE_STEP = 0.1d;
private Dimension initialSize;
private Point origin;
private double previousZoom = zoom;
AffineTransform tx = new AffineTransform();
private double scrollX = 0d;
private double scrollY = 0d;
private Rectangle2D rect = new Rectangle2D.Double(0,0, 800, 600);

public Canvas(double zoom) {
this.zoom = zoom;
addMouseWheelListener(this);
addMouseMotionListener(this);
addMouseListener(this);
setAutoscrolls(true);
}

public Dimension getInitialSize() {
return initialSize;
}

@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
Graphics2D g2d = (Graphics2D) g.create();
g2d.clearRect(0, 0, getWidth(), getHeight());
g2d.transform(tx);
g2d.setColor(Color.DARK_GRAY);
g2d.fill(rect);
g2d.setColor(Color.GRAY);
g2d.setStroke(new BasicStroke(5.0f));
g2d.draw(rect);
g2d.dispose();
}

@Override
public void setSize(Dimension size) {
super.setSize(size);
if (initialSize == null) {
this.initialSize = size;
}
}

@Override
public void setPreferredSize(Dimension preferredSize) {
super.setPreferredSize(preferredSize);
if (initialSize == null) {
this.initialSize = preferredSize;
}
}

public void mouseWheelMoved(MouseWheelEvent e) {
double zoomFactor = - SCALE_STEP*e.getPreciseWheelRotation()*zoom;
zoom = Math.abs(zoom + zoomFactor);
//Here we calculate new size of canvas relative to zoom.
Rectangle realView = getVisibleRect();
Dimension d = new Dimension(
(int)(initialSize.width*zoom),
(int)(initialSize.height*zoom));
// if (d.getWidth() >= realView.getWidth() && d.getHeight() >= realView.getHeight()) {
setPreferredSize(d);
setSize(d);
validate();
followMouseOrCenter(e);
// }

//Here we calculate transform for the canvas graphics to scale relative to mouse
translate(e);
repaint();
previousZoom = zoom;
}

private void translate(MouseWheelEvent e) {
Rectangle realView = getVisibleRect();
Point2D p1 = e.getPoint();
Point2D p2 = null;
try {
p2 = tx.inverseTransform(p1, null);
} catch (NoninvertibleTransformException ex) {
ex.printStackTrace();
return;
}
Dimension d = getSize();
if (d.getWidth() <= realView.getWidth() && d.getHeight() <= realView.getHeight()) {
//Zooming and translating relative to the mouse position
tx.setToIdentity();
tx.translate(p1.getX(), p1.getY());
tx.scale(zoom, zoom);
tx.translate(-p2.getX(), -p2.getY());
} else {
//Only zooming, translate is not needed because scrollRectToVisible works;
tx.setToIdentity();
tx.scale(zoom, zoom);
}
// What to do next?
// The only translation works when rect is smaller then canvas size.
// Rect bigger then canvas must be scrollable, but relative to mouse position as before.
// But when the rect gets bigger than canvas, there is a terrible jump of a graphics.
//So there must be some combination of translation ans scroll to achieve a smooth scale.
//... brain explosion(((
}


public void followMouseOrCenter(MouseWheelEvent e) {
Point2D point = e.getPoint();
Rectangle visibleRect = getVisibleRect();

scrollX = point.getX()/previousZoom*zoom - (point.getX()-visibleRect.getX());
scrollY = point.getY()/previousZoom*zoom - (point.getY()-visibleRect.getY());

visibleRect.setRect(scrollX, scrollY, visibleRect.getWidth(), visibleRect.getHeight());
scrollRectToVisible(visibleRect);
}

public void mouseDragged(MouseEvent e) {
if (origin != null) {
int deltaX = origin.x - e.getX();
int deltaY = origin.y - e.getY();
Rectangle view = getVisibleRect();
Dimension size = getSize();
view.x += deltaX;
view.y += deltaY;
scrollRectToVisible(view);
}
}

public void mouseMoved(MouseEvent e) {
}

public void mouseClicked(MouseEvent e) {
}

public void mousePressed(MouseEvent e) {
origin = new Point(e.getPoint());
}

public void mouseReleased(MouseEvent e) {

}

public void mouseEntered(MouseEvent e) {

}

public void mouseExited(MouseEvent e) {

}

}

最佳答案

我终于开悟了=)

我们可以简单地使 Canvas 尺寸比绘图对象的尺寸大得多,从而忘记计算任何难以理解的变换。

我最初将 Canvas 设置为比绘制矩形大 100 倍。然后我在绘画时缩放 Graphics2D 并将缩放后的图形平移到 Canvas 的中心。接下来,我计算一个新的visibleRect来跟随鼠标点并滚动到它。

当 Canvas 变得不可滚动时,跟随鼠标是不合理的,因为绘图对象太小(比其初始大小小100倍),所以我只是将其居中以使其始终可见。它完全按照我的需要工作。

因此,我们有一个工作示例,其中跟随鼠标缩放并通过鼠标拖动。代码如下。

CanvasPane 类:

import javax.swing.*;
import java.awt.*;


public class CanvasPane extends JPanel {

private static Canvas canvas;

public CanvasPane(boolean isDoubleBuffered) {
super(isDoubleBuffered);
setLayout(new BorderLayout());
canvas = new Canvas(1.0);
JScrollPane pane = new JScrollPane(canvas);
pane.getViewport().setBackground(Color.DARK_GRAY);
add(pane, BorderLayout.CENTER);
}

public static void main(String[] args) {
JFrame frame = new JFrame("Test Graphics");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new CanvasPane(true), BorderLayout.CENTER);
frame.setSize(new Dimension(1000, 800));
frame.pack();
frame.setVisible(true);

//Initial scrolling of the canvas to its center
Rectangle rect = canvas.getBounds();
Rectangle visibleRect = canvas.getVisibleRect();
double tx = (rect.getWidth() - visibleRect.getWidth())/2;
double ty = (rect.getHeight() - visibleRect.getHeight())/2;
visibleRect.setBounds((int)tx, (int)ty, visibleRect.width, visibleRect.height);
canvas.scrollRectToVisible(visibleRect);
}
}

类 Canvas :

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;


public class Canvas extends JComponent implements MouseWheelListener, MouseMotionListener, MouseListener {
private double zoom = 1.0;
public static final double SCALE_STEP = 0.1d;
private Dimension initialSize;
private Point origin;
private double previousZoom = zoom;
private double scrollX = 0d;
private double scrollY = 0d;
private Rectangle2D rect = new Rectangle2D.Double(0,0, 800, 600);
private float hexSize = 3f;

public Canvas(double zoom) {
this.zoom = zoom;
addMouseWheelListener(this);
addMouseMotionListener(this);
addMouseListener(this);
setAutoscrolls(true);

//Set preferred size to be 100x bigger then drawing object
//So the canvas will be scrollable until our drawing object gets 100x smaller then its natural size.
//When the drawing object became so small, it is unnecessary to follow mouse on it,
//and we only center it on the canvas

setPreferredSize(new Dimension((int)(rect.getWidth()*100), (int)(rect.getHeight()*100)));
}

@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;

//Obtain a copy of graphics object without any transforms
Graphics2D g2d = (Graphics2D) g.create();
g2d.clearRect(0, 0, getWidth(), getHeight());

//Zoom graphics
g2d.scale(zoom, zoom);

//translate graphics to be always in center of the canvas
Rectangle size = getBounds();
double tx = ((size.getWidth() - rect.getWidth() * zoom) / 2) / zoom;
double ty = ((size.getHeight() - rect.getHeight() * zoom) / 2) / zoom;
g2d.translate(tx, ty);

//Draw
g2d.setColor(Color.LIGHT_GRAY);
g2d.fill(rect);
g2d.setColor(Color.DARK_GRAY);
g2d.setStroke(new BasicStroke(5.0f));
g2d.draw(rect);

//Forget all transforms
g2d.dispose();
}

@Override
public void setSize(Dimension size) {
super.setSize(size);
if (initialSize == null) {
this.initialSize = size;
}
}

@Override
public void setPreferredSize(Dimension preferredSize) {
super.setPreferredSize(preferredSize);
if (initialSize == null) {
this.initialSize = preferredSize;
}
}

public void mouseWheelMoved(MouseWheelEvent e) {
double zoomFactor = - SCALE_STEP*e.getPreciseWheelRotation()*zoom;
zoom = Math.abs(zoom + zoomFactor);
//Here we calculate new size of canvas relative to zoom.
Dimension d = new Dimension(
(int)(initialSize.width*zoom),
(int)(initialSize.height*zoom));
setPreferredSize(d);
setSize(d);
validate();
followMouseOrCenter(e.getPoint());
previousZoom = zoom;
}

public void followMouseOrCenter(Point2D point) {
Rectangle size = getBounds();
Rectangle visibleRect = getVisibleRect();
scrollX = size.getCenterX();
scrollY = size.getCenterY();
if (point != null) {
scrollX = point.getX()/previousZoom*zoom - (point.getX()-visibleRect.getX());
scrollY = point.getY()/previousZoom*zoom - (point.getY()-visibleRect.getY());
}

visibleRect.setRect(scrollX, scrollY, visibleRect.getWidth(), visibleRect.getHeight());
scrollRectToVisible(visibleRect);
}

public void mouseDragged(MouseEvent e) {
if (origin != null) {
int deltaX = origin.x - e.getX();
int deltaY = origin.y - e.getY();
Rectangle view = getVisibleRect();
view.x += deltaX;
view.y += deltaY;
scrollRectToVisible(view);
}
}

public void mouseMoved(MouseEvent e) {
}

public void mouseClicked(MouseEvent e) {
}

public void mousePressed(MouseEvent e) {
origin = new Point(e.getPoint());
}

public void mouseReleased(MouseEvent e) {
}

public void mouseEntered(MouseEvent e) {
}

public void mouseExited(MouseEvent e) {
}

}

关于Java2D 相对于鼠标位置的缩放和滚动,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34693583/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com