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

news2024/11/18 17:25:01

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


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());
		}
	}

}


 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1115367.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux 下安装配置部署MySql8.0

一 . 准备工作 MySQL安装包&#xff1a;在官网下载需要的版本&#xff0c;这里我用的版本是 MySQL 8.0.34 https://dev.mysql.com/downloads/mysql/ 本次linux机器使用的是阿里云ECS实例 二 . 开始部署 1. 将安装包上传至服务器 解压到当前文件夹 tar -zxvf mysql-8.0.34…

Python 实现http server接收mutipart/form-data文件 方法1

Python 实现http server接收mutipart/form-data文件 方法1 1 Server端代码2 客户端截图3 代码说明 1 Server端代码 import os from flask import Flask, request from werkzeug.utils import secure_filenameapp Flask(__name__) app.config[UPLOAD_FOLDER] E://recv//app.ro…

玩游戏缺失“d3d11.dll丢失“的问题的五种解决方案

在我日常的计算机维护工作中&#xff0c;经常遇到一些用户报告他们遇到了"d3d11.dll丢失"的问题。这是一个常见的Windows系统错误&#xff0c;通常会导致程序无法正常运行。在这篇文章中&#xff0c;我将分享我找到的五种有效的解决方法&#xff0c;以帮助这些用户解…

开源的容器运行时项目 Podman

本心、输入输出、结果 文章目录 开源的容器运行时项目 Podman前言Podman 简介Podman 与 Docker 的区别Podman 在使用上和 Docker 有什么区别从构建者角度分析 Podman 在使用上和 Docker 有什么区别从使用者角度分析 Podman 在使用上和 Docker 有什么区别 Podman 常用命令容器镜…

Linux shell编程学习笔记14:编写和运行第一个shell脚本hello world!

* 20231020 写这篇博文断断续续花了好几天&#xff0c;为了说明不同shell在执行同一脚本文件时的差别&#xff0c;我分别在csdn提供线上Linux环境 &#xff08;使用的shell是zsh&#xff09;和自己的电脑上&#xff08;使用的shell是bash&#xff09;做测试。功夫不负有心人&am…

详解如何利用Pytest Cache Fixture实现测试结果缓存

这篇文章主要为大家详细介绍了如何利用Pytest Cache Fixture实现测试结果缓存,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起了解一下− 前言 接口自动关过程中&#xff0c;经常会遇到这样一些场景&#xff0c;“请求2需要用到请求1响应的数据”&#xff0c;常见的…

人脸识别顶会论文及源码合集,含2023最新

今天和大家聊聊人脸识别。 人脸识别的技术经过不断发展已经相当成熟&#xff0c;在门禁、监控、手机解锁、移动支付等实际场景都能看到。我们比较熟悉的识别方式是基于可见光图像的人脸识别&#xff0c;这种方式有个非常明显的缺点&#xff1a;光线限制。 在近两年的人脸识别…

如何解决NSIS 2G文件的限制

Internal compiler error #12345: error mmapping datablock to 33556079.Note: you may have one or two (large) stale temporary file(s) left in your temporary directory (Generally this only happens on Windows 9x). 最近在使用NSIS打包一个7.3GB的可执行程序时&…

【算法|动态规划No.25】leetcode LCR 020. 回文子串

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【手撕算法系列专栏】【LeetCode】 &#x1f354;本专栏旨在提高自己算法能力的同时&#xff0c;记录一下自己的学习过程&#xff0c;希望…

Spring学习笔记注解式开发(3)

Spring学习笔记&#xff08;3&#xff09; 一、Bean的注解式开发1.1、注解开发的基本和Component1.2 注解式开发8.3、Component的三个衍生注解 二、Bean依赖注入注解开发2.1、依赖注入相关注解2.2、Autowired扩展 三、非自定义Bean注解开发四、Bean配置类的注解开发五、Spring注…

编译安装Nginx+GeoIP2自动更新+防盗链+防爬虫+限制访问速度+限制连接数

