add: images.findImage; change images.findColor implementation to opencv
This commit is contained in:
1 parent
35955faba9
commit
328ab6efa7
25 files changed
+551
-811
No files matched your search
Generated
+1
-3
@@ -2,9 +2,7 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectModuleManager">
|
<component name="ProjectModuleManager">
|
||||||
<modules>
|
<modules>
|
||||||
<module fileurl="file://$PROJECT_DIR$/Auto.js.iml" filepath="$PROJECT_DIR$/Auto.js.iml" />
|
<module fileurl="file://$PROJECT_DIR$/NoRootScriptDroid.iml" filepath="$PROJECT_DIR$/NoRootScriptDroid.iml" />
|
||||||
<module fileurl="file://E:\YiBin\AndroidStudioProjects\NoRootScriptDroid\Auto.js.iml" filepath="E:\YiBin\AndroidStudioProjects\NoRootScriptDroid\Auto.js.iml" />
|
|
||||||
<module fileurl="file://C:\Users\Stardust\Documents\AndroidProjects\Auto.js\NoRootScriptDroid.iml" filepath="C:\Users\Stardust\Documents\AndroidProjects\Auto.js\NoRootScriptDroid.iml" />
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
|
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/autojs/autojs.iml" filepath="$PROJECT_DIR$/autojs/autojs.iml" />
|
<module fileurl="file://$PROJECT_DIR$/autojs/autojs.iml" filepath="$PROJECT_DIR$/autojs/autojs.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/automator/automator.iml" filepath="$PROJECT_DIR$/automator/automator.iml" />
|
<module fileurl="file://$PROJECT_DIR$/automator/automator.iml" filepath="$PROJECT_DIR$/automator/automator.iml" />
|
||||||
|
|||||||
+2
-2
@@ -9,8 +9,8 @@ android {
|
|||||||
applicationId "com.stardust.scriptdroid"
|
applicationId "com.stardust.scriptdroid"
|
||||||
minSdkVersion 17
|
minSdkVersion 17
|
||||||
targetSdkVersion 23
|
targetSdkVersion 23
|
||||||
versionCode 221
|
versionCode 222
|
||||||
versionName "3.0.0 Alpha21"
|
versionName "3.0.0 Alpha22"
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
ndk {
|
ndk {
|
||||||
|
|||||||
@@ -3,11 +3,10 @@ if(!requestScreenCapture()){
|
|||||||
stop();
|
stop();
|
||||||
}
|
}
|
||||||
var img = captureScreen();
|
var img = captureScreen();
|
||||||
//0xffffff为白色
|
|
||||||
toastLog("开始找色");
|
toastLog("开始找色");
|
||||||
//指定在位置(90, 220)宽高为900*1000的区域找色。
|
//指定在位置(100, 220)宽高为400*400的区域找色。
|
||||||
//0xff00cc是编辑器的深粉红色字体(字符串)颜色
|
//#75438a是编辑器默认主题的棕红色字体(数字)颜色,位置大约在第5行的"2000",坐标大约为(283, 465)
|
||||||
var point = findColorInRegion(img, "#ff00cc", 90, 220, 900, 1000);
|
var point = findColorInRegion(img, "#75438a", 90, 220, 900, 1000);
|
||||||
if(point){
|
if(point){
|
||||||
toastLog("x = " + point.x + ", y = " + point.y);
|
toastLog("x = " + point.x + ", y = " + point.y);
|
||||||
}else{
|
}else{
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ if(!requestScreenCapture()){
|
|||||||
}
|
}
|
||||||
var img = captureScreen();
|
var img = captureScreen();
|
||||||
toastLog("开始找色");
|
toastLog("开始找色");
|
||||||
//0x006699为编辑器蓝色字体(var)的颜色
|
//0x1d75b3为编辑器默认主题蓝色字体(if, var等关键字)的颜色
|
||||||
//找到颜色与0x006699完全相等的颜色
|
//找到颜色与0x1d75b3完全相等的颜色
|
||||||
var point = findColorEquals(img, 0x006699);
|
var point = findColorEquals(img, 0x006699);
|
||||||
if(point){
|
if(point){
|
||||||
toastLog("x = " + point.x + ", y = " + point.y);
|
toastLog("x = " + point.x + ", y = " + point.y);
|
||||||
|
|||||||
@@ -44,6 +44,9 @@ import com.stardust.scriptdroid.tool.AccessibilityServiceTool;
|
|||||||
import com.stardust.view.accessibility.LayoutInspector;
|
import com.stardust.view.accessibility.LayoutInspector;
|
||||||
import com.stardust.view.accessibility.NotificationListener;
|
import com.stardust.view.accessibility.NotificationListener;
|
||||||
|
|
||||||
|
import org.opencv.android.BaseLoaderCallback;
|
||||||
|
import org.opencv.android.OpenCVLoader;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Stardust on 2017/4/2.
|
* Created by Stardust on 2017/4/2.
|
||||||
@@ -78,7 +81,7 @@ public class AutoJs {
|
|||||||
mContext = context;
|
mContext = context;
|
||||||
mUiHandler = new UiHandler(context);
|
mUiHandler = new UiHandler(context);
|
||||||
mAppUtils = new AppUtils(context);
|
mAppUtils = new AppUtils(context);
|
||||||
mGlobalConsole = new GlobalStardustConsole(mUiHandler){
|
mGlobalConsole = new GlobalStardustConsole(mUiHandler) {
|
||||||
@Override
|
@Override
|
||||||
public String println(int level, CharSequence charSequence) {
|
public String println(int level, CharSequence charSequence) {
|
||||||
String log = super.println(level, charSequence);
|
String log = super.println(level, charSequence);
|
||||||
@@ -93,6 +96,8 @@ public class AutoJs {
|
|||||||
mScriptEngineService.registerGlobalScriptExecutionListener(new ScriptExecutionGlobalListener());
|
mScriptEngineService.registerGlobalScriptExecutionListener(new ScriptExecutionGlobalListener());
|
||||||
registerActivityLifecycleCallbacks();
|
registerActivityLifecycleCallbacks();
|
||||||
InputEventObserver.initGlobal(context);
|
InputEventObserver.initGlobal(context);
|
||||||
|
OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_13, context, new BaseLoaderCallback(context) {
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private ScriptEngineService buildScriptEngineService() {
|
private ScriptEngineService buildScriptEngineService() {
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -51,6 +51,7 @@ dependencies {
|
|||||||
compile 'com.android.support:design:25.4.0'
|
compile 'com.android.support:design:25.4.0'
|
||||||
compile 'com.android.support:appcompat-v7:25.4.0'
|
compile 'com.android.support:appcompat-v7:25.4.0'
|
||||||
compile 'com.github.hyb1996:EnhancedFloaty:0.17'
|
compile 'com.github.hyb1996:EnhancedFloaty:0.17'
|
||||||
|
compile 'com.github.hyb1996:OpenCvLib:2.4.13.4-imgproc'
|
||||||
// Terminal emulator
|
// Terminal emulator
|
||||||
compile(name: 'libtermexec-release', ext: 'aar')
|
compile(name: 'libtermexec-release', ext: 'aar')
|
||||||
compile(name: 'emulatorview-release', ext: 'aar')
|
compile(name: 'emulatorview-release', ext: 'aar')
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ module.exports = function(runtime, scope){
|
|||||||
}
|
}
|
||||||
call.enqueue(new Callback({
|
call.enqueue(new Callback({
|
||||||
onResponse: function(call, res){
|
onResponse: function(call, res){
|
||||||
callback(res);
|
callback(wrapResponse(res));
|
||||||
},
|
},
|
||||||
onFailure: function(call, ex){
|
onFailure: function(call, ex){
|
||||||
callback(null, ex);
|
callback(null, ex);
|
||||||
@@ -103,6 +103,7 @@ module.exports = function(runtime, scope){
|
|||||||
function wrapResponse(res){
|
function wrapResponse(res){
|
||||||
var r = {};
|
var r = {};
|
||||||
r.statusCode = res.code();
|
r.statusCode = res.code();
|
||||||
|
r.statusMessage = res.message();
|
||||||
var headers = res.headers();
|
var headers = res.headers();
|
||||||
r.headers = {};
|
r.headers = {};
|
||||||
for(var i = 0; i < headers.size(); i++){
|
for(var i = 0; i < headers.size(); i++){
|
||||||
@@ -112,6 +113,7 @@ module.exports = function(runtime, scope){
|
|||||||
r.body.json = function(){
|
r.body.json = function(){
|
||||||
return JSON.parse(r.body.string());
|
return JSON.parse(r.body.string());
|
||||||
}
|
}
|
||||||
|
r.body.contentType = r.body.contentType();
|
||||||
r.request = res.request();
|
r.request = res.request();
|
||||||
r.url = r.request.url();
|
r.url = r.request.url();
|
||||||
r.method = r.request.method();
|
r.method = r.request.method();
|
||||||
|
|||||||
@@ -17,13 +17,15 @@ module.exports = function(__runtime__, scope){
|
|||||||
|
|
||||||
images.captureScreen = rtImages.captureScreen.bind(rtImages);
|
images.captureScreen = rtImages.captureScreen.bind(rtImages);
|
||||||
|
|
||||||
|
images.read = rtImages.read.bind(rtImages);
|
||||||
|
|
||||||
images.saveImage = rtImages.saveImage.bind(rtImages);
|
images.saveImage = rtImages.saveImage.bind(rtImages);
|
||||||
|
|
||||||
images.pixel = rtImages.pixel;
|
images.pixel = rtImages.pixel;
|
||||||
|
|
||||||
images.detectsColor = function(img, color, x, y, threshold, algorithm){
|
images.detectsColor = function(img, color, x, y, threshold, algorithm){
|
||||||
color = parseColor(color);
|
color = parseColor(color);
|
||||||
algorithm = algorithm || "rgb";
|
algorithm = algorithm || "diff";
|
||||||
threshold = threshold || 16;
|
threshold = threshold || 16;
|
||||||
var colorDetector = getColorDetector(color, algorithm, threshold);
|
var colorDetector = getColorDetector(color, algorithm, threshold);
|
||||||
var pixel = images.pixel(img, x, y);
|
var pixel = images.pixel(img, x, y);
|
||||||
@@ -34,54 +36,80 @@ module.exports = function(__runtime__, scope){
|
|||||||
color = parseColor(color);
|
color = parseColor(color);
|
||||||
options = options || {};
|
options = options || {};
|
||||||
var region = options.region || [];
|
var region = options.region || [];
|
||||||
var x = region[0] || 0;
|
|
||||||
var y = region[1] || 0;
|
|
||||||
var width = region[2] || (img.getWidth() - x);
|
|
||||||
var height = region[3] || (img.getHeight() - y);
|
|
||||||
var threads = options.threads || 2;
|
|
||||||
if(options.similarity){
|
if(options.similarity){
|
||||||
var threshold = parseInt(255 * (1 - options.similarity));
|
var threshold = parseInt(255 * (1 - options.similarity));
|
||||||
}else{
|
}else{
|
||||||
var threshold = options.threshold || 16;
|
var threshold = options.threshold || 16;
|
||||||
}
|
}
|
||||||
algorithm = options.algorithm || "rgb";
|
if(options.region){
|
||||||
var rect = new android.graphics.Rect(x, y, width + x, height + y);
|
return rtImages.toAndroidPoint(colorFinder.findColor(img, color, threshold, buildRegion(options.region, img)));
|
||||||
var colorDetector = getColorDetector(color, algorithm, threshold);
|
}else{
|
||||||
return colorFinder.findColorConcurrently(img, colorDetector, rect, threads);
|
return rtImages.toAndroidPoint(colorFinder.findColor(img, color, threshold, null));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
images.findColorInRegion = function(img, color, x, y, width, height, threads, algorithm, threshold){
|
images.findColorInRegion = function(img, color, x, y, width, height, threshold){
|
||||||
return findColor(img, color, {
|
return findColor(img, color, {
|
||||||
region: [x, y, width, height],
|
region: [x, y, width, height],
|
||||||
algorithm: algorithm,
|
threshold: threshold
|
||||||
threshold: threshold,
|
|
||||||
threads: threads
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
images.findColorEquals = function(img, color, x, y, width, height, threads){
|
images.findColorEquals = function(img, color, x, y, width, height){
|
||||||
return findColor(img, color, {
|
return findColor(img, color, {
|
||||||
region: [x, y, width, height],
|
region: [x, y, width, height],
|
||||||
algorithm: "equal",
|
threshold: 0
|
||||||
threads: threads
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getColorDetector(color, algorithm, threshold){
|
images.findColors = function(img, color, options){
|
||||||
switch(algorithm){
|
color = parseColor(color);
|
||||||
case "rgb":
|
options = options || {};
|
||||||
return new com.stardust.autojs.core.image.ColorDetector.RGBDistanceDetector(color, threshold);
|
if(options.similarity){
|
||||||
case "equal":
|
var threshold = parseInt(255 * (1 - options.similarity));
|
||||||
return new com.stardust.autojs.core.image.ColorDetector.EqualityDetector(color);
|
}else{
|
||||||
case "diff":
|
var threshold = options.threshold || 16;
|
||||||
return new com.stardust.autojs.core.image.ColorDetector.DifferenceDetector(color, threshold);
|
}
|
||||||
case "rgb+":
|
if(options.region){
|
||||||
return new com.stardust.autojs.core.image.ColorDetector.WeightedRGBDistanceDetector(color, threshold);
|
return toPointArray(colorFinder.findAllColors(img, color, threshold, buildRegion(options.region, img)));
|
||||||
case "hs":
|
}else{
|
||||||
return new com.stardust.autojs.core.image.ColorDetector.HSDistanceDetector(color, threshold);
|
return toPointArray(colorFinder.findAllColors(img, color, threshold, null));
|
||||||
}
|
}
|
||||||
throw new Error("Unknown algorithm: " + algorithm);
|
}
|
||||||
}
|
|
||||||
|
images.findImage = function(img, template, options){
|
||||||
|
options = options || {};
|
||||||
|
var threshold = options.threshold || 0.9;
|
||||||
|
var maxLevel = options.level || -1;
|
||||||
|
if(options.region){
|
||||||
|
return rtImages.findImage(img, template, threshold, buildRegion(options, img), maxLevel);
|
||||||
|
}else{
|
||||||
|
return rtImages.findImage(img, template, threshold, null, maxLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
images.findImageInRegion = function(img, template, x, y, width, height, threshold){
|
||||||
|
return images.findImage(img, template, {
|
||||||
|
region: [x, y, width, height],
|
||||||
|
threshold: threshold
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function toPointArray(points){
|
||||||
|
var arr = [];
|
||||||
|
for(var i = 0; i < points.length; i++){
|
||||||
|
arr.push(rtImages.toAndroidPoint(points[i]));
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildRegion(region, img){
|
||||||
|
var x = region[0] || 0;
|
||||||
|
var y = region[1] || 0;
|
||||||
|
var width = region[2] || (img.getWidth() - x);
|
||||||
|
var height = region[3] || (img.getHeight() - y);
|
||||||
|
return new org.opencv.core.Rect(x, y, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
function parseColor(color){
|
function parseColor(color){
|
||||||
if(typeof(color) == 'string'){
|
if(typeof(color) == 'string'){
|
||||||
@@ -94,7 +122,7 @@ module.exports = function(__runtime__, scope){
|
|||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
|
|
||||||
scope.__asGlobal__(images, ['requestScreenCapture', 'captureScreen', 'findColor', 'findColorInRegion', 'findColorEquals']);
|
scope.__asGlobal__(images, ['requestScreenCapture', 'captureScreen', 'findImage', 'findImageInRegion', 'findColor', 'findColorInRegion', 'findColorEquals']);
|
||||||
|
|
||||||
scope.colors = colors;
|
scope.colors = colors;
|
||||||
|
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ exports.format = function(f) {
|
|||||||
if (!isString(f)) {
|
if (!isString(f)) {
|
||||||
var objects = [];
|
var objects = [];
|
||||||
for (var i = 0; i < arguments.length; i++) {
|
for (var i = 0; i < arguments.length; i++) {
|
||||||
var v = isJavaObject(arguments[i]) ? arguments[i].toString() :
|
var v = (arguments[i] && isJavaObject(arguments[i])) ? arguments[i].toString() :
|
||||||
arguments[i];
|
arguments[i];
|
||||||
objects.push(inspect(v));
|
objects.push(inspect(v));
|
||||||
}
|
}
|
||||||
return objects.join(' ');
|
return objects.join(' ');
|
||||||
|
|||||||
@@ -1,199 +0,0 @@
|
|||||||
package com.stardust.autojs.core.image;
|
|
||||||
|
|
||||||
import android.graphics.Color;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by Stardust on 2017/5/20.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public interface ColorDetector {
|
|
||||||
|
|
||||||
boolean detectsColor(int red, int green, int blue);
|
|
||||||
|
|
||||||
abstract class AbstractColorDetector implements ColorDetector {
|
|
||||||
|
|
||||||
protected final int mColor;
|
|
||||||
protected final int mR, mG, mB;
|
|
||||||
|
|
||||||
public AbstractColorDetector(int color) {
|
|
||||||
mColor = color;
|
|
||||||
mR = Color.red(color);
|
|
||||||
mG = Color.green(color);
|
|
||||||
mB = Color.blue(color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EqualityDetector extends AbstractColorDetector {
|
|
||||||
|
|
||||||
|
|
||||||
public EqualityDetector(int color) {
|
|
||||||
super(color);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean detectsColor(int red, int green, int blue) {
|
|
||||||
return mR == red && mG == green && mB == blue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DifferenceDetector extends AbstractColorDetector {
|
|
||||||
|
|
||||||
private final int mThreshold;
|
|
||||||
|
|
||||||
public DifferenceDetector(int color, int threshold) {
|
|
||||||
super(color);
|
|
||||||
mThreshold = threshold * 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean detectsColor(int R, int G, int B) {
|
|
||||||
return Math.abs(R - mR) + Math.abs(G - mG) + Math.abs(B - mB) <= mThreshold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RDistanceDetector extends AbstractColorDetector {
|
|
||||||
|
|
||||||
private final int mThreshold;
|
|
||||||
|
|
||||||
public RDistanceDetector(int color, int threshold) {
|
|
||||||
super(color);
|
|
||||||
mThreshold = threshold;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean detectsColor(int R, int G, int B) {
|
|
||||||
return Math.abs(mR - R) <= mThreshold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RGBDistanceDetector extends AbstractColorDetector {
|
|
||||||
|
|
||||||
private final int mThreshold;
|
|
||||||
|
|
||||||
public RGBDistanceDetector(int color, int threshold) {
|
|
||||||
super(color);
|
|
||||||
mThreshold = threshold * threshold * 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean detectsColor(int R, int G, int B) {
|
|
||||||
int dR = R - mR;
|
|
||||||
int dG = G - mG;
|
|
||||||
int dB = B - mB;
|
|
||||||
int d = dR * dR + dG * dG + dB * dB;
|
|
||||||
return d <= mThreshold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class WeightedRGBDistanceDetector extends AbstractColorDetector {
|
|
||||||
|
|
||||||
private final int mThreshold;
|
|
||||||
private final int mR, mG, mB;
|
|
||||||
|
|
||||||
public WeightedRGBDistanceDetector(int color, int threshold) {
|
|
||||||
super(color);
|
|
||||||
mR = (color & 0xff0000) >> 16;
|
|
||||||
mG = (color & 0x00ff00) >> 8;
|
|
||||||
mB = color & 0xff;
|
|
||||||
mThreshold = threshold * threshold * 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean detectsColor(int R, int G, int B) {
|
|
||||||
int dR = R - mR;
|
|
||||||
int dG = G - mG;
|
|
||||||
int dB = B - mB;
|
|
||||||
double meanR = (mR + R) / 2;
|
|
||||||
double weightR = 2 + meanR / 256;
|
|
||||||
double weightG = 4.0;
|
|
||||||
double weightB = 2 + (255 - meanR) / 256;
|
|
||||||
return weightR * dR * dR + weightG * dG * dG + weightB * dB * dB <= mThreshold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class HDistanceDetector extends AbstractColorDetector {
|
|
||||||
|
|
||||||
private final int mH;
|
|
||||||
private final int mThreshold;
|
|
||||||
|
|
||||||
public HDistanceDetector(int color, int threshold) {
|
|
||||||
super(color);
|
|
||||||
mH = getH(mR, mG, mB);
|
|
||||||
mThreshold = threshold;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean detectsColor(int R, int G, int B) {
|
|
||||||
return Math.abs(mH - getH(R, G, B)) <= mThreshold;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int getH(int R, int G, int B) {
|
|
||||||
int max, min, H;
|
|
||||||
if (R > G) {
|
|
||||||
min = Math.min(G, B);
|
|
||||||
max = Math.max(R, B);
|
|
||||||
} else {
|
|
||||||
min = Math.min(R, B);
|
|
||||||
max = Math.max(G, B);
|
|
||||||
}
|
|
||||||
if (R == max) {
|
|
||||||
H = (G - B) / (max - min) * 60;
|
|
||||||
} else if (G == max) {
|
|
||||||
H = 120 + (B - R) / (max - min) * 60;
|
|
||||||
} else {
|
|
||||||
H = 240 + (R - G) / (max - min) * 60;
|
|
||||||
}
|
|
||||||
if (H < 0) H = H + 360;
|
|
||||||
return H;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class HSDistanceDetector extends AbstractColorDetector {
|
|
||||||
|
|
||||||
private final int mH, mS;
|
|
||||||
private final int mThreshold;
|
|
||||||
|
|
||||||
public HSDistanceDetector(int color, int threshold) {
|
|
||||||
super(color);
|
|
||||||
long HS = getHS(mR, mG, mB);
|
|
||||||
mH = (int) (HS & 0xffffffffL);
|
|
||||||
mS = (int) ((HS >> 32) & 0xffffffffL);
|
|
||||||
mThreshold = threshold * 3729600 / 255;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HSDistanceDetector(int color, float similarity) {
|
|
||||||
this(color, (int) (1.0f - similarity) * 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean detectsColor(int R, int G, int B) {
|
|
||||||
long hs = getHS(R, G, B);
|
|
||||||
int dH = (int) (hs & 0xffffffffL) - mH;
|
|
||||||
int dS = (int) ((hs >> 32) & 0xffffffffL) - mS;
|
|
||||||
return dH * dH + dS * dS <= mThreshold;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static long getHS(int R, int G, int B) {
|
|
||||||
int max, min, H;
|
|
||||||
if (R > G) {
|
|
||||||
min = Math.min(G, B);
|
|
||||||
max = Math.max(R, B);
|
|
||||||
} else {
|
|
||||||
min = Math.min(R, B);
|
|
||||||
max = Math.max(G, B);
|
|
||||||
}
|
|
||||||
if (R == max) {
|
|
||||||
H = (G - B) / (max - min) * 60;
|
|
||||||
} else if (G == max) {
|
|
||||||
H = 120 + (B - R) / (max - min) * 60;
|
|
||||||
} else {
|
|
||||||
H = 240 + (R - G) / (max - min) * 60;
|
|
||||||
}
|
|
||||||
if (H < 0) H = H + 360;
|
|
||||||
int S = (max - min) * 100 / max;
|
|
||||||
return H & ((long) S << 32);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
package com.stardust.autojs.core.image;
|
package com.stardust.autojs.core.image;
|
||||||
|
|
||||||
import android.graphics.Point;
|
import android.graphics.Color;
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.media.Image;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.support.annotation.RequiresApi;
|
import android.support.annotation.RequiresApi;
|
||||||
|
|
||||||
@@ -10,6 +8,14 @@ import com.stardust.autojs.runtime.exception.ScriptInterruptedException;
|
|||||||
import com.stardust.concurrent.VolatileBox;
|
import com.stardust.concurrent.VolatileBox;
|
||||||
import com.stardust.util.ScreenMetrics;
|
import com.stardust.util.ScreenMetrics;
|
||||||
|
|
||||||
|
import org.opencv.android.Utils;
|
||||||
|
import org.opencv.core.Core;
|
||||||
|
import org.opencv.core.Mat;
|
||||||
|
import org.opencv.core.MatOfPoint;
|
||||||
|
import org.opencv.core.Point;
|
||||||
|
import org.opencv.core.Rect;
|
||||||
|
import org.opencv.core.Scalar;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
import java.util.concurrent.SynchronousQueue;
|
import java.util.concurrent.SynchronousQueue;
|
||||||
@@ -23,235 +29,50 @@ import java.util.concurrent.TimeUnit;
|
|||||||
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
||||||
public class ColorFinder {
|
public class ColorFinder {
|
||||||
|
|
||||||
private static ThreadPoolExecutor sThreadPoolExecutor = new ThreadPoolExecutor(4, 16, 5, TimeUnit.MINUTES, new SynchronousQueue<Runnable>());
|
public static Point findColorEquals(ImageWrapper imageWrapper, int color) {
|
||||||
|
return findColorEquals(imageWrapper, color, null);
|
||||||
static {
|
|
||||||
sThreadPoolExecutor.allowCoreThreadTimeOut(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ThreadPoolExecutor mThreadPoolExecutor;
|
public static Point findColorEquals(ImageWrapper imageWrapper, int color, Rect region) {
|
||||||
private ScreenMetrics mScreenMetrics;
|
return findColor(imageWrapper, color, 0, region);
|
||||||
|
|
||||||
public ColorFinder(ThreadPoolExecutor threadPoolExecutor) {
|
|
||||||
mThreadPoolExecutor = threadPoolExecutor;
|
|
||||||
mScreenMetrics = new ScreenMetrics();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ColorFinder() {
|
public static Point findColor(ImageWrapper imageWrapper, int color, int threshold) {
|
||||||
this(sThreadPoolExecutor);
|
return findColor(imageWrapper, color, threshold, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void prestartThreads() {
|
public static Point findColor(ImageWrapper imageWrapper, int color, int threshold, Rect region) {
|
||||||
mThreadPoolExecutor.prestartAllCoreThreads();
|
Point[] points = findAllColors(imageWrapper, color, threshold, region);
|
||||||
}
|
if (points.length == 0) {
|
||||||
|
return null;
|
||||||
public Point[] findAllColors(Image image, ColorDetector detector, Rect rect, int threadCount) {
|
|
||||||
List<Point> result = new Vector<>();
|
|
||||||
ColorIterator[] iterators = divide(image, rect, threadCount);
|
|
||||||
for (int i = 1; i < threadCount; i++) {
|
|
||||||
mThreadPoolExecutor.execute(new FindAllColorsRunnable(result, iterators[i], detector));
|
|
||||||
}
|
}
|
||||||
new FindAllColorsRunnable(result, iterators[0], detector).run();
|
return points[0];
|
||||||
Point[] points = new Point[result.size()];
|
}
|
||||||
for (int i = 0; i < points.length; i++) {
|
|
||||||
points[i] = scalePoint(result.get(i), image.getWidth(), image.getHeight());
|
public static Point[] findAllColors(ImageWrapper image, int color, int threshold, Rect rect) {
|
||||||
|
Mat bi = new Mat();
|
||||||
|
Scalar lowerBound = new Scalar(Color.red(color) - threshold, Color.green(color) - threshold,
|
||||||
|
Color.blue(color) - threshold, 255);
|
||||||
|
Scalar upperBound = new Scalar(Color.red(color) + threshold, Color.green(color) + threshold,
|
||||||
|
Color.blue(color) + threshold, 255);
|
||||||
|
if (rect != null) {
|
||||||
|
Core.inRange(new Mat(image.getMat(), rect), lowerBound, upperBound, bi);
|
||||||
|
} else {
|
||||||
|
Core.inRange(image.getMat(), lowerBound, upperBound, bi);
|
||||||
|
}
|
||||||
|
Mat nonZeroPos = new Mat();
|
||||||
|
Core.findNonZero(bi, nonZeroPos);
|
||||||
|
if (nonZeroPos.rows() == 0 || nonZeroPos.cols() == 0) {
|
||||||
|
return new Point[0];
|
||||||
|
}
|
||||||
|
Point[] points = new MatOfPoint(nonZeroPos).toArray();
|
||||||
|
if (rect != null) {
|
||||||
|
for (int i = 0; i < points.length; i++) {
|
||||||
|
points[i].x += rect.x;
|
||||||
|
points[i].y += rect.y;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return points;
|
return points;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Point findColorConcurrently(Image image, ColorDetector detector, Rect rect, int threadCount) {
|
|
||||||
if (threadCount <= 1) {
|
|
||||||
return findColor(image, detector, rect);
|
|
||||||
}
|
|
||||||
VolatileBox<Point> result = new VolatileBox<>();
|
|
||||||
ColorIterator[] iterators = divide(image, rect, threadCount);
|
|
||||||
for (int i = 1; i < threadCount; i++) {
|
|
||||||
mThreadPoolExecutor.execute(new FindColorRunnable(result, iterators[i], detector));
|
|
||||||
}
|
|
||||||
new FindColorRunnable(result, iterators[0], detector).run();
|
|
||||||
return scalePoint(result.get(), image.getWidth(), image.getHeight());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Point scalePoint(Point point, int width, int height) {
|
|
||||||
if (point == null)
|
|
||||||
return null;
|
|
||||||
mScreenMetrics.setDesignHeight(height);
|
|
||||||
mScreenMetrics.setDesignWidth(width);
|
|
||||||
point.set(mScreenMetrics.scaleX(point.x), mScreenMetrics.scaleY(point.y));
|
|
||||||
return point;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ColorIterator[] divide(Image image, Rect rect, int count) {
|
|
||||||
Rect[] subAreas = divideIntoSubAreas(rect, count);
|
|
||||||
int centerY = rect.centerY();
|
|
||||||
ColorIterator[] iterators = new ColorIterator[count];
|
|
||||||
for (int i = 1; i < subAreas.length; i++) {
|
|
||||||
Rect subArea = subAreas[i];
|
|
||||||
if (subArea.top > centerY) {
|
|
||||||
iterators[i] = new ColorIterator.SequentialIterator(image, subArea, true);
|
|
||||||
} else {
|
|
||||||
iterators[i] = new ColorIterator.SequentialIterator(image, subArea, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
iterators[0] = new ColorIterator.SequentialIterator(image, subAreas[0], false);
|
|
||||||
return iterators;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Rect[] divideIntoSubAreas(Rect rect, int count) {
|
|
||||||
int row, column;
|
|
||||||
switch (count) {
|
|
||||||
case 4:
|
|
||||||
case 6:
|
|
||||||
case 8:
|
|
||||||
case 10:
|
|
||||||
case 14:
|
|
||||||
row = count / 2;
|
|
||||||
column = 2;
|
|
||||||
break;
|
|
||||||
case 9:
|
|
||||||
case 12:
|
|
||||||
case 15:
|
|
||||||
row = count / 3;
|
|
||||||
column = 3;
|
|
||||||
break;
|
|
||||||
case 16:
|
|
||||||
row = 4;
|
|
||||||
column = 4;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
row = count;
|
|
||||||
column = 1;
|
|
||||||
}
|
|
||||||
Rect[] cells = new Rect[count];
|
|
||||||
int cellWidth = rect.width() / column;
|
|
||||||
int cellHeight = rect.height() / row;
|
|
||||||
for (int i = 0; i < row; i++) {
|
|
||||||
for (int j = 0; j < column; j++) {
|
|
||||||
int x = rect.left + j * cellWidth;
|
|
||||||
int y = rect.top + i * cellHeight;
|
|
||||||
cells[i * column + j] = new Rect(x, y, x + cellWidth, y + cellHeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cells;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Point findColorConcurrently(Image image, int color, Rect rect, int threadCount, int threshold) {
|
|
||||||
return findColorConcurrently(image, defaultColorDetector(color, threshold), rect, threadCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Point findColorConcurrently(Image image, int color, Rect rect, int threadCount) {
|
|
||||||
return findColorConcurrently(image, color, rect, threadCount, 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Point findColorConcurrently(Image image, int color, int threadCount) {
|
|
||||||
Rect rect = new Rect(0, 0, image.getWidth(), image.getHeight());
|
|
||||||
return findColorConcurrently(image, defaultColorDetector(color), rect, threadCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Point findColorEqualsConcurrently(Image image, int color, Rect rect, int threadCount) {
|
|
||||||
return findColorConcurrently(image, new ColorDetector.EqualityDetector(color), rect, threadCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Point findColor(ColorIterator iterator, ColorDetector detector) {
|
|
||||||
Thread thread = Thread.currentThread();
|
|
||||||
ColorIterator.Pixel pixel = new ColorIterator.Pixel();
|
|
||||||
while (iterator.hasNext() && !thread.isInterrupted()) {
|
|
||||||
iterator.nextColor(pixel);
|
|
||||||
iterator.nextColor(pixel);
|
|
||||||
if (detector.detectsColor(pixel.red, pixel.green, pixel.blue)) {
|
|
||||||
return new Point(iterator.getX(), iterator.getY());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (thread.isInterrupted()) {
|
|
||||||
throw new ScriptInterruptedException();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ColorDetector defaultColorDetector(int color) {
|
|
||||||
return new ColorDetector.RGBDistanceDetector(color, 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ColorDetector defaultColorDetector(int color, int threshold) {
|
|
||||||
return new ColorDetector.RGBDistanceDetector(color, threshold);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ColorIterator defaultColorIterator(Image image, Rect rect) {
|
|
||||||
return new ColorIterator.SequentialIterator(image, rect);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Point findColor(Image image, ColorDetector detector, Rect rect) {
|
|
||||||
return scalePoint(findColor(defaultColorIterator(image, rect), detector), image.getWidth(), image.getHeight());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Point findColor(Image image, int color, Rect rect) {
|
|
||||||
return scalePoint(findColor(defaultColorIterator(image, rect), defaultColorDetector(color)), image.getWidth(), image.getHeight());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Point findColor(Image image, int color) {
|
|
||||||
return findColor(image, color, new Rect(0, 0, image.getWidth(), image.getHeight()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Point findColorEquals(Image image, int color, Rect rect) {
|
|
||||||
return scalePoint(findColor(defaultColorIterator(image, rect), new ColorDetector.EqualityDetector(color)), image.getWidth(), image.getHeight());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class FindColorRunnable implements Runnable {
|
|
||||||
|
|
||||||
private final VolatileBox<Point> mResultBox;
|
|
||||||
private final ColorIterator mColorIterator;
|
|
||||||
private final ColorDetector mColorDetector;
|
|
||||||
|
|
||||||
private FindColorRunnable(VolatileBox<Point> resultBox, ColorIterator colorIterator, ColorDetector colorDetector) {
|
|
||||||
mResultBox = resultBox;
|
|
||||||
mColorIterator = colorIterator;
|
|
||||||
mColorDetector = colorDetector;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Thread thread = Thread.currentThread();
|
|
||||||
ColorIterator.Pixel pixel = new ColorIterator.Pixel();
|
|
||||||
while (mResultBox.isNull() && mColorIterator.hasNext() && !thread.isInterrupted()) {
|
|
||||||
mColorIterator.nextColor(pixel);
|
|
||||||
if (mColorDetector.detectsColor(pixel.red, pixel.green, pixel.blue)) {
|
|
||||||
mResultBox.set(new Point(mColorIterator.getX(), mColorIterator.getY()));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (thread.isInterrupted()) {
|
|
||||||
throw new ScriptInterruptedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class FindAllColorsRunnable implements Runnable {
|
|
||||||
|
|
||||||
private final List<Point> mResult;
|
|
||||||
private final ColorIterator mColorIterator;
|
|
||||||
private final ColorDetector mColorDetector;
|
|
||||||
|
|
||||||
private FindAllColorsRunnable(List<Point> result, ColorIterator colorIterator, ColorDetector colorDetector) {
|
|
||||||
mResult = result;
|
|
||||||
mColorIterator = colorIterator;
|
|
||||||
mColorDetector = colorDetector;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Thread thread = Thread.currentThread();
|
|
||||||
ColorIterator.Pixel pixel = new ColorIterator.Pixel();
|
|
||||||
while (mColorIterator.hasNext() && !thread.isInterrupted()) {
|
|
||||||
mColorIterator.nextColor(pixel);
|
|
||||||
if (mColorDetector.detectsColor(pixel.red, pixel.green, pixel.blue)) {
|
|
||||||
mResult.add(new Point(mColorIterator.getX(), mColorIterator.getY()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (thread.isInterrupted()) {
|
|
||||||
throw new ScriptInterruptedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,187 +0,0 @@
|
|||||||
package com.stardust.autojs.core.image;
|
|
||||||
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.media.Image;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by Stardust on 2017/5/20.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public interface ColorIterator {
|
|
||||||
|
|
||||||
class Pixel {
|
|
||||||
int red;
|
|
||||||
int green;
|
|
||||||
int blue;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean hasNext();
|
|
||||||
|
|
||||||
void nextColor(Pixel pixel);
|
|
||||||
|
|
||||||
int getX();
|
|
||||||
|
|
||||||
int getY();
|
|
||||||
|
|
||||||
abstract class ImageColorIterator implements ColorIterator {
|
|
||||||
|
|
||||||
protected final ByteBuffer mByteBuffer;
|
|
||||||
protected final Rect mIterateArea;
|
|
||||||
|
|
||||||
public ImageColorIterator(Image image, Rect area, boolean duplicateBuffer) {
|
|
||||||
Image.Plane plane = image.getPlanes()[0];
|
|
||||||
if (duplicateBuffer) {
|
|
||||||
mByteBuffer = plane.getBuffer().duplicate();
|
|
||||||
} else {
|
|
||||||
mByteBuffer = plane.getBuffer();
|
|
||||||
}
|
|
||||||
mIterateArea = area;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImageColorIterator(Image image, Rect area) {
|
|
||||||
this(image, area, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected void skip(int i) {
|
|
||||||
mByteBuffer.position(mByteBuffer.position() + i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SequentialIterator extends ImageColorIterator {
|
|
||||||
|
|
||||||
private static final String LOG_TAG = "SequentialIterator";
|
|
||||||
|
|
||||||
private final int mRowStride;
|
|
||||||
private final int mSkipPerRow;
|
|
||||||
private final int mWidth;
|
|
||||||
private final int mHeight;
|
|
||||||
private int mX = -1;
|
|
||||||
private int mY = 0;
|
|
||||||
|
|
||||||
public SequentialIterator(Image image, Rect area, boolean duplicateBuffer) {
|
|
||||||
super(image, area, duplicateBuffer);
|
|
||||||
Image.Plane plane = image.getPlanes()[0];
|
|
||||||
int pixelStride = plane.getPixelStride();
|
|
||||||
mRowStride = plane.getRowStride();
|
|
||||||
mWidth = area.width();
|
|
||||||
mHeight = area.height();
|
|
||||||
int rowPadding = mRowStride - pixelStride * image.getWidth();
|
|
||||||
mSkipPerRow = rowPadding + (image.getWidth() - mWidth) * pixelStride;
|
|
||||||
int offset = mIterateArea.top * mRowStride + mIterateArea.left * pixelStride;
|
|
||||||
mByteBuffer.position(offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SequentialIterator(Image image, Rect area) {
|
|
||||||
this(image, area, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SequentialIterator(Image image) {
|
|
||||||
this(image, new Rect(0, 0, image.getWidth(), image.getHeight()));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasNext() {
|
|
||||||
return mX < mWidth - 1 || mY < mHeight - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getX() {
|
|
||||||
return mIterateArea.left + mX;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getY() {
|
|
||||||
return mIterateArea.top + mY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void nextColor(Pixel pixel) {
|
|
||||||
if (mX == mWidth - 1) {
|
|
||||||
skip(mSkipPerRow);
|
|
||||||
mX = 0;
|
|
||||||
mY++;
|
|
||||||
} else {
|
|
||||||
mX++;
|
|
||||||
}
|
|
||||||
pixel.red = mByteBuffer.get() & 0xff;
|
|
||||||
pixel.green = mByteBuffer.get() & 0xff;
|
|
||||||
pixel.blue = mByteBuffer.get() & 0xff;
|
|
||||||
mByteBuffer.get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// TODO: 2017/5/29 中心螺旋。未完成。
|
|
||||||
class CentralSpiralIterator extends ImageColorIterator {
|
|
||||||
|
|
||||||
private static final int DIRECTION_RIGHT = 0;
|
|
||||||
private static final int DIRECTION_TOP = 1;
|
|
||||||
private static final int DIRECTION_LEFT = 2;
|
|
||||||
private static final int DIRECTION_BOTTOM = 3;
|
|
||||||
|
|
||||||
private final int mPixelStride, mRowStride;
|
|
||||||
private int mNextStepSkip;
|
|
||||||
private int mStepCount;
|
|
||||||
private int mMaxStep = 1;
|
|
||||||
private int mDirection = DIRECTION_RIGHT;
|
|
||||||
|
|
||||||
public CentralSpiralIterator(Image image, Rect area, boolean duplicateBuffer) {
|
|
||||||
super(image, area, duplicateBuffer);
|
|
||||||
Image.Plane plane = image.getPlanes()[0];
|
|
||||||
mPixelStride = mNextStepSkip = plane.getPixelStride();
|
|
||||||
mRowStride = plane.getRowStride();
|
|
||||||
mByteBuffer.position(area.centerX() * mPixelStride + area.centerY() * mRowStride);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CentralSpiralIterator(Image image, Rect area) {
|
|
||||||
this(image, area, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasNext() {
|
|
||||||
return mByteBuffer.position() < mByteBuffer.limit();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void nextColor(Pixel pixel) {
|
|
||||||
int c = mByteBuffer.getInt();
|
|
||||||
skip(mNextStepSkip);
|
|
||||||
mStepCount++;
|
|
||||||
if (mStepCount == mMaxStep) {
|
|
||||||
mStepCount = 0;
|
|
||||||
mMaxStep++;
|
|
||||||
mDirection = (mDirection + 1) & 4;
|
|
||||||
switch (mDirection) {
|
|
||||||
case DIRECTION_RIGHT:
|
|
||||||
mNextStepSkip = mPixelStride;
|
|
||||||
break;
|
|
||||||
case DIRECTION_TOP:
|
|
||||||
mNextStepSkip = -mRowStride;
|
|
||||||
break;
|
|
||||||
case DIRECTION_LEFT:
|
|
||||||
mNextStepSkip = -mPixelStride;
|
|
||||||
break;
|
|
||||||
case DIRECTION_BOTTOM:
|
|
||||||
mNextStepSkip = mRowStride;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getX() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getY() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
package com.stardust.autojs.core.image;
|
|
||||||
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.media.Image;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by Stardust on 2017/5/21.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public interface ConcurrentColorIterator {
|
|
||||||
|
|
||||||
void nextColor(Pixel pixel);
|
|
||||||
|
|
||||||
class Pixel {
|
|
||||||
|
|
||||||
public int x;
|
|
||||||
public int y;
|
|
||||||
public int color;
|
|
||||||
public boolean valid = true;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class ConcurrentImageColorIterator implements ConcurrentColorIterator {
|
|
||||||
|
|
||||||
protected final ByteBuffer mByteBuffer;
|
|
||||||
protected final int mImageWidth, mImageHeight;
|
|
||||||
protected final Rect mIterateArea;
|
|
||||||
protected final int mAreaWidth, mAreaHeight;
|
|
||||||
protected volatile int mX = -1, mY;
|
|
||||||
|
|
||||||
public ConcurrentImageColorIterator(Image image, Rect area) {
|
|
||||||
Image.Plane plane = image.getPlanes()[0];
|
|
||||||
mByteBuffer = plane.getBuffer();
|
|
||||||
mImageWidth = image.getWidth();
|
|
||||||
mImageHeight = image.getHeight();
|
|
||||||
mIterateArea = area;
|
|
||||||
mAreaWidth = area.width();
|
|
||||||
mAreaHeight = area.height();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected void skip(int i) {
|
|
||||||
mByteBuffer.position(mByteBuffer.position() + i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ConcurrentSequentialIterator extends ConcurrentImageColorIterator {
|
|
||||||
|
|
||||||
private final int mSkipPerRow;
|
|
||||||
|
|
||||||
public ConcurrentSequentialIterator(Image image, Rect area) {
|
|
||||||
super(image, area);
|
|
||||||
Image.Plane plane = image.getPlanes()[0];
|
|
||||||
int pixelStride = plane.getPixelStride();
|
|
||||||
int rowStride = plane.getRowStride();
|
|
||||||
int rowPadding = rowStride - pixelStride * mImageWidth;
|
|
||||||
mSkipPerRow = (mImageWidth - mAreaWidth) * pixelStride + rowPadding;
|
|
||||||
int offset = mIterateArea.top * rowStride + mIterateArea.left * pixelStride;
|
|
||||||
mByteBuffer.position(offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: 2017/5/21 对锁的竞争造成并发速度极慢。能否做到无锁?
|
|
||||||
@Override
|
|
||||||
public synchronized void nextColor(Pixel pixel) {
|
|
||||||
if (!(mY < mAreaHeight - 1 || mX < mAreaWidth - 1)) {
|
|
||||||
pixel.valid = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int c = mByteBuffer.getInt();
|
|
||||||
c = ((c & 0xff) << 16) | (c & 0xff00) | ((c & 0xff0000) >> 16) | 0xff000000;
|
|
||||||
mX++;
|
|
||||||
if (mX == mAreaWidth) {
|
|
||||||
mX = 0;
|
|
||||||
mY++;
|
|
||||||
if (mSkipPerRow > 0) {
|
|
||||||
skip(mSkipPerRow);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pixel.x = mX + mIterateArea.left;
|
|
||||||
pixel.y = mY + mIterateArea.top;
|
|
||||||
pixel.color = c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package com.stardust.autojs.core.image;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.ImageFormat;
|
||||||
|
import android.media.Image;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.support.annotation.RequiresApi;
|
||||||
|
|
||||||
|
import com.stardust.autojs.runtime.api.Images;
|
||||||
|
import com.stardust.pio.UncheckedIOException;
|
||||||
|
|
||||||
|
import org.opencv.android.Utils;
|
||||||
|
import org.opencv.core.Core;
|
||||||
|
import org.opencv.core.CvType;
|
||||||
|
import org.opencv.core.Mat;
|
||||||
|
import org.opencv.core.MatOfInt;
|
||||||
|
import org.opencv.highgui.Highgui;
|
||||||
|
import org.opencv.imgproc.Imgproc;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Stardust on 2017/11/25.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class ImageWrapper {
|
||||||
|
|
||||||
|
private Mat mMat;
|
||||||
|
private int mWidth;
|
||||||
|
private int mHeight;
|
||||||
|
private Bitmap mBitmap;
|
||||||
|
|
||||||
|
public ImageWrapper(Mat mat) {
|
||||||
|
mMat = mat;
|
||||||
|
mWidth = mat.cols();
|
||||||
|
mHeight = mat.rows();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageWrapper(Bitmap bitmap) {
|
||||||
|
mBitmap = bitmap;
|
||||||
|
mWidth = bitmap.getWidth();
|
||||||
|
mHeight = bitmap.getHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
||||||
|
public static ImageWrapper ofImage(Image image) {
|
||||||
|
if (image == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new ImageWrapper(toBitmap(image));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ImageWrapper ofBitmap(Bitmap bitmap) {
|
||||||
|
if (bitmap == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new ImageWrapper(bitmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
||||||
|
public static Bitmap toBitmap(Image image) {
|
||||||
|
Image.Plane plane = image.getPlanes()[0];
|
||||||
|
ByteBuffer buffer = plane.getBuffer();
|
||||||
|
buffer.position(0);
|
||||||
|
int pixelStride = plane.getPixelStride();
|
||||||
|
int rowPadding = plane.getRowStride() - pixelStride * image.getWidth();
|
||||||
|
Bitmap bitmap = Bitmap.createBitmap(image.getWidth() + rowPadding / pixelStride, image.getHeight(), Bitmap.Config.ARGB_8888);
|
||||||
|
bitmap.copyPixelsFromBuffer(buffer);
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWidth() {
|
||||||
|
return mWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHeight() {
|
||||||
|
return mHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mat getMat() {
|
||||||
|
if (mMat == null) {
|
||||||
|
mMat = new Mat();
|
||||||
|
Utils.bitmapToMat(mBitmap, mMat);
|
||||||
|
}
|
||||||
|
return mMat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveTo(String path) {
|
||||||
|
if (mBitmap != null) {
|
||||||
|
try {
|
||||||
|
mBitmap.compress(Bitmap.CompressFormat.PNG, 100, new FileOutputStream(path));
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Highgui.imwrite(path, mMat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPixel(int x, int y) {
|
||||||
|
if (mBitmap != null) {
|
||||||
|
return mBitmap.getPixel(x, y);
|
||||||
|
}
|
||||||
|
double[] channels = mMat.get(x, y);
|
||||||
|
return Color.argb((int) channels[3], (int) channels[0], (int) channels[1], (int) channels[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bitmap getBitmap() {
|
||||||
|
return mBitmap;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -83,25 +83,26 @@ public class ScreenCapturer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setImageListener(Handler handler) {
|
private void setImageListener(Handler handler) {
|
||||||
mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
|
mImageReader.setOnImageAvailableListener(reader -> {
|
||||||
@Override
|
if (mCachedImage != null) {
|
||||||
public void onImageAvailable(ImageReader reader) {
|
synchronized (mCachedImageLock) {
|
||||||
if (mCachedImage != null) {
|
if (mCachedImage != null) {
|
||||||
synchronized (mCachedImageLock) {
|
mCachedImage.close();
|
||||||
if (mCachedImage != null) {
|
|
||||||
mCachedImage.close();
|
|
||||||
}
|
|
||||||
mCachedImage = reader.acquireLatestImage();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
mCachedImage = reader.acquireLatestImage();
|
||||||
|
mCachedImageLock.notify();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
mCachedImage = reader.acquireLatestImage();
|
|
||||||
}
|
}
|
||||||
|
mCachedImage = reader.acquireLatestImage();
|
||||||
}, handler);
|
}, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public Image capture() {
|
public Image capture() {
|
||||||
|
if (mUnderUsingImage == null && mCachedImage == null) {
|
||||||
|
waitForImageAvailable();
|
||||||
|
}
|
||||||
if (mCachedImage != null) {
|
if (mCachedImage != null) {
|
||||||
if (mUnderUsingImage != null)
|
if (mUnderUsingImage != null)
|
||||||
mUnderUsingImage.close();
|
mUnderUsingImage.close();
|
||||||
@@ -113,6 +114,16 @@ public class ScreenCapturer {
|
|||||||
return mUnderUsingImage;
|
return mUnderUsingImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void waitForImageAvailable() {
|
||||||
|
synchronized (mCachedImageLock) {
|
||||||
|
try {
|
||||||
|
mCachedImageLock.wait();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new ScriptInterruptedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public int getScreenWidth() {
|
public int getScreenWidth() {
|
||||||
return mScreenWidth;
|
return mScreenWidth;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,182 @@
|
|||||||
|
package com.stardust.autojs.core.image;
|
||||||
|
|
||||||
|
import android.util.Pair;
|
||||||
|
import android.util.TimingLogger;
|
||||||
|
|
||||||
|
import com.stardust.util.Nath;
|
||||||
|
|
||||||
|
import org.opencv.core.Core;
|
||||||
|
import org.opencv.core.CvType;
|
||||||
|
import org.opencv.core.Mat;
|
||||||
|
import org.opencv.core.Point;
|
||||||
|
import org.opencv.core.Rect;
|
||||||
|
import org.opencv.core.Size;
|
||||||
|
import org.opencv.imgproc.Imgproc;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Stardust on 2017/11/25.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class TemplateMatching {
|
||||||
|
|
||||||
|
private static final String LOG_TAG = "TemplateMatching";
|
||||||
|
|
||||||
|
public static final int MAX_LEVEL_AUTO = -1;
|
||||||
|
|
||||||
|
public static Point fastTemplateMatching(Mat img, Mat template, float threshold) {
|
||||||
|
return fastTemplateMatching(img, template, Imgproc.TM_CCOEFF_NORMED, 0.75f, threshold, MAX_LEVEL_AUTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Point fastTemplateMatching(Mat img, Mat template, int matchMethod, float weakThreshold, float strictThreshold, int maxLevel) {
|
||||||
|
TimingLogger logger = new TimingLogger(LOG_TAG, "fast_tm");
|
||||||
|
if (maxLevel == MAX_LEVEL_AUTO) {
|
||||||
|
maxLevel = selectPyramidLevel(img, template);
|
||||||
|
logger.addSplit("selectPyramidLevel:" + maxLevel);
|
||||||
|
}
|
||||||
|
Point p = null;
|
||||||
|
Mat matchResult;
|
||||||
|
double similarity = 0;
|
||||||
|
for (int level = maxLevel; level >= 0; level--) {
|
||||||
|
Mat src = getPyramidDownAtLevel(img, level);
|
||||||
|
Mat currentTemplate = getPyramidDownAtLevel(template, level);
|
||||||
|
if (p == null) {
|
||||||
|
if (!shouldContinueMatching(level, maxLevel)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
matchResult = matchTemplate(src, currentTemplate, matchMethod);
|
||||||
|
Pair<Point, Double> bestMatched = getBestMatched(matchResult, matchMethod, weakThreshold);
|
||||||
|
p = bestMatched.first;
|
||||||
|
similarity = bestMatched.second;
|
||||||
|
} else {
|
||||||
|
Rect r = getROI(p, src, currentTemplate);
|
||||||
|
matchResult = matchTemplate(new Mat(src, r), currentTemplate, matchMethod);
|
||||||
|
Pair<Point, Double> bestMatched = getBestMatched(matchResult, matchMethod, weakThreshold);
|
||||||
|
if (bestMatched.second < weakThreshold) {
|
||||||
|
p = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
p = bestMatched.first;
|
||||||
|
similarity = bestMatched.second;
|
||||||
|
p.x += r.x;
|
||||||
|
p.y += r.y;
|
||||||
|
if (bestMatched.second >= strictThreshold) {
|
||||||
|
pyrUp(p, level);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.addSplit("level:" + level + " point:" + p);
|
||||||
|
}
|
||||||
|
logger.addSplit("result:" + p);
|
||||||
|
logger.dumpToLog();
|
||||||
|
if (similarity < strictThreshold) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Mat getPyramidDownAtLevel(Mat m, int level) {
|
||||||
|
if (level == 0) {
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
int cols = m.cols();
|
||||||
|
int rows = m.rows();
|
||||||
|
for (int i = 0; i < level; i++) {
|
||||||
|
cols = (cols + 1) / 2;
|
||||||
|
rows = (rows + 1) / 2;
|
||||||
|
}
|
||||||
|
Mat r = new Mat(rows, cols, m.type());
|
||||||
|
Imgproc.resize(m, r, new Size(cols, rows));
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void pyrUp(Point p, int level) {
|
||||||
|
for (int i = 0; i < level; i++) {
|
||||||
|
p.x *= 2;
|
||||||
|
p.y *= 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean shouldContinueMatching(int level, int maxLevel) {
|
||||||
|
if (level == maxLevel && level != 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (maxLevel <= 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return level == maxLevel - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Rect getROI(Point p, Mat src, Mat currentTemplate) {
|
||||||
|
int x = (int) (p.x * 2 - currentTemplate.rows() / 4);
|
||||||
|
x = Math.max(0, x);
|
||||||
|
int y = (int) (p.y * 2 - currentTemplate.cols() / 4);
|
||||||
|
y = Math.max(0, y);
|
||||||
|
int w = (int) (currentTemplate.rows() * 1.5);
|
||||||
|
int h = (int) (currentTemplate.cols() * 1.5);
|
||||||
|
if (x + w >= src.cols()) {
|
||||||
|
w = src.cols() - x - 1;
|
||||||
|
}
|
||||||
|
if (y + h >= src.rows()) {
|
||||||
|
h = src.rows() - y - 1;
|
||||||
|
}
|
||||||
|
return new Rect(x, y, w, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int selectPyramidLevel(Mat img, Mat template) {
|
||||||
|
int minDim = Nath.min(img.rows(), img.cols(), template.rows(), template.cols());
|
||||||
|
//这里选取12为图像缩小后的最小宽高,从而用log(2, minDim / 16)得到最多可以经过几次缩小。
|
||||||
|
int maxLevel = (int) (Math.log(minDim / 7) / Math.log(2));
|
||||||
|
if (maxLevel < 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
//上限为6
|
||||||
|
return Math.min(6, maxLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static List<Mat> buildPyramid(Mat mat, int maxLevel) {
|
||||||
|
List<Mat> pyramid = new ArrayList<>();
|
||||||
|
pyramid.add(mat);
|
||||||
|
for (int i = 0; i < maxLevel; i++) {
|
||||||
|
Mat m = new Mat((mat.rows() + 1) / 2, (mat.cols() + 1) / 2, mat.type());
|
||||||
|
Imgproc.pyrDown(mat, m);
|
||||||
|
pyramid.add(m);
|
||||||
|
mat = m;
|
||||||
|
}
|
||||||
|
return pyramid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Mat matchTemplate(Mat img, Mat temp, int match_method) {
|
||||||
|
int result_cols = img.cols() - temp.cols() + 1;
|
||||||
|
int result_rows = img.rows() - temp.rows() + 1;
|
||||||
|
Mat result = new Mat(result_rows, result_cols, CvType.CV_32FC1);
|
||||||
|
Imgproc.matchTemplate(img, temp, result, match_method);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Pair<Point, Double> getBestMatched(Mat tmResult, int matchMethod, float threshold) {
|
||||||
|
TimingLogger logger = new TimingLogger(LOG_TAG, "best_matched_point");
|
||||||
|
// FIXME: 2017/11/26 正交化?
|
||||||
|
// Core.normalize(tmResult, tmResult, 0, 1, Core.NORM_MINMAX, -1, new Mat());
|
||||||
|
Core.MinMaxLocResult mmr = Core.minMaxLoc(tmResult);
|
||||||
|
logger.addSplit("minMaxLoc");
|
||||||
|
double value;
|
||||||
|
Point pos;
|
||||||
|
if (matchMethod == Imgproc.TM_SQDIFF || matchMethod == Imgproc.TM_SQDIFF_NORMED) {
|
||||||
|
pos = mmr.minLoc;
|
||||||
|
value = -mmr.minVal;
|
||||||
|
} else {
|
||||||
|
pos = mmr.maxLoc;
|
||||||
|
value = mmr.maxVal;
|
||||||
|
}
|
||||||
|
logger.addSplit("value:" + value);
|
||||||
|
logger.dumpToLog();
|
||||||
|
return new Pair<>(pos, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -164,8 +164,8 @@ public class RootAutomator {
|
|||||||
|
|
||||||
public void touchUp(int id) throws IOException {
|
public void touchUp(int id) throws IOException {
|
||||||
sendEvent(EV_ABS, ABS_MT_TRACKING_ID, id);
|
sendEvent(EV_ABS, ABS_MT_TRACKING_ID, id);
|
||||||
sendEvent(EV_KEY, BTN_TOUCH, 0x00000000);
|
// sendEvent(EV_KEY, BTN_TOUCH, 0x00000000);
|
||||||
sendEvent(EV_KEY, BTN_TOOL_FINGER, 0x00000000);
|
// sendEvent(EV_KEY, BTN_TOOL_FINGER, 0x00000000);
|
||||||
sendEvent(EV_SYN, SYN_REPORT, 0x00000000);
|
sendEvent(EV_SYN, SYN_REPORT, 0x00000000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,16 +60,30 @@ public class ScriptExecuteActivity extends AppCompatActivity implements Thread.U
|
|||||||
prepare();
|
prepare();
|
||||||
doExecution();
|
doExecution();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
mExecutionListener.onException(mScriptExecution, e);
|
onException(e);
|
||||||
super.finish();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onException(Exception e) {
|
||||||
|
mExecutionListener.onException(mScriptExecution, e);
|
||||||
|
super.finish();
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private void doExecution() {
|
private void doExecution() {
|
||||||
mScriptEngine.setTag(ScriptEngine.TAG_SOURCE, mScriptSource);
|
mScriptEngine.setTag(ScriptEngine.TAG_SOURCE, mScriptSource);
|
||||||
mExecutionListener.onStart(mScriptExecution);
|
mExecutionListener.onStart(mScriptExecution);
|
||||||
mResult = mScriptEngine.execute(mScriptSource);
|
((LoopBasedJavaScriptEngine) mScriptEngine).execute(mScriptSource, new LoopBasedJavaScriptEngine.ExecuteCallback() {
|
||||||
|
@Override
|
||||||
|
public void onResult(Object r) {
|
||||||
|
mResult = r;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException(Exception e) {
|
||||||
|
ScriptExecuteActivity.this.onException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void prepare() {
|
private void prepare() {
|
||||||
@@ -94,8 +108,7 @@ public class ScriptExecuteActivity extends AppCompatActivity implements Thread.U
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void uncaughtException(Thread t, Throwable e) {
|
public void uncaughtException(Thread t, Throwable e) {
|
||||||
mExecutionListener.onException(mScriptExecution, (Exception) e);
|
onException((Exception) e);
|
||||||
super.finish();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class ActivityScriptExecution extends ScriptExecution.AbstractScriptExecution {
|
private static class ActivityScriptExecution extends ScriptExecution.AbstractScriptExecution {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.content.Intent;
|
|||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.graphics.Matrix;
|
import android.graphics.Matrix;
|
||||||
|
import android.graphics.Point;
|
||||||
import android.media.Image;
|
import android.media.Image;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
@@ -16,8 +17,10 @@ import android.view.Surface;
|
|||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
|
|
||||||
import com.stardust.autojs.core.image.ColorFinder;
|
import com.stardust.autojs.core.image.ColorFinder;
|
||||||
|
import com.stardust.autojs.core.image.ImageWrapper;
|
||||||
import com.stardust.autojs.core.image.ScreenCaptureRequester;
|
import com.stardust.autojs.core.image.ScreenCaptureRequester;
|
||||||
import com.stardust.autojs.core.image.ScreenCapturer;
|
import com.stardust.autojs.core.image.ScreenCapturer;
|
||||||
|
import com.stardust.autojs.core.image.TemplateMatching;
|
||||||
import com.stardust.autojs.runtime.ScriptRuntime;
|
import com.stardust.autojs.runtime.ScriptRuntime;
|
||||||
import com.stardust.autojs.runtime.exception.ScriptInterruptedException;
|
import com.stardust.autojs.runtime.exception.ScriptInterruptedException;
|
||||||
import com.stardust.autojs.annotation.ScriptVariable;
|
import com.stardust.autojs.annotation.ScriptVariable;
|
||||||
@@ -25,6 +28,15 @@ import com.stardust.concurrent.VolatileBox;
|
|||||||
import com.stardust.pio.UncheckedIOException;
|
import com.stardust.pio.UncheckedIOException;
|
||||||
import com.stardust.util.ScreenMetrics;
|
import com.stardust.util.ScreenMetrics;
|
||||||
|
|
||||||
|
import org.opencv.android.Utils;
|
||||||
|
import org.opencv.contrib.FaceRecognizer;
|
||||||
|
import org.opencv.core.Core;
|
||||||
|
import org.opencv.core.Mat;
|
||||||
|
import org.opencv.core.Rect;
|
||||||
|
import org.opencv.core.Scalar;
|
||||||
|
import org.opencv.highgui.Highgui;
|
||||||
|
import org.opencv.imgproc.Imgproc;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
@@ -56,7 +68,6 @@ public class Images {
|
|||||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||||
public boolean requestScreenCapture(final int width, final int height) {
|
public boolean requestScreenCapture(final int width, final int height) {
|
||||||
mScriptRuntime.requiresApi(21);
|
mScriptRuntime.requiresApi(21);
|
||||||
colorFinder.prestartThreads();
|
|
||||||
final VolatileBox<Boolean> requestResult = new VolatileBox<>();
|
final VolatileBox<Boolean> requestResult = new VolatileBox<>();
|
||||||
mScreenCaptureRequester.setOnActivityResultCallback(new ScreenCaptureRequester.Callback() {
|
mScreenCaptureRequester.setOnActivityResultCallback(new ScreenCaptureRequester.Callback() {
|
||||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||||
@@ -84,19 +95,17 @@ public class Images {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||||
public Image captureScreen() {
|
public ImageWrapper captureScreen() {
|
||||||
mScriptRuntime.requiresApi(21);
|
mScriptRuntime.requiresApi(21);
|
||||||
if (mScreenCapturer == null) {
|
if (mScreenCapturer == null) {
|
||||||
throw new SecurityException("No screen capture permission");
|
throw new SecurityException("No screen capture permission");
|
||||||
}
|
}
|
||||||
colorFinder.prestartThreads();
|
return ImageWrapper.ofImage(mScreenCapturer.capture());
|
||||||
return mScreenCapturer.capture();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||||
public boolean captureScreen(String path) {
|
public boolean captureScreen(String path) {
|
||||||
mScriptRuntime.requiresApi(21);
|
ImageWrapper image = captureScreen();
|
||||||
Image image = mScreenCapturer.capture();
|
|
||||||
if (image != null) {
|
if (image != null) {
|
||||||
saveImage(image, path);
|
saveImage(image, path);
|
||||||
return true;
|
return true;
|
||||||
@@ -104,22 +113,10 @@ public class Images {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveImage(Image image, String path) {
|
public void saveImage(ImageWrapper image, String path) {
|
||||||
Bitmap bitmap = toBitmap(image);
|
image.saveTo(path);
|
||||||
saveBitmap(bitmap, path);
|
|
||||||
bitmap.recycle();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Bitmap toBitmap(Image image) {
|
|
||||||
Image.Plane plane = image.getPlanes()[0];
|
|
||||||
ByteBuffer buffer = plane.getBuffer();
|
|
||||||
buffer.position(0);
|
|
||||||
int pixelStride = plane.getPixelStride();
|
|
||||||
int rowPadding = plane.getRowStride() - pixelStride * image.getWidth();
|
|
||||||
Bitmap bitmap = Bitmap.createBitmap(image.getWidth() + rowPadding / pixelStride, image.getHeight(), Bitmap.Config.ARGB_8888);
|
|
||||||
bitmap.copyPixelsFromBuffer(buffer);
|
|
||||||
return bitmap;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int pixel(Image image, int x, int y) {
|
public static int pixel(Image image, int x, int y) {
|
||||||
int originX = x;
|
int originX = x;
|
||||||
@@ -134,15 +131,15 @@ public class Images {
|
|||||||
return (c & 0xff000000) + ((c & 0xff) << 16) + (c & 0x00ff00) + ((c & 0xff0000) >> 16);
|
return (c & 0xff000000) + ((c & 0xff) << 16) + (c & 0x00ff00) + ((c & 0xff0000) >> 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int pixel(Bitmap bitmap, int x, int y) {
|
public static int pixel(ImageWrapper image, int x, int y) {
|
||||||
x = ScreenMetrics.rescaleX(x, bitmap.getWidth());
|
x = ScreenMetrics.rescaleX(x, image.getWidth());
|
||||||
y = ScreenMetrics.rescaleY(y, bitmap.getHeight());
|
y = ScreenMetrics.rescaleY(y, image.getHeight());
|
||||||
return bitmap.getPixel(x, y);
|
return image.getPixel(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static Bitmap read(String path) {
|
public ImageWrapper read(String path) {
|
||||||
return BitmapFactory.decodeFile(path);
|
return ImageWrapper.ofBitmap(BitmapFactory.decodeFile(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void saveBitmap(Bitmap bitmap, String path) {
|
public static void saveBitmap(Bitmap bitmap, String path) {
|
||||||
@@ -153,24 +150,6 @@ public class Images {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void saveBitmap(Bitmap bitmap, String path, int width, int height) {
|
|
||||||
if (width != bitmap.getWidth() || height != bitmap.getHeight()) {
|
|
||||||
Bitmap scaleBitmap = scaleBitmap(bitmap, width, height);
|
|
||||||
saveBitmap(scaleBitmap, path);
|
|
||||||
if (scaleBitmap != bitmap) {
|
|
||||||
scaleBitmap.recycle();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
saveBitmap(bitmap, path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void saveImage(Image image, String path, int width, int height) {
|
|
||||||
Bitmap bitmap = toBitmap(image);
|
|
||||||
saveBitmap(bitmap, path, width, height);
|
|
||||||
bitmap.recycle();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Bitmap scaleBitmap(Bitmap origin, int newWidth, int newHeight) {
|
public static Bitmap scaleBitmap(Bitmap origin, int newWidth, int newHeight) {
|
||||||
if (origin == null) {
|
if (origin == null) {
|
||||||
return null;
|
return null;
|
||||||
@@ -190,4 +169,37 @@ public class Images {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Point findImage(ImageWrapper image, ImageWrapper template) {
|
||||||
|
return findImage(image, template, 0.9f, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Point findImage(ImageWrapper image, ImageWrapper template, float threshold) {
|
||||||
|
return findImage(image, template, threshold, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Point findImage(ImageWrapper image, ImageWrapper template, float threshold, Rect rect) {
|
||||||
|
return findImage(image, template, threshold, rect, TemplateMatching.MAX_LEVEL_AUTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Point findImage(ImageWrapper image, ImageWrapper template, float threshold, Rect rect, int maxLevel) {
|
||||||
|
Mat src = image.getMat();
|
||||||
|
if (rect != null) {
|
||||||
|
src = new Mat(src, rect);
|
||||||
|
}
|
||||||
|
org.opencv.core.Point point = TemplateMatching.fastTemplateMatching(src, template.getMat(), threshold);
|
||||||
|
if (point != null && rect != null) {
|
||||||
|
point.x += rect.x;
|
||||||
|
point.y += rect.y;
|
||||||
|
}
|
||||||
|
return toAndroidPoint(point);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Point toAndroidPoint(org.opencv.core.Point p) {
|
||||||
|
if (p == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new Point((int) p.x, (int) p.y);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+9
-2
@@ -1,12 +1,13 @@
|
|||||||
package com.stardust.autojs.runtime.exception;
|
package com.stardust.autojs.runtime.exception;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Stardust on 2017/4/30.
|
* Created by Stardust on 2017/4/30.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class ScriptInterruptedException extends ScriptException {
|
public class ScriptInterruptedException extends ScriptException {
|
||||||
|
|
||||||
public ScriptInterruptedException(){
|
public ScriptInterruptedException() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -15,7 +16,13 @@ public class ScriptInterruptedException extends ScriptException {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean causedByInterrupted(Throwable e) {
|
public static boolean causedByInterrupted(Throwable e) {
|
||||||
return e instanceof ScriptInterruptedException || e.getCause() instanceof ScriptInterruptedException;
|
while (e != null) {
|
||||||
|
if (e instanceof ScriptInterruptedException || e instanceof InterruptedException) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
e = e.getCause();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -57,9 +57,9 @@ public class Notification extends android.app.Notification {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
return "Notification{" +
|
return "Notification{" +
|
||||||
"packageName='" + mPackageName + '\'' +
|
"packageName='" + mPackageName + "', " +
|
||||||
"title = " + getTitle() + '\'' +
|
"title='" + getTitle() + ", " +
|
||||||
"text='" + getText() + '\'' +
|
"text='" + getText() + "'" +
|
||||||
"} ";
|
"} ";
|
||||||
}
|
}
|
||||||
return super.toString();
|
return super.toString();
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ public class IntentExtras implements Serializable {
|
|||||||
|
|
||||||
private IntentExtras(int id) {
|
private IntentExtras(int id) {
|
||||||
mMap = extraStore.get(id);
|
mMap = extraStore.get(id);
|
||||||
|
extraStore.remove(id);
|
||||||
mMaxId = id;
|
mMaxId = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.stardust.util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Stardust on 2017/11/26.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class Nath {
|
||||||
|
|
||||||
|
public static int min(int... ints) {
|
||||||
|
int min = ints[0];
|
||||||
|
for (int i = 1; i < ints.length; i++) {
|
||||||
|
min = ints[i] < min ? ints[i] : min;
|
||||||
|
}
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user