Java模拟按键精灵(四)-屏幕搜索

楮阳
2023-12-01

功能介绍

  本篇是介绍屏幕搜索功能,屏幕搜索主要是指在屏幕指定区域内,搜索特定的图形或特定的颜色。开始的时候走了弯路,我是直接遍历屏幕像素点,然后取色比较的,这样做效率特别低,10X10的像素区域,就要1秒多,不得已还用了多线程。后来找到一篇文章,先截屏然后在生成的图片对象中查找,速度快很多 。文章内容参考“java中的图像匹配实现_java实现图像模版匹配(蜗牛学院)

代码

  

  1. 屏幕区域截图工具类
package com.analog.tools;

import java.awt.AWTException;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;

public class CutImageTool {

	/**
	 * 截取指定左上角坐标,右下角坐标的屏幕区域图像
	 * @param topLeft
	 * @param bottomRight
	 * @return
	 * @throws AWTException
	 */
	public static BufferedImage getScreeImage(int topLeftX, int topLeftY, int bottomRightX, int bottomRightY) throws AWTException {
		int width = bottomRightX - topLeftX;
		int height = bottomRightY - topLeftY;
		Rectangle rectangle = new Rectangle(topLeftX, topLeftY, width, height);
		return CommonUtil.getRobot().createScreenCapture(rectangle);
	}
	
	/**
	 * 截取指定左上角坐标,右下角坐标的屏幕区域图像
	 * @param topLeft
	 * @param bottomRight
	 * @return
	 * @throws AWTException
	 */
	public static BufferedImage getScreeImage(Point topLeft, Point bottomRight) throws AWTException {
		int X = topLeft.x;
		int Y = topLeft.y;
		int width = bottomRight.x - topLeft.x;
		int height = bottomRight.y - topLeft.y;
		Rectangle rectangle = new Rectangle(X, Y, width, height);
		return CommonUtil.getRobot().createScreenCapture(rectangle);
	}
	
	/**
	 * 截取指定屏幕区域图像
	 * @param topLeft
	 * @param bottomRight
	 * @return
	 * @throws AWTException
	 */
	public static BufferedImage getScreeImage(Rectangle rectangle) throws AWTException {
		return CommonUtil.getRobot().createScreenCapture(rectangle);
	}
	
	/**
	 * 截取全屏幕图像
	 * @return
	 * @throws AWTException
	 */
	public static BufferedImage getFullScreeImage() throws AWTException {
		Rectangle rectangle = new Rectangle(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()));
		return CommonUtil.getRobot().createScreenCapture(rectangle);
	}
}

  1. 图形搜索工具类
      搜索区域的特点是,坐标固定(通常是全屏幕,或游戏全窗口),这个需要实时抓取不需要保存;而待搜索的特定图案的特使,是图案是不变,坐标位置不确定(这个位置就是我们要找的),因此可以保存成文件,用的时候再读取内容。
      实时截取大图,小图,然后在大图中搜索小图没有问题。但是把小图保存成jpg文件,读取小图内容,再在大图中搜索就失败了。我猜测可能是保存时,颜色信息失真了。因此保存小图时,我是直接保存颜色的rgb值的信息到文件里.
      搜索工具类代码
package com.analog.tools;

import java.awt.AWTException;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.List;

import com.analog.games.mir2.entity.GameScreen;

/**
 * 图像匹配
 * @author Jiyh
 *
 */
public class ImageMatchTools {

	/**
	 * 全屏幕查找指定区域的图像
	 * @param topLeft
	 * @param bottomRight
	 * @return
	 * @throws AWTException 
	 */
	public static int[] findImageInFullScreen(Point topLeft, Point bottomRight) throws AWTException {
		BufferedImage smallImage = CutImageTool.getScreeImage(topLeft, bottomRight);
		BufferedImage screencapture = CutImageTool.getFullScreeImage();
		
		return findImage(screencapture, smallImage);
	}
	
	/**
	 * 全屏幕区域查找指定文件代表的图像
	 * @param bigImage
	 * @param smallImageFileName
	 * @return
	 * @throws AWTException 
	 */
	public static int[] findImage( String smallImageFileName) throws AWTException {
		BufferedImage screenImage = CutImageTool.getFullScreeImage();
		return findImage(screenImage,  ImageMatchTools.readImageRGB2Object(smallImageFileName));
	}
	
	/**
	 * 在指定屏幕区域查找指定文件代表的图像
	 * @param bigImage
	 * @param smallImageFileName
	 * @return
	 */
	public static int[] findImage(BufferedImage creenImage, String smallImageFileName) {
		return findImage(creenImage,  ImageMatchTools.readImageRGB2Object(smallImageFileName));
	}
	
	/**
	 * 在游戏屏幕区域查找指定文件代表的图像
	 * @param gameScreenTopLeft
	 * @param gameScreenBottomRight
	 * @param smallImageFileName
	 * @return
	 * @throws AWTException 
	 */
	public static int[] findImageInGameScreen( String smallImageFileName) throws AWTException {
		BufferedImage gameScreenImage = CutImageTool.getScreeImage(GameScreen.topLeft, GameScreen.bottomRight);
		return findImage(gameScreenImage, ImageMatchTools.readImageRGB2Object(smallImageFileName));
	}
	
	/**
	 * 在指定屏幕区域查找指定二位数组代表的图像
	 * @param bigImage
	 * @param smallRgbArray
	 * @return
	 */
	public static int[] findImage(BufferedImage bigImage, int[][] smallRgbArray) {
		int bigWidth = bigImage.getWidth();

		int bigHeight = bigImage.getHeight();

		int smallWidth = smallRgbArray.length;

		int smallHeight = smallRgbArray[0].length;

		int[][] bigData = getImageRGB(bigImage);

		int[][] smallData = smallRgbArray;

		int[] target = { -1, -1 };
		
		int yEnd =  bigHeight - smallHeight;
		int xEnd = bigWidth - smallWidth;

		for (int y = 0; y < yEnd; y++) {
			for (int x = 0; x < xEnd; x++) {
				// 对关键点进行先期匹配,降低运算复杂度。如果关键点本身就不匹配,就没必要再去匹配小图的每一个像素点
				if (bigData[x][y] == smallData[0][0] // 左上角
						&& bigData[x + smallWidth - 1][y]
								== smallData[smallWidth - 1][0] // 右上角
						&& bigData[x][y + smallHeight - 1] == smallData[0][smallHeight - 1] // 左下角
						&& bigData[x + smallWidth - 1][y + smallHeight - 1] == smallData[smallWidth - 1][smallHeight
								- 1] // 右下角
						&& bigData[x + smallWidth / 2][y + smallHeight / 2] == smallData[smallWidth / 2][smallHeight
								/ 2]) {

					// 进行全像素匹配
					boolean isMatched = checkAllMatch(x, y, smallHeight, smallWidth, bigData, smallData);
					if (isMatched) {
						System.out.println("像素点X" + x + " : Y" + y + ",对应的值为:" + bigData[x][y]);

						// 获取小图的中心位置的点
						int centerX = x + smallWidth/2;
						int centerY = y + smallHeight/2;
						target[0] = centerX;
						target[1] = centerY;
						return target;
					}
				}

			}
		}

		return target;
	}
	
	/**
	 * 在指定屏幕区域查找指定图像
	 * @param bigImage
	 * @param smallImage
	 * @return
	 */
	public static int[] findImage(BufferedImage bigImage, BufferedImage smallImage) {

		int bigWidth = bigImage.getWidth();

		int bigHeight = bigImage.getHeight();

		int smallWidth = smallImage.getWidth();

		int smallHeight = smallImage.getHeight();

		int[][] bigData = getImageRGB(bigImage);

		int[][] smallData = getImageRGB(smallImage);

		int[] target = { -1, -1 };
		
		int yEnd =  bigHeight - smallHeight;
		int xEnd = bigWidth - smallWidth;

		for (int y = 0; y < yEnd; y++) {
			for (int x = 0; x < xEnd; x++) {
				// 对关键点进行先期匹配,降低运算复杂度。如果关键点本身就不匹配,就没必要再去匹配小图的每一个像素点
				if (bigData[x][y] == smallData[0][0] // 左上角
						&& bigData[x + smallWidth - 1][y] == smallData[smallWidth - 1][0] // 右上角
						&& bigData[x][y + smallHeight - 1] == smallData[0][smallHeight - 1] // 左下角
						&& bigData[x + smallWidth - 1][y + smallHeight - 1] == smallData[smallWidth - 1][smallHeight
								- 1] // 右下角
						&& bigData[x + smallWidth / 2][y + smallHeight / 2] == smallData[smallWidth / 2][smallHeight
								/ 2]) {

					// 进行全像素匹配
					boolean isMatched = checkAllMatch(x, y, smallHeight, smallWidth, bigData, smallData);
					if (isMatched) {
						System.out.println("像素点X" + x + " : Y" + y + ",对应的值为:" + bigData[x][y]);

						// 获取小图的中心位置的点
						int centerX = x + smallWidth/2;
						int centerY = y + smallHeight/2;
						target[0] = centerX;
						target[1] = centerY;
						return target;
					}
				}

			}
		}

		return target;
	}
	
