您的位置:首页 > 其它

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

2012-04-19 10:35 495 查看
忽然心血来潮,想写一个辅助工具,让朋友们在“大家来找茬”之类的游戏中可以少费一些眼睛。

在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);


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

还有一些问题就写在注释当中了,朋友们发现有问题,请留言提醒我噢。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: