|
|
package xyz.wbsite.jmacro;
|
|
|
|
|
|
import cn.hutool.core.convert.Convert;
|
|
|
import cn.hutool.core.util.NumberUtil;
|
|
|
import cn.hutool.core.util.RandomUtil;
|
|
|
import xyz.wbsite.jmacro.base.Legend;
|
|
|
import xyz.wbsite.jmacro.base.ViewColor;
|
|
|
import xyz.wbsite.jmacro.base.ViewPoint;
|
|
|
import xyz.wbsite.jmacro.base.ViewRect;
|
|
|
import xyz.wbsite.jmacro.util.*;
|
|
|
|
|
|
import java.awt.*;
|
|
|
import java.awt.event.InputEvent;
|
|
|
import java.awt.image.BufferedImage;
|
|
|
import java.io.File;
|
|
|
import java.util.ArrayList;
|
|
|
import java.util.HashMap;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
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. <code>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 / 15 + (absZ % 15 > 0 ? 1 : 0));
|
|
|
int interval = (int) Math.max((absZ / 4) / times, 10);
|
|
|
|
|
|
times = Math.min(times, 10);
|
|
|
// 分times次移动到指定点
|
|
|
for (int i = 1; i <= times; i++) {
|
|
|
double d = ValueUtil.easeOut(i * 1.0D / times);
|
|
|
int dx = (int) (startX + (endX - startX) * d);
|
|
|
int dy = (int) (startY + (endY - startY) * d);
|
|
|
robot.mouseMove(dx, dy);
|
|
|
delay(RandomUtil.randomInt(interval - 3, interval + 3));
|
|
|
}
|
|
|
} else {
|
|
|
robot.mouseMove(endX, endY);
|
|
|
delay(10);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 鼠标左键单击
|
|
|
*
|
|
|
* @param rect 矩形区域
|
|
|
*/
|
|
|
public void mouseLeftClick(ViewRect rect) {
|
|
|
mouseLeftClick(rect, 100);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 鼠标左键单击
|
|
|
*
|
|
|
* @param rect 矩形区域
|
|
|
*/
|
|
|
public void mouseLeftClick(ViewRect rect, long delay) {
|
|
|
mouseLeftClick(new ViewPoint(rect.getCenter().getX(), rect.getCenter().getY()), delay);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 鼠标左键单击
|
|
|
*
|
|
|
* @param rect 点
|
|
|
*/
|
|
|
public void mouseLeftClick(ViewPoint rect) {
|
|
|
mouseLeftClick(rect, 100);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 鼠标左键单击
|
|
|
*
|
|
|
* @param rect 点
|
|
|
*/
|
|
|
public void mouseLeftClick(ViewPoint rect, long delay) {
|
|
|
|
|
|
// 获取当前鼠标位置
|
|
|
Point mousePoint = MouseInfo.getPointerInfo().getLocation();
|
|
|
if (mousePoint.x != rect.getX() || mousePoint.y != rect.getY()) {
|
|
|
Logger.info("鼠标移动至[{},{}]", rect.getX(), rect.getY());
|
|
|
mouseMove(rect);
|
|
|
}
|
|
|
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
|
|
|
delay(delay);
|
|
|
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 鼠标左键拖拽
|
|
|
*
|
|
|
* @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().getX(), rect.getCenter().getY());
|
|
|
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().getX(), rect.getCenter().getY());
|
|
|
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().getX(), rect.getCenter().getY());
|
|
|
delayUnstable();
|
|
|
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
|
|
|
delay(60);
|
|
|
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
|
|
|
delay(100);
|
|
|
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
|
|
|
delay(60);
|
|
|
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() {
|
|
|
return focusRect;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 获取聚焦区域
|
|
|
*/
|
|
|
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(), pic, minSimilar);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 查找图片
|
|
|
*
|
|
|
* @param viewRect 查找范围(不设时,取全屏)
|
|
|
* @param pic 待查找图片
|
|
|
* @param minSimilar 相似度
|
|
|
* @return 匹配区域
|
|
|
*/
|
|
|
public ViewRect findPic(ViewRect viewRect, File pic, double minSimilar) {
|
|
|
if (!pic.exists()) {
|
|
|
Logger.info("[{}] does not exist!", pic.getAbsolutePath());
|
|
|
return null;
|
|
|
}
|
|
|
if (!pic.isFile()) {
|
|
|
Logger.info("[{}] is not a file!", pic.getAbsolutePath());
|
|
|
return null;
|
|
|
}
|
|
|
Logger.info("查找图片:{}", pic.getAbsolutePath());
|
|
|
BufferedImage bufferedImage = ImageUtil.load(pic);
|
|
|
return findPic(viewRect, bufferedImage, minSimilar, true);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 查找图片
|
|
|
*
|
|
|
* @param viewRect 查找范围(不设时,取全屏)
|
|
|
* @param pic 待查找图片
|
|
|
* @param minSimilar 相似度
|
|
|
* @param fast 是否快速查找(快速查找通过优先定位四个顶点+中心,因此图的边缘和中心不能存在半透明等情况)
|
|
|
* @return 匹配区域
|
|
|
*/
|
|
|
public ViewRect findPic(ViewRect viewRect, BufferedImage pic, double minSimilar, boolean fast) {
|
|
|
// 当聚焦区域为null时,默认全屏查找
|
|
|
if (viewRect == null) {
|
|
|
viewRect = getFocusRect() != null ? getFocusRect() : getScreenRect();
|
|
|
}
|
|
|
// 当查找区域比图片还小时,直接返回失败
|
|
|
if (viewRect.getWidth() < pic.getWidth() || viewRect.getHeight() < pic.getHeight()) {
|
|
|
Logger.info("视口尺寸小于图片");
|
|
|
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();
|
|
|
|
|
|
double maxSimilar = 0;
|
|
|
for (int y = yMin; y <= yMax; y++) {
|
|
|
for (int x = xMin; x <= xMax; x++) {
|
|
|
if (fast) {
|
|
|
// 对关键点进行先期匹配,降低运算复杂度。如果关键点本身就不匹配,就没必要再去匹配小图的每一个像素点
|
|
|
// 左上角
|
|
|
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) {
|
|
|
continue;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 统计相似点数
|
|
|
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());
|
|
|
maxSimilar = Math.max(maxSimilar, similar);
|
|
|
if (similar >= minSimilar) {
|
|
|
return new ViewRect(x, y, x + pic.getWidth(), y + pic.getHeight(), similar);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
if (fast) {
|
|
|
|
|
|
}
|
|
|
Logger.info("最大相似度{}", NumberUtil.formatPercent(maxSimilar, 0));
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 等待并查找图片
|
|
|
*
|
|
|
* @param rect 查找区域
|
|
|
* @param pic 图片
|
|
|
* @param minSimilar 最低相似度
|
|
|
* @return 匹配区域
|
|
|
*/
|
|
|
public ViewRect waitAndFindPic(ViewRect rect, File pic, double minSimilar) {
|
|
|
return waitAndFindPic(rect, pic, minSimilar, defaultTimeOut);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 等待并查找图片
|
|
|
*
|
|
|
* @param pic 图片
|
|
|
* @param minSimilar 最低相似度
|
|
|
* @return 匹配区域
|
|
|
*/
|
|
|
public ViewRect waitAndFindPic(File 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 rect 查找区域
|
|
|
* @param picFile 图片
|
|
|
* @param minSimilar 最低相似度
|
|
|
* @param seconds 最长等待秒数
|
|
|
* @return 匹配区域
|
|
|
*/
|
|
|
public ViewRect waitAndFindPic(ViewRect rect, File picFile, double minSimilar, long seconds) {
|
|
|
Logger.info("等待并查找图片:{}", picFile.getAbsolutePath());
|
|
|
BufferedImage pic = ImageUtil.load(picFile);
|
|
|
return TaskUtil.timeTask(() -> {
|
|
|
while (JMainService.getInstance().run) {
|
|
|
ViewRect result = findPic(rect, pic, minSimilar, true);
|
|
|
if (result != null) {
|
|
|
return result;
|
|
|
}
|
|
|
}
|
|
|
return null;
|
|
|
}, seconds, TimeUnit.SECONDS);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 聚焦区域查找图例并点击
|
|
|
*
|
|
|
* @param legend 图例
|
|
|
* @param minSimilar 最低相似度
|
|
|
* @return 匹配区域
|
|
|
*/
|
|
|
public boolean findLegendAndClick(String legend, double minSimilar) {
|
|
|
ViewRect matchLegend = matchLegend(legend, minSimilar);
|
|
|
if (matchLegend != null) {
|
|
|
Logger.info("点击【{}】", legend);
|
|
|
mouseLeftClick(matchLegend);
|
|
|
delay(500);
|
|
|
return true;
|
|
|
} else {
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 聚焦区域查找图例并点击
|
|
|
*
|
|
|
* @param legend 图例
|
|
|
* @param minSimilar 最低相似度
|
|
|
* @return 匹配区域
|
|
|
*/
|
|
|
public boolean findLegendAndClick(Legend legend, double minSimilar) {
|
|
|
ViewRect matchLegend = matchLegend(legend, minSimilar);
|
|
|
if (matchLegend != null) {
|
|
|
Logger.info("点击【{}】", legend);
|
|
|
mouseLeftClick(matchLegend);
|
|
|
delay(500);
|
|
|
return true;
|
|
|
} else {
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 指定区域查找图例并点击
|
|
|
*
|
|
|
* @param legend 图例
|
|
|
* @param minSimilar 最低相似度
|
|
|
* @return 匹配区域
|
|
|
*/
|
|
|
public boolean findLegendAndClick(ViewRect rect, String legend, double minSimilar) {
|
|
|
ViewRect matchLegend = findLegend(rect, legend, minSimilar);
|
|
|
if (matchLegend != null) {
|
|
|
Logger.info("点击【{}】", legend);
|
|
|
mouseLeftClick(matchLegend);
|
|
|
delay(500);
|
|
|
return true;
|
|
|
} else {
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 指定区域查找图例并点击
|
|
|
*
|
|
|
* @param legend 图例
|
|
|
* @param minSimilar 最低相似度
|
|
|
* @return 匹配区域
|
|
|
*/
|
|
|
public boolean findLegendAndClick(ViewRect rect, Legend legend, double minSimilar) {
|
|
|
ViewRect matchLegend = findLegend(rect, legend, minSimilar);
|
|
|
if (matchLegend != null) {
|
|
|
Logger.info("点击【{}】", legend);
|
|
|
mouseLeftClick(matchLegend);
|
|
|
delay(500);
|
|
|
return true;
|
|
|
} else {
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 查找图片
|
|
|
*
|
|
|
* @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 findLegend(getFocusRect(), legend, minSimilar, true);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 查找图例
|
|
|
*
|
|
|
* @param rect 范围
|
|
|
* @param legend 图例
|
|
|
* @param minSimilar 最低相似度
|
|
|
* @return 匹配区域
|
|
|
*/
|
|
|
public ViewRect findLegend(ViewRect rect, String legend, double minSimilar) {
|
|
|
return findLegend(rect, Legend.inflate(legend), minSimilar);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 查找图例
|
|
|
*
|
|
|
* @param rect 范围
|
|
|
* @param legend 图例
|
|
|
* @param minSimilar 最低相似度
|
|
|
* @return 匹配区域
|
|
|
*/
|
|
|
public ViewRect findLegend(ViewRect rect, Legend legend, double minSimilar) {
|
|
|
return findLegend(rect, legend, minSimilar, false);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 查找图例
|
|
|
*
|
|
|
* @param legend 图例
|
|
|
* @param minSimilar 最低相似度
|
|
|
* @return 匹配区域
|
|
|
*/
|
|
|
public ViewRect findLegend(ViewRect rect, String legend, double minSimilar, boolean fast) {
|
|
|
return findLegend(rect, Legend.inflate(legend), minSimilar, fast);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 查找图例
|
|
|
*
|
|
|
* @param legend 图例
|
|
|
* @param minSimilar 最低相似度
|
|
|
* @return 匹配区域
|
|
|
*/
|
|
|
public ViewRect findLegend(ViewRect rect, Legend legend, double minSimilar, boolean fast) {
|
|
|
Logger.info("查找图例:{}", legend.getName());
|
|
|
return findPic(rect, legend.getImage(), minSimilar, fast);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 等待并查找图例
|
|
|
*
|
|
|
* @param legend 图例
|
|
|
* @param minSimilar 最低相似度
|
|
|
* @return 匹配区域
|
|
|
*/
|
|
|
public ViewRect waitAndFindLegend(ViewRect rect, Legend legend, double minSimilar, long seconds) {
|
|
|
Logger.info("等待并查找图例:{}", legend.getName());
|
|
|
return TaskUtil.timeTask(() -> {
|
|
|
while (JMainService.getInstance().run) {
|
|
|
ViewRect result = findPic(rect, legend.getImage(), minSimilar, true);
|
|
|
if (result != null) {
|
|
|
return result;
|
|
|
}
|
|
|
}
|
|
|
return null;
|
|
|
}, seconds, TimeUnit.SECONDS);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 等待并查找图例
|
|
|
*
|
|
|
* @param legend 图例
|
|
|
* @param minSimilar 最低相似度
|
|
|
* @return 匹配区域
|
|
|
*/
|
|
|
public ViewRect waitAndFindLegend(ViewRect rect, String legend, double minSimilar, long seconds) {
|
|
|
return waitAndFindLegend(rect, Legend.inflate(legend), minSimilar, seconds);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 等待并查找图例
|
|
|
*
|
|
|
* @param legend 图例
|
|
|
* @param minSimilar 最低相似度
|
|
|
* @return 匹配区域
|
|
|
*/
|
|
|
public ViewRect waitAndFindLegend(Legend legend, double minSimilar, long seconds) {
|
|
|
return waitAndFindLegend(getFocusRect(), legend, minSimilar, seconds);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 等待并查找图例
|
|
|
*
|
|
|
* @param legend 图例
|
|
|
* @param minSimilar 最低相似度
|
|
|
* @return 匹配区域
|
|
|
*/
|
|
|
public ViewRect waitAndFindLegend(String legend, double minSimilar, long seconds) {
|
|
|
return waitAndFindLegend(getFocusRect(), Legend.inflate(legend), minSimilar, seconds);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 等待并查找图例
|
|
|
*
|
|
|
* @param legend 图例
|
|
|
* @param minSimilar 最低相似度
|
|
|
* @return 匹配区域
|
|
|
*/
|
|
|
public ViewRect waitAndFindLegend(Legend legend, double minSimilar) {
|
|
|
return waitAndFindLegend(getFocusRect(), legend, minSimilar, defaultTimeOut);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 等待并查找图例
|
|
|
*
|
|
|
* @param legend 图例
|
|
|
* @param minSimilar 最低相似度
|
|
|
* @return 匹配区域
|
|
|
*/
|
|
|
public ViewRect waitAndFindLegend(String legend, double minSimilar) {
|
|
|
return waitAndFindLegend(getFocusRect(), Legend.inflate(legend), minSimilar, defaultTimeOut);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 匹配图例和点击
|
|
|
*
|
|
|
* @param legend 图例
|
|
|
* @param minSimilar 最低相似度
|
|
|
* @return 是否成功
|
|
|
*/
|
|
|
public boolean matchLegendAndClick(String legend, double minSimilar) {
|
|
|
ViewRect matchLegend = matchLegend(legend, minSimilar);
|
|
|
if (matchLegend != null) {
|
|
|
Logger.info("点击【{}】", legend);
|
|
|
mouseLeftClick(matchLegend);
|
|
|
delay(500);
|
|
|
return true;
|
|
|
} else {
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 匹配图例和点击
|
|
|
*
|
|
|
* @param legend 图例
|
|
|
* @param minSimilar 最低相似度
|
|
|
* @return 是否成功
|
|
|
*/
|
|
|
public boolean matchLegendAndClick(Legend legend, double minSimilar) {
|
|
|
ViewRect matchLegend = matchLegend(legend, minSimilar);
|
|
|
if (matchLegend != null) {
|
|
|
Logger.info("点击【{}】", legend);
|
|
|
mouseLeftClick(matchLegend);
|
|
|
delay(500);
|
|
|
return true;
|
|
|
} else {
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 匹配图例
|
|
|
*
|
|
|
* @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) {
|
|
|
BufferedImage image = ImageUtil.load(legend.getFile());
|
|
|
// 获取图例相对坐标
|
|
|
int offsetX = legend.getLocation().getX();
|
|
|
int offsetY = legend.getLocation().getY();
|
|
|
// 根据原点计算图例绝对坐标
|
|
|
ViewRect viewRect = new ViewRect();
|
|
|
viewRect.setLeft(getFocusRect().getLeft() + offsetX);
|
|
|
viewRect.setTop(getFocusRect().getTop() + offsetY);
|
|
|
viewRect.setRight(viewRect.getLeft() + image.getWidth());
|
|
|
viewRect.setBottom(viewRect.getTop() + image.getHeight());
|
|
|
Logger.info("匹配图例:{},区域:{}", legend.getName(), viewRect);
|
|
|
return findPic(viewRect, image, minSimilar, false);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 等待并匹配图例
|
|
|
*
|
|
|
* @param legend 图例
|
|
|
* @param minSimilar 最低相似度
|
|
|
* @param seconds 最长等待秒数
|
|
|
* @return 匹配区域
|
|
|
*/
|
|
|
public ViewRect waitAndMatchLegend(Legend legend, double minSimilar, long seconds) {
|
|
|
BufferedImage image = ImageUtil.load(legend.getFile());
|
|
|
// 获取图例相对坐标
|
|
|
int offsetX = legend.getLocation().getX();
|
|
|
int offsetY = legend.getLocation().getY();
|
|
|
// 根据原点计算图例绝对坐标
|
|
|
ViewRect viewRect = new ViewRect();
|
|
|
viewRect.setLeft(getFocusRect().getLeft() + offsetX);
|
|
|
viewRect.setTop(getFocusRect().getTop() + offsetY);
|
|
|
viewRect.setRight(viewRect.getLeft() + image.getWidth());
|
|
|
viewRect.setBottom(viewRect.getTop() + image.getHeight());
|
|
|
Logger.info("等待并匹配图例:{},区域:{}", legend.getName(), viewRect);
|
|
|
return TaskUtil.timeTask(() -> {
|
|
|
while (JMainService.getInstance().run) {
|
|
|
ViewRect result = findPic(viewRect, image, minSimilar, false);
|
|
|
if (result != null) {
|
|
|
return result;
|
|
|
}
|
|
|
}
|
|
|
return null;
|
|
|
}, seconds, TimeUnit.SECONDS);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 等待并匹配图例
|
|
|
*
|
|
|
* @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 最低相似度
|
|
|
* @return 匹配区域
|
|
|
*/
|
|
|
public ViewRect waitAndMatchLegend(String legend, double minSimilar) {
|
|
|
return waitAndMatchLegend(legend, minSimilar, defaultTimeOut);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 等待并匹配图例
|
|
|
*
|
|
|
* @param legend 图例
|
|
|
* @param minSimilar 最低相似度
|
|
|
* @return 匹配区域
|
|
|
*/
|
|
|
public ViewRect waitAndMatchLegend(Legend legend, double minSimilar) {
|
|
|
return waitAndMatchLegend(legend, minSimilar, defaultTimeOut);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 图例组合匹配,图例区域结果和入参长度一致,任意图例检测到后则会返回,其他未检测到的区域则为null
|
|
|
* <p>
|
|
|
* 说明:一个操作之后可能会有多个预期结果,以发起【进攻】操作为例,可能会提示多种结果如下:
|
|
|
* 1.成功发起
|
|
|
* 2.敌人已逃走
|
|
|
* 3.兵力不足
|
|
|
* 不同的操作返回结果,会有不同的后续操作;如果以获取某预期结果去检测,考虑处理时间延迟等需要加上等待时间
|
|
|
* 不同结果的获取就会依次串行,这样检查肯定会浪费大量检测时间。因此正对此情况做了并联检测机制。
|
|
|
*
|
|
|
* @param legends 图例
|
|
|
* @param minSimilar 最低相似度
|
|
|
* @return 匹配图片
|
|
|
*/
|
|
|
public ViewRect[] matchLegends(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, minSimilar);
|
|
|
if (viewRects[i] != null) {
|
|
|
return viewRects;
|
|
|
}
|
|
|
}
|
|
|
return viewRects;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 等待并匹配图例组合
|
|
|
*
|
|
|
* @param legends 图例组
|
|
|
* @param minSimilar 最低相似度
|
|
|
* @param seconds 最长等待秒数
|
|
|
* @return 匹配区域
|
|
|
*/
|
|
|
public ViewRect[] waitAndMatchLegends(String[] legends, double minSimilar, long seconds) {
|
|
|
if (legends.length == 0) {
|
|
|
return new ViewRect[0];
|
|
|
}
|
|
|
Logger.info("等待并匹配图例组合:{}", String.join(",", legends));
|
|
|
ViewRect[] result = TaskUtil.timeTask(() -> {
|
|
|
while (JMainService.getInstance().run) {
|
|
|
ViewRect[] viewRects = matchLegends(legends, minSimilar);
|
|
|
for (ViewRect viewRect : viewRects) {
|
|
|
if (viewRect != null) {
|
|
|
return viewRects;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
return new ViewRect[legends.length];
|
|
|
}, seconds, TimeUnit.SECONDS);
|
|
|
if (result == null) {
|
|
|
result = new ViewRect[legends.length];
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 等待并匹配图例组合
|
|
|
*
|
|
|
* @param legends 图例组
|
|
|
* @param minSimilar 最低相似度
|
|
|
* @return 匹配区域
|
|
|
*/
|
|
|
public ViewRect[] waitAndMatchLegends(String[] legends, double minSimilar) {
|
|
|
return waitAndMatchLegends(legends, minSimilar, defaultTimeOut);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 匹配指定坐标色值
|
|
|
*
|
|
|
* @param color 色值坐标点
|
|
|
* @return 是否匹配
|
|
|
*/
|
|
|
public boolean matchColor(ViewColor color) {
|
|
|
ViewRect rect = new ViewRect(color.getX(), color.getY(), color.getX(), color.getY());
|
|
|
// 获取实时屏幕
|
|
|
BufferedImage pointImage = capture(robot, rect);
|
|
|
ImageUtil.show(pointImage);
|
|
|
int[][] pointData = ImageUtil.getImageRGB(pointImage);
|
|
|
boolean similar = ColorUtil.isSimilar(pointData[0][0], color.getColor());
|
|
|
Logger.info("比较色值:{},{} ==> {}", Integer.toHexString(pointData[0][0]), Integer.toHexString(color.getColor()), similar ? "相似" : "不同");
|
|
|
return similar;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 查找颜色值占比 0~1
|
|
|
*
|
|
|
* @param color 颜色值例,如:#ffffff
|
|
|
* @return 占比
|
|
|
*/
|
|
|
public double findColor(int left, int top, int right, int bottom, String color) {
|
|
|
return findColor(of(left, top, right, bottom), color);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 查找颜色值占比 0~1
|
|
|
*
|
|
|
* @param rect 查找区域
|
|
|
* @param color 颜色值例,如:#ffffff
|
|
|
* @return 占比
|
|
|
*/
|
|
|
public double findColor(ViewRect rect, String color) {
|
|
|
// 获取实时屏幕
|
|
|
BufferedImage capture = capture(robot, rect);
|
|
|
ImageUtil.show(capture);
|
|
|
List<String> colors = new ArrayList<>();
|
|
|
for (int y = 0; y < capture.getHeight(); y++) {
|
|
|
for (int x = 0; x < capture.getWidth(); x++) {
|
|
|
colors.add("#" + Integer.toHexString(capture.getRGB(x, y) & 0xFFFFFF));
|
|
|
}
|
|
|
}
|
|
|
Map<String, Integer> countMap = new HashMap<>();
|
|
|
for (String c : colors) {
|
|
|
countMap.put(c, countMap.getOrDefault(c, 0) + 1);
|
|
|
}
|
|
|
|
|
|
double has = countMap.getOrDefault(color, 0);
|
|
|
return has / colors.size();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 指定区域查找颜色值,查到后点击
|
|
|
*
|
|
|
* @param color 颜色值例,如:#ffffff
|
|
|
*/
|
|
|
public boolean hasColorAndClick(int left, int top, int right, int bottom, String... color) {
|
|
|
ViewRect viewRect = of(left, top, right, bottom);
|
|
|
boolean hasColor = hasColor(viewRect, color);
|
|
|
if (hasColor) {
|
|
|
Logger.info("点击【{}】", viewRect);
|
|
|
mouseLeftClick(viewRect);
|
|
|
delay(500);
|
|
|
return true;
|
|
|
} else {
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 指定区域查找颜色
|
|
|
*
|
|
|
* @param color 颜色值例,如:#ffffff
|
|
|
* @return 占比
|
|
|
*/
|
|
|
public boolean hasColor(int left, int top, int right, int bottom, String... color) {
|
|
|
return hasColor(of(left, top, right, bottom), color);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 指定区域查找颜色值,查到后点击
|
|
|
*
|
|
|
* @param rect 查找区域
|
|
|
* @param color 颜色值例,如:#ffffff
|
|
|
* @return 操作结果
|
|
|
*/
|
|
|
public boolean hasColorAndClick(ViewRect rect, String... color) {
|
|
|
boolean hasColor = hasColor(rect, color);
|
|
|
if (hasColor) {
|
|
|
Logger.info("点击【{}】", rect);
|
|
|
mouseLeftClick(rect);
|
|
|
delay(500);
|
|
|
return true;
|
|
|
} else {
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 指定区域查找颜色值
|
|
|
*
|
|
|
* @param rect 查找区域
|
|
|
* @param color 颜色值例,如:#ffffff
|
|
|
* @return 占比
|
|
|
*/
|
|
|
public boolean hasColor(ViewRect rect, String... color) {
|
|
|
// 获取实时屏幕
|
|
|
BufferedImage capture = capture(robot, rect);
|
|
|
ImageUtil.show(capture);
|
|
|
for (int y = 0; y < capture.getHeight(); y++) {
|
|
|
for (int x = 0; x < capture.getWidth(); x++) {
|
|
|
String col = "#" + Integer.toHexString(capture.getRGB(x, y) & 0xFFFFFF);
|
|
|
for (String s : color) {
|
|
|
if (ColorUtil.isSimilar(ColorUtil.hexToColor(s), ColorUtil.hexToColor(col))) {
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 等待并匹配图例
|
|
|
*
|
|
|
* @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);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 将相对坐标转为色值坐标
|
|
|
*
|
|
|
* @return 绝对坐标
|
|
|
*/
|
|
|
public String[] of(String... legends) {
|
|
|
return legends;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 将相对坐标转为色值坐标
|
|
|
*
|
|
|
* @param relativePoint 相对坐标
|
|
|
* @return 绝对区域
|
|
|
*/
|
|
|
public ViewColor of(ViewPoint relativePoint, String hexColor) {
|
|
|
return of(relativePoint.getX(), relativePoint.getY(), hexColor);
|
|
|
}
|
|
|
} |