add screen capture and color searching

This commit is contained in:
hyb1996
2017-05-22 15:41:11 +08:00
parent b3fad201f7
commit 302879333a
45 changed files with 1547 additions and 92 deletions

View File

@@ -9,8 +9,8 @@ android {
applicationId "com.stardust.scriptdroid"
minSdkVersion 19
targetSdkVersion 23
versionCode 132
versionName "2.0.11 Alpha3"
versionCode 134
versionName "2.0.12 Alpha"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled true
ndk {

View File

@@ -84,6 +84,9 @@
<activity android:name=".ui.help.LocalWebViewActivity"/>
<activity android:name=".external.tasker.TaskerScriptEditActivity"/>
<activity android:name=".ui.edit.ViewSampleActivity"/>
<activity
android:name=".autojs.api.ScreenCaptureRequestActivity"
android:taskAffinity="com.stardust.scriptdroid.autojs.api.ScreenCaptureRequestActivity"/>
<activity
android:name=".ui.error.IssueReporterActivity"

View File

@@ -0,0 +1,3 @@
### requestScreenCapture(\[width, height\])
* width \<Number\> 可选参数

View File

@@ -5,7 +5,8 @@ var dialogs = {};
dialogs.rawInput = function(title, prefill){
prefill = prefill || "";
return String(__runtime__.dialogs.rawInput(title, prefill));
var s = __runtime__.dialogs.rawInput(title, prefill);
return s ? String(s) : null;
};
dialogs.input = function(title, prefill){

View File

@@ -0,0 +1,17 @@
if(!requestScreenCapture()){
toast("请求截图失败");
stop();
}
var img = captureScreen();
//0xffffff为白色
toastLog("开始找色");
//指定在位置(90, 220)宽高为900*1000的区域找色。
//0xff00cc是编辑器的深粉红色字体(字符串)颜色
var point = findColorInRegion(img, 0xff00cc, 90, 220, 900, 1000);
if(point){
toastLog("x = " + point.x + ", y = " + point.y);
}else{
toastLog("没有找到");
}

View File

@@ -0,0 +1,18 @@
if(!requestScreenCapture()){
toast("请求截图失败");
stop();
}
var img = captureScreen();
//0xffffff为白色
toastLog("开始找色");
//指定在位置(90, 220)宽高为900*1000的区域找色。
//0xff00cc是编辑器的深粉红色字体(字符串)颜色
var point = findColor(img, 0xff00cc, {
region: [90, 220, 900, 1000],
threads: 8
});
if(point){
toastLog("x = " + point.x + ", y = " + point.y);
}else{
toastLog("没有找到");
}

View File

@@ -0,0 +1,19 @@
//减少截图分辨率以提高速度
if(!requestScreenCapture(640, 960)){
toast("请求截图失败");
stop();
}
var img = captureScreen();
toastLog("开始找色");
//0x02b902为输入法绿色字体的颜色
var point = findColor(img, 0x02b902, {
//指定用8个线程找色
threads: 8
});
if(point){
toastLog("x = " + point.x + ", y = " + point.y);
}else{
toastLog("没有找到");
}

View File

@@ -0,0 +1,6 @@
if(!requestScreenCapture()){
toast("请求截图失败");
stop();
}
var img = captureScreen();
images.saveImage(img, "/sdcard/1.png");

View File

@@ -0,0 +1,6 @@
//指定截图分辨率为 640×960
if(!requestScreenCapture(640, 960)){
toast("请求截图失败");
stop();
}
captureScreen("/sdcard/1.png");

View File

@@ -0,0 +1,16 @@
if(!requestScreenCapture()){
toast("请求截图失败");
stop();
}
launchApp("QQ");
sleep(2000);
var img = captureScreen();
toastLog("开始找色");
var point = findColor(img, 0xf64d30);
if(point){
toastLog("x = " + point.x + ", y = " + point.y);
}else{
toastLog("没有找到");
}

View File

@@ -0,0 +1,22 @@
if(!requestScreenCapture()){
toast("请求截图失败");
stop();
}
var img = captureScreen();
//0xffffff为白色
toastLog("开始找色");
var point = findColor(img, 0xffffff, {
//指定算法为rgb+,更默认算法rgb更准确,但时间更久
algorithm: "rgb+",
//指定颜色临界值为16
threshold: 16,
//指定用8个线程找色
threads: 8
});
if(point){
toastLog("x = " + point.x + ", y = " + point.y);
}else{
toastLog("没有找到");
}

View File

@@ -0,0 +1,15 @@
if(!requestScreenCapture()){
toast("请求截图失败");
stop();
}
var img = captureScreen();
//0x9966ff为编辑器紫色字体的颜色
toastLog("开始找色");
var point = findColor(img, 0x9966ff);
if(point){
toastLog("x = " + point.x + ", y = " + point.y);
}else{
toastLog("没有找到");
}

View File

@@ -0,0 +1,16 @@
if(!requestScreenCapture()){
toast("请求截图失败");
stop();
}
var img = captureScreen();
toastLog("开始找色");
//0x006699为编辑器蓝色字体(var)的颜色
//找到颜色与0x006699完全相等的颜色
var point = findColorEquals(img, 0x006699);
if(point){
toastLog("x = " + point.x + ", y = " + point.y);
}else{
toastLog("没有找到");
}

View File

@@ -1,6 +1,7 @@
package com.stardust.scriptdroid;
import android.app.Activity;
import android.app.Application;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.multidex.MultiDexApplication;
@@ -79,26 +80,7 @@ public class App extends MultiDexApplication {
}
private void registerActivityLifecycleCallback() {
registerActivityLifecycleCallbacks(new SimpleActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
super.onActivityCreated(activity, savedInstanceState);
AutoJs.getInstance().getAppUtils().setCurrentActivity(activity);
}
@Override
public void onActivityPaused(Activity activity) {
AutoJs.getInstance().getAppUtils().setCurrentActivity(null);
}
@Override
public void onActivityResumed(Activity activity) {
ScreenMetrics.initIfNeeded(activity);
AutoJs.getInstance().getAppUtils().setCurrentActivity(activity);
}
});
}
public static String getResString(int id) {

View File

@@ -1,9 +1,15 @@
package com.stardust.scriptdroid.autojs;
import android.accessibilityservice.AccessibilityService;
import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import com.stardust.app.OnActivityResultDelegate;
import com.stardust.app.SimpleActivityLifecycleCallbacks;
import com.stardust.autojs.ScriptEngineService;
import com.stardust.autojs.ScriptEngineServiceBuilder;
import com.stardust.autojs.engine.RhinoJavaScriptEngineManager;
@@ -12,6 +18,7 @@ import com.stardust.autojs.runtime.AccessibilityBridge;
import com.stardust.autojs.runtime.ScriptStopException;
import com.stardust.autojs.runtime.api.AbstractShell;
import com.stardust.autojs.runtime.api.AppUtils;
import com.stardust.autojs.runtime.api.image.ScreenCaptureRequester;
import com.stardust.automator.AccessibilityEventCommandHost;
import com.stardust.automator.simple_action.SimpleActionPerformHost;
import com.stardust.pio.PFile;
@@ -19,8 +26,10 @@ import com.stardust.pio.UncheckedIOException;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.Pref;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.autojs.api.ScreenCaptureRequestActivity;
import com.stardust.scriptdroid.autojs.api.Shell;
import com.stardust.scriptdroid.ui.console.StardustConsole;
import com.stardust.util.ScreenMetrics;
import com.stardust.util.Supplier;
import com.stardust.util.UiHandler;
import com.stardust.view.accessibility.AccessibilityInfoProvider;
@@ -59,6 +68,23 @@ public class AutoJs implements AccessibilityBridge {
private final AccessibilityInfoProvider mAccessibilityInfoProvider;
private final UiHandler mUiHandler;
private final AppUtils mAppUtils;
private final ScreenCaptureRequester mScreenCaptureRequester = new ScreenCaptureRequester.AbstractScreenCaptureRequester() {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public void request() {
Activity activity = mAppUtils.getCurrentActivity();
if (activity instanceof OnActivityResultDelegate.DelegateHost) {
ScreenCaptureRequester requester = new ActivityScreenCaptureRequester(
((OnActivityResultDelegate.DelegateHost) activity).getOnActivityResultDelegateMediator(), activity);
requester.setOnActivityResultCallback(mCallback);
requester.request();
} else {
ScreenCaptureRequestActivity.request(mUiHandler.getContext(), mCallback);
}
}
};
private AutoJs(final Context context) {
@@ -75,8 +101,8 @@ public class AutoJs implements AccessibilityBridge {
@Override
public com.stardust.autojs.runtime.ScriptRuntime get() {
return new ScriptRuntime.Builder()
.setAppUtils(mAppUtils)
.setConsole(new StardustConsole(mUiHandler))
.setScreenCaptureRequester(mScreenCaptureRequester)
.setAccessibilityBridge(AutoJs.this)
.setUiHandler(mUiHandler)
.setShellSupplier(new Supplier<AbstractShell>() {
@@ -90,6 +116,28 @@ public class AutoJs implements AccessibilityBridge {
.build();
addAccessibilityServiceDelegates();
mScriptEngineService.registerGlobalScriptExecutionListener(new ScriptExecutionGlobalListener());
registerActivityLifecycleCallbacks();
}
private void registerActivityLifecycleCallbacks() {
App.getApp().registerActivityLifecycleCallbacks(new SimpleActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
ScreenMetrics.initIfNeeded(activity);
mAppUtils.setCurrentActivity(activity);
}
@Override
public void onActivityPaused(Activity activity) {
mAppUtils.setCurrentActivity(null);
}
@Override
public void onActivityResumed(Activity activity) {
mAppUtils.setCurrentActivity(activity);
}
});
}
private ScriptEngineManager createScriptEngineManager(Context context) {

View File

@@ -7,6 +7,7 @@ import android.view.ContextThemeWrapper;
import com.afollestad.materialdialogs.MaterialDialog;
import com.stardust.autojs.runtime.ScriptInterface;
import com.stardust.autojs.runtime.ScriptInterruptedException;
import com.stardust.autojs.runtime.api.AppUtils;
import com.stardust.concurrent.VolatileBox;
import com.stardust.scriptdroid.R;
@@ -35,7 +36,7 @@ public class Dialogs {
.input(null, prefill, true, result)
.title(title)
.show();
return result.blockedGet();
return result.blockedGetOrThrow(ScriptInterruptedException.class);
}
@ScriptInterface
@@ -49,7 +50,7 @@ public class Dialogs {
builder.content(content);
}
builder.show();
lock.blockedGet();
lock.blockedGetOrThrow(ScriptInterruptedException.class);
}
@ScriptInterface
@@ -65,7 +66,7 @@ public class Dialogs {
builder.content(content);
}
builder.show();
return result.blockedGet();
return result.blockedGetOrThrow(ScriptInterruptedException.class);
}
private Context getContext() {
@@ -83,7 +84,7 @@ public class Dialogs {
.title(title)
.items((CharSequence[]) items)
.show();
return result.blockedGet();
return result.blockedGetOrThrow(ScriptInterruptedException.class);
}
@ScriptInterface
@@ -95,7 +96,7 @@ public class Dialogs {
.positiveText(R.string.ok)
.items((CharSequence[]) items)
.show();
return result.blockedGet();
return result.blockedGetOrThrow(ScriptInterruptedException.class);
}
@ScriptInterface
@@ -107,7 +108,7 @@ public class Dialogs {
.positiveText(R.string.ok)
.items((CharSequence[]) items)
.show();
return ArrayUtils.unbox(result.blockedGet());
return ArrayUtils.unbox(result.blockedGetOrThrow(ScriptInterruptedException.class));
}

View File

@@ -0,0 +1,50 @@
package com.stardust.scriptdroid.autojs.api;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import com.stardust.app.OnActivityResultDelegate;
import com.stardust.autojs.runtime.api.image.ScreenCaptureRequester;
import com.stardust.scriptdroid.ui.BaseActivity;
/**
* Created by Stardust on 2017/5/22.
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class ScreenCaptureRequestActivity extends BaseActivity {
private static ScreenCaptureRequester.Callback sCallback;
public static void request(Context context, ScreenCaptureRequester.Callback callback) {
if (sCallback != null) {
return;
}
sCallback = callback;
context.startActivity(new Intent(context, ScreenCaptureRequestActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
private OnActivityResultDelegate.Mediator mOnActivityResultDelegateMediator = new OnActivityResultDelegate.Mediator();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ScreenCaptureRequester requester = new ScreenCaptureRequester.ActivityScreenCaptureRequester(mOnActivityResultDelegateMediator, this);
requester.setOnActivityResultCallback(sCallback);
requester.request();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
mOnActivityResultDelegateMediator.onActivityResult(requestCode, resultCode, data);
finish();
sCallback = null;
}
}

View File

@@ -0,0 +1,83 @@
package com.stardust.scriptdroid.service;
import android.app.Activity;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.ImageReader;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.Build;
import android.os.IBinder;
import android.support.annotation.RequiresApi;
import android.util.DisplayMetrics;
import android.view.WindowManager;
/**
* Created by Stardust on 2017/1/19.
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public class MediaProjectionScreenCaptureService extends Service {
private ImageReader mImageReader;
private int mScreenWidth;
private int mScreenHeight;
private int mScreenDensity;
private MediaProjection mMediaProjection;
private VirtualDisplay mVirtualDisplay;
public static Intent mResultData;
@Override
public void onCreate() {
if (mResultData == null) {
}
WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics metrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(metrics);
mScreenDensity = metrics.densityDpi;
mScreenWidth = metrics.widthPixels;
mScreenHeight = metrics.heightPixels;
createImageReader();
startVirtualDisplay();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
private void createImageReader() {
mImageReader = ImageReader.newInstance(mScreenWidth, mScreenHeight, PixelFormat.RGBA_8888, 1);
}
public void startVirtualDisplay() {
mMediaProjection = getMediaProjectionManager().getMediaProjection(Activity.RESULT_OK, mResultData);
mVirtualDisplay = mMediaProjection.createVirtualDisplay("screen-mirror",
mScreenWidth, mScreenHeight, mScreenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
mImageReader.getSurface(), null, null);
}
private MediaProjectionManager getMediaProjectionManager() {
return (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
}
@Override
public void onDestroy() {
if (mVirtualDisplay != null) {
mVirtualDisplay.release();
mVirtualDisplay = null;
}
if (mMediaProjection != null) {
mMediaProjection.stop();
mMediaProjection = null;
}
super.onDestroy();
}
}

View File

@@ -117,7 +117,6 @@ public class SQLiteStaticsStorage implements ScriptStaticsStorage {
private static class SQLiteOpenHelper extends android.database.sqlite.SQLiteOpenHelper {
SQLiteOpenHelper(Context context) {
super(context, DATABASE_NAME, null, VERSION);
}

View File

@@ -25,7 +25,7 @@ public class ImageSelector implements OnActivityResultDelegate {
private Activity mActivity;
private ImageSelectorCallback mCallback;
public ImageSelector(Activity activity, Mediator mediator, ImageSelectorCallback callback) {
public ImageSelector(Activity activity, OnActivityResultDelegate.Mediator mediator, ImageSelectorCallback callback) {
mediator.addDelegate(REQUEST_CODE, this);
mActivity = activity;
mCallback = callback;

View File

@@ -21,10 +21,10 @@ import com.jecelyin.editor.v2.core.widget.TextView;
import com.jecelyin.editor.v2.ui.EditorDelegate;
import com.jecelyin.editor.v2.view.EditorView;
import com.jecelyin.editor.v2.view.menu.MenuDef;
import com.stardust.app.OnActivityResultDelegate;
import com.stardust.autojs.execution.ScriptExecution;
import com.stardust.autojs.script.FileScriptSource;
import com.stardust.autojs.script.JsBeautifier;
import com.stardust.autojs.script.StringScriptSource;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.autojs.AutoJs;
import com.stardust.scriptdroid.script.ScriptFile;
@@ -49,7 +49,7 @@ import java.io.File;
* Created by Stardust on 2017/1/29.
*/
public class EditActivity extends Editor920Activity {
public class EditActivity extends Editor920Activity implements OnActivityResultDelegate.DelegateHost {
public static class InputMethodEnhanceBarBridge implements InputMethodEnhanceBar.EditTextBridge {
@@ -76,6 +76,8 @@ public class EditActivity extends Editor920Activity {
public TextView getEditText() {
return mTextView;
}
}
@@ -102,6 +104,7 @@ public class EditActivity extends Editor920Activity {
private EditorDelegate mEditorDelegate;
private SparseArray<ToolbarMenuItem> mMenuMap;
private boolean mReadOnly = false;
private OnActivityResultDelegate.Mediator mActivityResultMediator = new OnActivityResultDelegate.Mediator();
private BroadcastReceiver mOnRunFinishedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -359,6 +362,17 @@ public class EditActivity extends Editor920Activity {
.show();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
mActivityResultMediator.onActivityResult(requestCode, resultCode, data);
}
@Override
public OnActivityResultDelegate.Mediator getOnActivityResultDelegateMediator() {
return mActivityResultMediator;
}
@Override
public void doCommand(Command command) {
mEditorDelegate.doCommand(command);

View File

@@ -15,6 +15,7 @@ import com.jecelyin.editor.v2.common.Command;
import com.jecelyin.editor.v2.ui.EditorDelegate;
import com.jecelyin.editor.v2.view.EditorView;
import com.jecelyin.editor.v2.view.menu.MenuDef;
import com.stardust.app.OnActivityResultDelegate;
import com.stardust.autojs.execution.ScriptExecution;
import com.stardust.autojs.script.StringScriptSource;
import com.stardust.scriptdroid.R;
@@ -42,7 +43,7 @@ import static com.stardust.scriptdroid.script.Scripts.EXTRA_EXCEPTION_MESSAGE;
* Created by Stardust on 2017/4/29.
*/
public class ViewSampleActivity extends Editor920Activity {
public class ViewSampleActivity extends Editor920Activity implements OnActivityResultDelegate.DelegateHost {
public static void view(Context context, Sample sample) {
@@ -56,6 +57,7 @@ public class ViewSampleActivity extends Editor920Activity {
private ScriptExecution mScriptExecution;
private EditorDelegate mEditorDelegate;
private SparseArray<ToolbarMenuItem> mMenuMap;
private OnActivityResultDelegate.Mediator mMediator = new OnActivityResultDelegate.Mediator();
private BroadcastReceiver mOnRunFinishedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -185,4 +187,13 @@ public class ViewSampleActivity extends Editor920Activity {
}
}
@Override
public OnActivityResultDelegate.Mediator getOnActivityResultDelegateMediator() {
return mMediator;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
mMediator.onActivityResult(requestCode, resultCode, data);
}
}

View File

@@ -62,10 +62,11 @@ import org.greenrobot.eventbus.Subscribe;
import java.io.IOException;
public class MainActivity extends BaseActivity {
public class MainActivity extends BaseActivity implements OnActivityResultDelegate.DelegateHost {
public static final String MESSAGE_CLEAR_BACKGROUND_SETTINGS = "MESSAGE_CLEAR_BACKGROUND_SETTINGS";
private static final String LOG_TAG = "MainActivity";
private static final String EXTRA_ACTION = "EXTRA_ACTION";
private static final String ACTION_ON_ACTION_RECORD_STOPPED = "ACTION_ON_ACTION_RECORD_STOPPED";
@@ -99,6 +100,11 @@ public class MainActivity extends BaseActivity {
showAnnunciationIfNeeded();
}
@Override
protected void onStart() {
super.onStart();
}
private void showAnnunciationIfNeeded() {
if (!Pref.shouldShowAnnunciation()) {
return;
@@ -406,4 +412,8 @@ public class MainActivity extends BaseActivity {
context.startActivity(intent);
}
@Override
public OnActivityResultDelegate.Mediator getOnActivityResultDelegateMediator() {
return mActivityResultMediator;
}
}

View File

@@ -5,7 +5,6 @@
android:accessibilityFlags="flagIncludeNotImportantViews|flagReportViewIds|flagRetrieveInteractiveWindows|flagRequestEnhancedWebAccessibility|flagRequestFilterKeyEvents"
android:canPerformGestures="true"
android:canRequestEnhancedWebAccessibility="true"
android:canRequestTouchExplorationMode="true"
android:canRetrieveWindowContent="true"
android:description="@string/text_accessibility_service_description"
android:notificationTimeout="100"/>

View File

@@ -1,18 +1,13 @@
package com.stardust.autojs;
import android.app.Instrumentation;
import android.app.UiAutomation;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.support.test.uiautomator.UiDevice;
import android.view.InputEvent;
import android.test.ActivityInstrumentationTestCase2;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
/**
* Instrumentation test, which will execute on an Android device.

View File

@@ -27,7 +27,7 @@ require("__general__")(__runtime__, this);
(function(scope){
var modules = ['app', 'automator', 'console', 'io', 'selector', 'shell', 'web', 'ui'];
var modules = ['app', 'automator', 'console', 'io', 'selector', 'shell', 'web', 'ui', "images"];
var len = modules.length;
for(var i = 0; i < len; i++) {
var m = modules[i];

View File

@@ -13,7 +13,7 @@ module.exports = function(__runtime__, scope){
}
automator.click = function(){
if(arguments.length >= 2 && typeof(arguments[0]) == 'number' && typeof(arguments[1]) == 'number'){
if(arguments.length == 2 && typeof(arguments[0]) == 'number' && typeof(arguments[1]) == 'number'){
return __runtime__.automator.click(arguments[0], arguments[1]);
}
return performAction(function(target){
@@ -34,7 +34,7 @@ module.exports = function(__runtime__, scope){
automator.gesture = __runtime__.automator.gesture.bind(__runtime__.automator, 0);
automator.gestureAsync = __runtime__.automator.gestureAsync.bind(__runtime__.automator, 0);
automator.swipe = __runtime__.automator.swipe.bind(__runtime__.automator);
automator.gestures = function(){
automator.gestures = function(){
return __runtime__.automator.gestures(toStrokes(arguments));
}

View File

@@ -4,6 +4,11 @@ module.exports = function(__runtime__, scope){
__runtime__.toast(text);
}
scope.toastLog = function(text){
__runtime__.toast(text);
scope.log(text);
}
scope.sleep = function(millis){
__runtime__.sleep(millis);
}

View File

@@ -0,0 +1,64 @@
module.exports = function(__runtime__, scope){
var images = {};
var colorFinder = __runtime__.images.colorFinder;
images.requestScreenCapture = __runtime__.images.requestScreenCapture.bind(__runtime__.images);
images.captureScreen = __runtime__.images.captureScreen.bind(__runtime__.images);
images.saveImage = __runtime__.images.saveImage.bind(__runtime__.images);
images.findColor = function(img, color, options){
options = options || {};
var region = options.region || [];
x = region[0] || 0;
y = region[1] || 0;
width = region[2] || (img.getWidth() - x);
height = region[3] || (img.getHeight() - y);
threads = options.threads || 4;
if(options.threshold !== 0){
threshold = options.threshold || 8;
}
algorithm = options.algorithm || "rgb";
var rect = new android.graphics.Rect(x, y, width + x, height + y);
var colorDetector = getColorDetector(color, algorithm, threshold);
return colorFinder.findColorConcurrently(img, colorDetector, rect, threads);
}
images.findColorInRegion = function(img, color, x, y, width, height, threads, algorithm, threshold){
return findColor(img, color, {
region: [x, y, width, height],
algorithm: algorithm,
threshold: threshold,
threads: threads
});
}
images.findColorEquals = function(img, color, x, y, width, height, threads){
return findColor(img, color, {
region: [x, y, width, height],
algorithm: "equal",
threads: threads
});
}
function getColorDetector(color, algorithm, threshold){
switch(algorithm){
case "rgb":
return new com.stardust.autojs.runtime.api.image.ColorDetector.RGBDistanceDetector(color, threshold);
case "equal":
return new com.stardust.autojs.runtime.api.image.ColorDetector.EqualityDetector(color);
case "rgb+":
return new com.stardust.autojs.runtime.api.image.ColorDetector.WeightedRGBDistanceDetector(color, threshold);
case "hs":
return new com.stardust.autojs.runtime.api.image.ColorDetector.HSDistanceDetector(color, threshold);
}
throw new Error("Unknown algorithm: " + algorithm);
}
scope.__asGlobal__(images, ['requestScreenCapture', 'captureScreen', 'findColor', 'findColorInRegion', 'findColorEquals']);
return images;
}

View File

@@ -9,20 +9,7 @@ module.exports = function(__runtime__, scope){
scope[method] = (function(method) {
return function(){
var s = selector();
//这里不知道怎么写。尴尬。只能写成这样。
if(arguments.length == 0){
return s[method]();
}else if(arguments.length == 1){
return s[method](arguments[0]);
}else if(arguments.length == 2){
return s[method](arguments[0], arguments[1]);
}else if(arguments.length == 3){
return s[method](arguments[0], arguments[1], arguments[2]);
}else if(arguments.length == 4){
return s[method](arguments[0], arguments[1], arguments[2], arguments[3]);
}else{
return s[method].call(s, Array.prototype.slice.call(arguments));
}
return s[method].apply(s, Array.prototype.slice.call(arguments));
};
})(method);
}
@@ -33,4 +20,3 @@ module.exports = function(__runtime__, scope){
};
}

View File

@@ -1,10 +1,15 @@
package com.stardust.autojs.runtime;
import android.content.Context;
import android.support.annotation.CallSuper;
import com.stardust.autojs.engine.ScriptEngine;
import com.stardust.autojs.runtime.api.AbstractShell;
import com.stardust.autojs.runtime.api.AppUtils;
import com.stardust.autojs.runtime.api.Console;
import com.stardust.autojs.runtime.api.UiSelector;
import com.stardust.autojs.runtime.api.image.Images;
import com.stardust.autojs.runtime.api.image.ScreenCaptureRequester;
import com.stardust.autojs.runtime.api.ui.UI;
import com.stardust.autojs.runtime.simpleaction.SimpleActionAutomator;
import com.stardust.util.ScreenMetrics;
@@ -31,15 +36,16 @@ public abstract class AbstractScriptRuntime {
@ScriptVariable
public UI ui;
public AbstractScriptRuntime(AppUtils app, Console console, AccessibilityBridge bridge, UI ui) {
this.app = app;
@ScriptVariable
public Images images;
public AbstractScriptRuntime(Context context, Console console, AccessibilityBridge bridge, ScreenCaptureRequester screenCaptureRequester) {
this.app = new AppUtils(context);
this.console = console;
this.automator = new SimpleActionAutomator(bridge, this);
this.info = bridge.getInfoProvider();
this.ui = ui;
}
public AbstractScriptRuntime() {
this.ui = new UI(context);
images = new Images(context, this, screenCaptureRequester);
}
@ScriptInterface
@@ -80,5 +86,8 @@ public abstract class AbstractScriptRuntime {
public abstract void ensureAccessibilityServiceEnabled();
public abstract void onStop();
@CallSuper
public void onStop() {
images.releaseScreenCapturer();
}
}

View File

@@ -10,6 +10,7 @@ import com.stardust.autojs.runtime.api.AbstractShell;
import com.stardust.autojs.runtime.api.AppUtils;
import com.stardust.autojs.runtime.api.Console;
import com.stardust.autojs.runtime.api.UiSelector;
import com.stardust.autojs.runtime.api.image.ScreenCaptureRequester;
import com.stardust.concurrent.VolatileBox;
import com.stardust.autojs.runtime.api.ui.UI;
import com.stardust.pio.UncheckedIOException;
@@ -35,23 +36,16 @@ public class ScriptRuntime extends AbstractScriptRuntime {
private static final String TAG = "ScriptRuntime";
public static class Builder {
private AppUtils mAppUtils;
private UiHandler mUiHandler;
private Console mConsole;
private AccessibilityBridge mAccessibilityBridge;
private Supplier<AbstractShell> mShellSupplier;
public UI mUi;
private ScreenCaptureRequester mScreenCaptureRequester;
public Builder() {
}
public Builder setAppUtils(AppUtils appUtils) {
mAppUtils = appUtils;
return this;
}
public Builder setUiHandler(UiHandler uiHandler) {
mUiHandler = uiHandler;
return this;
@@ -72,8 +66,8 @@ public class ScriptRuntime extends AbstractScriptRuntime {
return this;
}
public Builder setUI(UI ui) {
mUi = ui;
public Builder setScreenCaptureRequester(ScreenCaptureRequester requester) {
mScreenCaptureRequester = requester;
return this;
}
@@ -92,13 +86,11 @@ public class ScriptRuntime extends AbstractScriptRuntime {
protected ScriptRuntime(Builder builder) {
super(builder.mAppUtils, builder.mConsole, builder.mAccessibilityBridge, builder.mUi);
super(builder.mUiHandler.getContext(), builder.mConsole, builder.mAccessibilityBridge, builder.mScreenCaptureRequester);
mAccessibilityBridge = builder.mAccessibilityBridge;
mUiHandler = builder.mUiHandler;
mShellSupplier = builder.mShellSupplier;
if (ui == null) {
ui = new UI(mUiHandler.getContext());
}
ui = new UI(mUiHandler.getContext());
automator.setScreenMetrics(mScreenMetrics);
}
@@ -142,7 +134,7 @@ public class ScriptRuntime extends AbstractScriptRuntime {
clip.setAndNotify(ClipboardUtil.getClipOrEmpty(mUiHandler.getContext()).toString());
}
});
return clip.blockedGet();
return clip.blockedGetOrThrow(ScriptInterruptedException.class);
}
@Override
@@ -193,7 +185,6 @@ public class ScriptRuntime extends AbstractScriptRuntime {
throw new ScriptInterruptedException();
}
@Override
public void setScreenMetrics(int width, int height) {
mScreenMetrics.setScreenMetrics(width, height);
@@ -210,6 +201,7 @@ public class ScriptRuntime extends AbstractScriptRuntime {
@Override
public void onStop() {
super.onStop();
if (mRootShell != null) {
mRootShell.exit();
mRootShell = null;

View File

@@ -69,6 +69,9 @@ public abstract class AbstractShell {
}
public void SetScreenMetrics(int width, int height) {
if (mScreenMetrics == null) {
mScreenMetrics = new ScreenMetrics();
}
mScreenMetrics.setScreenMetrics(width, height);
}

View File

@@ -31,7 +31,8 @@ public class AppUtils {
public boolean launchPackage(String packageName) {
try {
PackageManager packageManager = mContext.getPackageManager();
mContext.startActivity(packageManager.getLaunchIntentForPackage(packageName));
mContext.startActivity(packageManager.getLaunchIntentForPackage(packageName)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
return true;
} catch (Exception e) {
return false;

View File

@@ -0,0 +1,203 @@
package com.stardust.autojs.runtime.api.image;
/**
* Created by Stardust on 2017/5/20.
*/
public interface ColorDetector {
boolean detectsColor(int color);
abstract class AbstractColorDetector implements ColorDetector {
protected final int mColor;
public AbstractColorDetector(int color) {
mColor = color;
}
}
class EqualityDetector implements ColorDetector {
private final int mColor;
public EqualityDetector(int color) {
mColor = color & 0xffffff;
}
@Override
public boolean detectsColor(int color) {
return mColor == (color & 0xffffff);
}
}
class DifferenceDetector extends AbstractColorDetector {
private final int mThreshold;
public DifferenceDetector(int color, int threshold) {
super(color);
mThreshold = threshold;
}
@Override
public boolean detectsColor(int color) {
return Math.abs(mColor - color) <= mThreshold;
}
}
class RDistanceDetector extends AbstractColorDetector {
private final int mR;
private final int mThreshold;
public RDistanceDetector(int color, int threshold) {
super(color);
mThreshold = threshold;
mR = (color & 0xff0000) >> 16;
}
@Override
public boolean detectsColor(int color) {
int R = (color & 0xff0000) >> 16;
return Math.abs(mR - R) <= mThreshold;
}
}
class RGBDistanceDetector extends AbstractColorDetector {
private final int mThreshold;
private final int mR, mG, mB;
public RGBDistanceDetector(int color, int threshold) {
super(color);
mR = (color & 0xff0000) >> 16;
mG = (color & 0x00ff00) >> 8;
mB = color & 0xff;
mThreshold = threshold * threshold;
}
@Override
public boolean detectsColor(int color) {
int dR = ((color & 0xff0000) >> 16) - mR;
int dG = ((color & 0x00ff00) >> 8) - mG;
int dB = (color & 0xff) - 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;
}
@Override
public boolean detectsColor(int color) {
int R = (color & 0xff0000) >> 16;
int dR = R - mR;
int dG = ((color & 0x00ff00) >> 8) - mG;
int dB = (color & 0xff) - 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(color);
mThreshold = threshold;
}
@Override
public boolean detectsColor(int color) {
return Math.abs(mH - getH(color)) <= mThreshold;
}
private static int getH(int color) {
int R = (color & 0xff0000) >> 16;
int G = (color & 0x00ff00) >> 8;
int B = color & 0xff;
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(color);
mH = (int) (HS & 0xffffffffL);
mS = (int) ((HS >> 32) & 0xffffffffL);
mThreshold = threshold * threshold;
}
@Override
public boolean detectsColor(int color) {
long hs = getHS(color);
int dH = (int) (hs & 0xffffffffL) - mH;
int dS = (int) ((hs >> 32) & 0xffffffffL) - mS;
return dH * dH + dS * dS <= mThreshold;
}
private static long getHS(int color) {
int R = (color & 0xff0000) >> 16;
int G = (color & 0x00ff00) >> 8;
int B = color & 0xff;
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) / max;
return H & ((long) S << 32);
}
}
}

View File

@@ -0,0 +1,249 @@
package com.stardust.autojs.runtime.api.image;
import android.graphics.Point;
import android.graphics.Rect;
import android.media.Image;
import com.stardust.autojs.runtime.ScriptInterruptedException;
import com.stardust.concurrent.VolatileBox;
import com.stardust.util.ScreenMetrics;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Created by Stardust on 2017/5/18.
*/
public class ColorFinder {
private static ThreadPoolExecutor sThreadPoolExecutor = new ThreadPoolExecutor(4, 16, 5, TimeUnit.MINUTES, new SynchronousQueue<Runnable>());
static {
sThreadPoolExecutor.allowCoreThreadTimeOut(true);
}
private ThreadPoolExecutor mThreadPoolExecutor;
public ColorFinder(ThreadPoolExecutor threadPoolExecutor) {
mThreadPoolExecutor = threadPoolExecutor;
}
public ColorFinder() {
this(sThreadPoolExecutor);
}
public void prestartThreads() {
mThreadPoolExecutor.prestartAllCoreThreads();
}
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();
Point[] points = new Point[result.size()];
for (int i = 0; i < points.length; i++) {
points[i] = scalePoint(result.get(i), image.getWidth(), image.getHeight());
}
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;
if (ScreenMetrics.getDeviceScreenHeight() == height && ScreenMetrics.getDeviceScreenWidth() == height) {
return point;
}
point.set(ScreenMetrics.scaleX(point.x, width), ScreenMetrics.scaleY(point.y, height));
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();
while (!thread.isInterrupted() && iterator.hasNext()) {
int c = iterator.nextColor();
if (detector.detectsColor(c)) {
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();
while (mResultBox.isNull() && mColorIterator.hasNext() && !thread.isInterrupted()) {
int c = mColorIterator.nextColor();
if (mColorDetector.detectsColor(c)) {
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();
while (mColorIterator.hasNext() && !thread.isInterrupted()) {
int c = mColorIterator.nextColor();
if (mColorDetector.detectsColor(c)) {
mResult.add(new Point(mColorIterator.getX(), mColorIterator.getY()));
}
}
if (thread.isInterrupted()) {
throw new ScriptInterruptedException();
}
}
}
}

View File

@@ -0,0 +1,186 @@
package com.stardust.autojs.runtime.api.image;
import android.graphics.Rect;
import android.media.Image;
import android.util.Log;
import java.nio.ByteBuffer;
/**
* Created by Stardust on 2017/5/20.
*/
public interface ColorIterator {
boolean hasNext();
int nextColor();
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 int nextColor() {
if (mX == mWidth - 1) {
skip(mSkipPerRow);
mX = 0;
mY++;
} else {
mX++;
}
int c = (mByteBuffer.get() & 0xff) << 16;
c |= (mByteBuffer.get() & 0xff) << 8;
c |= (mByteBuffer.get() & 0xff);
c |= (mByteBuffer.get() & 0xff) << 24;
return c;
}
}
/**
* 中心螺旋。未完成。
*/
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 int nextColor() {
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;
}
}
return c;
}
@Override
public int getX() {
return 0;
}
@Override
public int getY() {
return 0;
}
}
}

View File

@@ -0,0 +1,88 @@
package com.stardust.autojs.runtime.api.image;
import android.graphics.Rect;
import android.media.Image;
import android.util.Log;
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;
}
}
}

View File

@@ -0,0 +1,140 @@
package com.stardust.autojs.runtime.api.image;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.media.Image;
import android.os.Build;
import android.support.annotation.RequiresApi;
import com.stardust.autojs.runtime.AbstractScriptRuntime;
import com.stardust.autojs.runtime.ScriptInterruptedException;
import com.stardust.autojs.runtime.ScriptVariable;
import com.stardust.concurrent.VolatileBox;
import com.stardust.pio.UncheckedIOException;
import com.stardust.util.ScreenMetrics;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
/**
* Created by Stardust on 2017/5/20.
*/
public class Images {
private AbstractScriptRuntime mScriptRuntime;
private ScreenCaptureRequester mScreenCaptureRequester;
private ScreenCapturer mScreenCapturer;
private Context mContext;
@ScriptVariable
public ColorFinder colorFinder = new ColorFinder();
public Images(Context context, AbstractScriptRuntime scriptRuntime, ScreenCaptureRequester screenCaptureRequester) {
mScriptRuntime = scriptRuntime;
mScreenCaptureRequester = screenCaptureRequester;
mContext = context;
}
public boolean requestScreenCapture(final int width, final int height) {
mScriptRuntime.requiresApi(21);
colorFinder.prestartThreads();
final VolatileBox<Boolean> requestResult = new VolatileBox<>();
mScreenCaptureRequester.setOnActivityResultCallback(new ScreenCaptureRequester.Callback() {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public void onRequestResult(int result, Intent data) {
if (result == Activity.RESULT_OK) {
mScreenCapturer = new ScreenCapturer(mContext, data, width, height);
requestResult.setAndNotify(true);
} else {
requestResult.setAndNotify(false);
}
}
});
mScreenCaptureRequester.request();
return requestResult.blockedGetOrThrow(ScriptInterruptedException.class);
}
public boolean requestScreenCapture() {
return requestScreenCapture(ScreenMetrics.getDeviceScreenWidth(), ScreenMetrics.getDeviceScreenHeight());
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public Image captureScreen() {
mScriptRuntime.requiresApi(21);
colorFinder.prestartThreads();
return mScreenCapturer.capture();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public boolean captureScreen(String path) {
mScriptRuntime.requiresApi(21);
Image image = mScreenCapturer.capture();
if (image != null) {
saveImage(image, path);
image.close();
return true;
}
return false;
}
public void saveImage(Image image, String path) {
Bitmap bitmap = toBitmap(image);
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 void saveBitmap(Bitmap bitmap, String path) {
try {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, new FileOutputStream(path));
} catch (FileNotFoundException e) {
throw new UncheckedIOException(e);
}
}
public void saveImage(Image image, String path, int width, int height) {
Bitmap bitmap = toBitmap(image);
if (width != bitmap.getWidth() || height != bitmap.getHeight()) {
bitmap = scaleBitmap(bitmap, width, height);
}
saveBitmap(bitmap, path);
bitmap.recycle();
}
public static Bitmap scaleBitmap(Bitmap origin, int newWidth, int newHeight) {
if (origin == null) {
return null;
}
int height = origin.getHeight();
int width = origin.getWidth();
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);
return Bitmap.createBitmap(origin, 0, 0, width, height, matrix, false);
}
public void releaseScreenCapturer() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && mScreenCapturer != null) {
mScreenCapturer.release();
}
}
}

View File

@@ -0,0 +1,64 @@
package com.stardust.autojs.runtime.api.image;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.media.projection.MediaProjectionManager;
import android.os.Build;
import android.preference.PreferenceManager;
import android.support.annotation.RequiresApi;
import com.stardust.app.OnActivityResultDelegate;
/**
* Created by Stardust on 2017/5/17.
*/
public interface ScreenCaptureRequester {
interface Callback {
void onRequestResult(int result, Intent data);
}
void request();
void setOnActivityResultCallback(Callback callback);
abstract class AbstractScreenCaptureRequester implements ScreenCaptureRequester {
protected Callback mCallback;
@Override
public void setOnActivityResultCallback(Callback callback) {
mCallback = callback;
}
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
class ActivityScreenCaptureRequester extends AbstractScreenCaptureRequester implements ScreenCaptureRequester, OnActivityResultDelegate {
private static final int REQUEST_CODE_MEDIA_PROJECTION = "Eating...Today is 17.5.20 yet...The 90 days、、、".hashCode() >> 16;
private OnActivityResultDelegate.Mediator mMediator;
private Activity mActivity;
public ActivityScreenCaptureRequester(Mediator mediator, Activity activity) {
mMediator = mediator;
mActivity = activity;
mMediator.addDelegate(REQUEST_CODE_MEDIA_PROJECTION, this);
}
@Override
public void request() {
mActivity.startActivityForResult(((MediaProjectionManager) mActivity.getSystemService(Context.MEDIA_PROJECTION_SERVICE)).createScreenCaptureIntent(), REQUEST_CODE_MEDIA_PROJECTION);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
mMediator.removeDelegate(this);
mCallback.onRequestResult(resultCode, data);
}
}
}

View File

@@ -0,0 +1,77 @@
package com.stardust.autojs.runtime.api.image;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.Image;
import android.media.ImageReader;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.Build;
import android.os.Looper;
import android.support.annotation.RequiresApi;
import com.stardust.util.ScreenMetrics;
/**
* Created by Stardust on 2017/5/17.
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public class ScreenCapturer {
private ImageReader mImageReader;
private MediaProjection mMediaProjection;
private VirtualDisplay mVirtualDisplay;
private final Object mImageLock = new Object();
public ScreenCapturer(Context context, Intent data, int screenWidth, int screenHeight, int screenDensity) {
MediaProjectionManager manager = (MediaProjectionManager) context.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
initVirtualDisplay(manager, data, screenWidth, screenHeight, screenDensity);
}
public ScreenCapturer(Context context, Intent data, int screenWidth, int screenHeight) {
this(context, data, screenWidth, screenHeight, ScreenMetrics.getDeviceScreenDensity());
}
public ScreenCapturer(Context context, Intent data) {
this(context, data, ScreenMetrics.getDeviceScreenWidth(), ScreenMetrics.getDeviceScreenHeight());
}
private void initVirtualDisplay(MediaProjectionManager manager, Intent data, int screenWidth, int screenHeight, int screenDensity) {
mImageReader = ImageReader.newInstance(screenWidth, screenHeight, PixelFormat.RGBA_8888, 1);
mMediaProjection = manager.getMediaProjection(Activity.RESULT_OK, data);
mVirtualDisplay = mMediaProjection.createVirtualDisplay("screen-mirror",
screenWidth, screenHeight, screenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
mImageReader.getSurface(), null, null);
}
public Image capture() {
Image image = mImageReader.acquireLatestImage();
if (image == null) {
Looper.prepare();
mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
Looper.myLooper().quit();
}
}, null);
Looper.loop();
image = mImageReader.acquireLatestImage();
}
return image;
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void release() {
if (mMediaProjection != null) {
mMediaProjection.stop();
}
if (mVirtualDisplay != null) {
mVirtualDisplay.release();
}
}
}

View File

@@ -1,5 +1,11 @@
package com.stardust.autojs;
import com.stardust.autojs.runtime.api.image.ColorDetector;
import org.junit.Test;
import java.util.Arrays;
/**
* Example local unit test, which will execute on the development machine (host).
*
@@ -8,4 +14,12 @@ package com.stardust.autojs;
public class ExampleUnitTest {
@Test
public void test() {
}
public boolean equals(int i, int j) {
return i == j;
}
}

View File

@@ -1,6 +1,7 @@
package com.stardust.app;
import android.content.Intent;
import android.support.annotation.NonNull;
import android.util.SparseArray;
import java.util.ArrayList;
@@ -12,11 +13,11 @@ import java.util.List;
public interface OnActivityResultDelegate {
void onActivityResult(int requestCode, int resultCode, Intent data);
interface DelegateHost {
Mediator getDelegateManger();
@NonNull
Mediator getOnActivityResultDelegateMediator();
}
class Mediator implements OnActivityResultDelegate {

View File

@@ -1,6 +1,8 @@
package com.stardust.concurrent;
import java.lang.reflect.Constructor;
/**
* Created by Stardust on 2017/5/8.
*/
@@ -25,6 +27,14 @@ public class VolatileBox<T> {
mValue = value;
}
public boolean isNull() {
return mValue == null;
}
public boolean notNull() {
return mValue != null;
}
public void setAndNotify(T value) {
mValue = value;
synchronized (this) {
@@ -43,4 +53,18 @@ public class VolatileBox<T> {
return mValue;
}
public T blockedGetOrThrow(Class<? extends RuntimeException> exception) {
synchronized (this) {
try {
this.wait();
} catch (InterruptedException e) {
try {
throw exception.newInstance();
} catch (InstantiationException | IllegalAccessException e1) {
throw new RuntimeException(e1);
}
}
}
return mValue;
}
}

View File

@@ -12,6 +12,7 @@ public class ScreenMetrics {
private static int deviceScreenHeight;
private static int deviceScreenWidth;
private static boolean initialized = false;
private static int deviceScreenDensity;
public static void initIfNeeded(Activity activity) {
if (!initialized) {
@@ -19,6 +20,7 @@ public class ScreenMetrics {
activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
deviceScreenHeight = metrics.heightPixels;
deviceScreenWidth = metrics.widthPixels;
deviceScreenDensity = metrics.densityDpi;
initialized = true;
}
}
@@ -31,10 +33,26 @@ public class ScreenMetrics {
return deviceScreenWidth;
}
public static int getDeviceScreenDensity() {
return deviceScreenDensity;
}
public static int scaleX(int x, int width) {
if (width == 0 || !initialized)
return x;
return x * deviceScreenWidth / width;
}
public static int scaleY(int y, int height) {
if (height == 0 || !initialized)
return y;
return y * deviceScreenHeight / height;
}
private int mScreenWidth;
private int mScreenHeight;
public void setScreenWidth(int screenWidth) {
mScreenWidth = screenWidth;
}
@@ -44,17 +62,14 @@ public class ScreenMetrics {
}
public int scaleX(int x) {
if (mScreenWidth == 0 || !initialized)
return x;
return x * deviceScreenWidth / mScreenWidth;
return scaleX(x, mScreenWidth);
}
public int scaleY(int y) {
if (mScreenHeight == 0 || !initialized)
return y;
return y * deviceScreenHeight / mScreenHeight;
return scaleY(y, mScreenHeight);
}
public void setScreenMetrics(int width, int height) {
mScreenWidth = width;
mScreenHeight = height;