此文章是Nginx的GeoIP2模块和MaxMind国家IP库相互结合&#xff0c;达到客户端IP访问的一个数据记录以及分析&#xff0c;同时还针对一些业务需求做出对Nginx中间件的控制&#xff0c;如&#xff1a;防盗链、防爬虫、限制访问速度、限制连接数等 该篇文章是从一个热爱搞技术的博…

TSINGSEE智慧加油站可视化监管与风险预警方案

一、方案背景 加油站属于危化品行业&#xff0c;如何在日常加油卸油作业过程中保障人员、财产安全是重中之重。国内加油站日常管理主要依靠人为管控、监控摄像头监督及人工巡检等方式&#xff0c;管控手段存在低效性和滞后性&#xff0c;迫切需要将人工智能、物联网、大数据等…

07-React-redux和redux的使用

07.react-redux和redux的使用 1.redux的使用 1).redux的理解 a.redux是什么 redux是一个专门用于做状态管理的JS库(不是react插件库)。它可以用在react, angular, vue等项目中, 但基本与react配合使用。作用: 集中式管理react应用中多个组件共享的状态。 b.什么情况下需要使…

时间序列预测 | LightTS轻量采样的MLP结构网络用于多变量时间序列预测

首先,假设输入的时序维度为[B, T, N],作者便做了2种采样: 连续采样:侧重于捕获短期局部模式。 间隔采样:侧重于捕获长期依赖性。 如下图所示,很好理解,新的数据维度为[B, C, T/C, N],N代表时序的数量。 注意:论文上IEBlockC是直接出预测结果,但代码上,如上图红色标注…

【练习题】一.线性表

1.将两个递增的有序链表合并为一个递增的有序链表。要求结果链表仍使用原来两个链表《存储空间,不另外占用其他的存储空间。表中不允许有重复的数据。 2.将两个非递减的有序链表合并为一个非递增的有序链表。要求结果链表仍使用原来两个表的存储空间,不另外占用其他的存储空间…

计算机缺失d3dcompiler_47.dll解决方案,如何修复电脑缺失d3d文件

在计算机系统中&#xff0c;DLL文件&#xff08;动态链接库&#xff09;是一种重要的共享库&#xff0c;它包含了可被多个程序使用的代码和数据。然而&#xff0c;当某些DLL文件丢失或损坏时&#xff0c;可能会导致程序无法正常运行。本文将介绍四种解决D3DCompiler_47.dll缺失…

Adobe Audition 2024: 音频处理的未来,今天就在您的指尖

随着科技的飞速发展和人们对音频质量要求的不断提高&#xff0c;Adobe Audition 2024 (Au2024) 已经成为音频处理领域的领军者。这款强大的软件将为您的音频带来革命性的改变&#xff0c;让您的创作如虎添翼。 在Adobe Audition 2024中&#xff0c;用户可以体验到全新的界面设…

业内专业人士揭秘:双11即将来临,挑选SSD硬盘避坑指南

再过几天&#xff0c;各大电商的双11狂欢季。同时&#xff0c;随着存储产品涨价潮也在磨刀霍霍&#xff0c;这个双11可能是未来1年最合适买硬盘的窗口期。 小编从购物网站的价格趋势变化来看&#xff0c;已经有厂商按耐不住开始涨价了&#xff0c;NAND原厂的SSD价格相对较平稳。…

伊朗相关的OilRig组织在为期8个月的网络攻击中针对中东政府

导语 伊朗相关的OilRig组织最近在中东政府中展开了一场长达8个月的网络攻击行动。这次攻击导致了文件和密码的被窃取&#xff0c;并且在其中一次攻击中&#xff0c;攻击者还使用了一种名为PowerExchange的PowerShell后门。据Symantec的威胁猎人团队称&#xff0c;他们在一份与T…

Vue中 使用 Scss 实现配置、切换主题

1. 样式文件目录介绍 本项目中的公共样式文件均位于 src/assets/css 目录下&#xff0c;其中 index.scss是总的样式文件的汇总入口 &#xff0c;common.scss 是供全局使用的一些基本样式(常量&#xff09;&#xff0c; _theme.scss、_handle.scss 两个文件是进行主题颜色配置的…