“图片差异检查”辅助工具(即“大家来找茬”辅助工具)源码分享


忽然心血来潮,想写一个辅助工具,让朋友们在“大家来找茬”之类的游戏中可以少费一些眼睛。
在Java方面我是新手,在折腾了一段时间后,终于还是写出了一个基本可用的测试版程序。详细的使用方法和使用效果,可以参见这个博客http://blog.sina.com.cn/s/blog_9245c9e0010136by.html
这个小程序也可以通过这种方法进行下载——CSDN的资源中搜索“自己用Java写的图像比较器”

这里我想和大家分享一下编写过程中遇到的问题,同时也把源代码公布于此,朋友们可以批评指正。


思路:

类似于“大家来找茬”的游戏,可以用这样的思路来破解:
1.截取第一幅图A
2.截图第二幅图B
3.通过逐个像素比较,将差异部分显示出来

但是实际编程时,发现了问题——你很难确保两次截图时的大小位置完全一致!

开始我还想着通过算法来判断,后来想到了一个好解法——机器是死的,人是活的啊。所以步骤2可以调整为“将第一幅截图显示在窗口中,通过人工移动窗口,将两幅图叠加”



源码分享:

最终的程序由三个类构成

myScreenCapture:实现截图功能,并把截图的数据传给PicCheckFrame
PicCheckFrame:创建一个新窗口,用户可以手工移动该窗口,实现两幅图片的叠加

PicCheckPanel:实现步骤3中的算法,显示两幅图片的差异



/**
 * @author LiuCC
 * 本类实现了截屏功能
 * 即在屏幕上选择一块区域(如大家来找茬中的A图片),然后将选定的区域交给另一个类去处理比较
 *
 */
public class myScreenCapture extends JFrame{
	
	private JButton screenCaptureButton, exitButton;

	/**
	 * 程序入口
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		new myScreenCapture();  //非常简单,直接new即可
	}
	
	/*
	 * 构造函数
	 * 一个截图按钮,一个退出按钮
	 */
	public myScreenCapture(){
		super("Picture Checker by LiuCC");
		initWindow(); //把这个窗体构建函数拆开拆开以便于修改和复用		
	}
	
