add screen capture and color searching
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
64
autojs/src/main/assets/modules/__images__.js
Normal file
64
autojs/src/main/assets/modules/__images__.js
Normal 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;
|
||||
}
|
||||
@@ -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){
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user