在这一个来月的开发过程中,自行封装了很多有用的方法、工具类,有需要的可自取
分享是一种美德,如果对你有帮助,请在文末评论感谢。
赠人玫瑰,手有余香
自定义消息气泡
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 = config
2、公共常量类
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 = websocketHandler
2、通用工具类
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) }
发表评论