	private static boolean checkAllMatch(int x, int y, int smallHeight, int smallWidth, int[][] bigData,
			int[][] smallData) {
		boolean isMatched = true;
		for (int smallY = 0; smallY < smallHeight; smallY++) {
			for (int smallX = 0; smallX < smallWidth; smallX++) {
				// 如果发现有一个像素点,两者的值不一样,则认为不相等,如果不相等,则没必要继续比较其它点.
				if (bigData[x + smallX][y + smallY] != smallData[smallX][smallY]) {
					isMatched = false;
					return isMatched;
				}
			}
		}
		return isMatched;
	}

	public static int[][] getImageRGB(BufferedImage bfImage) {
		int width = bfImage.getWidth();

		int height = bfImage.getHeight();

		int[][] result = new int[width][height];

		for (int y = 0; y < height; y++) {
			for (int x = 0; x < width; x++) {
				// 对某个像素点的RGB编码并存入数据库
				result[x][y] = bfImage.getRGB(x, y) & 0xFFFFFF;

				// 单独获取每一个像素点的Red,Green,和Blue的值。

				// int r = (bfImage.getRGB(x, y) & 0xFF0000) >> 16;

				// int g = (bfImage.getRGB(x, y) & 0xFF00) >> 8;

				// int b = bfImage.getRGB(x, y) & 0xFF;

			}

		}

		return result;

	}
	
	public static int[][] readImageRGB(String fileName) {
		List<int[]> rgbList = new ArrayList<int[]>();
		TxtFileReader tfr = null;
		try {
			tfr = new TxtFileReader(fileName);
			String line = null;
			while ((line = tfr.readLine()) != null){
				String[] rgbStringArray = line.split(",");
				int[] rfgArray = new int[rgbStringArray.length];
				for (int i = 0; i < rgbStringArray.length; i++) {
					rfgArray[i] = Integer.parseInt(rgbStringArray[i]);
				}
				rgbList.add(rfgArray);
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			if(tfr != null){
				tfr.close();
			}
		}
		
		int[][] rgbArray = new int[rgbList.size()][];
		for (int i = 0; i < rgbArray.length; i++) {
			rgbArray[i] = rgbList.get(i);
		}

		return rgbArray;
	}
	public static int[][] readImageRGB2Object(String filePath) {
		File file = new File(filePath);
		FileInputStream fis = null;
		ObjectInputStream ois = null;
		try {
			fis = new FileInputStream(file);
			ois = new ObjectInputStream(fis);
			Object data = ois.readObject();
			return (int[][])data;
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			if(ois != null){
				try {
					ois.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			if(fis != null){
				try {
					fis.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		
		return null;
	}
	
}

  1. 保存屏幕截图
      这里说明一下,保存rgb颜色信息文件,为什么使用Object。原因有点尴尬:直接遍历数据组,一行行写入文件后,在读取出来,数据结构和原来的结构不一样。调整几次也没成功。因此就直接写数组对象本身到文件里了,反正写成数字也是看不懂的。
		//图片保存路径->自定义
		String baseDir = "E:\\games\\keyMacro\\mir2\\";
		//rgb颜色信息文件名(搜索时使用的文件)
		String fileName = "waigua.clr";
		//jpg文件名,用于查看截图是否正确
		String jpgFileName = "waigua.clr.jpg";
		
		//微信截图工具获取的目标图形的坐标信息
		int topleftX = 550;
		int topleftY = 270;
		int bottomrightX = 560;
		int bottomrightY = 280;
		
		//获取图形对象
		BufferedImage bfImage = CutImageTool.getScreeImage(topleftX, topleftY, bottomrightX, bottomrightY);
		
		//获取图像的颜色信息数组
		//转换为搜索时需要的数据结构
		int[][] rgbArray = ImageMatchTools.getImageRGB(bfImage);
		
		写rgb颜色信息文件
		File file = new File(baseDir + fileName);
		FileOutputStream fos = new FileOutputStream(file);//新建一个文件输出流
		ObjectOutputStream oos = new ObjectOutputStream(fos);//在文件输出流上套一个对象输出流
		
		oos.writeObject(rgbArray);
		oos.flush();
		oos.close();
		
		//写jpg图形文件
		File jpgFile = new File(baseDir + jpgFileName);
		ImageIO.write(bfImage, "jpg", jpgFile);

小结一下

  到这里,Java版按键精灵的基础功能都OK了,后面的内容是我在自己的游戏中的实践,仅供参考。。

 类似资料: