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

@@ -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();
}
}
}