6.7.0 - Alpha16 - 打包应用支持自带离线 Paddle OCR 引擎, 避免依赖插件安装
This commit is contained in:
@@ -2,6 +2,22 @@
|
||||
* @author TonyJiangWJ
|
||||
*/
|
||||
!function originalClassesForPaddleOcr() {
|
||||
|
||||
let hintForAutoJs670AndAbove = '自 AutoJs6 6.7.0 起, Paddle OCR 已进行功能插件化, 因此无法再使用内置原始类.\n\n' +
|
||||
'如需了解插件 API 使用示例, 可参考 "PaddleOCR (内置 API).js" 示例代码.';
|
||||
|
||||
dialogs.build({
|
||||
title: R.string.text_prompt,
|
||||
content: hintForAutoJs670AndAbove,
|
||||
positiveText: R.string.text_exit,
|
||||
stubborn: true,
|
||||
}).on('positive', (d) => {
|
||||
d.dismiss();
|
||||
exit();
|
||||
}).show();
|
||||
|
||||
exit();
|
||||
|
||||
const Predictor = com.baidu.paddle.lite.ocr.Predictor;
|
||||
|
||||
console.show();
|
||||
232
app/src/main/assets-app/sample/OCR/PaddleOCR (截图识别) [v6.7.0+].js
Normal file
232
app/src/main/assets-app/sample/OCR/PaddleOCR (截图识别) [v6.7.0+].js
Normal file
@@ -0,0 +1,232 @@
|
||||
/**
|
||||
* Create by TonyJiangWJ on Nov 15, 2023.
|
||||
* Modified by SuperMonster003 as of Jan 18, 2026.
|
||||
*/
|
||||
!function sampleForPaddleOcr() {
|
||||
|
||||
let currentEngine = engines.myEngine();
|
||||
let runningEngines = engines.all();
|
||||
let currentSource = `${currentEngine.getSource()}`;
|
||||
if (runningEngines.length > 1) {
|
||||
runningEngines.forEach(compareEngine => {
|
||||
let compareSource = `${compareEngine.getSource()}`;
|
||||
if (currentEngine.getId() !== compareEngine.getId() && compareSource === currentSource) {
|
||||
// Force stop scripts with the same name.
|
||||
// zh-CN: 强制关闭同名的脚本.
|
||||
compareEngine.forceStop();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!requestScreenCapture()) {
|
||||
// Failed to request screenshot permission.
|
||||
toastError('请求截图权限失败');
|
||||
exit();
|
||||
}
|
||||
|
||||
sleep(500);
|
||||
|
||||
// Use slim model for faster inference.
|
||||
// zh-CN: 使用 slim 模型以获得更快速度.
|
||||
let useSlim = true;
|
||||
|
||||
// CPU thread count.
|
||||
// zh-CN: CPU 线程数量.
|
||||
let cpuThreadNum = 4;
|
||||
|
||||
// Whether to request OpenCL.
|
||||
// zh-CN: 是否请求 OpenCL.
|
||||
let useOpenCL = false;
|
||||
|
||||
// Recognition results and screenshot information.
|
||||
// zh-CN: 识别结果和截图信息.
|
||||
let result = [];
|
||||
let img = null;
|
||||
let running = true;
|
||||
let capturing = false;
|
||||
|
||||
// Get status bar height offset.
|
||||
// zh-CN: 获取状态栏高度偏移.
|
||||
let offset = /* -getStatusBarHeightCompat(); */ 0;
|
||||
|
||||
// Draw OCR results.
|
||||
// zh-CN: 绘制 OCR 结果.
|
||||
let window = floaty.rawWindow(
|
||||
<canvas id="canvas" layout_weight="1"/>,
|
||||
);
|
||||
|
||||
// Set float window position.
|
||||
// zh-CN: 设置悬浮窗位置.
|
||||
ui.post(() => {
|
||||
window.setPosition(0, offset);
|
||||
window.setSize(WIDTH, HEIGHT);
|
||||
window.setTouchable(false);
|
||||
window.exitOnClose();
|
||||
});
|
||||
|
||||
// Operation buttons.
|
||||
// zh-CN: 操作按钮.
|
||||
let clickButtonWindow = floaty.rawWindow(
|
||||
<vertical>
|
||||
{/* Screenshot OCR */}
|
||||
<button id="captureAndOcr" text="截图识别"/>
|
||||
{/* Exit */}
|
||||
<button id="closeBtn" text="退出"/>
|
||||
</vertical>,
|
||||
);
|
||||
|
||||
// OCR worker.
|
||||
// zh-CN: OCR 工作线程.
|
||||
function captureAndOcrAsync() {
|
||||
if (capturing) {
|
||||
// Busy, please wait.
|
||||
toastLog('正在处理中, 请稍候.');
|
||||
return;
|
||||
}
|
||||
capturing = true;
|
||||
|
||||
// Clear last result immediately to avoid drawing stale boxes after capture.
|
||||
// zh-CN: 立即清空上一次结果, 避免截取完成后仍显示旧框.
|
||||
result = [];
|
||||
|
||||
// Hide overlay windows before capturing to avoid them being captured into screenshot.
|
||||
// zh-CN: 截图前隐藏悬浮窗, 避免悬浮窗内容被截图到.
|
||||
ui.run(function () {
|
||||
clickButtonWindow.setPosition(WIDTH, HEIGHT);
|
||||
window.setPosition(WIDTH, HEIGHT);
|
||||
});
|
||||
|
||||
threads.start(function () {
|
||||
try {
|
||||
// Wait for UI to apply window position changes (1-2 frames).
|
||||
// zh-CN: 等待 UI 应用窗口位置变化 (1-2 帧).
|
||||
sleep(150);
|
||||
|
||||
img && img.recycle();
|
||||
img = images.captureScreen();
|
||||
if (!img) {
|
||||
// Failed to capture screenshot.
|
||||
toastError('截图失败');
|
||||
return;
|
||||
}
|
||||
|
||||
let start = new Date();
|
||||
result = ocr.paddle.detect(img, { useSlim, cpuThreadNum, useOpenCL });
|
||||
// OCR cost time.
|
||||
toastLog(`OCR 耗时 ${new Date() - start}ms`);
|
||||
} catch (e) {
|
||||
toastError(e);
|
||||
exit();
|
||||
} finally {
|
||||
capturing = false;
|
||||
|
||||
// Restore overlay windows.
|
||||
// zh-CN: 恢复悬浮窗位置.
|
||||
ui.run(function () {
|
||||
window.setPosition(0, offset);
|
||||
window.setSize(WIDTH, HEIGHT);
|
||||
window.setTouchable(false);
|
||||
|
||||
clickButtonWindow.setPosition(
|
||||
cX(0.5) - ~~(clickButtonWindow.getWidth() / 2),
|
||||
cY(0.65),
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
clickButtonWindow['captureAndOcr'].click(function () {
|
||||
captureAndOcrAsync();
|
||||
});
|
||||
|
||||
clickButtonWindow['closeBtn'].click(function () {
|
||||
exit();
|
||||
});
|
||||
|
||||
clickButtonWindow.exitOnClose();
|
||||
|
||||
let paint = new Paint();
|
||||
paint.setStrokeWidth(1);
|
||||
paint.setTypeface(Typeface.DEFAULT_BOLD);
|
||||
paint.setTextAlign(Paint.Align.LEFT);
|
||||
paint.setAntiAlias(true);
|
||||
paint.setStrokeJoin(Paint.Join.ROUND);
|
||||
paint.setDither(true);
|
||||
|
||||
window.canvas.on('draw', function (canvas) {
|
||||
if (!running || capturing) return;
|
||||
|
||||
// Clear canvas.
|
||||
// zh-CN: 清空画布.
|
||||
canvas.drawColor(0xFFFFFF, android.graphics.PorterDuff.Mode.CLEAR);
|
||||
if (result && result.length > 0) {
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
let ocrResult = result[i];
|
||||
drawRectAndText(ocrResult.label, ocrResult.bounds, '#00ff00', canvas, paint);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Keep script alive.
|
||||
// zh-CN: 保持脚本运行.
|
||||
keepAlive();
|
||||
|
||||
events.on('exit', () => {
|
||||
// Mark stop to avoid canvas crash.
|
||||
// zh-CN: 标记停止, 避免 canvas 导致闪退.
|
||||
running = false;
|
||||
// Remove all listeners.
|
||||
// zh-CN: 撤销监听.
|
||||
window.canvas.removeAllListeners();
|
||||
// Recycle image.
|
||||
// zh-CN: 回收图片.
|
||||
img && img.recycle();
|
||||
});
|
||||
|
||||
/**
|
||||
* Draw text and rectangle.
|
||||
* zh-CN: 绘制文本和方框.
|
||||
*/
|
||||
function drawRectAndText(desc, rect, colorStr, canvas, paint) {
|
||||
let color = colors.parseColor(colorStr);
|
||||
|
||||
paint.setStrokeWidth(1);
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
// Invert color.
|
||||
// zh-CN: 反色.
|
||||
paint.setARGB(255, 255 - (color >> 16 & 0xff), 255 - (color >> 8 & 0xff), 255 - (color & 0xff));
|
||||
canvas.drawRect(rect, paint);
|
||||
|
||||
paint.setARGB(255, color >> 16 & 0xff, color >> 8 & 0xff, color & 0xff);
|
||||
paint.setStrokeWidth(1);
|
||||
paint.setTextSize(20);
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
canvas.drawText(desc, rect.left, rect.top, paint);
|
||||
|
||||
paint.setTextSize(10);
|
||||
paint.setStrokeWidth(1);
|
||||
paint.setARGB(255, 0, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status bar height.
|
||||
* zh-CN: 获取状态栏高度.
|
||||
*/
|
||||
function getStatusBarHeightCompat() {
|
||||
let result = 0;
|
||||
let resId = context.getResources().getIdentifier('status_bar_height', 'dimen', 'android');
|
||||
if (resId > 0) {
|
||||
result = context.getResources().getDimensionPixelOffset(resId);
|
||||
}
|
||||
if (result <= 0) {
|
||||
result = context.getResources().getDimensionPixelOffset(R.dimen.dimen_25dp);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Run once at start.
|
||||
// zh-CN: 启动后先执行一次.
|
||||
captureAndOcrAsync();
|
||||
|
||||
}();
|
||||
@@ -1,198 +0,0 @@
|
||||
/**
|
||||
* create by TonyJiangWJ
|
||||
*/
|
||||
!function sampleForPaddleOcr() {
|
||||
|
||||
const Predictor = com.baidu.paddle.lite.ocr.Predictor;
|
||||
|
||||
let currentEngine = engines.myEngine();
|
||||
let runningEngines = engines.all();
|
||||
let currentSource = `${currentEngine.getSource()}`;
|
||||
if (runningEngines.length > 1) {
|
||||
runningEngines.forEach(compareEngine => {
|
||||
let compareSource = `${compareEngine.getSource()}`;
|
||||
if (currentEngine.getId() !== compareEngine.getId() && compareSource === currentSource) {
|
||||
// 强制关闭同名的脚本
|
||||
compareEngine.forceStop();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!requestScreenCapture()) {
|
||||
toastLog('请求截图权限失败');
|
||||
exit();
|
||||
}
|
||||
|
||||
sleep(1000);
|
||||
|
||||
// 指定是否用精简版模型 速度较快
|
||||
let useSlim = true;
|
||||
|
||||
// 创建检测器
|
||||
let predictor = new Predictor();
|
||||
|
||||
// predictor.cpuThreadNum = 4 // 可以自定义使用 CPU 的线程数
|
||||
// predictor.checkModelLoaded = false // 可以自定义是否需要校验模型是否成功加载 默认开启 使用内置 Base64 图片进行校验 识别测试文本来校验模型是否加载成功
|
||||
|
||||
// 初始化模型 首次运行时会比较耗时
|
||||
let loading = threads.disposable();
|
||||
|
||||
// 建议在新线程中初始化模型
|
||||
threads.start(function () {
|
||||
loading.setAndNotify(predictor.init(context, useSlim));
|
||||
// loading.setAndNotify(predictor.init(context)) 为默认不使用精简版
|
||||
// 内置默认 modelPath 为 models/ocr_v3_for_cpu, 初始化自定义模型请写绝对路径否则无法获取到
|
||||
// 内置默认 labelPath 为 labels/ppocr_keys_v1.txt
|
||||
// let modelPath = files.path('./models/customize') // 指定自定义模型路径
|
||||
// let labelPath = files.path('./models/customize') // 指定自定义 label 路径
|
||||
// 使用自定义模型时 det rec cls 三个模型文件名称需要手动指定
|
||||
// predictor.detModelFilename = 'det_opt.nb'
|
||||
// predictor.recModelFilename = 'rec_opt.nb'
|
||||
// predictor.clsModelFilename = 'cls_opt.nb'
|
||||
// loading.setAndNotify(predictor.init(context, modelPath, labelPath))
|
||||
});
|
||||
let loadSuccess = loading.blockedGet();
|
||||
if (!loadSuccess) {
|
||||
toastLog('初始化 ocr 失败');
|
||||
exit();
|
||||
}
|
||||
|
||||
// 识别结果和截图信息
|
||||
let result = [];
|
||||
let img = null;
|
||||
let running = true;
|
||||
let capturing = true;
|
||||
|
||||
/**
|
||||
* 截图并识别 OCR 文本信息
|
||||
*/
|
||||
function captureAndOcr() {
|
||||
capturing = true;
|
||||
img && img.recycle();
|
||||
img = captureScreen();
|
||||
if (!img) {
|
||||
toastLog('截图失败');
|
||||
}
|
||||
let start = new Date();
|
||||
result = predictor.runOcr(img.getBitmap());
|
||||
toastLog(`耗时 ${new Date() - start}ms`);
|
||||
capturing = false;
|
||||
}
|
||||
|
||||
captureAndOcr();
|
||||
|
||||
// 获取状态栏高度
|
||||
let offset = -getStatusBarHeightCompat();
|
||||
|
||||
// 绘制识别结果
|
||||
let window = floaty.rawWindow(
|
||||
<canvas id="canvas" layout_weight="1"/>,
|
||||
);
|
||||
|
||||
// 设置悬浮窗位置
|
||||
ui.post(() => {
|
||||
window.setPosition(0, offset);
|
||||
window.setSize(device.width, device.height);
|
||||
window.setTouchable(false);
|
||||
});
|
||||
|
||||
// 操作按钮
|
||||
let clickButtonWindow = floaty.rawWindow(
|
||||
<vertical>
|
||||
<button id="captureAndOcr" text="截图识别"/>
|
||||
<button id="closeBtn" text="退出"/>
|
||||
</vertical>,
|
||||
);
|
||||
ui.run(function () {
|
||||
clickButtonWindow.setPosition(device.width / 2 - ~~(clickButtonWindow.getWidth() / 2), device.height * 0.65);
|
||||
});
|
||||
|
||||
// 点击识别
|
||||
clickButtonWindow['captureAndOcr'].click(function () {
|
||||
result = [];
|
||||
ui.run(function () {
|
||||
clickButtonWindow.setPosition(device.width, device.height);
|
||||
});
|
||||
setTimeout(() => {
|
||||
captureAndOcr();
|
||||
ui.run(function () {
|
||||
clickButtonWindow.setPosition(device.width / 2 - ~~(clickButtonWindow.getWidth() / 2), device.height * 0.65);
|
||||
});
|
||||
}, 500);
|
||||
});
|
||||
|
||||
// 点击关闭
|
||||
clickButtonWindow['closeBtn'].click(function () {
|
||||
exit();
|
||||
});
|
||||
|
||||
let Typeface = android.graphics.Typeface;
|
||||
let paint = new Paint();
|
||||
paint.setStrokeWidth(1);
|
||||
paint.setTypeface(Typeface.DEFAULT_BOLD);
|
||||
paint.setTextAlign(Paint.Align.LEFT);
|
||||
paint.setAntiAlias(true);
|
||||
paint.setStrokeJoin(Paint.Join.ROUND);
|
||||
paint.setDither(true);
|
||||
window.canvas.on('draw', function (canvas) {
|
||||
if (!running || capturing) {
|
||||
return;
|
||||
}
|
||||
// 清空内容
|
||||
canvas.drawColor(0xFFFFFF, android.graphics.PorterDuff.Mode.CLEAR);
|
||||
if (result && result.length > 0) {
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
let ocrResult = result[i];
|
||||
drawRectAndText(ocrResult.label, ocrResult.bounds, '#00ff00', canvas, paint);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setInterval(() => {
|
||||
/* Empty body. */
|
||||
}, 10000);
|
||||
events.on('exit', () => {
|
||||
// 标记停止 避免 canvas 导致闪退
|
||||
running = false;
|
||||
// 撤销监听
|
||||
window.canvas.removeAllListeners();
|
||||
// 回收图片
|
||||
img && img.recycle();
|
||||
});
|
||||
|
||||
/**
|
||||
* 绘制文本和方框
|
||||
*/
|
||||
function drawRectAndText(desc, rect, colorStr, canvas, paint) {
|
||||
let color = colors.parseColor(colorStr);
|
||||
|
||||
paint.setStrokeWidth(1);
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
// 反色
|
||||
paint.setARGB(255, 255 - (color >> 16 & 0xff), 255 - (color >> 8 & 0xff), 255 - (color & 0xff));
|
||||
canvas.drawRect(rect, paint);
|
||||
paint.setARGB(255, color >> 16 & 0xff, color >> 8 & 0xff, color & 0xff);
|
||||
paint.setStrokeWidth(1);
|
||||
paint.setTextSize(20);
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
canvas.drawText(desc, rect.left, rect.top, paint);
|
||||
paint.setTextSize(10);
|
||||
paint.setStrokeWidth(1);
|
||||
paint.setARGB(255, 0, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取状态栏高度
|
||||
*/
|
||||
function getStatusBarHeightCompat() {
|
||||
let result = 0;
|
||||
let resId = context.getResources().getIdentifier('status_bar_height', 'dimen', 'android');
|
||||
if (resId > 0) {
|
||||
result = context.getResources().getDimensionPixelOffset(resId);
|
||||
}
|
||||
if (result <= 0) {
|
||||
result = context.getResources().getDimensionPixelOffset(R.dimen.dimen_25dp);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}();
|
||||
Reference in New Issue
Block a user