基于Java实现的截图工具
摘 要
当今时代是飞速发展的信息时代,人们在对信息的处理中对图像的处理量与日俱增,这一点在文档人员上显得非常突出。
本软件采用Java语言进行模拟qq截图功能,获取屏幕方式灵活,可以获取指定的屏幕,并且可以对获取到的截图进行存储,编辑(添加文字)等操作,对这些操作设置了自定义热键。该软件主要有捕获光标功能,可以指定截取屏幕的区域。软件具有捕捉光标,可以随着鼠标的移动获取屏幕区域,为之后的存储和编辑提供前置条件。图片可以自命名,获取到的截图可以依据当前系统时间进行命名,也可以通过模板(文件名)进行命名,拥有自命名功能。获取到的截图可以在窗口中央位置进行预览,并且可以指定存储区域将截图用bmp,jpeg,png(静态), gif(动态)其中的某一种图像格式将其存储,拥有预览和存储功能。可以直接将获取到的截图复制到剪切板中,之后可以从剪切板中粘贴到word等文档中。多层菜单栏,可以获取一到多个截图,并以多个界面显示出来,拥有层叠功能。可以通过编辑为截图添加文字,为截图添加一些描述信息之后仍可以将截图存储,用户有编辑功能。
展望未来,为了更方便的获取信息,可以将此截图工具进行扩充,添加一些其他功能,比如可以添加画笔,进行涂鸦。
关键词 :截图; 存储; 编辑
Abstract
Our age is the rapid development of information era, people in the handling of information processing of the images is growing, it appears very prominent on the document personnel.
The software adopting JAVA imitates QQ screenshot, which can obtain designed screens and flexible to achieve it, as well as store and edit (adding words).Besides, user-defined not keys are set to help operate it. The software can capture cursor and move to capture screen area following the mouse which can provide preconditions to store and edit. The software has the ability to capture the cursor, which allows the cursor to be moved to the screen area, providing preconditions for subsequent storage and editing. The photo can be named itself and captured photos can be named the templates(document name). Captured photos can be previewed in the center of chat window and stored in designed position in the from of bmp, jpg, PNG, gif. In the short,it can be previewed and stored. The captured photos can be copied to clipboard then pasted to documents. Multilayer menu bar can capture multiple photos and be showed by various interface. It can add words to captured photos by editing and can be stored. After adding some descriptive information. It can be edited.
Looking forward to the future, in order to more convenient access to information, you can use this screenshot tool to expand, add some other functions, such as can add brush, for graffiti.
Key words : capture; Storage; The editor
1. 绪论
截图就是绝对的记录计算机某一的区域画面,以图片的方式进行存储,供人们使用,按照个人的需要选择其中的某个片段将其保存下来,并且截取到的屏幕与你眼睛所看到的内容一模一样,根据需求可以保存下来自己使用,或者发送给其他人一同使用该截图。
截图经过电脑获取的能呈现在显示屏幕以及别的液晶设施上的可观察图像。一般来说,截图可以使用计算机本身自带的或者某些程序附带的截图小程序如qq截取,当然也有某些特定的截图工具可以进行截图如FastStone Capture8.2,同时相机拍摄也可以达到一些效果。截图也和某些事物相似,也有动静之分,静态截图能够生成一个像素文件,如以.bmp,.png,.jpg为后缀的文件。动态截图生成一个动态文件,如以.gif为后缀的文件。截图常常用来展示某一时间,某一空间下的各个元素之间的状态,以及各种游戏的每个精心的辉宏的场景,当然也为了清晰的传达当时想要表达的意思。
1.1 课题背景
当今时代是飞速发展的信息时代,人们在对信息的处理中对图像的处理量与日俱增,这一点在文档人员上显得非常突出。不管你是在qq聊天还是在办公室里执行文档制作,都会时不时的截取一张或两张甚至更多的图片作为信息进行处理、进行编辑。高效的工作已经成为社会主题,为了提高生活节奏和工作效率,我们可以使用一些截图工具来处理一些复杂的文档。截图工具能够截图,存储,拷贝,可以帮我们改善生活,提高工作效率,我们生活中的一些问题可以通过它来解决。常用的截图方法有:各种浏览器自带的截图,某些聊天工具自带的截图,FastStone Capture8.2等专用截图工具截图,计算机操作系统自身附带的截图小软件截图,但这些方法都有一些这样或那样的局限性,这样,这款精致高效的、即学即用的Java截图软件就能够满足这方面客户需求。
1.2 课题研究内容
以简单易操作为出发点,设计出这款单机版的截图工具,获取屏幕灵活,可以随机获取屏幕任意区域并且可以对获取到的截图进行存储,编辑(添加文字)等操作,对这些操作设置了自定义热键。该软件主要有捕获光标功能,可以指定截取屏幕的区域。
图片可以自命名,获取到的截图可以依据当前系统时间进行命名,也可以通过模板(文件名)进行命名,拥有自命名功能。
获取到的截图可以在窗口中央位置进行预览,并且可以指定存储位置将截图用bmp,jpeg,png等(静态), gif(动态)其中的某一种图像格式将其存储,拥有预览和存储功能。
多层菜单栏,可以获取一到多个截图,并以多个界面显示出来,拥有层叠功能。
可以通过编辑为截图添加文字,为截图添加一些描述信息之后仍可以将截图存储,用户有编辑功能。
2. 可行性分析及开发环境的选择
2.1 可行性分析
进行项目的可行性分析,就必须得结合多方面的知识以及常识,并将它们与即将进行的项目进行比较,从而将项目的至关重要的内容和相互呼应的条件,如社会的需要、提供各种物资的数量、各种方向建设的范围、抱有特色的目标、需求设备类型的选择、自然和人为环境的影响、项目启动所需的资金筹集、最终是否能够经济上获取极大的收入,从科学能力、社会发展、项目设计等众多方向想大量有关工作人员进行咨询,在此基础上展开对项目的仔仔细细的专研,以保证项目的进一步实施,并且结合多方面的资料,对项目给出详细的方案,当然也必须将收益进行预估,必须保证项目的收入满足成本的输出(如财务方面是否得到保证,经济是否能够得到大幅度的增长,社会环境是否有巨大的波动),然后为该项目提出值得放心的投资和尽心尽力的进行建设的宝贵建议,提供非常合理,说服力强的理论上的证据给软件项目进行方针的能够在方方面面进行合理处理的系统分析。可行性分析必须具备一些特征,比如:
-
能够大体的预估收益的大小
-
能够公平的进行分析与决断
-
能够保证项目进行的通畅
-
具有非常可观的理论依据
可行性研究的一般要求:进行可行性分析与设计往小了说,对于每个完整的项目建设与完善有着必不可少的帮助,往大了说,对中国的社会经济的进一步发展起着非常重要的推动作用,在进行可行性分析与研究的工作中为了充分的保证并维护各个环节是否有理论客观性、绝对公正性和社会科学性,尽最大的可能预防错误的产生和缺失关键数据,因此进行可行性分析与研究工作,就要求:
-
第一站在绝对公正的角度开展研究调查,提前收集相关的资料便于工作的展开。客观正确的对收集到的资料,按照社会上的实际规定从而进行相对的比较公正的定义,肯定不虚假地将中国社会经济发展的非主观规律通俗易懂的展现给大众,进行信息的研究讨论以及进行专业的,有理有据的分析,得出结论(项目能否继续进行下去)
-
可行性的分析和研究的有关内容的专用调研层次不得不符合国家的多定义的准则,设计的主体必须是完整的,不可有缺失的,尽最大的能力去获取非常丰富的信息文档来进行调研,巧妙的避开胡乱瞎写,只有形式无内容
2.1.1 技术可行性
Browser(浏览器)/Server(服务器)结构,简称B/S结构,Java语言以及网络技术是截图工具的主要技术,这些技术都是目前常用的技术,很容易找到相应的工具。因此,对于此系统,技术上没有麻烦的问题,并且维护和操作也较为方便。
2.1.2 经济可行性
截图工具对硬件方面没有硬性要求,且这个项目是我们自行开发,成本几乎可以不计,后期维护也不需要大量的费用。
2.1.3 法律可行性
eclipse的是一个开源的,免费的软件,代码同样是开放的,并且此设计并不用来营利,是自己设计开发的,所以不会引发责任以及侵权问题,满足法律可行性。
2.1.4 可行性结论
由上述可得,不管在上述哪方面都为可行的,要求也不是很高,因此,此系统是满足开发的可行性的。
2.2 运行环境的选择
此设计对环境没有硬性要求,对计算机也没有过高的要求,个人、学校、企业均可以使用。在Windows系统或者Linux系统下均可使用(但注意版本问题),操作,维护以及用户使用均很方便。
由于系统本身规模并不大,不需要多台计算机,只需一台即可,使用过程中不收取任何费用,成本不高,用户使用只需登录即可。
2.3 开发工具的选择
本项目的开发过程中语言选择java语言,软件选择eclipse,通过这两项来设计此项目。其中,eclipse这款软件的设计环境是开源的、免费的,这个环境在进行java开发时能够拓宽范围的。对于它自己来说,一个宽泛的大体架构以及某些特定的服务组成了eclipse,各种各样的插件以及大量的组件相互结合,统一起来就组成了eclipse的设计环境。当然这有必要提一提:eclipse它自己自带了一个比较规范的可以当作准则的collection,可以将所有插件以及组件储存起来,最为典型的就是java development kit。
eclipse是非常有名气的,因为它作为设计环境是是集合了许多的环境后形成的可供多个平台使用。刚开始的设计语言以java为主,在之后的发展中为了能够在计算机中使用其他语言[如c语言],将在eclipse中添加各种不同的插件和组件,从而达到这个目的。eclipse它自己作为平台本质上仅仅是一个范围较宽的架构,eclipse里的中的种种功能是通过向其内部添加数量较大的多种多样的插件加上多个组件来实现的,相比于某些相对笨拙的integrated driver electronics软件显著的特点是拥有非常好的灵活性。越来越多的软件开发商是用eclipse研发只属于他们自己的其他人不能使用的integrated driver electronics。
eclipse 刚开始是由加拿大的Optimization Technology Inc公司和International Business Machines Corporation公司两个公司合力将其integrated driver electronics整合创建的,在1999.04开始进行设计eclipse软件。eclipse最基本的内容是由International Business Machines Corporation公司提供的,这时它的主要内容包括platform、joint development team以及project director electronics。这个eclipse项目的启动由International Business Machines Corporation公司开始,形成了一个非常巨大的联盟即eclipse这个项目的发展,参与到这个项目研发的公司总机多达150多家,其中典型的公司有:赛贝斯公司、宝蓝公司及红帽公司等。eclipse是开源的免费的,不会收取任何费用的项目,便于每个用户使用,因而每个人都能够轻而易举的下载到,同时在了解这写相关的知识的基础上研发属于他们自己的,满足自身需求的各种插件和组件,也就是因为这个原因,它受到越来越多的人关注。之后又有许多大型公司加入了此项目的开发,其中典型的有oracle公司,eclipse是为了可以将任何语言集成在一起,实现跨平台,用户仅仅通过下载插件即可使用所需要用到的语言。
Java语言有许多特性,比如:平台无关性,多线程技术以及安全可靠性。
-
平台无关 :支持多变的网络环境。为了保证程序能够不加任何修改运行于网络上的任何计算机,而不管计算机是什么种类,什么平台,这样就极大减轻了系统管理员的工作
-
支持网络化嵌入式设备 :Java的平台无关性可以简化系统管理任务。无论是哪个网络的管理员,它只需关注程序本身即可
-
多线程:在计算机软硬件上同时处理一个以及n个线程并发进行的技术。电脑因为硬件做后盾支持所以可以在某一时间段内,处理一个或者n个线程,拥有多线程机制,从而让得计算机对线程的运行过程控制能力得到飞跃的质的飞跃。这里列举几个具有高性能处理机制的处理机:
-
相互对照的
-
多个关键的
-
纳米级的
-
多个线程同时的
-
安全可靠 :java有比较高的可靠性,因为其刚开始始为了设计消费类的电子产品。c++随时间的推移加上众多的努力研究就得到了java,但是它比c++可靠,能够避免大量的错误编程。主要使用java的开发是在网络应用方面,所以需要很高的安全性。如果java语言不能够严格的满足很高的安全性,那么将计算机联网进行资料共享,程序共享就有非常高的危险指数,很容易被木马病毒攻击系统,这对于所有用户来说都一样。java能够通过自己的预防木马等病毒机制对系统以及软件进行保护,能够强有力的将木马病毒程序阻拦在系统外部,形成一种保卫机制,为计算机的安全提供了强有力的保证
2.4 B/S结构
Browser/Server,当然为了方便,简化其名称为B/S,是网络潮流的带领下演化出来的比较时尚的,较为受欢迎的web架构。用户终端离不开的,广受大众欢迎的是能够连接Internet的浏览器。用户终端被集中在一起进行管理,所有重要的需求实现必须放在服务器端,使系统以及各种软件设计、运行和使用简单方便,清晰明了。客户机上只要安装一个浏览器,如谷歌浏览器或火狐浏览器,将oracle,mysql数据库安装到服务器端。各种浏览器与数据库之间的交互数据是通过网络服务进行的。
B/S的作用:因为C/S结构有着各种各样的问题,所以人们提出了基于C/S结构基础的新的三层结构系统应用结构,即B/S结构。B/S随着Internet的日新月异更新框架下,基于C/S架构的基础,加上众人不同的见解形成的一个新的架构。总体上来说,B/S结构同样是C/S结构中的一种特殊结构,它可以说是由经典的二层C/S结构经过实践发展得来的三层C/S结构一种新型结构在网络上使用的特殊典例。
B/S结构将日新月异的不断发展的网络浏览器技术纳入了自己的内容中:通过将浏览器内部的许多不同的脚本语言和网络化多媒体对象技术结合起来,仅仅使用普通浏览器就可以实现的强大功能,拒绝曾经的繁琐不简洁的软件,大大的降低了开发的开销。
B/S结构最突出的优点 :不需要任何专用的软件就能够在任何场所进行运行操作,只需有一台计算机.然后连上网络,就可以进行操作,客不需要在客户端安装任何东西、也不需要进行维护。系统的扩展非常容易。
B/S架构特点:
-
维护和升级方式简单 :如果软件是B/S框架的软件,那么只需在服务器进行管理,客户端不需要进行维护,因为客户端仅仅是浏览器,不能维护
-
缩减开支,拓宽选择方向。若软件是B/S框架,那么管理这个软件,仅仅将其安装在服务器(linux系统),这样就有很高的安全性
-
服务器在运行时产生的数据负荷相对比较严重。B/S框架所管理的软件,用户界面自己做的事情比较少,大多数都是服务器端在完成,当然此过程的完成必须使用万维网
3. 系统需求分析
需求分析,是在计算机系统中创建一个新的项目或者将计算机里现在存在的项目时进行修改时描述新项目的概念,目标:涵盖范围和实现功能时所需要进行的准备工作。在软件项目设计工程中,需求分析是必不可少的至关重要的过程之一。顾客有哪些具体需求是在这个过程中,准确无误的传递给分析系统的人员和软件设计师。因为他们必须在确定了用户的需求后,才能够从用户的需求中分析、设计、得到适合解决方案来适应新系统。明确用户需求的软件功能是软件工程中需求分析的工作。
需求分析的任务对现实世界中即将要进行操作的对象进行非常仔细认真的调查,并且保证非常了解原来系统在运行时的具体状态,准确的把握住用户的种种需要的功能及意见,之后设计基于原来系统基础的新系统功能。当然也需要明确系统需要的其他种种需求,因为软件的基本需求不单单只是功能需求,基本需求还包括其他几方面,一般来说,系统的设计与实现必须满足以下几个基本需求:
-
效果需要
-
性能供应
-
能够上市与有保障
-
解决异常
-
协接的处理
-
限制
-
反方向的处理
-
日后有几率产生的需要
需求分析步骤:
-
首先调查组织机构情况
-
然后调查各部门的业务活动情况
-
协助用户明确对新系统的各种要求
-
确定新系统的边界⑸分析系统功能⑹分析系统数据⑺编写分析报告
3.1 业务需求
能够实现对屏幕的随机截取,复制,保存以及添加文字等操作,便于用户对数据的处理。
3.2 用户需求
用户给设计者提出的软件上的需求,是对产品的的要求。
3.3 性能需求
指定的界面效果为系统效果,界面清晰,通俗易懂,操作简洁,用户使用时得心应手。
4. 系统设计
系统设计,现如今软件设计必不可少的physical design。依据用户在需求分析时提出的系统功能需求进行设计,最终确定系统的逻辑要求、性能模型,在满足用户需求,依据用户所使用的的系统环境,拿出一个方案使得此项目能够在电脑的web环境上运行,通过系统的设计建立软件项目的物理模块。
系统设计把软件项目按多级层次构造出来,并且将数据库和各个模块如何进行运行的流程构造出来作为此阶段的任务,系统设计是为了准确清晰的告诉所有用户软件系统的运行过程。系统设计又有:
-
总体设计
-
详细设计,这个具体任务步骤
总体设计是为了处理设计的项目,在每个模块的分界线处作出详细的设计并设计各个小内容内部的上下等级的结构并且构建相应的,适合的数据库;详细设计能够将已经划分好的各个小内容的运行过程加以控制,以及内部算法的编写和构建相对稳定的信息结构。
在进行系统设计时,通常有以下两种常用方法:
-
演绎法
-
归纳法
如果设计一个程序时,需要使用归纳法,那么就要对以下方面保持警惕:第一尽自己最大的能力去系统资料,当然是同类的,时间段不限,包括目前的和以前的资料;第二以这些资料为依据,进行系统构造,以及运行,在对结果进行分析,根据需求分析里用户的功能需求选择相应的系统功能,之后对部分的系统(当然必须是同类型)进行更改,最后生成一个相对比较理想的项目。
演绎法是由普通到特别的推理方法,即以客观的到处存在的规定和本质为出发点,依据众多的人员的广泛的知识和深邃的经历,从满足部分功能的模块集合中筛选出满足系统和用户所需要的功能的大量模块,最后以某种特定的方式将这些模块组合在一起,最终满足需求分析中用户所需要功能的软件项目被设计出来了。
4.1 系统的设计目标
自制Java截图工具将原来纯手动输入取代了,能够将所需要的信息以截图的形式保存下来,是操作更速度更方便。
该软件的功能:
-
随机获取屏幕指定区域
-
编辑(添加文字)截图
-
存储截图
4.2 系统功能整体设计
-
截图 :根据提示,随机获取指定屏幕区域
-
编辑 :在图片上添加文字
-
存储 :指定存储目录将截图进行存储
流程图 请看下图:
5. 系统设计实现
5.1 系统主界面实现
系统主界面有公共操作区、操作栏、界面布局三部分。
操作栏部分代码以及注解
java
JPanel all=new JPanel();
all.add(jp);
all.add(buttonJP);
通过getContentPane()方法得到窗体最下面那个面板然后往面板上添加控件c然后放到窗体的正中央位置;在窗体南方的位置上添加一个控件。
-
设置控件的尺寸宽500高400
-
设置窗口相对于指定组件的位置。 如果组件当前未显示,或者 c 为 null,则此窗口将置于屏幕的中央
-
设置组件的可见性,如果为true则组件可见
-
设置将该窗体永远在最前端显示
-
用户单击窗口的关闭按钮时程序执行的操作
界面布局注解
java
JPanel jp=new JPanel();
jp.add(system=new JRadioButton("系统界面",true));
system.addActionListener(this);
jp.setBorder(BorderFactory.createTitledBorder("界面风格"));
-
创建面板jp
-
给jp设置一个标题
-
标题名称为界面风格
-
将面板jp设置为系统界面
-
给其添加到动作监听器
页面效果代码及注解
public void actionPerformed(ActionEvent ae)
这里写一个动作开始执行的方法,参数是一个动作事件,先声明一个新的变量来获取这个事件源,然后在通过if语句判断动作类型:start执行的是开始截屏动作;cancel执行的是退出操作。如果事件源的值为system,则显示本软件的系统外观,这里需要捕获一下异常。
系统的外观如下图所示:
5.2 系统截图实现
设置起点终点横纵坐标,注解请看下面:
-
这里实现的是获取截取屏幕范围起点、终点的横纵坐标,然后将这个范围的边框设置为红色,画出对应截取屏幕的范围
-
对于纵坐标y,通过三目运算符判断,如果startY小于endY,那么y的值为startY,否则为endY
-
选择区域的变量设置为select,创建一个新的矩形框对象,他的宽是起点的横坐标减去终点的横坐标的绝对值;他的高是起点的纵坐标减去终点的纵坐标的绝对值
设置八点的位置,注解请看下面:
-
设置八个点的位置,先创建一个坐标对象,设置他的起始点和终点,起点就是x,y两个参数减去5个像素点,终点的位置在(10,10)
-
第二个点同上边的第一个点
-
然后是第三个点的位置,判断起点的横坐标是否大于终点的横坐标,如果结果为true,则起始点横坐标为startX,否则为EndX;终点的坐标依然是(10,10)
-
依次往后的点都如同第三个点
运行这个软件的效果:
通过if语句来判断代码是否开始执行,在这里调用前边写的方法,设置截图区域待确认状态下是青色,fillrect该函数用指定的画刷填充矩形,然后在设置截图边框颜色为红色,设置drawRect,将需要截图的矩形框坐标参数传入其中,设置他的颜色为黑色,然后设置drawString方法,在截图的时候输出一些提示用户的信息。
开始截图选择指定区域的4个顶点以及每条边的中点,展示图请看下图:
定义鼠标移动的处理方法,目的是初始化即将勾取的屏幕区域区域代码
创建带参数的私有的无返回值的方法doMouseMoved,进行鼠标移动勾选电脑屏幕的指定区域,在该方法中,如果所选的内容包括了me参数所获得的点的坐标,那么就将创建一个新的对象Cursor,并将该对象的值设定为MOVE_COURSOR,并将current的属性值移动;
如果不包括me,那么就将Status的值存入到一个数组中,进行循环,通过for循环,循环条件是声明一个局部的整型变量将i小于他的长度,i需要在一次执行结束进行自加一,再在循环里进行判断,如果它与me获得的值相同,那么将数组对应值传入到current在将此值设置到cursor里,在判断语句里写上return,若果满足则结束方法。循环结束后再将设置cursor,并将current的值设置为默认的。
将鼠标来回移动一次来获取此时此刻鼠标所处的位置,注解如下
-
通过八个方向(上北下南左西右东)决定x坐标与y坐标,并将它们进行刷新值
-
创建带参数的无返回值类型的initSelect方法,参数为state,即:private void initSelect(States state)
-
使用判断语句switch/case的判断方式进行多次判断,如果state状态为默认状态,那么将x坐标设置为0,y坐标设置为0,用break进行结束case
java
switch(state){
case DEFAULT:
currentX=0;
currentY=0;
break;
-
同理:对于不同的state将X,Y坐标进行设置;
-
若果state状态设置为东(EAST),那么将x坐标设置为三目运算并进行判断,如果x结束时的坐标大于x开始的坐标,就输出结束时的x坐标,反之就输出x开始时的坐标,y坐标设置为0,用break结束case;即:
java
currentX=(endX>startX?END_X:START_X);
currentY=0;
break;
-
若果state状态设置为西(WEST),那么将x坐标设置为三目运算并进行判断,如果x结束时的坐标大于x开始的坐标,就输出开始时的x坐标,反之就输出x结束时的坐标,y坐标设置为0,用break结束case
-
若果state状态设置为北(NORTH),那么将y坐标设置为三目运算并进行判断,如果y结束时的坐标小于y开始的坐标,就输出开始时的y坐标,反之就输出y结束时的坐标,x坐标设置为0,用break结束case
-
若果state状态设置为南(SOUTH),那么将y坐标设置为三目运算并进行判断,如果y结束时的坐标小于y开始的坐标,就输出结束时的y坐标,反之就输出y开始时的坐标,x坐标设置为0,用break结束case
-
若果state状态设置为东北(NORTH_EAST),那么将y坐标设置为三目运算并进行判断,如果y结束时的坐标小于y开始的坐标,就输出结束时的y坐标,反之就输出y开始时的坐标,x坐标设置为三目运算并进行判断,如果x结束时的坐大于x开始的坐标,就输出开始时的y坐标,反之输出结束时的y坐标,用break结束case
-
若果state状态设置为西北(NORTH_WEST),那么将y坐标设置为三目运算并进行判断,如果y结束时的坐标小于y开始的坐标,就输出开始时的y坐标,反之就输出y结束时的坐标,x坐标设置为三目运算并进行判断,如果x结束时的坐大于x开始的坐标,就输出开始时的x坐标,反之输出结束时的x坐标,用break结束case
-
若果state状态设置为东南(SOUTH_EAST),那么将y坐标设置为三目运算并进行判断,如果y结束时的坐标小于y开始的坐标,就输出结束时的y坐标,反之就输出y开始时的坐标,x坐标设置为三目运算并进行判断,如果x结束时的坐大于x开始的坐标,就输出结束时的x坐标,反之输出开始时的x坐标,用break结束case
-
若果state状态设置为西南(SOUTH_WEST),那么将y坐标设置为三目运算并进行判断,如果y结束时的坐标小于y开始的坐标,就输出结束时的y坐标,反之就输出y开始时的坐标,x坐标设置为三目运算并进行判断,如果x结束时的坐大于x开始的坐标,就输出开始时的x坐标,反之输出结束时的x坐标,用break结束case;当不属于以上情况时,将x的坐标设置为0,y坐标设置为0
在窗口显示所截图片注解
- 创建一个类Temp并且继承面板类最后实现鼠标监听接口:
java
private class Temp extends JPanel implements MouseListener,MouseMotionListener
在该类中声明几个对象,即:
-
私有的缓冲图片类型bi,即:
private BufferedImage bi;
-
私有的整型宽度以及长度,即:
private int width,height;
-
私有的整型x的起始坐标,y的起始坐标,x的结束坐标,y的结束坐标, 以及临时的x坐标,y坐标,即:
private int startX,startY,endX,endY,tempX,tempY;
-
私有的面板jf,即:
private JFrame jf;
-
私有的矩形select并初始化为0,即:
private Rectangle select=new Rectangle(0,0,0,0);
-
私有的cursor类型的cs,即:
private Cursor cs=new Cursor(Cursor.CROSSHAIR_CURSOR);
-
私有的状态current并设置为默认值,即:private States current=States.DEFAULT;`
-
私有的矩形数组,即:
private Rectangle[] rec;
-
公有的的静态常量x的开始坐标,即:
public static final int START_X=1;
-
公有的的静态常量x的结束坐标,即:
public static final int START_Y=2;
-
公有的的静态常量y的开始坐标,即:
public static final int END_X=3;
-
公有的的静态常量y的结束坐标,即:
java
public static final int END_Y=4;`
private Point p=new Point();
private boolean showTip=true;
创建公有的有参的Temp构造函数(参数为面板jf,缓冲图片bi,以及图片的长度,图片的宽度),即:
public Temp(JFrame jf,BufferedImage bi,int width,int height)
-
函数体将jf赋值给Temp的jf
-
bi赋值给Temp的bi
-
宽度赋值给Temp的宽度
-
高度赋值给Temp的高度
-
在将鼠标监听器加到Temp上
-
调用方法initRecs()
创建私有的无参无返回值类型的方法initRecs,方法体里创建新的矩形对象,for循环生成矩形的八点坐标;即:
private void initRecs()
创建公有的带参数的无返回值类型的方法paintComponent,方法体里进行划线,指定画线的起始以及终点坐标,即:public void paintComponent(Graphics g)
-
并将线设置成红色
-
划线x的起始坐标,y的起始坐标,x的终止坐标,y的起始坐标
-
画第二条线x的起始坐标,y的结束坐标,x的终止坐标,y的终止坐标
-
画第三条线x的起始坐标,y的起始坐标,x的起始坐标,y的终止坐标
-
画第四条线x的结束坐标,y的起始坐标,x的终止坐标,y的终止坐标
-
并将x坐标重新进行赋值,若x的起始坐标小于x的终止坐标,x值为x的起始坐标,反之为x的结束坐标
-
并将y坐标重新进行赋值,若y的起始坐标小于y的终止坐标,y值为y的起始坐标,反之为y的结束坐标
-
将新的矩形对象设置为(x,y,起始与终止的x坐标差的绝对值,起始与终止的y坐标差的绝对值)
-
设置x1的值为起始与终止坐标和的一半,设置y1的值为起始与终止坐标和的一半,将其设置为 (x1-2,startY-2,5,5);g.fillRect(x1-2,endY-2,5,5);g.fillRect(startX-2,y1-2,5,5);g.fillRect(endX-2,y1-2,5,5);g.fillRect(startX-2,startY-2,5,5);g.fillRect(startX-2,endY-2,5,5);g.fillRect(endX-2,startY-2,5,5);g.fillRect(endX-2,endY-2,5,5);
-
并且以矩形框设置第一个点的像素点 (x-5,y-5,10,10);
-
以矩形框设置第二个点的像素点 (x1-5,y-5,10,10);
-
以矩形框设置第三个点的像素点((startX>endX?startX:endX)-5,y-5,10,10);
-
以矩形框设置第四个点的像素点((startX>endX?startX:endX)-5,y1-5,10,10);
-
以矩形框设置第五个点的像素点((startX>endX?startX:endX)-5,(startY>endY?startY:endY)-5,10,10);
-
以矩形框设置第六个点的像素点(x1-5,(startY>endY?startY:endY)-5,10,10);
-
以矩形框设置第七个点的像素点(x-5,(startY>endY?startY:endY)-5,10,10);
-
以矩形框设置第八个点的像素点(x-5,y1-5,10,10);并将颜色设置为青色,再次将其设置为(p.x,p.y,170,20);设置颜色为红色,画框(p.x,p.y,170,20);设置颜色为黑色,在(p.x,p.y+15)写下内容“请按住鼠标左键不放选择截图区”。
如果成功截图,则进入截图展示页面,在多层菜单栏显示出第几张图片,图片正下方有四个选项:”复制到剪切板”,”存储”,”关闭”,”编辑”,展示效果请看下图:
操作栏四个按钮的设置,注解请看下面
-
声明一个私有的返回值为空的方法initPanel(),方法里声明四个按钮:“保存”、“复制到剪切板”、“关闭”、“编辑”
-
将他们分别赋值给四个变量使用:save、copy、close、update。然后分别将他们的菜单选项热键设置为:s、c、x、u
-
声明一个新的Jpanel类型的变量buttonPanel,将上边四个按钮添进去,然后分别给四个变量添加四个动作监听器
-
声明一个返回值为空的带动作事件参数的方法,是个动作监听的方法
-
在创建一个新的Object类型的变量source用于接收事件源
-
再通过if语句比对,来判断系统改做出设么样的动作:
- save调用对应的存储模块
- copy调用对应的拷贝模块
- close则调用关闭模块
5.3 截图的编辑实现
将要在截图上添加文字时,点击编辑,弹出编辑窗口,先将截图保存,之后选择截图进行添加文字,选择相关属性后点击添加。
主界面设置代码及注解
-
定义了一个私有的获取截图方法,并且声明一个截图对象,然后把他放到保存之后进行编辑(如:添加文字)
panel.setBorder(new TitledBorder(border, "设置",Font.LAYOUT_LEFT_TO_RIGHT,Font.LAYOUT_LEFT_TO_RIGHT, font));
-
里面运用一个布局管理器,部件如果想加入其中需要借助GridBagConstraints,利用组件的横纵坐标,并设置了组件所占行列数,也就是截图的宽度,还有截图的高度,当组件在其格内而不能撑满其格时,通过fill的值来设定填充方式:
java
gbc.insets = new Insets(5, 10, 5, 10);
gbc.fill = GridBagConstraints.BOTH;
gbc.gridwidth = 1;
JLabel label;
label = new JLabel("选择字体:", JLabel.RIGHT);
panel.add(label, gbc);
gbc.gridwidth = GridBagConstraints.REMAINDER;
fontCB = new JComboBox(Common.getAvailableFontFamilyNames());
panel.add(fontCB, gbc);
gbc.weightx = 1.0;
gbc.gridwidth = 1;
label = new JLabel("字体大小:", JLabel.RIGHT);
panel.add(label, gbc);
fontsizeTF = new JTextField("15");
panel.add(fontsizeTF, gbc);
label = new JLabel("水印位置:", JLabel.RIGHT);
panel.add(label, gbc);
gbc.gridwidth = GridBagConstraints.REMAINDER;
towardCB = new JComboBox(toward);
panel.add(towardCB, gbc);
gbc.gridwidth = 1;
label = new JLabel("字体样式:", JLabel.RIGHT);
panel.add(label, gbc);
fontstyleCB = new JComboBox(fontstyle);
panel.add(fontstyleCB, gbc);
label = new JLabel("水印颜色:", JLabel.RIGHT);
panel.add(label, gbc);
gbc.gridwidth = GridBagConstraints.REMAINDER;
fontcolorCB = new JComboBox(fontcolor);
panel.add(fontcolorCB, gbc);
gbc.weightx = 1.0;
gbc.gridwidth = 1;
label = new JLabel("透明度:", JLabel.RIGHT);
panel.add(label, gbc);
- 利用滑块,用户可以用来对截图进行大小滚动查看,如果再深加设置,还可以通过拖拽来调节一些信息,比如音量,背光亮度等,可以在代码中生成对应的输出口
java
panel.add(alphaSlider, gbc);
label = new JLabel("缩放大小:", JLabel.RIGHT);
panel.add(label, gbc);
gbc.gridwidth = GridBagConstraints.REMAINDER;
scaleCB = new JComboBox(scales);
panel.add(scaleCB, gbc);
gbc.gridwidth = 1;
label = new JLabel("水印内容:", JLabel.RIGHT);
panel.add(label, gbc);
gbc.gridwidth = GridBagConstraints.REMAINDER;
markTF = new JTextField("From CSDN Cannel_2020's blog");
panel.add(markTF, gbc);
gbc.gridwidth = 1;
label = new JLabel("执行进度:", JLabel.RIGHT);
panel.add(label, gbc);
gbc.gridwidth = GridBagConstraints.REMAINDER;
progressBar = new JProgressBar(0, 1);
progressBar.setStringPainted(true);
panel.add(progressBar, gbc);
Common.setComponentsFont(panel, font);
return panel;
部分按钮设置相关代码
java
protected enum ButtonStyle{
selectImage("选择图片"),
selectSavepath("选择存放路径"),
preview("预览效果"),
batching("批量添加"),
moreSetting("更多设置"),
drirect("添加文字");
private String name;
ButtonStyle(String name){
this.name = name;
}
public String getButtonName(){
return name;
}
}
界面以及按钮的展示图如下
实现添加文字的方法:
java
public static BufferedImage watermark(String filepath, Font font, Color color, int toward, String mark, float alpha,float scale) {
return watermark(new File(filepath), font, color, toward, mark, alpha, scale);
}
private static Image getFileImage(File file){
Image image = null;
if(Common.getFileExtension(file.getName()).equals("bmp")){
FileInputStream in = null;
try {
in = new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
image = BMPLoader.read(in);
}else{
try {
image = ImageIO.read(file);
} catch (IOException e) {
e.printStackTrace();
}
}
return image;
}
private static BufferedImage getMyBufferedImage(File file, float scale){
Image image = getFileImage(file);
BufferedImage buffImg = null;
try {
buffImg = javax.imageio.ImageIO.read(file);
} catch (IOException e) {
e.printStackTrace();
}
// 获取到了截图的宽度和高度,并把它放到了符合的类型中。排除非空情况
buffImg = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = buffImg.createGraphics();
g2d.setColor(Color.white);
g2d.fillRect(0, 0, width, height);
g2d.drawImage(image, 0, 0, width, height, null);
if(scale != 1.0f){
BufferedImage filteredBufImage =new BufferedImage((int) (width*scale), (int) (height*scale),BufferedImage.TYPE_INT_RGB);
AffineTransform transform = new AffineTransform();
transform.setToScale(scale, scale);
AffineTransformOp imageOp = new AffineTransformOp(transform, null);
imageOp.filter(buffImg, filteredBufImage);
buffImg = filteredBufImage;
}
return buffImg;
}
private static Point getStringWidthAndHeight(String str, Graphics2D g2d){
FontMetrics fontMetrics = g2d.getFontMetrics();
int stringWidth = fontMetrics.stringWidth(str);
int stringAscent = fontMetrics.getAscent();
return new Point(stringWidth, stringAscent);
}
private static void printWatemark(float alpha, String mark, Graphics2D g2d, int x, int y){
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP,alpha));
g2d.drawString(mark, x, y);
}
private static Point calculate(Point p, int toward, double imgWidth, double imgHeight){
int x = 0, y = 0;
if(toward == UP_LEFT_TO_DOWN_RIGHT){
double hypotenuse = Math.sqrt(Math.pow(imgWidth, 2) + Math.pow(imgHeight, 2));
double tempX = (p.x/2) * imgWidth/hypotenuse;
double tempY = (p.x/2) * imgHeight/hypotenuse;
x = (int) (imgWidth/2 - tempX);
y = (int) (imgHeight/2 - tempY);
}else if(toward == UP_RIGHT_TO_DOWN_LEFT){
double hypotenuse = Math.sqrt(Math.pow(imgWidth, 2) + Math.pow(imgHeight, 2));
double tempX = (p.x/2) * imgWidth/hypotenuse;
double tempY = (p.x/2) * imgHeight/hypotenuse;
x = (int) (imgWidth/2 - tempX);
y = (int) (imgHeight/2 + tempY);
}else if(toward == DOWN_RIGHT){
x = (int)imgWidth - p.x;
y = (int)imgHeight - p.y;
}else if(toward == DOWN_LEFT){
x = 5;
y = (int)imgHeight - p.y;
}else if(toward == UP_RIGHT){
x = (int)imgWidth - p.x - 3;
y = p.y;
}else if(toward == UP_LEFT){
x = 5;
y = p.y;
}else if(toward == MIDDLE){
x = (int)imgWidth/2-p.x/2;
y = (int)imgHeight/2;
}
return new Point(x, y);
}
public static BufferedImage watermark(File file, Font font, Color color, int toward, String mark, float alpha, float scale){
BufferedImage buffImg = getMyBufferedImage(file, scale);
Graphics2D g2d = buffImg.createGraphics();
g2d.setRenderingHints(getMyRenderingHints());
g2d.setFont(font);
g2d.setColor(color);
double imgWidth = buffImg.getWidth();
double imgHeight = buffImg .getHeight();
Point p = getStringWidthAndHeight(mark, g2d);
Point p1 = calculate(p, toward, imgWidth, imgHeight);
if(toward == UP_LEFT_TO_DOWN_RIGHT){
g2d.rotate(Math.atan(imgHeight/imgWidth), p1.x, p1.y);
}else if(toward == UP_RIGHT_TO_DOWN_LEFT){
g2d.rotate(-Math.atan(imgHeight/imgWidth), p1.x, p1.y);
}
printWatemark(alpha, mark, g2d, p1.x, p1.y);
g2d.dispose();
return buffImg;
}
生成添加文字之后的图片代码
java
private void generate(BufferedImage buffImg, String savePath) {
int temp = savePath.lastIndexOf(".") + 1;
try {
ImageIO.write(buffImg,savePath.substring(temp), newFile(savePath));
} catch (IOException e1) {
e1.printStackTrace();
}
}
预览截图的相关代码及注解
java
private static final long serialVersionUID = 1L;
public PreviewImage(final BufferedImage buffImg) {
int imgHeight = buffImg.getHeight();
int imgWidth = buffImg.getWidth();
JPanel imgPanel = new JPanel() {
private static final long serialVersionUID = 6246862165441423926L;
super.paintComponent(Graphics g)
// 通过调用父类的绘制事件,并重写父类的绘制函数,调用了父类的相应方法,并且父类的方法中还有可能包含一些默认操作,因为有些操作是必须的,绘制的时候并且,进行了非空判断,对于绘制的要求
if (buffImg != null) {
g2d.drawImage(buffImg, 0, 0, this);
}
};
setSize(imgWidth, imgHeight);
Common.setCentered(this)
setUndecorated(true);
addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
dispose();
}
}
});
DragPicListener listener = new DragPicListener(this);
addMouseListener(listener);
addMouseMotionListener(listener);
add(imgPanel);
setVisible(true);
}
预览效果展示图
5.4 截图的存储实现
获取截取完成后,单击按钮:”复制到剪切板”,可以看到提示框,内容写的是”已成功复制到剪切板”,单击鼠标左键确认,即成功的将截图复制到剪切板中.
请看下述代码及注解:
- 定义此信息框对于用户操作的一个判断这个方法的参数,在对于复制截图到剪切板中的时候,如果图片为空的话,就提示用户为空,否则就成功复制到剪切板。并添加了捕获异常的控件,如果出现错误,就提示用户说 复制到系统粘贴板出错,控件消失。
java
Transferable trans = new Transferable(){ // 内部类
public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[] { DataFlavor.imageFlavor };
}
public boolean isDataFlavorSupported(DataFlavor flavor) {
return DataFlavor.imageFlavor.equals(flavor);
}
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
if(isDataFlavorSupported(flavor))
return image;
throw new UnsupportedFlavorException(flavor);
}
};
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(trans, null);
JOptionPane.showMessageDialog(this,"已复制到系统粘帖板!!");
}catch(Exception exe){
exe.printStackTrace();
JOptionPane.showMessageDialog(this,"复制到系统粘帖板出错!!","错误",JOptionPane.ERROR_MESSAGE);
}
Transferable是一个用于不同存储介质间数据传输的接口,必须通过此接口才能实现两个存储介质间的数据传输,此接口还封装了数据传输过程的具体方式。主要的方法有:(1)Object getTransferData(DataFlavor flavor) 返回一个Transferable数据传输对象,参数则用来指定数据传输过程中的数据传输格式;(2)DataFlavor[] getTransferDataFlavors() 用来返回一个DataFlavor数组;(3)Boolean isDataFlavorSupported(DataFlavor flavor) 判断当前用来传输数据的Transferable对象的数据传输格式是否符合DataFlavor对象中的几种格式(stringFlavor,imageFlavor,plainTextFlavor)。
Transferable对象是用来传输数据的,DataFlavor则封装于该对象中用来指定该对象传输数据时的数据的格式。
展示图请看下图
新建一个word文档测试文件是否复制到剪切板中去,展示图请看下面:
新建的文档 | 文档里的内容 |
结果显示成功的复制到剪切板中,此模块成功的实现了功能。
获取图片后,单击按钮“存储”,进行保存截图,这时将会让你选择截图的存储位置,还要选择截图所需要保存的格式,如静态的(bmp,jpeg,png),动态的(gif)等格式。
请看下述注解:
-
定义了一个保存方法,并在方法中进行异常的捕获,开头的时候进行了保存图片的获得判断,如果它没有读到图片信息的话,就提示用户图片不能为空,否则就跳出这个方法,然后引入了保存不同类型,不同格式的图片,并一一进行了引入,传人了this这个状态信息,,并设置了格式过滤器,如果是静态的图片就选择相对应的保存格式,这些格式的判断方法在代码中已经一一进行了判断,保存格式有jpeg,png,bmp,如果是动态的图片,就需要就需要保存为gif的格式,并且如果再运行过程中,如果抓到异常,要在控制台打印错误信息!!
-
其中需要设置格式过滤器,选择所需要的格式:
-
这是向用户可选择的文件过滤器列表添加一个过滤器,如果应该保存该文件就返回true,否则返回false,用户点击保存按钮的时候,由继承了这个过滤器类上门的UI调用此方法,但要确保指定的文件是可见的,不是隐藏的,并要通知对此事件感兴趣的所有侦听器,最后返回一个bmp对象,给用户一个反馈
-
这是通过继承filechooser类来让用户最终确定保存的格式,给用户提供了一种简单的机制,并返回用户保存路径,通过用户选择的保存格式,通过它相对应的过滤器,如保存GIG格式的过滤器privateclassGIFfilter并且写了一个暂时类,来显示当前的屏幕图像,TEMP,引证了过滤器的判断,最终实现jpeg,bmp,png,和动态图片gif,的保存
展示图请看下面
点击保存后,进入到目录下进行查看,查看是否保存成功。
经查看已经成功存储。
6. 系统测试
系统测试,即整合起来众多的元素展开测试,测试他们集合在一起能否正常运行,这些内容包括各种方面种种信息比如,系统软件和应用软件、电脑硬件、外接鼠标键盘等硬件、Internet等方面结合起来,进行系统测试,进行信息系统的各种组装测试和确认测试,系统测试是针对整个产品系统进行的测试,目的是验证系统是否满足了需求规格的定义,找出与需求规格不符或与之矛盾的地方,从而提出更加完善的方案。
6.1 截图软件系统测试
进行系统测试,测试此项目的软件运行是否达到目标需求,分别进行截图保存的测试,复制的测试,编辑(添加文字)的测试。
6.1.1 保存选项测试
成功获取图片之后,图片不失效提示“成功存储”,反之,提示“未能成功存储”,若未写图片名进行存储那么不能存储。
截图保存
成功保存
经运行测试,证明保存截图功能没有问题,已经成功实现。
6.1.2 复制到剪切板选项测试
成功获取图片之后,图片不失效提示“成功存储”,反之,提示“未能成功存入到剪切板”。
复制到剪切板
复制到剪切板成功
经过运行测试,证明复制到剪切板功能没有问题,已经成功实现。
6.1.3 编辑选项测试
在图片上添加文字,成功显示文字则编辑成功,反之,失败。
添加文字
添加文字成功
经过运行测试,证明添加文字功能没有问题,已经成功实现。
6.2 测试评价
结果证明需求的功能已经被实现,满足了用户截图,存储,编辑功能,不足之处是功能仍然有些欠缺,系统外观的美化需要进一步去调整,期望以后加以调整完善。测试未能发现严重缺陷和漏洞。
参考文献
[1] 程新党;程强;黄河涛.基于Windows图形驱动的屏幕截图技术. 南阳师范学院计算机系;南阳师范学院计算机系 河南南阳473061;河南南阳473061
[2] 魏威.利用C#和GDI+制作屏幕截图小软件.图形图像处理(2013年23期)
[3] 张之.QQ屏幕截图的秘密.CFan加油站 (2007年10期).
[4] 吴颂涛;张瑶.电视直播截屏系统及方法. 电视中心 (2014年12期)
[5] 王志军.善用PowerPoint的截图工具. 办公与应用 (2013年12期).
[6] 何昭友;.WINCE下屏幕截图与为徒转换.瓦斯灾害监控与应急技术国家重点实验室;中国煤炭科工集团重庆研究院;.
[7] 庞赫;基于Windows图形驱动的屏幕截图技术.石家庄市外国语学校: 新技术(2015年06期)
[8] Bonnie Imler,Michelle Eichelberger. Using screen capture to study user research behavior. 1Penn State Altoona, Altoona, Pennsylvania, USA; Genesee Community College, Batavia, New York, USA. Library Hi Tech, 2011, Vol.29 (3)
[9] Pekka Makkonen,Kerstin Siakas,Shakespeare Vaidya. Teaching knowledge management by combining wikis and screen capture videos. 1Department of Information Systems, University of Jyväskylä, Jyväskylä, Finland; Department of Informatics, Technological Educational Institution of Thessaloniki, Thessaloniki, Greece; United Nations Development Program, Ottawa, Canada. Campus-Wide Information Systems, 2011, Vol.28 (5)
[10] Susan Goodwin. Using screen capture software for web site usability and redesign buy-in. 1Texas A&M University Libraries, Sterling C. Evans Library, Instructional Services Department, College Station, Texas, USA. Library Hi Tech, 2005, Vol.23 (4)
参考文献
- 基于J2EE的引航管理信息系统的研究及应用(大连海事大学·代平)
- 基于B/S架构的工程图纸管理系统(辽宁科技大学·孔庆涛)
- J2EE环境下通用数据操作框架的研究(山东大学·杨媛媛)
- 基于J2EE的高职院校在线考试系统(内蒙古大学·赵源)
- 利用JSP技术开发基于WEB的人事工资管理系统(大连铁道学院·杜欣然)
- 基于Spring框架的数据集成与转换研究(中国海洋大学·刘扬)
- 基于Web的工程图纸管理系统的研究与实现(大连理工大学·王晓军)
- 基于J2EE的学生网上考试系统的设计与实现(华南理工大学·李占新)
- 在线培训系统的设计与实现(云南大学·杨通)
- 多维数据展现开发工具的设计与实现(山东大学·展鹏)
- 在线培训系统的设计与实现(云南大学·杨通)
- 建筑设计院图档管理系统的设计与实现(吉林大学·时淮龙)
- 基于J2EE/XML的分布式WebGIS平台系统设计与实现(西北大学·郑建功)
- 基于MapXtreme Java Edition的WebGIS应用开发(北京邮电大学·张博)
- 多维数据展现开发工具的设计与实现(山东大学·展鹏)
本文内容包括但不限于文字、数据、图表及超链接等)均来源于该信息及资料的相关主题。发布者:毕设向导 ,原文地址:https://bishedaima.com/yuanma/35222.html