在这一个来月的开发过程中,自行封装了很多有用的方法、工具类,有需要的可自取
分享是一种美德,如果对你有帮助,请在文末评论感谢。
赠人玫瑰,手有余香
自定义消息气泡
http请求
上传本地文件到服务器
图片阈值化灰度化的处理
远程图片处理并上传
远程日志上传
坐标转换
随机点击
......
界面ui下拉框初始化(多级联动)
界面ui单选框初始化
UI缓存数据存取
......
websocket自动重连
websocket消息处理
websocket心跳处理
......
图片二值化工具集成
浩然ocr文字识别工具集成
.......
一、依赖文件
1、配置文件
config.js
let config = {}
// http请求地址
config.httpBaseUrl = "http://hy.zjh336.cn"
// websocket请求地址
config.webSocketBaseUrl = "ws://hy.zjh336.cn/autoJsWs"
// 公共脚本key
config.commonScriptKey = "common";
// 业务脚本key
config.serviceScriptKey = "佣兵战纪";
isDev = false
if (isDev) {
// http请求地址
config.httpBaseUrl = "http://192.168.0.103:9999"
// websocket请求地址
config.webSocketBaseUrl = "ws://192.168.0.103:9999/autoJsWs"
}
// https://blog.csdn.net/wangsheng5454/article/details/117119402
// 安卓API版本 29 安卓10
config.SDK_API_VERSION = android.os.Build.VERSION.SDK_INT
module.exports = config2、公共常量类
commonConstant.js
// 公共设置key
let commonSettingKey = [
{ key: 'debugModel', type: "开关" },
{ key: 'webSocketLog', type: "开关" }
]
let constant = {
'commonSettingKey': commonSettingKey,
}
module.exports = constant 3、业务常量类
let 旅行点 = {
"贫瘠之地": [
{ name: "残暴的野猪人", "x": 636, "y": 377, matchingChart: "野猪人" },
{ name: "空气元素", "x": 1021, "y": 396, matchingChart: "空气" },
{ name: "塞瑞娜-血羽", "x": 1410, "y": 395, matchingChart: "血羽" },
{ name: "药剂师赫布瑞姆", "x": 658, "y": 788, matchingChart: "师" },
{ name: "烈日行者傲蹄", "x": 1032, "y": 769, matchingChart: "日行" },
{ name: "巴拉克-科多班恩", "x": 1405, "y": 791, matchingChart: "巴拉克" },
{ name: "疯狂投弹者", "x": 748, "y": 372, matchingChart: "狂投" },
{ name: "腐烂的普雷莫尔", "x": 1218, "y": 408, matchingChart: "烂的" },
{ name: "尼尔鲁-火刃", "x": 850, "y": 750, matchingChart: "尼尔" },
{ name: "神秘奶牛", "x": 1280, "y": 780, matchingChart: "奶牛" }
],
"费伍德森林": [
{ name: "猎手拉文", "x": 634, "y": 377, matchingChart: "拉文" },
{ name: "淬油之刃", "x": 1020, "y": 396, matchingChart: "油之" },
{ name: "堕落的守卫", "x": 1410, "y": 395, matchingChart: "守卫" },
{ name: "哈拉梵", "x": 658, "y": 788, matchingChart: "哈拉" },
{ name: "腐化的古树", "x": 1032, "y": 769, matchingChart: "化的" },
{ name: "魔王贝恩霍勒", "x": 1405, "y": 791, matchingChart: "王贝" }
],
"冬泉谷": [
{ name: "雪爪", "x": 718, "y": 634, matchingChart: "雪爪" },
{ name: "雪人猎手拉尼尔", "x": 689, "y": 1020, matchingChart: "手拉" },
{ name: "雪崩", "x": 696, "y": 1415, matchingChart: "雪崩" },
{ name: "厄苏拉-风怒", "x": 302, "y": 670, matchingChart: "风" },
{ name: "冰吼", "x": 307, "y": 1032, matchingChart: "冰吼" },
{ name: "冰霜之王埃霍恩", "x": 298, "y": 1397, matchingChart: "之王" }
]
}
let 队伍列表 = ["刷图", "任务", "火焰", "冰霜", "自然", "奥术", "圣光", "野兽"]
let 难度 = [{ id: 1, name: '普通' }, { id: 2, name: '英雄' }]
let 队伍模式 = [{ id: 1, name: '固定识图' }, { id: 2, name: '自定义识字' }]
// 业务设置key
let serviceSettingKey = [
{ key: 'select旅行点', type: "下拉框" },
{ key: 'select悬赏', type: "下拉框" },
{ key: 'select难度', type: "单选框" },
{ key: '队伍selectModel', type: "单选框" },
{ key: 'select队伍', type: "下拉框" },
{ key: '填写队伍', type: "输入框" },
{ key: '随机技能', type: "开关" }
]
let constant = {
'旅行点': 旅行点,
'队伍列表': 队伍列表,
'难度': 难度,
'队伍模式': 队伍模式,
'serviceSettingKey':serviceSettingKey
}
module.exports = constant二、工具类
1、websocket工具类
websocketHandler.js
let heartTimer = null // 心跳句柄
let reConnectTimer = null // 重连句柄
let webSocketConfig = {
isHeartData: true,
isReconnect: true,
heartTime: 10000,
reConnectTime: 20000
}
let isClose = true
let socketTask = null
let connectOK = false // 连接是否成功
// 导入配置类
let config = require("./config.js")
// 导入工具类
let utils = require("./utils.js")
let commonStorage = storages.create("zjh336.cn" + config.commonScriptKey);
let fixedMessageEnum = {
ping: '0', // 发送心跳
pong: '1', // 接收心跳回复
exit: '2', // 接收退出指令
update: '3', // 接收版本更新指令
asking_exit: '4', // 询问退出
confirm_exit: '5', // 确认退出
cancel_login: '6', // 取消登陆
connect_complete: '7'// 连接完成
}
function isJSON(str) {
if (typeof str == 'string') {
try {
var obj = JSON.parse(str);
if (typeof obj == 'object' && obj) {
return true;
} else {
return false;
}
} catch (e) {
return false;
}
}
}
function isNumberStr(str) {
return /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g.test(str)
}
// 脚本退出时取消WebSocket
events.on('exit', () => {
if (socketTask) {
conosle.log("退出脚本,关闭websocket")
socketTask.cancel();
}
});
let websocketHandler = {}
// 初始化
websocketHandler.initWebSocket = () => {
let deviceUUID = commonStorage.get('deviceUUID')
if (!deviceUUID) {
// 安卓10及以上 取androidId 10以下 取IMEI
deviceUUID = config.SDK_API_VERSION > 28 ? device.getAndroidId() : device.getIMEI()
commonStorage.put("deviceUUID", deviceUUID)
}
if (socketTask) {
socketTask.close(1000, null)
}
// 创建websocket链接
socketTask = $web.newWebSocket(config.webSocketBaseUrl + "/" + deviceUUID)
// 监听socket是否打开成功
socketTask.on("open", (res, ws) => {
let webSocketLog = commonStorage.get('webSocketLog')
if (webSocketLog) {
console.log("websocket连接成功!")
}
isClose = false
connectOK = true
if (webSocketConfig.isHeartData) {
websocketHandler.clearHeart()
websocketHandler.startHeart()
}
if (reConnectTimer) {
clearInterval(reConnectTimer)
}
reConnectTimer = null
})
// 监听到错误异常
socketTask.on("failure", (err, res, ws) => {
let webSocketLog = commonStorage.get('webSocketLog')
if (webSocketLog) {
console.log("websocket连接异常!", err)
}
// websocket连接异常
connectOK = false
if (webSocketConfig.isHeartData && heartTimer != null) {
websocketHandler.clearHeart()
}
if (reConnectTimer == null && webSocketConfig.isReconnect) {
// 执行重连操作
websocketHandler.reConnectSocket()
}
})
// 监听socket关闭
socketTask.on("closing", (code, reason, ws) => {
//console.log("websocket正在关闭!")
})
socketTask.on("closed", (code, reason, ws) => {
// console.log("websocket已关闭!")
connectOK = false
if (webSocketConfig.isHeartData && heartTimer != null) {
websocketHandler.clearHeart()
}
// 判断是否为异常关闭
if (reConnectTimer == null && !isClose && webSocketConfig.isReconnect) {
// 执行重连操作
websocketHandler.reConnectSocket()
}
});
// 接收到消息
socketTask.on('text', (text, ws) => {
if (isNumberStr(text)) {
// 是数字
// 固定格式消息处理
websocketHandler.fixedMessageHandler(text)
// 业务处理
} else if (text) {
// 写具体的业务操作
websocketHandler.objectMessageHandler(text)
} else {
console.log('非法数据,无法解析')
}
});
}
// 关闭连接
websocketHandler.close = () => {
if (socketTask) {
socketTask.cancel()
socketTask.close(1000, null)
socketTask = null
}
}
// 心跳
websocketHandler.startHeart = () => {
heartTimer = setInterval(() => {
let webSocketLog = commonStorage.get('webSocketLog')
if (webSocketLog) {
console.log("websocket发送心跳!")
}
// 发送心跳
socketTask.send(fixedMessageEnum['ping'].toString())
}, webSocketConfig.heartTime)
}
// 清除心跳
websocketHandler.clearHeart = () => {
if (heartTimer) {
clearInterval(heartTimer)
}
heartTimer = null
}
// 重连
websocketHandler.reConnectSocket = () => {
reConnectTimer = setInterval(() => {
let webSocketLog = commonStorage.get('webSocketLog')
if (webSocketLog) {
console.log("websocket重连!")
}
if (!connectOK) {
websocketHandler.initWebSocket()
} else {
if (reConnectTimer) {
clearInterval(reConnectTimer)
}
}
}, webSocketConfig.reConnectTime)
}
// 发送消息
websocketHandler.sendMessage = (message) => {
console.log("websocket发送消息:" + message)
socketTask.send(message)
}
// 消息处理
websocketHandler.objectMessageHandler = (text) => {
if (!isJSON(text)) {
return
}
let messageData = JSON.parse(text)
// 强制退出
if (messageData.action === "forcedExit") {
toastLog("收到退出指令")
if (socketTask) {
socketTask.cancel();
socketTask.close(1000, null);
}
exit()
// 远程处理操作
} else if (messageData.action === "remoteHandler") {
// 调用具体操作逻辑
utils.remoteHandler(messageData.message)
}
}
// 全局固定格式消息处理
websocketHandler.fixedMessageHandler = (message) => {
switch (message) {
case fixedMessageEnum['pong']:
let webSocketLog = commonStorage.get('webSocketLog')
if (webSocketLog) {
console.log("websocket心跳回复")
}
break
case fixedMessageEnum['exit']:
break
case fixedMessageEnum['update']:
break
case fixedMessageEnum['asking_exit']:
// 接受到重复登陆指令,服务器询问是否踢人
// 确认
break
case fixedMessageEnum['connect_complete']:
}
}
module.exports = websocketHandler2、通用工具类
utils.js
importClass(android.widget.Toast);
importClass(android.view.Gravity);
let config = require("./config.js")
let toastCustom = null;
let view = null
let utilsObj = {}
// 分辨率 以竖屏标准看
let screenWidth = 1080
let screenHeight = 2400
// 数据map
let dataMap = {}
let commonStorage = storages.create("zjh336.cn" + config.commonScriptKey);
// 获取设备uuid
let deviceUUID = commonStorage.get('deviceUUID')
if (!deviceUUID) {
// 安卓10及以上 取androidId 10以下 取IMEI
deviceUUID = config.SDK_API_VERSION > 28 ? device.getAndroidId() : device.getIMEI()
commonStorage.put("deviceUUID", deviceUUID)
}
let curOcrName = commonStorage.get("文字识别插件") || "浩然"
// 浩然文字识别
let hrOcr = null
// tomato文字识别
let tomatoOcr = null
// tomato文字识别类
let tomatoOcrClass = null
utilsObj.initOcr = (ocrName) => {
curOcrName = ocrName
// commonStorage.put("文字识别插件", ocrName)
try {
// 如果有则先关闭
if (tomatoOcr) {
tomatoOcr.end()
}
if ("浩然" === ocrName) {
hrOcr = hrOcr || $plugins.load("com.hraps.ocr");
console.log("初始化浩然ocr")
} else if ("tomato" === ocrName) {
tomatoOcrClass = tomatoOcrClass || $plugins.load('com.tomato.ocr');
tomatoOcr = new tomatoOcrClass();
console.log("初始化tomatoOcr")
}
} catch (error) {
alert("文字识别插件初始化错误")
console.error("文字识别插件初始化错误", error)
}
}
/**
* 获取deviceUUID
* @returns
*/
utilsObj.getDeviceUUID = () => {
return deviceUUID;
}
/**
* 自定义消息气泡
* @param {*} msg
*/
utilsObj.toast = (msg) => {
if (!toastCustom) {
toastCustom = new Toast(context);
}
if (!view) {
view = Toast.makeText(context, msg, Toast.LENGTH_SHORT).getView();
}
toastCustom.setView(view);
toastCustom.setText(msg);
toastCustom.setDuration(200);
toastCustom.show();
}
/**
* 获取内存
* @returns
*/
utilsObj.getMemoryInfo = () => {
let runtime = java.lang.Runtime.getRuntime();
let maxMemory = runtime.maxMemory() / 1024 / 1024; // 能从操作系统那里挖到的最大的内存 单位字节Byte 536870912B === 512MB
let totalMemory = runtime.totalMemory() / 1024 / 1024; // 已经从操作系统那里挖过来的内存大小, 慢慢挖, 用多少挖多少
let freeMemory = runtime.freeMemory() / 1024 / 1024; // 挖过来而又没有用上的内存, 一般情况下都是很小
/* var sh = new Shell(true);
let packName = commonStorage.get('curAppPackage')
console.log(packName)
sh.exec("dumpsys meminfo "+packName);
sh.setCallback({
onNewLine: function(line){
//有新的一行输出时打印到控制台
log(line);
}
})
sh.exitAndWaitFor()
sh.exit(); */
return "最大内存:" + Number(maxMemory).toFixed(2) + "MB,已用内存" + Number(totalMemory).toFixed(2) + "MB,占用" + (Number(totalMemory / maxMemory) * 100).toFixed(2) + "%";
}
/**
* 清理内存
*/
utilsObj.clearMemory = () => {
let runtime = java.lang.Runtime.getRuntime();
runtime.gc();
}
/**
* 退出app
* @param {} appName
*/
utilsObj.exitApp = (appName, callback) => {
// 根据应用名称获取包名
let packageName = app.getPackageName(appName)
// 打开应用设置
app.openAppSetting(packageName);
text(app.getAppName(packageName)).waitFor();
let is_sure = textMatches(/(.*强.*|.*停.*|.*结.*|.*结束运行.*)/).findOne();
if (is_sure.enabled()) {
if (is_sure.clickable()) {
is_sure.click()
} else {
is_sure.clickCenter()
}
textMatches(/(.*确.*|.*定.*)/).findOne().click();
console.log(app.getAppName(packageName) + "应用已被关闭");
sleep(1000);
back();
sleep(1000);
if (callback) {
callback()
}
} else {
log(app.getAppName(packageName) + "应用不能被正常关闭或不在后台运行");
back();
sleep(1000);
if (callback) {
callback()
}
}
}
/**
* 创建排序方法
* @param {Array} array 原数组
* @param {String} key 排序key
* @param {Boolean} order 排序方法 true正序 false倒序
* @returns
*/
utilsObj.sortByKey = (array, key, order) => {
return array.sort(function (a, b) {
var x = a[key]; var y = b[key]
if (order) {
return ((x < y) ? -1 : ((x > y) ? 1 : 0))
} else {
return ((x < y) ? ((x > y) ? 1 : 0) : -1)
}
})
}
/**
* 数字转汉字
* @param {*} numberVal
* @returns
*/
utilsObj.convertNumberToChart = (numberVal) => {
if (Number(numberVal) === 1) {
return "一"
} else if (Number(numberVal) === 2) {
return "二"
} else if (Number(numberVal) === 3) {
return "三"
} else if (Number(numberVal) === 4) {
return "四"
} else if (Number(numberVal) === 5) {
return "五"
} else if (Number(numberVal) === 6) {
return "六"
}
}
/**
* 汉字转数字
* @param {*} chartVal
* @returns
*/
utilsObj.convertChartToNumber = (chartVal) => {
if (String(chartVal) === "一") {
return 1
} else if (String(chartVal) === "二") {
return 2
} else if (String(chartVal) === "三") {
return 3
} else if (String(chartVal) === "四") {
return 4
} else if (String(chartVal) === "五") {
return 5
} else if (String(chartVal) === "六") {
return 6
}
}
/**
* 数组includes 包含
* @param {*} arr
* @param {*} val
*/
utilsObj.includesContains = (arr, val) => {
let isIncludes = false
for (let i = 0; i < arr.length; i++) {
let key = arr[i]
if (val.indexOf(key) !== -1) {
isIncludes = true;
break;
}
}
return isIncludes;
}
/**
* http请求
* @param {*} url 请求地址
* @param {*} requestMethod 请求方法
* @param {*} requestBody 消息体
* @param {*} callback 回调函数
*/
utilsObj.request = (url, requestMethod, requestBody, callback) => {
// GET-键值对 POST-JSON
let contentType = requestMethod === "GET" ? "application/x-www-form-urlencoded" : 'application/json'
http.request(config.httpBaseUrl + "/ajControl/" + url, {
headers: {
"deviceUUID": deviceUUID
},
method: requestMethod,
contentType: contentType,
body: requestBody
}, callback);
}
/**
* 上传本地文件到服务器
* @param {String} localPath 本地文件路径
* @param {String} fileName 文件名称
* @param {Function} callback 回调函数
*/
utilsObj.uploadFileToServer = (localPath, fileName, callback) => {
http.postMultipart(config.httpBaseUrl + "/attachmentInfo/uploadFileToAutoJs", {
file: open(localPath),
imageName: fileName
}, null, (res, error) => {
if (!res) {
console.error("上传文件到服务器错误", error)
return;
}
let data = res.body.json()
if (res) {
if (callback) {
callback(config.httpBaseUrl + "/" + data.data)
}
} else {
console.error("上传文件到服务器错误", error)
}
});
}
/**
* 远程裁图灰度化阈值化并上传到服务端
* @param {int} x1 区域坐标x1
* @param {int} y1 区域坐标y1
* @param {int} x2 区域坐标x2
* @param {int} y2 区域坐标y2
* @param {int} threshold 阈值化相似度
* @param {int} maxVal 阈值化最大值
* @param {String} localImageName 要保存的本地图片名称
* @param {int} isOpenThreshold 是否开启灰度化阈值化
* @returns {String} remoteImageUrl 远程图片地址
*/
utilsObj.remoteClipGrayscaleAndThresholdToServer = (x1, y1, x2, y2, threshold, maxVal, localImageName, isOpenThreshold) => {
// 调用本地裁剪以及灰度化阈值化处理图片方法 返回本地图片路径
let localPathName = utilsObj.generateClipImgGrayThresholdToLocal(x1, y1, x2, y2, threshold, maxVal, localImageName, isOpenThreshold)
if (commonStorage.get("debugModel")) {
console.log("生成本地路径" + localPathName)
}
// 调用远程上传文件方法
utilsObj.uploadFileToServer(localPathName, deviceUUID + "/" + localImageName, (remoteImageURL) => {
if (commonStorage.get("debugModel")) {
console.log("远程图片地址:" + remoteImageURL)
}
})
}
/**
* 远程裁图灰度化阈值化文字识别并上传到服务端
* @param {Image} img 大图对象(一般为截全屏的图片对象)
* @param {int} x1 区域坐标x1
* @param {int} y1 区域坐标y1
* @param {int} x2 区域坐标x2
* @param {int} y2 区域坐标y2
* @param {int} threshold 阈值化相似度
* @param {int} maxVal 阈值化最大值
* @param {*} isOpenThreshold 是否开启灰度化、阈值化
*/
utilsObj.remoteClipGrayscaleAndThresholdAnalysisChartToServer = (x1, y1, x2, y2, threshold, maxVal, localImageName, isOpenThreshold) => {
// 截图全屏
let img = captureScreen();
// 调用本地裁剪 已经灰度化阈值化处理图片方法 并进行文字识别canvas重绘 返回本地图片路径
let localPathName = utilsObj.regionalAnalysisChartToCanvasImg(img, x1, y1, x2, y2, threshold, maxVal, localImageName, isOpenThreshold);
if (commonStorage.get("debugModel")) {
console.log("生成本地路径" + localPathName)
}
img.recycle();
// 调用远程上传文件方法
utilsObj.uploadFileToServer(localPathName, deviceUUID + "/" + localImageName, (remoteImageURL) => {
if (commonStorage.get("debugModel")) {
console.log("远程图片地址:" + remoteImageURL)
}
})
}
/**
* 远程执行脚本
* @param {脚本内容} scriptText
*/
utilsObj.remoteExecScript = (scriptText) => {
try {
// 解码
scriptText = decodeURIComponent(scriptText)
if (commonStorage.get("debugModel")) {
console.log("远程脚本内容:" + scriptText)
}
eval(scriptText)
console.log("远程执行脚本完成")
} catch (error) {
console.error("远程执行脚本错误:", error)
}
}
/**
* 远程上传日志到服务器
* @param {*} logName 日志名称
*/
utilsObj.remoteUploadLogToServer = (logName) => {
let localPathName = "/sdcard/autoJsLog/" + logName
// 调用远程上传文件方法
utilsObj.uploadFileToServer(localPathName, deviceUUID + "/" + logName, (remoteImageURL) => {
if (commonStorage.get("debugModel")) {
console.log("远程日志地址:" + remoteImageURL)
}
})
}
/**
* 远程重启脚本
* @param {*} restartType 重启类型 script mainScript
*/
utilsObj.remoteRestartScript = (restartType) => {
if (commonStorage.get("debugModel")) {
console.log("执行远程重启脚本:", restartType)
}
events.broadcast.emit("restartScript", restartType);
}
/**
* 远程处理操作
* @param {String} message base64加密后的json字符串
*/
utilsObj.remoteHandler = (message) => {
// 解密后字符串
let decodeAftrJson = $base64.decode(message)
// json字符串转换js对象
let operateObj = JSON.parse(decodeAftrJson)
// 调用方法名称
let functionName = operateObj.functionName
// 方法参数 例如:[1,2,3]
let functionParam = operateObj.functionParam
if (commonStorage.get("debugModel")) {
// 日志
console.log("远程执行方法", functionName, functionParam)
}
threads.start(() => {
if (['remoteClipGrayscaleAndThresholdToServer', 'remoteClipGrayscaleAndThresholdAnalysisChartToServer'].includes(functionName)) {
try {
images.stopScreenCapture()
images.requestScreenCapture()
sleep(500)
} catch (error) {
if (commonStorage.get('debugModel')) {
console.error("远程请求截图错误", error)
}
}
setTimeout(() => {
if (commonStorage.get('debugModel')) {
console.log("主程序刷新截图权限")
}
events.broadcast.emit("refreshScreenCapture", "");
}, 3000)
}
// 调用方法
utilsObj[functionName].apply(utilsObj, functionParam)
})
}
/**
* 转换坐标
* @desc 用于转换不同分辨率下的x y值
* @param {int} x 当前x坐标
* @param {int} y 当前y坐标
* @returns {x:int,y:int} 转换后的坐标
*/
utilsObj.convertXY = (x, y) => {
// 获取设备配置的分辨率
let curScreenWith = device.width
let curScreenHeight = device.height
// x系数
let xCoefficient = curScreenHeight / screenHeight
// y系数
let yCoefficient = curScreenWith / screenWidth
let result = { x: Math.round(x * xCoefficient), y: Math.round(y * yCoefficient) }
return result
}
/**
* 随机点击
* @desc 随机点击方法 支持偏移随机数
* @param {int} x x坐标值
* @param {int} y y坐标值
* @param {int} randomNum 随机数 0~当前随机数
* @param {boolean} needConvertXy 是否需要转换坐标
*/
utilsObj.randomClick = (x, y, randomNum, needConvertXy) => {
// 转换坐标
let xy = needConvertXy ? utilsObj.convertXY(x, y) : { x: x, y: y }
// 转换小于0的随机数
randomNum = randomNum < 0 ? 0 : randomNum
// 随机数 大于0.5为+ 否则为-
let plusNum = random()
// 计算随机坐标
let x1 = plusNum > 0.5 ? (Number(xy["x"]) + random(0, randomNum)) : Number(xy["x"]) + random(0, randomNum)
let y1 = plusNum > 0.5 ? (Number(xy["y"]) + random(0, randomNum)) : Number(xy["y"]) + random(0, randomNum)
// 点击随机坐标
click(x1, y1)
}
/**
* 灰度化、阈值化图片
* @desc 图片处理的基本方法
* @param {Image} img 需要处理的图片对象
* @param {int} threshold 阈值化相似度
* @param {int} maxVal 阈值化最大值
* @returns {Image} 处理后的图片对象
*/
utilsObj.grayscaleAndThreshold = (img, threshold, maxVal) => {
// 先灰度化
let newImg = images.grayscale(img);
// 再阈值化
let newimg2 = images.threshold(newImg, threshold, maxVal, 'BINARY');
// 回收图片
newImg.recycle()
return newimg2;
}
/**
* 灰度化、阈值化找图
* @desc 灰度化、阈值化后 从大图中找小图
* @param {Image} bigImg 原始大图对象
* @param {Image} smallImg 原始小图对象
* @param {int} threshold 阈值化相似度
* @param {int} maxVal 阈值化最大值
* @param {int} imgThreshold 找图相似度
* @returns {Object} 返回找图结果 images.findImage的返回结果
*/
utilsObj.grayThresholdFindImg = (bigImg, smallImg, threshold, maxVal, imgThreshold) => {
// 大图 灰度化、阈值化
let bigImgAfter = utilsObj.grayscaleAndThreshold(bigImg, threshold, maxVal);
// 小图 灰度化、阈值化
let smallImgAfter = utilsObj.grayscaleAndThreshold(smallImg, threshold, maxVal);
// 设置默认找图相似度
if (!imgThreshold) {
imgThreshold = 0.9
}
// 调用官方的找图方法
let findResult = images.findImage(bigImgAfter, smallImgAfter, { threshold: imgThreshold })
// 回收图片
bigImgAfter.recycle()
smallImgAfter.recycle()
// 返回结果
return findResult
}
/**
* 灰度化、阈值化区域找图
* @desc 在大图的区域坐标范围内,进行灰度化阈值化处理后,寻找目标图片,并返回基于大图的坐标
* @param {Image} img 大图对象(一般为截全屏的图片对象)
* @param {Image} targetImg 目标图对象
* @param {int} x1 区域坐标x1
* @param {int} y1 区域坐标y1
* @param {int} x2 区域坐标x2
* @param {int} y2 区域坐标y2
* @param {int} threshold 阈值化相似度
* @param {int} maxVal 阈值化最大值
* @param {int} imgThreshold 图片相似度
* @returns {x:int,y:int} 找图坐标对象
*/
utilsObj.regionalFindImg = (img, targetImg, x1, y1, x2, y2, threshold, maxVal, imgThreshold) => {
// 坐标转换
let xy1 = utilsObj.convertXY(x1, y1)
let xy2 = utilsObj.convertXY(x2, y2)
// 按照区域坐标裁剪大图
let clipImg = images.clip(img, xy1["x"], xy1["y"], xy2["x"] - xy1["x"], xy2["y"] - xy1["y"]);
// 调用灰度化阈值化找图 在大图中找小图
let findResult = utilsObj.grayThresholdFindImg(clipImg, targetImg, threshold, maxVal, imgThreshold)
// 回收裁剪图片
clipImg.recycle()
// 返回基于大图的坐标
return {
"x": findResult ? (xy1["x"] + findResult["x"]) : -1,
"y": findResult ? (xy1["y"] + findResult["y"]) : -1
}
}
/**
* 区域获取匹配图片
* @desc 在大图的区域坐标范围内,进行灰度化阈值化处理后,寻找目标图片,并返回基于大图的匹配结果最多5个,传入回调函数处理结果
* @param {Image} img 大图对象(一般为截全屏的图片对象)
* @param {Image} targetImg 目标图对象
* @param {int} x1 区域坐标x1
* @param {int} y1 区域坐标y1
* @param {int} x2 区域坐标x2
* @param {int} y2 区域坐标y2
* @param {int} threshold 阈值化相似度
* @param {int} maxVal 阈值化最大值
* @param {int} imgThreshold 图片相似度
* @param {int} matchingCount 匹配数量
* @param {Boolean} transparentMask 是否开启透明模板找图
* @return matcingResult
* first()取第一个匹配结果
* last()取最后一个匹配结果
* leftmost()取最左匹配结果
* topmost()取最上匹配结果
* rightmost()取最右匹配结果
* bottommost()取最下匹配结果
* best()取最高匹配结果
* worst()取最低匹配结果
* sortBy(cmp)匹配结果位置排序 指定方向 top-left 从上到下 从左到右
*/
utilsObj.regionalMatchTemplate = (img, targetImg, x1, y1, x2, y2, threshold, maxVal, imgThreshold, matchingCount, transparentMask) => {
// 坐标转换
let xy1 = utilsObj.convertXY(x1, y1)
let xy2 = utilsObj.convertXY(x2, y2)
// 按照区域坐标裁剪大图
let clipImg = images.clip(img, xy1["x"], xy1["y"], xy2["x"] - xy1["x"], xy2["y"] - xy1["y"]);
// 获取灰度化阈值化后的图片
let grayThresholdImg = utilsObj.grayscaleAndThreshold(clipImg, threshold, maxVal)
// 回收图片
clipImg.recycle()
files.createWithDirs("/sdcard/autoJsAfterImg/")
// 临时图片路径
let tempImgPath = "/sdcard/autoJsAfterImg/tempImg" + new Date().getTime() + ".png"
// 保存临时图片
images.save(grayThresholdImg, tempImgPath, "png", 100);
// 读取临时图片
let tempImg = images.read(tempImgPath)
// 调用匹配图片方法
let matchingResult = images.matchTemplate(tempImg, targetImg, {
threshold: imgThreshold,
max: matchingCount,
transparentMask: transparentMask
})
// 回收图片
grayThresholdImg.recycle()
tempImg.recycle()
files.remove(tempImgPath)
return matchingResult
}
/**
* 区域灰度化阈值化找圆
* @param {Image} img 大图对象(一般为截全屏的图片对象)
* @param {int} x1 区域坐标x1
* @param {int} y1 区域坐标y1
* @param {int} x2 区域坐标x2
* @param {int} y2 区域坐标y2
* @param {int} threshold 阈值化相似度
* @param {int} maxVal 阈值化最大值
*/
utilsObj.regionalFindCircles = (img, x1, y1, x2, y2, threshold, maxVal) => {
// 坐标转换
let xy1 = utils.convertXY(x1, y1)
let xy2 = utils.convertXY(x2, y2)
// 按照区域坐标裁剪大图
let clipImg = images.clip(img, xy1["x"], xy1["y"], xy2["x"] - xy1["x"], xy2["y"] - xy1["y"]);
// 灰度化、阈值化图片
let imgAfter = utils.grayscaleAndThreshold(clipImg, threshold, maxVal);
files.createWithDirs("/sdcard/autoJsAfterImg/")
// 临时图片路径
let tempImgPath = "/sdcard/autoJsAfterImg/tempImg" + new Date().getTime() + ".png"
// 保存临时图片
images.save(imgAfter, tempImgPath, "png", 100);
// 读取图片
let tempImage = images.read(tempImgPath)
// 灰度化
let grayImg = images.grayscale(tempImage)
imgAfter.recycle()
// 灰度化图片
let resultArr = images.findCircles(grayImg)
tempImage.recycle()
clipImg.recycle()
grayImg.recycle()
// 删除临时图片
files.remove(tempImgPath)
let returnResultArr = []
resultArr.forEach(item => {
returnResultArr.push({
x: Number(item.x) + Number(xy1["x"]),
y: Number(item.y) + Number(xy1["y"]),
radius: Number(item.radius).toFixed(2)
})
})
// 返回找圆结果
return returnResultArr;
}
/**
* 裁剪图片并灰度化阈值化图片
* @desc 全屏截图并裁剪坐标区域的图片,再进行灰度化、阈值化处理
* @param {int} x1 区域坐标x1
* @param {int} y1 区域坐标y1
* @param {int} x2 区域坐标x2
* @param {int} y2 区域坐标y2
* @param {int} threshold 阈值化相似度
* @param {int} maxVal 阈值化最大值
* @param {int} isOpenThreshold 是否开启灰度化阈值化
* @returns {Image} 返回处理后的图片对象
*/
utilsObj.generateClipImgGrayThreshold = (x1, y1, x2, y2, threshold, maxVal, isOpenThreshold) => {
// 截全屏
let img = captureScreen();
// 裁剪区域部分
let clipImg = images.clip(img, x1, y1, x2 - x1, y2 - y1);
if (!isOpenThreshold) {
// 回收全屏图片
img.recycle()
// 返回裁剪图片
return clipImg;
} else {
// 灰度化、阈值化图片
let imgAfter = utilsObj.grayscaleAndThreshold(clipImg, threshold, maxVal);
// 回收裁剪图片
clipImg.recycle()
// 回收全屏图片
img.recycle()
// 返回处理后的图片
return imgAfter
}
}
/**
* 裁剪图片并灰度化阈值化图片再保存到本地
* @desc 全屏截图并裁剪坐标区域的图片,再进行灰度化、阈值化处理
* @param {int} x1 区域坐标x1
* @param {int} y1 区域坐标y1
* @param {int} x2 区域坐标x2
* @param {int} y2 区域坐标y2
* @param {int} threshold 阈值化相似度
* @param {int} maxVal 阈值化最大值
* @param {String} localImageName 要保存的本地图片名称
* @param {int} isOpenThreshold 是否开启灰度化阈值化
* @returns {String} 本地图片路径
*/
utilsObj.generateClipImgGrayThresholdToLocal = (x1, y1, x2, y2, threshold, maxVal, localImageName, isOpenThreshold) => {
// 裁剪图片并 灰度化阈值化 若不开启灰度化阈值化则仅返回裁剪图片
let img = utilsObj.generateClipImgGrayThreshold(x1, y1, x2, y2, threshold, maxVal, isOpenThreshold)
// 本地图片路径
let localImagePath = "/sdcard/autoJsLocalImg/"
// 创建本地目录
files.createWithDirs(localImagePath)
// 保存到本地
images.save(img, localImagePath + localImageName, "png", 100);
// 回收图片
img.recycle()
// 返回本地图片路径
return localImagePath + localImageName
}
/**
* 灰度化、阈值化多点找色
* @desc 基于灰度化阈值化的多点找色
* @param {Image} bigImg 大图对象
* @param {int} threshold 阈值化相似度
* @param {int} maxVal 阈值化最大值
* @param {string} color 目标颜色值(第一个点的颜色值)
* @param {Array} colorOther 其他颜色数组 例如:[[35, 30, "#FFFFFF"], [-28, -2, "#000000"], [-23, 20, "#000000"]]
* @param {int} colorThreshold 颜色相似度
* @returns {Object} 返回找色结果 images.findMultiColors的返回结果
*/
utilsObj.grayThresholdFindMultipleColor = (bigImg, threshold, maxVal, color, colorOther, colorThreshold) => {
// 大图 进行灰度化、阈值化处理
let bigImgAfter = utilsObj.grayscaleAndThreshold(bigImg, threshold, maxVal);
files.createWithDirs("/sdcard/autoJsAfterImg/")
// 临时图片路径
let tempImgPath = "/sdcard/autoJsAfterImg/tempImg" + new Date().getTime() + ".png"
// 保存临时图片
images.save(bigImgAfter, tempImgPath, "png", 10);
// 调用官方多点找色方法
let findResult = images.findMultiColors(bigImgAfter, color, colorOther, { threshold: colorThreshold });
// 删除临时图片
files.remove(tempImgPath)
// 回收处理后的大图
bigImgAfter.recycle()
// 返回结果
return findResult
}
/**
* 灰度化、阈值化区域多点找色
* @desc 在大图的区域坐标范围内,进行灰度化阈值化处理后,寻找匹配的多点颜色, 并返回基于大图的坐标
* @param {Image} img 大图对象(一般为截全屏的图片对象)
* @param {int} x1 区域坐标x1
* @param {int} y1 区域坐标y1
* @param {int} x2 区域坐标x2
* @param {int} y2 区域坐标y2
* @param {int} threshold 阈值化相似度
* @param {int} maxVal 阈值化最大值
* @param {String} color 目标颜色值(第一个点的颜色值)
* @param {Array} colorOther 其他颜色数组 例如:[[35, 30, "#FFFFFF"], [-28, -2, "#000000"], [-23, 20, "#000000"]]
* @param {int} colorThreshold 颜色相似度
* @returns {x:int,y:int} 找色坐标对象
*/
utilsObj.regionalFindMultipleColor = (img, x1, y1, x2, y2, threshold, maxVal, color, colorOther, colorThreshold) => {
// 坐标转换
let xy1 = utilsObj.convertXY(x1, y1)
let xy2 = utilsObj.convertXY(x2, y2)
// 按照区域坐标裁剪大图
let clipImg = images.clip(img, xy1["x"], xy1["y"], xy2["x"] - xy1["x"], xy2["y"] - xy1["y"]);
// 灰度化、阈值化多点找色
let findResult = utilsObj.grayThresholdFindMultipleColor(clipImg, threshold, maxVal, color, colorOther, colorThreshold)
// 回收裁剪图片
clipImg.recycle()
// 返回基于大图的坐标
return {
"x": findResult ? (xy1["x"] + findResult["x"]) : -1,
"y": findResult ? (xy1["y"] + findResult["y"]) : -1
}
}
/**
* 灰度化、阈值化区域识别文字
* @desc 在大图的区域坐标范围内,进行灰度化阈值化处理后 再进行文字识别
* @param {Image} img 大图对象(一般为截全屏的图片对象)
* @param {int} x1 区域坐标x1
* @param {int} y1 区域坐标y1
* @param {int} x2 区域坐标x2
* @param {int} y2 区域坐标y2
* @param {int} threshold 阈值化相似度
* @param {int} maxVal 阈值化最大值
* @returns {Array} 文字识别内容
*/
utilsObj.regionalAnalysisChart = (img, x1, y1, x2, y2, threshold, maxVal) => {
// 坐标转换
let xy1 = utilsObj.convertXY(x1, y1)
let xy2 = utilsObj.convertXY(x2, y2)
// 按照区域坐标裁剪大图
let clipImg = images.clip(img, xy1["x"], xy1["y"], xy2["x"] - xy1["x"], xy2["y"] - xy1["y"]);
// 灰度化、阈值化图片
let imgAfter = utilsObj.grayscaleAndThreshold(clipImg, threshold, maxVal);
// 回收裁剪图片
clipImg.recycle();
// 获取文字识别结果
let resultStr = utilsObj.ocrGetContentStr(imgAfter)
// 回收灰度化、阈值化后的图片
imgAfter.recycle();
return resultStr;
}
/**
* ocr获取文字识别内容字符串结果
* @param {*} img
*/
utilsObj.ocrGetContentStr = (img) => {
// 当前使用浩然ocr且已经初始化
if (curOcrName === "浩然" && hrOcr) {
// 文字识别
let results = hrOcr.detect(img.getBitmap(), 1);
// 读取文字识别内容
let contentArr = Object.values(results).map(item => item.text) || []
// 控制台是否打印识图结果
if (commonStorage.get("debugModel")) {
console.info("【文字识别】:" + contentArr.join(''))
}
// 返回文字识别内容结果
return contentArr.join('')
// 当前使用tomatoOcr且已经初始化
} else if (curOcrName === "tomato" && tomatoOcr) {
// 文字识别
let results = tomatoOcr.ocrBitmap(img.getBitmap(), 2);
// 读取文字识别内容
let ocrArr = results ? JSON.parse(results) : []
// 文字识别2
let results2 = tomatoOcr.ocrBitmap(img.getBitmap(), 3);
// 读取文字识别内容
let ocrArr2 = results2 ? JSON.parse(results2) : []
let contentArr = ocrArr.map(item => item.words)
let contentArr2 = ocrArr2.map(item => item.words)
contentArr = contentArr.concat(contentArr2)
// 控制台是否打印识图结果
if (commonStorage.get("debugModel")) {
// 读取文字识别内容
console.info("【文字识别】:" + contentArr.join(''))
}
// 返回文字识别内容结果
return contentArr.join('')
}
return '';
}
/**
* ocr获取文字识别固定内容匹配坐标
* @param {*} img
*/
utilsObj.ocrGetPositionByContent = (img, matchingContent, x1, y1, x2, y2) => {
// 当前使用浩然ocr且已经初始化
if (curOcrName === "浩然" && hrOcr) {
// 文字识别
let results = hrOcr.detect(img.getBitmap(), 1);
// 读取文字识别内容
let ocrArr = Object.values(results)
// 控制台是否打印识图结果
if (commonStorage.get("debugModel")) {
// 读取文字识别内容
let contentArr = ocrArr.map(item => item.text)
console.info("【文字识别】:" + contentArr.join(''))
}
// 匹配目标orc对象
let targetOcr = ocrArr.find(item => item.text.indexOf(matchingContent) !== -1)
// 未匹配返回空
if (!targetOcr) {
return {
x: -1, y: -1
}
}
// 获取坐标
let frame = targetOcr.frame
return {
x: (frame[0] + (frame[4] - frame[0]) / 2),
y: (frame[1] + (frame[5] - frame[1]) / 2)
}
// 当前使用tomatoOcr且已经初始化
} else if (curOcrName === "tomato" && tomatoOcr) {
// 文字识别
let results = tomatoOcr.ocrBitmap(img.getBitmap(), 2);
// 读取文字识别内容
let ocrArr = JSON.parse(results) || []
// 控制台是否打印识图结果
if (commonStorage.get("debugModel")) {
// 读取文字识别内容
let contentArr = ocrArr.map(item => item.words)
console.info("【文字识别】:" + contentArr.join(''))
}
// 匹配目标orc对象
let targetOcr = ocrArr.find(item => item.words.indexOf(matchingContent) !== -1)
// 未匹配上,再次进行全屏匹配
if (!targetOcr) {
// 第二次文字识别
let results2 = tomatoOcr.ocrBitmap(img.getBitmap(), 3);
// 读取文字识别内容
let ocrArr2 = JSON.parse(results2) || []
// 控制台是否打印识图结果
if (commonStorage.get("debugModel")) {
// 读取文字识别内容
let contentArr = ocrArr2.map(item => item.words)
console.info("【文字识别】:" + contentArr.join(''))
}
targetOcr = ocrArr2.find(item => item.words.indexOf(matchingContent) !== -1)
// 未匹配返回空
if (!targetOcr) {
return {
x: -1, y: -1
}
}
// 计算匹配结果
let location = targetOcr.location
return {
x: (location[0][0] + (location[2][0] - location[0][0]) / 2),
y: (location[0][1] + (location[2][1] - location[0][1]) / 2)
}
}
return {
x: ((x2 - x1) / 2),
y: ((y2 - y1) / 2)
}
}
return {
x: -1,
y: -1
}
}
/**
* 灰度化、阈值化区域识别文字获取坐标
* @desc 在大图的区域坐标范围内,进行灰度化阈值化处理后 再进行文字识别 寻找与目标内容匹配的坐标位置
* @param {Image} img 大图对象(一般为截全屏的图片对象)
* @param {int} x1 区域坐标x1
* @param {int} y1 区域坐标y1
* @param {int} x2 区域坐标x2
* @param {int} y2 区域坐标y2
* @param {int} threshold 阈值化相似度
* @param {int} maxVal 阈值化最大值
* @param {String} matchingContent 匹配内容
* @returns {x:int,y:int} 匹配文字的坐标
*/
utilsObj.regionalAnalysisChartPostion = (img, x1, y1, x2, y2, threshold, maxVal, matchingContent) => {
// 坐标转换
let xy1 = utilsObj.convertXY(x1, y1)
let xy2 = utilsObj.convertXY(x2, y2)
// 按照区域坐标裁剪大图
let clipImg = images.clip(img, xy1["x"], xy1["y"], xy2["x"] - xy1["x"], xy2["y"] - xy1["y"]);
// 灰度化、阈值化图片
let imgAfter = utilsObj.grayscaleAndThreshold(clipImg, threshold, maxVal);
// 回收裁剪图片
clipImg.recycle();
// 根据内容获取匹配文字坐标
let matchingPosition = utilsObj.ocrGetPositionByContent(imgAfter, matchingContent, x1, y1, x2, y2)
// 回收灰度化、阈值化后的图片
imgAfter.recycle();
// 为找内容直接返回
if (matchingPosition.x === -1) {
return null
}
// 中心点x
let centerX = xy1["x"] + matchingPosition.x
// 中心点y
let centerY = xy1["y"] + matchingPosition.y
// 返回基于大图的坐标
return {
x: centerX, y: centerY
}
}
/**
* ocr获取文字识别内容结果(canvas绘画专用)
* @param {*} img
*/
utilsObj.ocrGetResultToCanvas = (img) => {
// 读取
let canvas = new Canvas(img);
let rectanglePaint = new Paint();
rectanglePaint.setStrokeWidth(3);
rectanglePaint.setColor(colors.parseColor("#00ff00"));
rectanglePaint.setStyle(Paint.Style.STROKE); //空心矩形框
let textPaint = new Paint();
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setTextSize(30);
textPaint.setStyle(Paint.Style.FILL);
textPaint.setColor(colors.parseColor("#f000ff"));
let fontMetrics = textPaint.getFontMetrics();
// 当前使用浩然ocr且已经初始化
if (curOcrName === "浩然" && hrOcr) {
// 文字识别
let results = hrOcr.detect(img.getBitmap(), 1);
// 读取文字识别内容
let ocrArr = Object.values(results)
// 控制台是否打印识图结果
if (commonStorage.get("debugModel")) {
// 读取文字识别内容
let contentArr = ocrArr.map(item => item.text)
console.info("【文字识别】:" + contentArr.join(''))
}
let len = results.size();
for (var i = 0; i < len; i++) {
let data = results.get(i);
let frame = data.frame;
let rect = [frame.get(0), frame.get(1), frame.get(4), frame.get(5)];
canvas.drawRect(rect[0], rect[1], rect[2], rect[3], rectanglePaint);
canvas.drawText(
data.text,
rect[0] + parseInt((rect[2] - rect[0]) / 2),
rect[3] + Math.abs(fontMetrics.top),
textPaint
);
}
// 当前使用tomatoOcr且已经初始化
} else if (curOcrName === "tomato" && tomatoOcr) {
// 文字识别
let results = tomatoOcr.ocrBitmap(img.getBitmap(), 3);
console.log("直接获取结果", results)
// 读取文字识别内容
let ocrArr = JSON.parse(results) || []
// 控制台是否打印识图结果
if (commonStorage.get("debugModel")) {
// 读取文字识别内容
let contentArr = ocrArr.map(item => item.words)
console.info("【文字识别】:" + contentArr.join(''))
}
ocrArr.forEach(item => {
let location = item.location;
let rect = [location[0][0], location[0][1], location[2][0], location[2][1]];
canvas.drawRect(rect[0], rect[1], rect[2], rect[3], rectanglePaint);
canvas.drawText(
item.words,
rect[0] + parseInt((rect[2] - rect[0]) / 2),
rect[3] + Math.abs(fontMetrics.top),
textPaint
);
})
}
let image = canvas.toImage();
return image;
}
/**
* 灰度化、阈值化 区域识别文字并使用canvas生成图片保存到本地
* @param {Image} img 大图对象(一般为截全屏的图片对象)
* @param {int} x1 区域坐标x1
* @param {int} y1 区域坐标y1
* @param {int} x2 区域坐标x2
* @param {int} y2 区域坐标y2
* @param {int} threshold 阈值化相似度
* @param {int} maxVal 阈值化最大值
* @param {String} localImageName 本地图片路径
* @param {*} isOpenThreshold 是否开启灰度化、阈值化
*/
utilsObj.regionalAnalysisChartToCanvasImg = (img, x1, y1, x2, y2, threshold, maxVal, localImageName, isOpenThreshold) => {
// 坐标转换
let xy1 = utilsObj.convertXY(x1, y1)
let xy2 = utilsObj.convertXY(x2, y2)
// 按照区域坐标裁剪大图
let clipImg = images.clip(img, xy1["x"], xy1["y"], xy2["x"] - xy1["x"], xy2["y"] - xy1["y"]);
// 处理图片
let handlerImg
// 开启灰度化、阈值化
if (isOpenThreshold) {
// 灰度化、阈值化图片
handlerImg = utilsObj.grayscaleAndThreshold(clipImg, threshold, maxVal);
// 回收裁剪图片
clipImg.recycle();
// 不开启 直接使用裁剪图片
} else {
handlerImg = clipImg;
}
// 返回img
let canvasImg = utilsObj.ocrGetResultToCanvas(handlerImg);
let newFilepath = "/sdcard/autoJsTools/analysisChart/" + localImageName;
// 创建目录
files.createWithDirs(newFilepath);
// 保存canvas重绘图片
images.save(canvasImg, newFilepath);
// 回收图片
canvasImg.recycle();
// 回收图片
handlerImg.recycle();
// 返回新图片路径
return newFilepath;
}
/**
* 灰度化、阈值化 区域点击文字
* @desc 在大图的区域坐标范围内,进行灰度化阈值化处理后 再进行文字识别 寻找与目标内容匹配的坐标位置 再点击坐标
* @param {Image} img 大图对象(一般为截全屏的图片对象)
* @param {int} x1 区域坐标x1
* @param {int} y1 区域坐标y1
* @param {int} x2 区域坐标x2
* @param {int} y2 区域坐标y2
* @param {int} threshold 阈值化相似度
* @param {int} maxVal 阈值化最大值
* @param {String} matchingContent 匹配内容
* @param {Function} successCall 成功回调
*/
utilsObj.regionalClickText = (img, x1, y1, x2, y2, threshold, maxVal, matchingContent, successCall) => {
// 灰度化、阈值化区域识别文字获取坐标
let macthingXy = utilsObj.regionalAnalysisChartPostion(img, x1, y1, x2, y2, threshold, maxVal, matchingContent)
if (macthingXy) {
utilsObj.randomClick(macthingXy.x, macthingXy.y, 1, false);
if (successCall) {
successCall()
}
}
}
/**
* 初始化单选框组
* @param {*} UIID ui组件id
* @param {*} dataList 数据列表 {id:1,name:''}
* @param {*} defaultCheckId 默认选中id
* @param {*} checkCallback 选中回调事件
*/
utilsObj.initRadioGroup = (UIID, dataList, defaultCheckId, checkCallback) => {
if (!UIID || !dataList) {
return
}
ui[UIID].removeAllViews();
// 初始化单选框组数据
dataList.forEach((item) => {
let radioButton = new android.widget.RadioButton(context);
let lp = new android.widget.RadioGroup.LayoutParams(android.widget.RadioGroup.LayoutParams.WRAP_CONTENT, android.widget.RadioGroup.LayoutParams.WRAP_CONTENT);
// lp.setMargins(0,0,0,0);
//radioButton.setPadding(0); // 设置文字距离按钮四周的距离
radioButton.setId(item.id);//设置radiobutton的id
radioButton.setText(item.name);
//radioButton.setTextColor(android.R.drawable.textcolor_recharge_radiobutton);//字体颜色
/* if (item.id == defaultCheckId) {
// 初始化默认选中
radioButton.setChecked(true)
} */
ui[UIID].addView(radioButton, lp);
})
// 初始化checked事件
utilsObj.radioGroupCheckedEvent(UIID, dataList, checkCallback);
// 初始化默认选中
ui[UIID].check(defaultCheckId)
/* let needCheckButton = ui[UIID].findViewById(defaultCheckId);
needCheckButton.setChecked(true); */
}
/**
* 设置单选框change事件
* @param {*} UIID ui的id
* @param {*} dataList 数据列表
* @param {*} callback 回调函数
* @returns
*/
utilsObj.radioGroupCheckedEvent = (UIID, dataList, callback) => {
if (!UIID) {
return
}
dataMap[UIID] = dataList
ui[UIID].setOnCheckedChangeListener(new android.widget.RadioGroup.OnCheckedChangeListener({
onCheckedChanged: function (parent, checkedId) {
if (callback) {
//let buttonId = parent.getCheckedRadioButtonId()
//console.log(buttonId, checkedId)
// 获取id匹配的数据项
let data = dataList.find(item => item.id === checkedId)
let textContent = data ? data.name : ""
callback(textContent, parent, dataList, checkedId)
}
}
}))
}
/**
* 初始化下拉框
* @param {*} UIID ui组件id
* @param {*} dataList 数据列表
* @param {*} defaultSelectIndex 默认选中的数据索引(优先下标 小于0时根据defaultSelectItem取值)
* @param {*} defaultSelectItem 默认选中数据项
* @param {*} changeCallback 下拉框change回调
* @returns
*/
utilsObj.initSelect = (UIID, dataList, defaultSelectIndex, defaultSelectItem, changeCallback) => {
if (!UIID || !dataList) {
return
}
// 初始化下拉框
let adapter = new android.widget.ArrayAdapter(context, android.R.layout.simple_spinner_item, dataList);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
ui[UIID].setAdapter(adapter);
// 初始化change事件
utilsObj.selectChangeEvent(UIID, dataList, changeCallback);
// 初始化默认值
let selectIndex = defaultSelectIndex && defaultSelectIndex > 0 ? defaultSelectIndex : (defaultSelectItem ? dataList.findIndex(item => item === defaultSelectItem) : -1)
ui[UIID].setSelection(selectIndex)
}
/**
* 下拉框change事件
* @param {*} UIID Ui的id
* @param {*} dataList 数据列表
* @param {*} callback 回调函数
* @returns
*/
utilsObj.selectChangeEvent = (UIID, dataList, callback) => {
if (!UIID) {
return
}
dataMap[UIID] = dataList
ui[UIID].setOnItemSelectedListener(new android.widget.AdapterView.OnItemSelectedListener({
onItemSelected: function (parent, view, position, id) {
if (callback) {
let textContent = parent.getItemAtPosition(position) || ui[UIID].getSelectedItem()
callback(textContent, parent, view, position, id, UIID)
}
}
}))
}
/**
* 开关change事件
* @param {*} UIID Ui的id
* @param {*} callback 回调函数
*/
utilsObj.switchChangeEvent = (UIID, callback) => {
ui[UIID].on("check", function (checked) {
callback(checked)
});
}
/**
* 设置UI缓存数据
* @param {*} settingkeyArr UI设置key数组
* @param {*} storageObj 储存对象
*/
utilsObj.setUICacheData = (settingkeyArr, storageObj) => {
settingkeyArr.forEach(item => {
let key = item.key // key
let type = item.type // 类型
let dataList = dataMap[key] || [] // 字典数据列表
if (!ui[key]) {
return;
}
let value = ''
switch (type) {
case "下拉框":
// 获取选中的内容
value = ui[key].getSelectedItem()
break;
case "单选框":
// 获取选中的id
let checkId = ui[key].getCheckedRadioButtonId()
// 数据
let data = dataList.find(item => item.id === checkId)
// 赋值
value = data ? data.name : ""
break;
case "开关":
value = ui[key].isChecked()
break;
case "输入框":
value = ui[key].getText()
break;
}
storageObj.put(key, value)
})
}
/**
* 获取UI缓存数据
* @param {*} settingkeyArr UI设置key数组
* @param {*} storageObj 储存对象
*/
utilsObj.getUICacheData = (settingkeyArr, storageObj) => {
settingkeyArr.forEach(item => {
let key = item.key // key
let type = item.type // 类型
let value = storageObj.get(key) || "" // 值
let dataList = dataMap[key] || [] // 字典数据列表
if (!ui[key]) {
return;
}
switch (type) {
case "下拉框":
// 获取数据项对应的下标
let selectIndex = dataList.findIndex(item => item === value)
// 根据下标设置选中项
ui[key].setSelection(selectIndex)
break;
case "单选框":
// 根据名称匹配数据
let data = dataList.find(item => item.name === value)
// 根据数据获取id
let checkId = data ? data.id : -1
// 初始化默认选中
ui[key].check(checkId)
break;
case "开关":
ui[key].setChecked(value || false)
break;
case "输入框":
ui[key].attr("text", value)
break;
}
})
}
module.exports = utilsObj三、工具箱分享
1、灰度化阈值化
调用代码
threads.start(() => {
setTimeout(() => {
// 截全屏
let allImg
try {
images.stopScreenCapture()
images.requestScreenCapture()
sleep(100)
// 开始截图
allImg = captureScreen();
} catch (error) {
console.error("工具请求截图错误", error)
}
// 图片保存到本地
files.createWithDirs("/sdcard/autoJsTools/")
let pathName = "/sdcard/autoJsTools/allScreen.png"
images.save(allImg, pathName, "png", 100)
allImg.recycle()
console.log("截屏完毕,等待开启工具")
try {
// 执行脚本
engines.execScriptFile("./module/imageTools.js");
} catch (error) {
console.error("执行工具imageTools脚本错误", error)
}
}, 500)
})实现代码
"ui";
let config = require('./common/config.js')
var commonStorage = storages.create("zjh336.cn" + config.commonScriptKey);
//$debug.setMemoryLeakDetectionEnabled(true)
$debug.gc()
// 切换为竖屏
activity.setRequestedOrientation(android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
const cancle = "@drawable/ic_close_black_48dp"
var nowimg, oldImg;
var isgray = false;
events.on("exit", function(){
console.log("停止图像处理脚本,结束运行");
$debug.gc()
});
ui.layout(
<drawer id="drawer">
<vertical h="*" w="*">
<appbar>
<toolbar id="toolbar" h='40' textSize="16sp" title="图片二值化" />
</appbar>
<frame id="page" h="*" w="*">
</frame>
</vertical>
</drawer>
)
activity.setSupportActionBar(ui.toolbar);
var isFirstPage = true;
ui.emitter.on("key_up", function (keyCode, event, e) {
toastLog(keyCode)
log(events)
log(e)
});
//加载界面
function setContainer(v) {
ui.page.removeAllViews();
ui.page.addView(v, new android.widget.FrameLayout.LayoutParams(-1, -1));
}
function setBody(v) {
ui.body.removeAllViews();
ui.body.addView(v, new android.widget.FrameLayout.LayoutParams(-1, -1));
}
var firstPage = {
ui: ui.inflate(
<vertical>
<frame>
<img id='img' src='{{cancle}}' w='auto' h='{{device.height/2}}px'></img>
</frame>
<text bg='#00ff00' w='*' h='5' />
<vertical>
<horizontal>
<button id='grayscale' w='auto' text='灰度化'></button>
<button id='threshold' w='auto' text='阈值化'></button>
</horizontal>
<horizontal>
<button id='old' w='auto' text='原图'></button>
</horizontal>
</vertical>
<frame id='body'>
</frame>
</vertical>
)
, initList: function () {
try {
let pathName = "/sdcard/autoJsTools/allScreen.png"
// 读取全屏图片
let allScreenImg = images.read(pathName)
// 设置图片到当前页面
ui.img.setImageBitmap(allScreenImg.bitmap)
nowimg = allScreenImg;
oldImg = nowimg;
// 回收
//allScreenImg.recycle()
} catch (error) {
console.error("【工具】图片读取错误", error)
}
this.ui.img.on('click', function () {
openPic();
})
ui.emitter.on("activity_result", (requestCode, resultCode, data) => {
if (resultCode == activity.RESULT_OK) {
if (requestCode == 1) {
nowimg = images.read(getRealPathFromURI(data.getData()));
oldImg = nowimg;
ui.img.setImageBitmap(nowimg.bitmap);
isgray = false;
}
}
});
this.ui.grayscale.on('click', function () {
if (!isgray) {
isgray = true;
//oldImg = nowimg;
if (!!nowimg) {
nowimg = images.grayscale(nowimg);
ui.img.setImageBitmap(nowimg.bitmap);
} else {
toast('请选择图片')
}
}
})
this.ui.threshold.on('click', function () {
//oldImg = nowimg;
if (!!nowimg) {
nowimg = images.threshold(nowimg, 100, 255, 'BINARY');
ui.img.setImageBitmap(nowimg.bitmap);
thresholdView.activate()
} else {
toast('请选择图片')
}
})
this.ui.old.on('click', function () {
isgray = false;
if (!!oldImg) {
nowimg = oldImg;
ui.img.setImageBitmap(oldImg.bitmap);
} else {
toast('请选择图片')
}
})
}, activate: function () {
isFirstPage = true;
setContainer(this.ui);
if (!this.inited) this.initList();
this.inited = true;
}
}
var thresholdView = {
ui: ui.inflate(
<vertical>
<horizontal>
<text id='yu_value' textSize="16sp" textColor="black" text="阈值:0" />
<seekbar id='yu' w='*' ></seekbar>
</horizontal>
<horizontal>
<text id='max_value' textSize="16sp" textColor="black" text="最大值:0" />
<seekbar id='max' w='*' ></seekbar>
</horizontal>
<horizontal>
<text id='type_value' textSize="16sp" textColor="black" text="阈值类型:" />
<spinner id='type' entries='BINARY|BINARY_INV|TRUNC|TOZERO|TOZERO_INV'></spinner>
</horizontal>
<button id="ok" text="确定" />
</vertical>
), initList: function () {
// , 'OTSU', 'TRIANGLE'
var thresholdType = ['BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV'];
var yu_value = this.ui.yu.getProgress();
var max_value = this.ui.max.getProgress();
var type = thresholdType[0];
this.ui.yu.setMax(255);
this.ui.max.setMax(255);
this.ui.yu.setOnSeekBarChangeListener(new android.widget.SeekBar.OnSeekBarChangeListener() {
onProgressChanged(seekBar, progress, fromUser) {
nowimg = images.threshold(oldImg, progress, max_value, type);
yu_value = progress;
ui.yu_value.setText('阈值:' + progress)
ui.img.setImageBitmap(nowimg.bitmap)
}
});
this.ui.max.setOnSeekBarChangeListener(new android.widget.SeekBar.OnSeekBarChangeListener() {
onProgressChanged(seekBar, progress, fromUser) {
max_value = progress;
nowimg = images.threshold(oldImg, yu_value, progress, type);
ui.max_value.setText('最大值:' + progress)
ui.img.setImageBitmap(nowimg.bitmap)
}
});
this.ui.type.setOnItemSelectedListener(new android.widget.AdapterView.OnItemSelectedListener() {
onItemSelected(parent, view, pos, id) {
type = thresholdType[pos];
nowimg = images.threshold(oldImg, yu_value, max_value, thresholdType[pos]);
ui.img.setImageBitmap(nowimg.bitmap)
}
})
this.ui.ok.on('click', () => {
let newFilepath = "/sdcard/autoJsTools/imageHandlerAfter.png";
files.createWithDirs("/sdcard/autoJsTools/");
images.save(nowimg, newFilepath);
toastLog("图片已存入本地:" + newFilepath)
app.viewFile(newFilepath);
/*
无法打开png
app.startActivity({
action: "VIEW",
type: "image/png",
data: "file:///sdcard/tempImage.png"
}); */
/* //打开应用来查看图片文件
var i = app.intent({
action: "VIEW",
type: "image/png",
data: "file:///sdcard/autoJsTools/allScreen.png"
});
context.startActivity(i); */
})
}, activate: function () {
setBody(this.ui);
if (!this.inited) this.initList();
this.inited = true;
}
}
firstPage.activate()
function openPic() {
var intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
activity.startActivityForResult(intent, 1);
}
function getRealPathFromURI(contentURI) {
var result;
var cursor = context.getContentResolver().query(contentURI, null, null, null, null);
if (cursor == null) {
result = contentURI.getPath()
} else {
cursor.moveToFirst();
var index = cursor.getColumnIndex(android.provider.MediaStore.Images.ImageColumns.DATA);
result = cursor.getString(index);
cursor.close();
}
return result;
}2、文字识别(基于浩然ocr)
使用文字识别,需要先在手机安装插件
调用代码
threads.start(() => {
setTimeout(() => {
// 截全屏
let allImg
try {
images.stopScreenCapture()
images.requestScreenCapture()
sleep(100)
// 开始截图
allImg = captureScreen();
} catch (error) {
console.error("工具请求截图错误", error)
}
// 图片保存到本地
files.createWithDirs("/sdcard/autoJsTools/")
let pathName = "/sdcard/autoJsTools/allScreen.png"
images.save(allImg, pathName, "png", 100)
allImg.recycle()
console.log("截屏完毕,等待开启工具")
try {
// 执行脚本
engines.execScriptFile("./module/analysisChartTools.js");
} catch (error) {
console.error("执行工具analysisChartTools脚本错误", error)
}
}, 500)
})实现代码
"ui";
$debug.setMemoryLeakDetectionEnabled(true)
$debug.gc()
events.on("exit", function(){
console.log("停止图像处理脚本,结束运行");
$debug.gc()
});
// 切换为竖屏
activity.setRequestedOrientation(android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
ui.layout(
<drawer id="drawer">
<vertical>
<button text="旋转" id="rotate" />
<frame>
<img id='afterChartImg' w='auto' h='auto' />
</frame>
</vertical>
</drawer>
);
let newFilePathUrl
ui.rotate.on('click', () => {
if (!newFilePathUrl) {
return
}
// 读取
let newImgCache = images.read(newFilePathUrl)
// 旋转
let newImgCache2 = images.rotate(newImgCache, 90)
// 存入
images.save(newImgCache2, newFilePathUrl);
// 读取
ui.afterChartImg.attr('src', 'file://' + newFilePathUrl)
newImgCache.recycle()
newImgCache2.recycle()
})
function showData(dataList, imgPath) {
var img = images.read(imgPath);
var canvas = new Canvas(img);
let rectanglePaint = new Paint();
rectanglePaint.setStrokeWidth(3);
rectanglePaint.setColor(colors.parseColor("#00ff00"));
rectanglePaint.setStyle(Paint.Style.STROKE); //空心矩形框
let textPaint = new Paint();
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setTextSize(30);
textPaint.setStyle(Paint.Style.FILL);
textPaint.setColor(colors.parseColor("#f000ff"));
let fontMetrics = textPaint.getFontMetrics();
var len = dataList.size();
for (var i = 0; i < len; i++) {
let data = dataList.get(i);
let frame = data.frame;
let rect = [frame.get(0), frame.get(1), frame.get(4), frame.get(5)];
canvas.drawRect(rect[0], rect[1], rect[2], rect[3], rectanglePaint);
canvas.drawText(
data.text,
rect[0] + parseInt((rect[2] - rect[0]) / 2),
rect[3] + Math.abs(fontMetrics.top),
textPaint
);
}
var image = canvas.toImage();
let newFilename = files.getNameWithoutExtension(imgPath) + ".png";
let newFilepath = "/sdcard/autoJsTools/analysisChart/" + newFilename;
files.createWithDirs(newFilepath);
images.save(image, newFilepath);
console.log("识别后的图片保存路径: " + newFilepath);
img.recycle();
image.recycle();
return newFilepath;
}
let ocr = $plugins.load("com.hraps.ocr");
try {
let pathName = "/sdcard/autoJsTools/allScreen.png"
// 读取全屏图片
let allScreenImg = images.read(pathName)
// 文字识别
let results = ocr.detect(allScreenImg.getBitmap(), 1);
// 画图
let filePath = showData(results, pathName)
newFilePathUrl = filePath
ui.run(() => {
ui.afterChartImg.attr('src', 'file://' + filePath)
})
// 读取文字识别内容
let contentArr = Object.values(results).map(item => item.text + "\r\n")
allScreenImg.recycle();
/* console.setPosition(0, 0);
console.setSize(device.width / 2, device.height / 2)
console.show() */
console.log("【文字识别内容】:\r\n" + contentArr)
} catch (error) {
console.error("【工具】文字识别错误", error)
}

发表评论