	private void initWindow(){
		
		setLayout(new FlowLayout()); //若无此句,只会显示最后一个加入的按钮
		
		screenCaptureButton = new JButton("截图");
		add(screenCaptureButton);
		
		//设置截图的处理函数
		screenCaptureButton.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent e) {
				// 1.利用Toolkit获得全屏幕大小
				// 2.利用Robot将截屏结果放入BufferedImage
				// 3.建立一个全屏幕大小的Frame,里面放置一个自定义的Panel以做为鼠标截图的显示区域
				try{
					Toolkit tk = Toolkit.getDefaultToolkit();
					Dimension screenSize = tk.getScreenSize();
					Rectangle screenRectangle = new Rectangle(0,0,screenSize.width,screenSize.height);
					
					Robot myRobot = new Robot();
					BufferedImage screenBufferedImage = myRobot.createScreenCapture(screenRectangle);
					
					
					JFrame screenFrame = new JFrame();
					screenFrame.getContentPane().add(new scrCapturePanel(screenFrame, screenBufferedImage, 
							screenSize.width, screenSize.height));
					
					
					screenFrame.setUndecorated(true); //如此设置,可以让用户感觉不到该Frame的存在
					screenFrame.setAlwaysOnTop(true);
					
					//要有如下两句Frame才会显示
					screenFrame.setVisible(true);
					screenFrame.setSize(screenSize);
					
				}catch(Exception robotException){
					robotException.printStackTrace();
				}
				
				
			}
		});
		
		exitButton = new JButton("退出");
		add(exitButton);
		exitButton.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent e) {
				// TODO Auto-generated method stub
				System.exit(0);
			}
		});
		
		//有了以下两句,窗体才能显示
		//setSize(220,80);
		pack();
		setVisible(true);
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}
	

	
	
	/**
	 * 建立一个与屏幕一致的Panel,主要是为了显示个性化的鼠标,以及通过鼠标拉出选择框
	 * @author LiuCC
	 *
	 */
	private class scrCapturePanel extends JPanel implements MouseListener, MouseMotionListener{

		private JFrame parentFrame;
		private int width, height;
		private BufferedImage screenImage;
		private BufferedImage selectImage;
		
		private int startX, startY, endX, endY; //在一次选择过程中涉及到的坐标
		private Rectangle selectRectangle = new Rectangle(0,0,0,0);
		
		private int tempX, tempY;
		
		private Cursor specialCursor;
		
		
		/**
		 * 构造函数
		 * @param
		 * 参数依次为:父窗口; 在Panel中显示的画面; 宽,高
		 */
		public scrCapturePanel(JFrame parentFrame, BufferedImage showedImage, int width, int height){
			this.parentFrame = parentFrame;
			this.screenImage = showedImage;
			this.width = width;
			this.height = height;
			
			//设置一个特别的鼠标,以标识截图状态
			Image cursorImage = Toolkit.getDefaultToolkit().createImage(this.getClass().getResource("/image/icon.png"));
			specialCursor = Toolkit.getDefaultToolkit().createCustomCursor(cursorImage, new Point(0,0), "special icon");
			setCursor(specialCursor);
			
			//关联鼠标动作
			addMouseListener(this);
			addMouseMotionListener(this);
		}
		
		
		/**
		 * JPanel绘制的关键函数
		 */
		public void paintComponent(Graphics g){
			g.drawImage(screenImage, 0, 0, width, height, 0, 0, width, height, this);
			
			//以红框标识鼠标圈定的范围
			g.setColor(Color.RED);
            g.drawLine(startX,startY,endX,startY);
            g.drawLine(startX,endY,endX,endY);
            g.drawLine(startX,startY,startX,endY);
            g.drawLine(endX,startY,endX,endY);
            
            //记录选定区域,考虑了反向拖拽画框的情况
            int x=startX<endX?startX:endX;
            int y=startY<endY?startY:endY;
			selectRectangle = new Rectangle(x,y,Math.abs(startX-endX),Math.abs(startY-endY));	

		}
		
		
		@Override
		public void mousePressed(MouseEvent e) {
			// TODO Auto-generated method stub
			tempX = e.getX();
			tempY = e.getY();

		}
		


		@Override
		public void mouseReleased(MouseEvent e) {
			// TODO Auto-generated method stub
			//Do Nothing!						
		}
				
		
		@Override
		public void mouseDragged(MouseEvent e) {
			// TODO Auto-generated method stub
			startX=tempX;
            startY=tempY;
            endX=e.getX();
            endY=e.getY();

            repaint(); //重绘图像,以显示动态效果
		}
		

		@Override
		public void mouseMoved(MouseEvent e) {
			if(selectRectangle.contains(e.getPoint())){
				setCursor(new Cursor(Cursor.MOVE_CURSOR));	//在选定区域内外,采用不同的鼠标样式以示区别
			}else{
				setCursor(specialCursor);
			}
		}
			
	

		@Override
		public void mouseClicked(MouseEvent e) {
			if(2==e.getClickCount()){
				//在选定区域内双击表示选定该区域
				if(selectRectangle.contains(e.getPoint())){
					selectImage = screenImage.getSubimage(selectRectangle.x, selectRectangle.y, selectRectangle.width, selectRectangle.height);
					parentFrame.dispose();
										
					//供测试用,生成一个图片,考察效果
					try {
						ImageIO.write(selectImage, "jpg", new File("./test1.jpg"));
					} catch (IOException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
					
					//接下来启动对比图片所需的Frame
					new PicCheckFrame(selectImage);
					
					
				}else{//在选定区域外双击,则重新选择
					startX=0;
					startY=0;
					endX=0;
					endY=0;
					selectRectangle=new Rectangle(0,0,0,0);
					repaint();
				}
			}
			
		}

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

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

第二个类

/**
 * 建立一个JFrame的子类,包含两个区域——图片显示区域和一个退出按钮
 * @author LiuCC
 * 
 */
public class PicCheckFrame extends JFrame{

	
	private int relativeX, relativeY;  //表示鼠标按下时,该点与图像界面原点的相对位置
	private int absoluteX, absoluteY;  //鼠标拖拽时,鼠标所在绝对位置
	private int setX, setY;				//拖动时,鼠标相对于图像界面原点的位置
	private boolean mousePressedNow=false;  //true表示鼠标左键按下
		
	private PicCheckPanel pCkPanel;
	private JButton exitButton;
	
		
	/**
	 * 构造函数
	 * @param get
	 */
	public PicCheckFrame(BufferedImage capturedImage){
			
		pCkPanel = new PicCheckPanel(capturedImage);
		setUndecorated(true);	//这样设置可以免去调整标题栏的麻烦
								//我至今不知道如果加入了标题栏,在之后的函数中如何处理像素点相对位置
								//如果有朋友搞懂了,可以留言告诉我
		
		setLayout(new BorderLayout());
		add(pCkPanel, BorderLayout.CENTER);
		
		exitButton = new JButton("Exit");
		add(exitButton, BorderLayout.SOUTH);
		exitButton.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent arg0) {
				// TODO Auto-generated method stub
				System.exit(0);
			}
		});
		
		
		addMouseListener(new mouseFunction());
		addMouseMotionListener(new mouseMotionFunction());
		
		setSize(capturedImage.getWidth(), capturedImage.getHeight());
		setVisible(true);
	}
	
	
	/**
	 * 
	 * @author LiuCC
	 *
	 */
	private class mouseFunction extends MouseAdapter{
		@Override
		public void mousePressed(MouseEvent e){
			if(1==e.getClickCount()){
				mousePressedNow=true;
				relativeX = e.getX();
				relativeY = e.getY();  //e带来的是,鼠标对于当前Component的相对位置
			}
		}
		
		@Override
		public void mouseReleased(MouseEvent e){
			mousePressedNow=false;
		}
	}
	
	
	private class mouseMotionFunction extends MouseMotionAdapter{
		@Override
		public void mouseDragged(MouseEvent e){
			if(true==mousePressedNow){
				//说明此时在拖动窗口
															
				int tmpX = PicCheckFrame.this.getLocationOnScreen().x;
				int tmpY = PicCheckFrame.this.getLocationOnScreen().y;
				
				absoluteX = tmpX + e.getX();
				absoluteY = tmpY + e.getY();
				
				//如果SetLocation时用absoluteX,会让鼠标回到Component的原点,这样用户体验不好,所以还应该做如下修正
				setX = absoluteX - relativeX;
				setY = absoluteY - relativeY;
				
				pCkPanel.setPicCheckPanelLocation(tmpX, tmpY);  //为什么要传tmpX,而不是setX?这是我试出来的
																//原理我也不是很确定,如果有朋友弄清楚了,请指出
				setLocation(setX, setY);
			}
		}		
	}
}



第三个类

/**
 * 这个类完成了图像的对比工作
 * Frame传给本类一个Image,即基准的Image
 * 本类同时做了一次截屏,在Frame移动的过程中,本类不断地比较下方的图片和基准图像
 * 两者不相同的地方以蓝色显示
 * 
 * 由此可以大致判断出两幅图像的区别
 * @author LiuCC
 *
 */
public class PicCheckPanel extends JPanel{
	
	private int width, height;  
	private int positionX=0, positionY=0;
	
	private BufferedImage screenImage;
	private BufferedImage capturedImage;
	private BufferedImage showedImage;
	
	private Robot myRobot;
	
	private int scrR, scrG, scrB;
	private int capR, capG, capB;
	
	/**
	 * 构造函数
	 */
	public PicCheckPanel(BufferedImage inImage){
		this.capturedImage = inImage;
		this.width = inImage.getWidth();
		this.height = inImage.getHeight();
				
		screenImage = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);//
		showedImage = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
				
		try{
			myRobot = new Robot();
		}catch (Exception e) {
			// TODO: handle exception
		}
				
		screenImage=myRobot.createScreenCapture(new Rectangle(0, 0, Toolkit 
				.getDefaultToolkit().getScreenSize().width, Toolkit 
				.getDefaultToolkit().getScreenSize().height));				
	}
	
	
	
	/**
	 * 设置Panel的位置
	 * @param posX
	 * @param posY
	 */
	public void setPicCheckPanelLocation(int posX, int posY){
		this.positionX = posX;
		this.positionY = posY;
		validate();
		repaint();		//这个是关键的一步,会调用paintComponent函数
	}
	
	/**
	 * 设置Panel的大小
	 * @param width
	 * @param height
	 */
	public void setPicCheckPanelSize(int width, int height){
		this.width = width;
		this.height = height;
		repaint();      //这个是关键的一步,会调用paintComponent函数
	}
	
	
	/**
	 * 最关键的,绘图函数
	 * 对传入的图片capturedImage 与 当前在JPanel下方的图片(ScreenImage的一部分)做对比
	 * 逐个像素比较,若相同则显示,若不相同则显示出蓝色
	 * 
	 * 似乎完全相同是不可能的,我调试了多次,设置了一个阈值VAL,目前总体效果不错
	 * 但是图片叠加后,特别是边缘部分还是不怎么干净,如朋友们有更好的方法,也请告诉我
	 */
	@Override
	public void paintComponent(Graphics g){
		super.paintComponent((Graphics2D)g);
		
		for(int i=0; i<width; i++){
			for(int j=0; j<height; j++){
				
				//之前试过把 getRGB 的值直接进行比较的,但效果有问题,只好拆成R/G/B分别比较了
				Object scrData = screenImage.getRaster().getDataElements(i+positionX, j+positionY, null);
				scrR=screenImage.getColorModel().getRed(scrData);
				scrG=screenImage.getColorModel().getGreen(scrData);
				scrB=screenImage.getColorModel().getBlue(scrData);
				
				Object capData = capturedImage.getRaster().getDataElements(i, j, null);
				capR=capturedImage.getColorModel().getRed(capData);
				capG=capturedImage.getColorModel().getGreen(capData);
				capB=capturedImage.getColorModel().getBlue(capData);
				
				int VAL = 22; //阈值为什么选这个?只能说,这是我试验出来的
				
				//下面不得不采用了“差别不大就算相同”的判断方法
				if(Math.abs(scrR-capR)<=VAL&&Math.abs(scrG-capG)<=VAL&&Math.abs(scrB-capB)<=VAL)
					showedImage.setRGB(i, j, capturedImage.getRGB(i, j));
				else {
					showedImage.setRGB(i, j, Color.blue.getRGB());
				}
			}
		}
		
		
		g.drawImage( 
				showedImage, // 要画的图片 
				0, // 目标矩形的第一个角的x坐标 
				0, // 目标矩形的第一个角的y坐标 
				width, // 目标矩形的第二个角的x坐标 
				height, // 目标矩形的第二个角的y坐标 
				0, // 源矩形的第一个角的x坐标 
				0, // 源矩形的第一个角的y坐标 
				width, // 源矩形的第二个角的x坐标 
				height, // 源矩形的第二个角的y坐标 
				this );		
	}
}


