《java 桌面软件开发》swing 以鼠标为中心放大缩小移动图片

swing 使用Graphic2D 绘制图片,要实现对图片进行缩放和自由拖动。

1.以鼠标所在的位置为中心,滚轮控制缩放

2.缩放后再支持鼠标拖动。

基本原理:

利用scale() 函数。进行缩放。但是要注意的地方是,如果是在 public void paintComponent(Graphics g) 里面通过这个Graphics g 参数获取graphic对象进行绘制,scale不会影响下一次的绘制。
一:所以,我们可以自行创建一个 "绘图区", 创建一个空的ImageBuffer, 然后获取这个ImageBuffer的 Graphics, 后续全部往这个ImageBuffer的 Graphics 绘制.
二: 最后在frame的paintComponent把我们这个 绘图区原样展示出来即可。 即,frame的
paintComponent只是固定将 绘图区作为一个图片绘制。
三:甚至可以创建多个ImageBuffer ,实现类似于ps多图层的样子,各个图层独立,paitComponent 汇总显示图层。

自己创建的ImageBuffer的 Graphics ,每次scale都是以上一次作为基础,累计的缩放。

利用transrate进行移动,(移动的是坐标系)。 每次transrate都是以上一次作为基础,累计的平移。

来实现我们的关键代码:

作为demo, 代码尽量是一个 main()到底:

swingDemo.java

java 复制代码
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;

public class swingDemo {

	public static void main(String args[]) {
		new swingDemo();
	}

	public static Color BG_COLOR = new Color(128, 128, 128);

