package com.example.jmacro.wjdr; import cn.hutool.core.convert.Convert; import cn.hutool.core.util.RandomUtil; import com.example.jmacro.wjdr.base.Legend; import com.example.jmacro.wjdr.base.ViewColor; import com.example.jmacro.wjdr.base.ViewPoint; import com.example.jmacro.wjdr.base.ViewRect; import com.example.jmacro.wjdr.util.ColorUtil; import com.example.jmacro.wjdr.util.ImageUtil; import com.example.jmacro.wjdr.util.Logger; import com.example.jmacro.wjdr.util.TaskUtil; import java.awt.*; import java.awt.event.InputEvent; import java.awt.image.BufferedImage; import java.io.File; import java.util.concurrent.TimeUnit; /** * 脚本 * * @author wangbing * @version 0.0.1 * @since 1.8 */ public abstract class JMacro { /** * 默认超时秒数 */ private int defaultTimeOut = 10; /** * 机器人操作实例 */ private final Robot robot; /** * 视口区域 */ protected ViewRect focusRect; public JMacro() { try { // 机器人初始化 this.robot = new JRoot(); this.robot.setAutoDelay(30); this.startFocus(); } catch (AWTException e) { throw new RuntimeException(e); } } public int getDefaultTimeOut() { return defaultTimeOut; } public void setDefaultTimeOut(int defaultTimeOut) { this.defaultTimeOut = defaultTimeOut; } public Robot getRobot() { return robot; } /** * 开始聚焦 */ public void startFocus() { TaskUtil.asyncTask(() -> { focusRect = TaskUtil.timeTask(this::focus, defaultTimeOut, TimeUnit.SECONDS); if (focusRect != null) { Logger.info("聚焦成功"); } else { Logger.error("聚焦失败"); } }); } /** * 获取视口区域 * * @return 视口区域 */ public abstract ViewRect focus(); /** * 执行脚本 */ public abstract void run(); /** * 键盘按键组 */ public void keyInput(int... keycodes) { for (int keycode : keycodes) { keyPress(keycode); } } /** * 键盘按键 * keycode Key to press (e.g. KeyEvent.VK_A) */ public void keyPress(int keycode) { this.robot.keyPress(keycode); } /** * 鼠标移动 * * @param point 坐标点 */ public void mouseMove(ViewPoint point) { mouseMove(point, false); } /** * 鼠标移动 * * @param point 坐标点 * @param smooth 平滑移动 */ public void mouseMove(ViewPoint point, boolean smooth) { int endX = point.getX(); int endY = point.getY(); if (smooth) { // 获取当前鼠标位置 Point mousePoint = MouseInfo.getPointerInfo().getLocation(); int startX = mousePoint.x; int startY = mousePoint.y; // 求两点距离 double absX = Math.abs(startX - endX); double absY = Math.abs(startY - endY); double absZ = Math.sqrt(Math.pow(absX, 2) + Math.pow(absY, 2)); int times = (int) (absZ / 10 + (absZ % 10 > 0 ? 1 : 0)); int interval = Math.min(500 / times, 10); times = Math.min(times, 10); // 分times次移动到指定点 for (int i = 1; i <= times; i++) { float d = i * 1.0f / times; int dx = (int) (startX + (endX - startX) * d); int dy = (int) (startY + (endY - startY) * d); robot.mouseMove(dx, dy); delay(RandomUtil.randomInt(interval - 5, interval + 5)); } } else { robot.mouseMove(endX, endY); } } /** * 鼠标左键单击 * * @param rect 点 */ public void mouseLeftClick(ViewPoint rect) { mouseMove(rect); robot.mousePress(InputEvent.BUTTON1_DOWN_MASK); delay(100); robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); } /** * 鼠标左键单击 * * @param rect 矩形区域 */ public void mouseLeftClick(ViewRect rect) { mouseLeftClick(new ViewPoint(rect.getCenter()[0], rect.getCenter()[1])); } /** * 鼠标左键拖拽 * * @param start 开始位置 * @param end 结束位置 */ public void mouseLeftDrag(ViewPoint start, ViewPoint end) { mouseLeftDrag(start, end, false); } /** * 鼠标左键拖拽 * * @param start 开始位置 * @param end 结束位置 * @param smooth 平滑移动 */ public void mouseLeftDrag(ViewPoint start, ViewPoint end, boolean smooth) { mouseMove(start, smooth); delay(); robot.mousePress(InputEvent.BUTTON1_DOWN_MASK); delay(); mouseMove(end, smooth); delay(); robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); } /** * 鼠标右键拖拽 * * @param start 开始位置 * @param end 结束位置 */ public void mouseRightDrag(ViewPoint start, ViewPoint end) { mouseRightDrag(start, end, false); } /** * 鼠标右键拖拽 * * @param start 开始位置 * @param end 结束位置 * @param smooth 平滑移动 */ public void mouseRightDrag(ViewPoint start, ViewPoint end, boolean smooth) { mouseMove(start, smooth); delayUnstable(); robot.mousePress(InputEvent.BUTTON3_DOWN_MASK); delayUnstable(); mouseMove(end, smooth); delayUnstable(); robot.mouseRelease(InputEvent.BUTTON3_DOWN_MASK); } /** * 鼠标滚轮单击 * * @param rect 矩形区域 */ public void mouseWheelClick(ViewRect rect) { delayUnstable(); robot.mouseMove(rect.getCenter()[0], rect.getCenter()[1]); delayUnstable(); robot.mousePress(InputEvent.BUTTON2_DOWN_MASK); delayUnstable(); robot.mouseRelease(InputEvent.BUTTON2_DOWN_MASK); } /** * 鼠标右键单击 * * @param rect 矩形区域 */ public void mouseRightClick(ViewRect rect) { delayUnstable(); robot.mouseMove(rect.getCenter()[0], rect.getCenter()[1]); delayUnstable(); robot.mousePress(InputEvent.BUTTON3_DOWN_MASK); delayUnstable(); robot.mouseRelease(InputEvent.BUTTON3_DOWN_MASK); } /** * 鼠标左键双击 * * @param rect 矩形区域 */ public void mouseLeftDoubleClick(ViewRect rect) { delayUnstable(); robot.mouseMove(rect.getCenter()[0], rect.getCenter()[1]); delayUnstable(); robot.mousePress(InputEvent.BUTTON1_DOWN_MASK); delay(100); robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); delay(100); robot.mousePress(InputEvent.BUTTON1_DOWN_MASK); delay(100); robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); } /** * 捕获指定区域屏幕 */ public BufferedImage capture(Robot robot, ViewRect viewRect) { return robot.createScreenCapture(new Rectangle(viewRect.getLeft(), viewRect.getTop(), viewRect.getWidth(), viewRect.getHeight())); } /** * 获取聚焦区域 */ public ViewRect getFocusRect() { if (focusRect != null) { return focusRect; } // 返回屏幕区域 return getScreenRect(); } /** * 获取聚焦区域 */ public ViewRect getScreenRect() { if (focusRect != null) { return focusRect; } // 返回屏幕区域 Toolkit tk = Toolkit.getDefaultToolkit(); return new ViewRect(0, 0, tk.getScreenSize().width, tk.getScreenSize().height); } /** * 延迟 */ public void delay() { delay(100); } /** * 延迟 */ public void delay(long millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { throw new RuntimeException(e); } } /** * 抖动延迟 */ public void delayUnstable() { delayUnstable(500); } /** * 抖动延迟 */ public void delayUnstable(long millis) { if (millis < 200) { delay(millis); return; } delay(RandomUtil.randomLong(millis - 100, millis + 100)); } /** * 全屏查找图片 * * @param pic 图片 * @param minSimilar 最低相似度 * @return 匹配图片区域 */ public ViewRect findPic(File pic, double minSimilar) { return findPic(getFocusRect(), ImageUtil.load(pic), minSimilar); } /** * 全屏查找图片 * * @param pic 图片 * @param minSimilar 最低相似度 * @return 匹配图片区域 */ public ViewRect findPic(BufferedImage pic, double minSimilar) { return findPic(getFocusRect(), pic, minSimilar); } /** * 查找图片 * * @param viewRect 查找范围(不设时,取全屏) * @param pic 待查找图片 * @param minSimilar 相似度 * @return 匹配图片区域 */ public ViewRect findPic(ViewRect viewRect, File pic, double minSimilar) { return findPic(viewRect, ImageUtil.load(pic), minSimilar); } /** * 查找图片 * * @param viewRect 查找范围(不设时,取全屏) * @param pic 待查找图片 * @param minSimilar 相似度 * @return 匹配图片区域 */ public ViewRect findPic(ViewRect viewRect, BufferedImage pic, double minSimilar) { // 当聚焦区域为null时,默认全屏查找 if (viewRect == null) { viewRect = getFocusRect() != null ? getFocusRect() : getScreenRect(); } // 当查找区域比图片还小时,直接返回失败 if (viewRect.getWidth() < pic.getWidth() || viewRect.getHeight() < pic.getHeight()) { Logger.error("视口尺寸小于图片"); return null; } if (minSimilar > 1) { throw new RuntimeException("this minSimilar must be less than 1"); } // 获取实时屏幕 BufferedImage screen = capture(robot, viewRect); ImageUtil.show(screen); int[][] screenData = ImageUtil.getImageRGB(screen); int[][] picData = ImageUtil.getImageRGB(pic); // 得到图片左上角范围 int xMin = viewRect.getLeft(); // 因为坐标取像素点是从0开始,因此需要-1 int xMax = viewRect.getRight() - pic.getWidth(); int yMin = viewRect.getTop(); // 因为坐标取像素点是从0开始,因此需要-1 int yMax = viewRect.getBottom() - pic.getHeight(); for (int y = yMin; y <= yMax; y++) { for (int x = xMin; x <= xMax; x++) { // 对关键点进行先期匹配,降低运算复杂度。如果关键点本身就不匹配,就没必要再去匹配小图的每一个像素点 // 左上角 boolean lt = ColorUtil.isSimilar(screenData[x - xMin][y - yMin], picData[0][0]); // 右上角 boolean rt = ColorUtil.isSimilar(screenData[x - xMin + pic.getWidth() - 1][y - yMin], picData[pic.getWidth() - 1][0]); // 左下角 boolean lb = ColorUtil.isSimilar(screenData[x - xMin][y - yMin + pic.getHeight() - 1], picData[0][pic.getHeight() - 1]); // 右下角 boolean rb = ColorUtil.isSimilar(screenData[x - xMin + pic.getWidth() - 1][y - yMin + pic.getHeight() - 1], picData[pic.getWidth() - 1][pic.getHeight() - 1]); //中心点 boolean cc = ColorUtil.isSimilar(screenData[x - xMin + pic.getWidth() / 2][y - yMin + pic.getHeight() / 2], picData[pic.getWidth() / 2][pic.getHeight() / 2]); if (lt && rt && lb && rb && cc) { // 统计相似点数 int samePixels = 0; for (int smallY = 0; smallY < pic.getHeight(); smallY++) { for (int smallX = 0; smallX < pic.getWidth(); smallX++) { if (ColorUtil.isSimilar(screenData[x + smallX - xMin][y + smallY - yMin], picData[smallX][smallY])) { samePixels++; } } } // 计算相似度 double similar = (double) samePixels / (pic.getWidth() * pic.getHeight()); if (similar >= minSimilar) { return new ViewRect(x, y, x + pic.getWidth(), y + pic.getHeight(), similar); } } } } return null; } /** * 查找图片 * * @param legend 图例 * @param minSimilar 最低相似度 * @return 匹配图片区域 */ public ViewRect findLegend(String legend, double minSimilar) { return findLegend(Legend.inflate(legend), minSimilar); } /** * 查找图片 * * @param legend 图例 * @param minSimilar 最低相似度 * @return 匹配图片区域 */ public ViewRect findLegend(Legend legend, double minSimilar) { return findPic(legend.getFile(), minSimilar); } /** * 查找图例 * * @param legend 图例 * @param minSimilar 最低相似度 * @return 匹配图片区域 */ public ViewRect findLegend(ViewRect rect, String legend, double minSimilar) { return findLegend(rect, Legend.inflate(legend), minSimilar); } /** * 查找图例 * * @param legend 图例 * @param minSimilar 最低相似度 * @return 匹配图片区域 */ public ViewRect findLegend(ViewRect rect, Legend legend, double minSimilar) { return findPic(rect, legend.getFile(), minSimilar); } /** * 查找图片 * * @param legend 图例 * @param minSimilar 最低相似度 * @return 匹配图片区域 */ public ViewRect waitAndFindLegend(Legend legend, double minSimilar, long seconds) { return waitAndFindPic(legend.getFile(), minSimilar, seconds); } /** * 查找图片 * * @param legend 图例 * @param minSimilar 最低相似度 * @return 匹配图片区域 */ public ViewRect waitAndFindLegend(ViewRect rect, Legend legend, double minSimilar, long seconds) { return waitAndFindPic(rect, legend.getFile(), minSimilar, seconds); } /** * 等待并查找图片 * * @param pic 图片 * @param minSimilar 最低相似度 * @return 匹配图片区域 */ public ViewRect waitAndFindPic(File pic, double minSimilar) { return waitAndFindPic(getFocusRect(), pic, minSimilar, defaultTimeOut); } /** * 等待并查找图片 * * @param pic 图片 * @param minSimilar 最低相似度 * @return 匹配图片区域 */ public ViewRect waitAndFindPic(BufferedImage pic, double minSimilar) { return waitAndFindPic(getFocusRect(), pic, minSimilar, defaultTimeOut); } /** * 等待并查找图片 * * @param pic 图片 * @param minSimilar 最低相似度 * @param seconds 最长等待秒数 * @return 匹配图片区域 */ public ViewRect waitAndFindPic(File pic, double minSimilar, long seconds) { return waitAndFindPic(getFocusRect(), pic, minSimilar, seconds); } /** * 等待并查找图片 * * @param pic 图片 * @param minSimilar 最低相似度 * @param seconds 最长等待秒数 * @return 匹配图片区域 */ public ViewRect waitAndFindPic(BufferedImage pic, double minSimilar, long seconds) { return waitAndFindPic(getFocusRect(), pic, minSimilar, seconds); } /** * 等待并查找图片 * * @param rect 查找区域 * @param pic 图片 * @param minSimilar 最低相似度 * @return 匹配图片区域 */ public ViewRect waitAndFindPic(ViewRect rect, File pic, double minSimilar) { return waitAndFindPic(rect, pic, minSimilar, defaultTimeOut); } /** * 等待并查找图片 * * @param rect 查找区域 * @param pic 图片 * @param minSimilar 最低相似度 * @param seconds 最长等待秒数 * @return 匹配图片区域 */ public ViewRect waitAndFindPic(ViewRect rect, File pic, double minSimilar, long seconds) { return waitAndFindPic(rect, ImageUtil.load(pic), minSimilar, seconds); } /** * 等待并查找图片 * * @param rect 查找区域 * @param pic 图片 * @param minSimilar 最低相似度 * @return 匹配图片区域 */ public ViewRect waitAndFindPic(ViewRect rect, BufferedImage pic, double minSimilar) { return waitAndFindPic(rect, pic, minSimilar, defaultTimeOut); } /** * 等待并查找图片 * * @param rect 查找区域 * @param pic 图片 * @param minSimilar 最低相似度 * @param seconds 最长等待秒数 * @return 匹配图片区域 */ public ViewRect waitAndFindPic(ViewRect rect, BufferedImage pic, double minSimilar, long seconds) { if (rect.getWidth() < pic.getWidth()) { Logger.error("查找图片区域宽度{}小于图片宽度{}", rect.getWidth(), pic.getWidth()); return null; } if (rect.getHeight() < pic.getHeight()) { Logger.error("查找图片区域宽度{}小于图片宽度{}", rect.getHeight(), pic.getHeight()); return null; } return TaskUtil.timeTask(() -> { while (JMainService.getInstance().run) { ViewRect result = findPic(rect, pic, minSimilar); if (result != null) { return result; } delayUnstable(); } return null; }, seconds, TimeUnit.SECONDS); } /** * 匹配图片 * * @param pic 图片 * @param location 定位 * @param minSimilar 最低相似度 * @return 匹配图片 */ public ViewRect matchPic(File pic, ViewPoint location, double minSimilar) { if (!pic.exists()) { Logger.error("file [{}] not exist", pic.getAbsolutePath()); return null; } return matchPic(ImageUtil.load(pic), location, minSimilar); } public ViewRect matchPic(BufferedImage pic, ViewPoint location, double minSimilar) { ViewRect focusRect = getFocusRect(); int offsetX = location.getX(); int offsetY = location.getY(); ViewRect viewRect = new ViewRect(); viewRect.setLeft(focusRect.getLeft() + offsetX); viewRect.setTop(focusRect.getTop() + offsetY); viewRect.setRight(viewRect.getLeft() + pic.getWidth()); viewRect.setBottom(viewRect.getTop() + pic.getHeight()); ImageUtil.show(viewRect); return findPic(viewRect, pic, minSimilar); } /** * 等待并匹配图片 * * @param pic 图片 * @param minSimilar 最低相似度 * @return 匹配图片区域 */ public ViewRect waitAndMatchPic(File pic, ViewPoint location, double minSimilar) { return waitAndMatchPic(ImageUtil.load(pic), location, minSimilar); } /** * 等待并匹配图片 * * @param pic 图片 * @param minSimilar 最低相似度 * @return 匹配图片区域 */ public ViewRect waitAndMatchPic(BufferedImage pic, ViewPoint location, double minSimilar) { return waitAndMatchPic(pic, location, minSimilar, defaultTimeOut); } /** * 等待并匹配图片 * * @param pic 图片 * @param minSimilar 最低相似度 * @param seconds 最长等待秒数 * @return 匹配图片区域 */ public ViewRect waitAndMatchPic(File pic, ViewPoint location, double minSimilar, long seconds) { return waitAndMatchPic(ImageUtil.load(pic), location, minSimilar, seconds); } /** * 等待并匹配图片 * * @param pic 图片 * @param minSimilar 最低相似度 * @param seconds 最长等待秒数 * @return 匹配图片区域 */ public ViewRect waitAndMatchPic(BufferedImage pic, ViewPoint location, double minSimilar, long seconds) { return TaskUtil.timeTask(() -> { while (JMainService.getInstance().run) { ViewRect matchPic = matchPic(pic, location, minSimilar); if (matchPic != null) { return matchPic; } delayUnstable(); } return null; }, seconds, TimeUnit.SECONDS); } /** * 匹配图例 * * @param legends 图例 * @param seconds 最长等待秒数 * @return 匹配图片区域 */ public ViewRect[] waitAndMatchLegend(String[] legends, long seconds) { return TaskUtil.timeTask(() -> { while (JMainService.getInstance().run) { ViewRect[] viewRects = matchLegend(legends); for (ViewRect viewRect : viewRects) { if (viewRect != null) { return viewRects; } } delayUnstable(); } return new ViewRect[legends.length]; }, seconds, TimeUnit.SECONDS); } /** * 匹配图例 * * @param legends 图例 * @return 匹配图片 */ public ViewRect[] matchLegend(String... legends) { return matchLegend(legends, 1.0); } /** * 匹配图例 * * @param legends 图例 * @param minSimilar 最低相似度 * @return 匹配图片 */ public ViewRect[] matchLegend(String[] legends, double minSimilar) { ViewRect[] viewRects = new ViewRect[legends.length]; for (int i = 0; i < legends.length; i++) { String legend = legends[i]; viewRects[i] = matchLegend(Legend.inflate(legend), minSimilar); if (viewRects[i] != null) { return viewRects; } } return viewRects; } /** * 匹配图例 * * @param legend 图例 * @param minSimilar 最低相似度 * @return 匹配图片 */ public ViewRect matchLegend(String legend, double minSimilar) { return matchLegend(Legend.inflate(legend), minSimilar); } /** * 匹配图片 * * @param legend 图例 * @param minSimilar 最低相似度 * @return 匹配图片 */ public ViewRect matchLegend(Legend legend, double minSimilar) { if (minSimilar > 1) { throw new RuntimeException("this minSimilar must be less than 1"); } int offsetX = legend.getLocation().getX(); int offsetY = legend.getLocation().getY(); ViewRect viewRect = new ViewRect(); viewRect.setLeft(getFocusRect().getLeft() + offsetX); viewRect.setTop(getFocusRect().getTop() + offsetY); BufferedImage image = ImageUtil.load(legend.getFile()); viewRect.setRight(viewRect.getLeft() + image.getWidth()); viewRect.setBottom(viewRect.getTop() + image.getHeight()); ImageUtil.show(viewRect); return findPic(viewRect, image, minSimilar); } /** * 等待并匹配图例 * * @param legend 图例 * @param minSimilar 最低相似度 * @return 匹配图片区域 */ public ViewRect waitAndMatchLegend(String legend, double minSimilar) { return waitAndMatchLegend(Legend.inflate(legend), minSimilar); } /** * 等待并匹配图例 * * @param legend 图例 * @param minSimilar 最低相似度 * @return 匹配图片区域 */ public ViewRect waitAndMatchLegend(Legend legend, double minSimilar) { return waitAndMatchLegend(legend, minSimilar, defaultTimeOut); } /** * 等待并匹配图例 * * @param legend 图例 * @param minSimilar 最低相似度 * @param seconds 最长等待秒数 * @return 匹配图片区域 */ public ViewRect waitAndMatchLegend(String legend, double minSimilar, long seconds) { return waitAndMatchLegend(Legend.inflate(legend), minSimilar, seconds); } /** * 等待并匹配图例 * * @param legend 图例 * @param minSimilar 最低相似度 * @param seconds 最长等待秒数 * @return 匹配图片区域 */ public ViewRect waitAndMatchLegend(Legend legend, double minSimilar, long seconds) { return waitAndMatchPic(legend.getFile(), legend.getLocation(), minSimilar, seconds); } /** * 匹配指定坐标色值 * * @param color 色值坐标点 * @return 是否匹配 */ public boolean matchColor(ViewColor color) { ViewRect of = of(color.getX(), color.getY(), color.getX(), color.getY()); // 获取实时屏幕 BufferedImage pointImage = capture(robot, of); ImageUtil.show(pointImage); int[][] pointData = ImageUtil.getImageRGB(pointImage); return ColorUtil.isSimilar(pointData[0][0], color.getColor()); } /** * 等待并匹配图例 * * @param color 色值坐标点 * @param seconds 最长等待秒数 * @return 匹配图片区域 */ public boolean waitAndMatchColor(ViewColor color, long seconds) { Boolean result = TaskUtil.timeTask(() -> { while (JMainService.getInstance().run) { if (matchColor(color)) { return true; } delayUnstable(); } return null; }, seconds, TimeUnit.SECONDS); return Convert.toBool(result, false); } /** * 获取图例 * * @return 图例 */ public Legend of(String legend) { return Legend.inflate(legend); } /** * 将相对坐标转为绝对坐标 * * @return 绝对区域 */ public ViewRect of(int left, int top, int right, int bottom) { int ox = getFocusRect().getLeft(); int oy = getFocusRect().getTop(); return new ViewRect(left + ox, top + oy, right + ox, bottom + oy); } /** * 将相对区域转为绝对区域 * * @param relativeRect 相对区域 * @return 绝对区域 */ public ViewRect of(ViewRect relativeRect) { return new ViewRect(relativeRect.getLeft(), relativeRect.getTop(), relativeRect.getRight(), relativeRect.getBottom()); } /** * 将相对坐标转为绝对坐标 * * @return 绝对坐标 */ public ViewPoint of(int x, int y) { int ox = getFocusRect().getLeft(); int oy = getFocusRect().getTop(); return new ViewPoint(x + ox, y + oy); } /** * 将相对坐标转为绝对坐标 * * @param relativePoint 相对坐标 * @return 绝对区域 */ public ViewPoint of(ViewPoint relativePoint) { return of(relativePoint.getX(), relativePoint.getY()); } /** * 将相对坐标转为色值坐标 * * @return 绝对坐标 */ public ViewColor of(int x, int y, String hexColor) { int ox = getFocusRect().getLeft(); int oy = getFocusRect().getTop(); return new ViewColor(x + ox, y + oy, hexColor); } /** * 将相对坐标转为色值坐标 * * @param relativePoint 相对坐标 * @return 绝对区域 */ public ViewColor of(ViewPoint relativePoint, String hexColor) { return of(relativePoint.getX(), relativePoint.getY(), hexColor); } }