功能基本实现,但是还是有一些地方存在疑点,请朋友们指正


如:在第二个类中

setUndecorated(true);	

这样设置可以免去调整标题栏的麻烦,我不知道如果加入了标题栏,在之后的函数中如何处理像素点相对位置(即,我不知道如何获得标题栏的宽度),如果有朋友搞懂了,可以留言告诉我


还有一些问题就写在注释当中了,朋友们发现有问题,请留言提醒我噢。








### 回答1: "大家来找"是一款经典的游戏,也有相关的Android源码提供。这款游戏的源码可以通过多种途径获得,包括从互联网上搜索下载,或者找到相关的开源项目进行学习和使用。 "大家来找"的Android源码通常涵盖了游戏的各个方面,如界面设计、游戏逻辑、图像处理等。这样的源码可以供开发者参考学习,了解游戏开发的基本流程和技术要点。 通过阅读这个源码,我们可以学习到如何构建一个基于Android平台的游戏应用。例如,源码中会包含一些基本的布局文件,用于展示游戏的主界面和各个菜单选项等。同时,源码中也会包含游戏逻辑的实现,如游戏关卡的设置、计分系统的设计等。此外,源码通常还包含了与用户交互的代码,比如通过触摸屏幕来选择和识别不同的差异。 当我们有了这个源码的基础上,我们可以对其进行进一步的修改和扩展。比如,可以根据自己的需求定制游戏界面的样式和布局,或者添加更多关卡和难度等级。同时,我们还可以加入其他的功能,如储存游戏进度、增加游戏道具等,以增强游戏的趣味性和可玩性。 总而言之,"大家来找"的Android源码为我们提供了一个学习和开发Android游戏的良好起点。通过深入研究源码,我们可以了解游戏开发的工作流程和一些重要的技术要点。同时,我们也可以根据自己的想法和需求,对源码进行修改和扩展,以满足个性化的游戏开发需求。 ### 回答2: 《大家来找 android源码》是一款寻找差异的游戏,主要是给用户提供了两张图片,让用户在两个图中找出不同之处。如果要开发这款游戏的Android版本,我们可以通过编写相应的源码来实现。 首先,我们需要创建一个游戏场景,在场景中放置两张图片并将它们显示给用户。可以使用ImageView来展示图片,在XML文件中定义两个ImageView,并设置图片路径。然后,在Java代码中使用findViewById方法获取ImageView的实例,然后使用setImageResource方法设置图片资源。 接下来,我们需要为图片添加点击事件,当用户点击某个图片时,我们需要判断用户是否点击了不同之处。可以通过比较两个图片的像素点来实现。可以通过Bitmap对象来获取每个像素点的颜色值,然后将两个图片相同位置的像素点进行对比。 在判断完用户点击的图片之后,我们需要给用户展示正确或者错误的提示。可以通过Toast来实现,当用户点击了不同的图片时,弹出一个提示称用户答案错误,如果用户点击的是相同的图片,则弹出一个提示称用户答案正确。 同时,我们可以增加难度,让用户在规定的时间内找出全部的不同之处。可以使用Handler类来实现定时的功能,在游戏开始时开启计时,在规定的时间到了之后,关闭计时并弹出用户的答题结果。 总结起来,要开发《大家来找 android源码》游戏,我们需要通过编写源码来实现图片的加载、点击事件的监听、像素点的比较、提示信息的展示等功能。这样,用户就可以通过观察两张图片差异,寻找出不同之处。 ### 回答3: 大家都喜欢找游戏,它能够锻炼我们的观察力和思维能力。而如果我们想要了解找游戏背后的代码,就要研究它的源码。在Android平台上,也有很多找游戏的源码可以研究和学习。 为了找到适合的Android找游戏源码,我们可以通过搜索引擎或者开源代码共享网站查找。一般来说,开源的代码质量较高,可以为我们提供更好的学习材料。我们可以选择一款评价较高、下载量较多的找游戏源码进行研究。 在研究源码时,我们需要注意以下几个方面: 首先,我们需要了解游戏的整体结构和逻辑,包括游戏的启动界面、主界面、关卡选择界面以及游戏内部的图片显示和答案验证等功能。 其次,我们需要学习游戏中图片加载和显示的方法。找游戏中,一般需要加载两张相似的图片,并在图片上标示找的位置。我们可以学习如何使用Android的图像处理库,如Picasso或Glide,来加载和显示图片。 另外,我们还需要学习如何实现找功能。这包括如何判断用户所点击的区域是否为找的位置,以及如何给用户提供答案和奖励。 通过学习找游戏的源码,我们可以获得很多宝贵的经验和技巧。同时,我们也可以将这些技术应用到其他项目中,提升自己的开发能力。因此,研究找游戏的源码是一种有益的学习方式。希望大家能够找到适合自己的源码,并在研究中有所收获。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值