	public swingDemo() {
		JFrame mjf = new JFrame("图片查看");
		ImagePanle mImgeView = new ImagePanle();
		mImgeView.setPreferredSize(new Dimension(500, 500));
		mImgeView.setMinimumSize(new Dimension(500, 500));
		mImgeView.setBackground(BG_COLOR);

		JMenuBar jmb = new JMenuBar();
		JMenu meSetting = new JMenu("文件");
		JMenuItem mOpen = new JMenuItem("打开");

		mOpen.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				// TODO Auto-generated method stub

				BufferedImage curBufferedImg;
				JFileChooser fileChooser = new JFileChooser();
				fileChooser.setMultiSelectionEnabled(true);
				fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
				fileChooser.setMultiSelectionEnabled(false);
				int option = fileChooser.showOpenDialog(mjf);
				if (option == JFileChooser.APPROVE_OPTION) {
					try {
						File file = fileChooser.getSelectedFile();
						curBufferedImg = ImageIO.read(new File(file.getAbsolutePath()));
						mImgeView.updateImage(curBufferedImg);
					} catch (IOException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
				}

			}
		});

		meSetting.add(mOpen);
		jmb.add(meSetting);

		mjf.setJMenuBar(jmb);
		mjf.add(mImgeView);

		mjf.setMinimumSize(new Dimension(800, 600));
		mjf.setVisible(true);
		mjf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

	}

	class ImagePanle extends JPanel {

		BufferedImage mSrcBuffeImg = null;
		private static final long serialVersionUID = 1L;
		private double mScale = 1.0;
		private static final boolean B_REAL_SIZE = true;
		private double mCurX = 0;
		private double mCurY = 0;
		private double mStartX = 0;
		private double mStartY = 0;
		private double mTranslateX = 0;
		private double mTranslateY = 0;

//		记录最初原始坐标系,用于清除背景
		AffineTransform mOriginTransform;
		BufferedImage mViewBufferImg;
		Graphics2D mViewG2d;

		void refreshView() {
			clear_buffer(mViewG2d, mOriginTransform, mViewBufferImg);
			mViewG2d.drawImage(mSrcBuffeImg, 0, 0, null);
			repaint();
		}

		void clear_buffer(Graphics2D g2d, AffineTransform org, BufferedImage bufImg) {
//			将保存的测量数据,重新在经过变换后的坐标系上进行绘制
			// 先恢复一下原始状态,保证清空的坐标是全部,执行清空,然后再切会来
			AffineTransform temp = g2d.getTransform();
			g2d.setTransform(org);
			g2d.clearRect(0, 0, bufImg.getWidth(), bufImg.getHeight());
			g2d.setTransform(temp);
		}

		public void updateImage(BufferedImage srcImage) {
			mSrcBuffeImg = srcImage;
			mViewBufferImg = new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage.TYPE_INT_ARGB);
			System.out.println("create buff image");
			mViewG2d = mViewBufferImg.createGraphics();
			mViewG2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
			mViewG2d.setBackground(BG_COLOR);
			System.out.println("crate bufg2d");
			mOriginTransform = mViewG2d.getTransform();
			refreshView();
		}

		private Point internal_getImagePoint(double mouseX, double mouseY) {

			// 不管是先平移后缩放还是先缩放后平移,都以 先减 再缩放的方式可以获取正确
			double rawTranslateX = mViewG2d.getTransform().getTranslateX();
			double rawTranslateY = mViewG2d.getTransform().getTranslateY();
			// 获取当前的 Scale Transform
			double scaleX = mViewG2d.getTransform().getScaleX();
			double scaleY = mViewG2d.getTransform().getScaleY();

//        		不管是先平移后缩放还是先缩放后平移,都以 先减 再缩放的方式可以获取正确
			int imageX = (int) ((mouseX - rawTranslateX) / scaleX);
			int imageY = (int) ((mouseY - rawTranslateY) / scaleY);

			return new Point(imageX, imageY);
		}

		public ImagePanle() {

//			启用双缓存
			setDoubleBuffered(true);

			this.addMouseWheelListener((MouseWheelListener) new MouseWheelListener() {
				@Override
				public void mouseWheelMoved(MouseWheelEvent e) {
					if (mViewG2d == null) {
						return;
					}
					mCurX = e.getX();
					mCurY = e.getY();

					int notches = e.getWheelRotation();
					if (notches < 0) {
						// 滚轮向上,放大画布
						mScale = 1.1;

					} else {
						// 滚轮向下,缩小画布
						mScale = 0.9;
					}

					Point imagePoint = internal_getImagePoint(e.getX(), e.getY());
					int imageX = imagePoint.x;
					int imageY = imagePoint.y;
					System.out.println("x:" + e.getX() + "y:" + e.getY() + ",imagex:" + imageX + "x" + imageY);

					double tralateX = mScale * imageX - imageX;
					double tralateY = mScale * imageY - imageY;

					mViewG2d.scale(mScale, mScale);
					mViewG2d.translate(-tralateX / mScale, -tralateY / mScale); // 图片方大,就需要把坐标往左移动,移动的尺度是要考虑缩放的
					// 先恢复一下原始状态,保证清空的坐标是全部,执行清空,然后再切会来
					AffineTransform temp = mViewG2d.getTransform();
					mViewG2d.setTransform(mOriginTransform);
					mViewG2d.clearRect(0, 0, mViewBufferImg.getWidth(), mViewBufferImg.getHeight());
					mViewG2d.setTransform(temp);

					mViewG2d.drawImage(mSrcBuffeImg, 0, 0, null);
					repaint(); // 重新绘制画布
				}

			});

			this.addMouseListener(new MouseListener() {

				@Override
				public void mouseReleased(MouseEvent e) {
					// TODO Auto-generated method stub
					System.out.println("mouseReleased:" + e.getX() + "x" + e.getY());
				}

				@Override
				public void mousePressed(MouseEvent e) {
					// TODO Auto-generated method stub
					System.out.println("mousePressed----:" + e.getX() + "x" + e.getY());
					mStartX = e.getX();
					mStartY = e.getY();
				}

				@Override
				public void mouseExited(MouseEvent e) {
					// TODO Auto-generated method stub

				}

				@Override
				public void mouseEntered(MouseEvent e) {
					// TODO Auto-generated method stub

				}

				@Override
				public void mouseClicked(MouseEvent e) {
					// TODO Auto-generated method stub
					System.out.println("mouseClicked----:" + e.getX() + "x" + e.getY());

				}
			});
			this.addMouseMotionListener(new MouseAdapter() {

				@Override
				public void mouseMoved(MouseEvent e) {
					// TODO Auto-generated method stub
				}

				@Override
				public void mouseDragged(MouseEvent e) {
					// TODO Auto-generated method stub
					if (mViewG2d == null) {
						return;
					}

					mCurX = e.getX();
					mCurY = e.getY();
					System.out.println("mouseDragged:" + e.getX() + "x" + e.getY() + "trans:" + (mCurX - mStartX) + ":"
							+ (mCurY - mStartY));

					// 平移坐标,也是相对于变换后的坐标系而言的,所以
					double scaleX = mViewG2d.getTransform().getScaleX();
					double scaleY = mViewG2d.getTransform().getScaleY();

					// TODO mCurX - mStartX 太小,比如为2, 而scalX 比较大,比如为3 则移动的时候回发生 (int)2/3 ==0; 不移动。
					// 解决方案,把移动 ,全部在原始坐标系上做,也就是最后绘制缓冲区的时候,drawimage(transX,transY)
					mTranslateX = (mCurX - mStartX) / scaleX;
					mTranslateY = (mCurY - mStartY) / scaleY;

					// 自身就是累计的
					mViewG2d.translate(mTranslateX, mTranslateY);

					mStartX = mCurX;
					mStartY = mCurY;
					System.out.println("mouseDragged: over+++");

					// 先恢复一下原始状态,保证清空的坐标是全部,执行清空,然后再切会来
					AffineTransform temp = mViewG2d.getTransform();
					mViewG2d.setTransform(mOriginTransform);
					mViewG2d.clearRect(0, 0, mViewBufferImg.getWidth(), mViewBufferImg.getHeight());
					mViewG2d.setTransform(temp);

					mViewG2d.drawImage(mSrcBuffeImg, 0, 0, null);
					repaint();
				}
			});

		}

		public void reset_scale() {
//			恢复到1.0 缩放,0,0 左上角对齐
			mCurX = 0;
			mCurY = 0;
			mScale = 1.0;
			mViewG2d.setTransform(mOriginTransform);
			mViewG2d.clearRect(0, 0, mViewBufferImg.getWidth(), mViewBufferImg.getHeight());
			mViewG2d.drawImage(mSrcBuffeImg, 0, 0, null);
			repaint(); // 重新绘制画布
		}

		@Override
		public void paintComponent(Graphics g) {

			super.paintComponent(g);
			if (mViewBufferImg == null) {
				return;
			}
//			如果有多个"图层"要注意图层的顺序
			Graphics2D g2d = ((Graphics2D) g);
			g2d.drawImage(mViewBufferImg, 0, 0, null);
			System.out.println("draw-----------:" + getWidth() + "x" + getHeight());
		}
	}

}
相关推荐
yufei-coder5 分钟前
C#基础语法
开发语言·c#·.net
数据龙傲天5 分钟前
1688商品API接口:电商数据自动化的新引擎
java·大数据·sql·mysql
长天一色5 分钟前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
_.Switch17 分钟前
Python机器学习模型的部署与维护:版本管理、监控与更新策略
开发语言·人工智能·python·算法·机器学习
醉颜凉20 分钟前
银河麒麟桌面操作系统修改默认Shell为Bash
运维·服务器·开发语言·bash·kylin·国产化·银河麒麟操作系统
NiNg_1_23425 分钟前
Vue3 Pinia持久化存储
开发语言·javascript·ecmascript
带带老表学爬虫34 分钟前
java数据类型转换和注释
java·开发语言
qianbo_insist37 分钟前
simple c++ 无锁队列
开发语言·c++
千里码aicood41 分钟前
【2025】springboot教学评价管理系统(源码+文档+调试+答疑)
java·spring boot·后端·教学管理系统
BigYe程普1 小时前
我开发了一个出海全栈SaaS工具,还写了一套全栈开发教程
开发语言·前端·chrome·chatgpt·reactjs·个人开发