6.0.1 - Fix - 修复与 VSCode 保持连接时控制台打印日志异常
This commit is contained in:
@@ -112,7 +112,7 @@ public class AutoJs extends com.stardust.autojs.AutoJs {
|
|||||||
@Override
|
@Override
|
||||||
public String println(int level, CharSequence charSequence) {
|
public String println(int level, CharSequence charSequence) {
|
||||||
String log = super.println(level, charSequence);
|
String log = super.println(level, charSequence);
|
||||||
DevPluginService.getInstance().print(log);
|
new Thread(() -> DevPluginService.getInstance().print(log)).start();
|
||||||
return log;
|
return log;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,21 @@
|
|||||||
|
// noinspection NpmUsedModulesInstalled
|
||||||
|
|
||||||
|
const Point = org.opencv.core.Point;
|
||||||
|
const Rect = org.opencv.core.Rect;
|
||||||
|
const Scalar = org.opencv.core.Scalar;
|
||||||
|
const Size = org.opencv.core.Size;
|
||||||
|
const Core = org.opencv.core.Core;
|
||||||
|
const Imgproc = org.opencv.imgproc.Imgproc;
|
||||||
|
const Mat = com.stardust.autojs.core.opencv.Mat;
|
||||||
|
const Images = com.stardust.autojs.runtime.api.Images;
|
||||||
|
|
||||||
|
const DEF_COLOR_THRESHOLD = 4;
|
||||||
|
|
||||||
module.exports = function (runtime, scope) {
|
module.exports = function (runtime, scope) {
|
||||||
const ResultAdapter = require("result_adapter");
|
|
||||||
|
|
||||||
const MatchingResult = (function () {
|
const ResultAdapter = require('result_adapter');
|
||||||
|
|
||||||
|
const MatchingResult = (function $iiFe() {
|
||||||
let comparators = {
|
let comparators = {
|
||||||
'left': (l, r) => l.point.x - r.point.x,
|
'left': (l, r) => l.point.x - r.point.x,
|
||||||
'top': (l, r) => l.point.y - r.point.y,
|
'top': (l, r) => l.point.y - r.point.y,
|
||||||
@@ -14,38 +27,30 @@ module.exports = function (runtime, scope) {
|
|||||||
if (Array.isArray(list)) {
|
if (Array.isArray(list)) {
|
||||||
this.matches = list;
|
this.matches = list;
|
||||||
} else {
|
} else {
|
||||||
this.matches = runtime.bridges.bridges.toArray(list);
|
this.matches = runtime.bridges.getBridges().toArray(list);
|
||||||
}
|
}
|
||||||
this.__defineGetter__('points', () => {
|
Object.defineProperty(this, 'points', {
|
||||||
if (typeof (this.__points__) == 'undefined') {
|
get() {
|
||||||
this.__points__ = this.matches.map(m => m.point);
|
if (typeof this.__points__ === 'undefined') {
|
||||||
}
|
this.__points__ = this.matches.map(m => m.point);
|
||||||
return this.__points__;
|
}
|
||||||
|
return this.__points__;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
MatchingResult.prototype.first = function () {
|
MatchingResult.prototype.first = function () {
|
||||||
if (this.matches.length == 0) {
|
return this.matches.length ? this.matches[0] : null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return this.matches[0];
|
|
||||||
};
|
};
|
||||||
MatchingResult.prototype.last = function () {
|
MatchingResult.prototype.last = function () {
|
||||||
if (this.matches.length == 0) {
|
return this.matches.length ? this.matches[this.matches.length - 1] : null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return this.matches[this.matches.length - 1];
|
|
||||||
};
|
};
|
||||||
MatchingResult.prototype.findMax = function (cmp) {
|
MatchingResult.prototype.findMax = function (cmp) {
|
||||||
if (this.matches.length == 0) {
|
if (!this.matches.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
var target = this.matches[0];
|
let target = this.matches[0];
|
||||||
this.matches.forEach(m => {
|
this.matches.forEach(m => target = cmp(target, m) > 0 ? m : target);
|
||||||
if (cmp(target, m) > 0) {
|
|
||||||
target = m;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return target;
|
return target;
|
||||||
};
|
};
|
||||||
MatchingResult.prototype.leftmost = function () {
|
MatchingResult.prototype.leftmost = function () {
|
||||||
@@ -67,24 +72,21 @@ module.exports = function (runtime, scope) {
|
|||||||
return this.findMax((l, r) => r.similarity - l.similarity);
|
return this.findMax((l, r) => r.similarity - l.similarity);
|
||||||
};
|
};
|
||||||
MatchingResult.prototype.sortBy = function (cmp) {
|
MatchingResult.prototype.sortBy = function (cmp) {
|
||||||
var comparatorFn = null;
|
let comparatorFn = null;
|
||||||
if (typeof (cmp) == 'string') {
|
if (typeof cmp === 'string') {
|
||||||
cmp.split('-').forEach(direction => {
|
cmp.split('-').forEach((direction) => {
|
||||||
var buildInFn = comparators[direction];
|
let buildInFn = comparators[direction];
|
||||||
if (!buildInFn) {
|
if (!buildInFn) {
|
||||||
throw new Error('unknown direction \'' + direction + '\' in \'' + cmp + '\'');
|
throw new Error('unknown direction \'' + direction + '\' in \'' + cmp + '\'');
|
||||||
}
|
}
|
||||||
(function (fn) {
|
(function (fn) {
|
||||||
if (comparatorFn == null) {
|
if (comparatorFn === null) {
|
||||||
comparatorFn = fn;
|
comparatorFn = fn;
|
||||||
} else {
|
} else {
|
||||||
comparatorFn = (function (comparatorFn, fn) {
|
comparatorFn = (function (comparatorFn, fn) {
|
||||||
return function (l, r) {
|
return function (l, r) {
|
||||||
var cmpValue = comparatorFn(l, r);
|
let cmpValue = comparatorFn(l, r);
|
||||||
if (cmpValue == 0) {
|
return cmpValue === 0 ? fn(l, r) : cmpValue;
|
||||||
return fn(l, r);
|
|
||||||
}
|
|
||||||
return cmpValue;
|
|
||||||
};
|
};
|
||||||
})(comparatorFn, fn);
|
})(comparatorFn, fn);
|
||||||
}
|
}
|
||||||
@@ -93,465 +95,459 @@ module.exports = function (runtime, scope) {
|
|||||||
} else {
|
} else {
|
||||||
comparatorFn = cmp;
|
comparatorFn = cmp;
|
||||||
}
|
}
|
||||||
var clone = this.matches.slice();
|
let clone = this.matches.slice();
|
||||||
clone.sort(comparatorFn);
|
clone.sort(comparatorFn);
|
||||||
return new MatchingResult(clone);
|
return new MatchingResult(clone);
|
||||||
};
|
};
|
||||||
|
|
||||||
return MatchingResult;
|
return MatchingResult;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
function images() {
|
const rtImages = runtime.getImages();
|
||||||
|
|
||||||
|
const colorFinder = rtImages.colorFinder;
|
||||||
|
|
||||||
|
function getColorDetector(color, algorithm, threshold) {
|
||||||
|
switch (algorithm) {
|
||||||
|
case 'rgb':
|
||||||
|
return new com.stardust.autojs.core.image.ColorDetector.RGBDistanceDetector(color, threshold);
|
||||||
|
case 'equal':
|
||||||
|
return new com.stardust.autojs.core.image.ColorDetector.EqualityDetector(color);
|
||||||
|
case 'diff':
|
||||||
|
return new com.stardust.autojs.core.image.ColorDetector.DifferenceDetector(color, threshold);
|
||||||
|
case 'rgb+':
|
||||||
|
return new com.stardust.autojs.core.image.ColorDetector.WeightedRGBDistanceDetector(color, threshold);
|
||||||
|
case 'hs':
|
||||||
|
return new com.stardust.autojs.core.image.ColorDetector.HSDistanceDetector(color, threshold);
|
||||||
|
}
|
||||||
|
throw new Error('Unknown algorithm: ' + algorithm);
|
||||||
}
|
}
|
||||||
if (android.os.Build.VERSION.SDK_INT >= 21) {
|
|
||||||
util.__assignFunctions__(runtime.images, images, ['captureScreen', 'read', 'copy', 'load', 'clip', 'pixel'])
|
function toPointArray(points) {
|
||||||
|
let arr = [];
|
||||||
|
for (let i = 0; i < points.length; i++) {
|
||||||
|
arr.push(points[i]);
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
}
|
}
|
||||||
images.opencvImporter = JavaImporter(
|
|
||||||
org.opencv.core.Point,
|
|
||||||
org.opencv.core.Point3,
|
|
||||||
org.opencv.core.Rect,
|
|
||||||
org.opencv.core.Algorithm,
|
|
||||||
org.opencv.core.Scalar,
|
|
||||||
org.opencv.core.Size,
|
|
||||||
org.opencv.core.Core,
|
|
||||||
org.opencv.core.CvException,
|
|
||||||
org.opencv.core.CvType,
|
|
||||||
org.opencv.core.TermCriteria,
|
|
||||||
org.opencv.core.RotatedRect,
|
|
||||||
org.opencv.core.Range,
|
|
||||||
org.opencv.imgproc.Imgproc,
|
|
||||||
com.stardust.autojs.core.opencv
|
|
||||||
);
|
|
||||||
with (images.opencvImporter) {
|
|
||||||
const defaultColorThreshold = 4;
|
|
||||||
|
|
||||||
var colors = Object.create(runtime.colors);
|
function buildRegion(region, img) {
|
||||||
colors.alpha = function (color) {
|
if (region === undefined) {
|
||||||
color = parseColor(color);
|
region = [];
|
||||||
return color >>> 24;
|
|
||||||
}
|
}
|
||||||
colors.red = function (color) {
|
let x = region[0] === undefined ? 0 : region[0];
|
||||||
color = parseColor(color);
|
let y = region[1] === undefined ? 0 : region[1];
|
||||||
return (color >> 16) & 0xFF;
|
let width = region[2] === undefined ? img.getWidth() - x : region[2];
|
||||||
}
|
let height = region[3] === undefined ? (img.getHeight() - y) : region[3];
|
||||||
colors.green = function (color) {
|
let r = new Rect(x, y, width, height);
|
||||||
color = parseColor(color);
|
if (x < 0 || y < 0 || x + width > img.width || y + height > img.height) {
|
||||||
return (color >> 8) & 0xFF;
|
throw new Error('out of region: region = [' + [x, y, width, height] + '], image.size = [' + [img.width, img.height] + ']');
|
||||||
}
|
|
||||||
colors.blue = function (color) {
|
|
||||||
color = parseColor(color);
|
|
||||||
return color & 0xFF;
|
|
||||||
}
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
colors.isSimilar = function (c1, c2, threshold, algorithm) {
|
function parseColor(color) {
|
||||||
c1 = parseColor(c1);
|
if (typeof color === 'string') {
|
||||||
c2 = parseColor(c2);
|
color = colors.parseColor(color);
|
||||||
threshold = threshold == undefined ? 4 : threshold;
|
|
||||||
algorithm = algorithm == undefined ? "diff" : algorithm;
|
|
||||||
var colorDetector = getColorDetector(c1, algorithm, threshold);
|
|
||||||
return colorDetector.detectsColor(colors.red(c2), colors.green(c2), colors.blue(c2));
|
|
||||||
}
|
}
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
var javaImages = runtime.getImages();
|
function newSize(size) {
|
||||||
|
if (!Array.isArray(size)) {
|
||||||
|
size = [size, size];
|
||||||
|
}
|
||||||
|
if (size.length === 1) {
|
||||||
|
size = [size[0], size[0]];
|
||||||
|
}
|
||||||
|
return new Size(size[0], size[1]);
|
||||||
|
}
|
||||||
|
|
||||||
var colorFinder = javaImages.colorFinder;
|
function initIfNeeded() {
|
||||||
|
rtImages.initOpenCvIfNeeded();
|
||||||
|
}
|
||||||
|
|
||||||
images.requestScreenCapture = function (landscape) {
|
const colors = Object.create(runtime.colors, {
|
||||||
let ScreenCapturer = com.stardust.autojs.core.image.capture.ScreenCapturer;
|
alpha: {
|
||||||
var orientation = ScreenCapturer.ORIENTATION_AUTO;
|
value(color) {
|
||||||
if (landscape === true) {
|
color = parseColor(color);
|
||||||
orientation = ScreenCapturer.ORIENTATION_LANDSCAPE;
|
return color >>> 24;
|
||||||
|
},
|
||||||
|
enumerable: true,
|
||||||
|
},
|
||||||
|
red: {
|
||||||
|
value(color) {
|
||||||
|
color = parseColor(color);
|
||||||
|
return (color >> 16) & 0xFF;
|
||||||
|
},
|
||||||
|
enumerable: true,
|
||||||
|
},
|
||||||
|
green: {
|
||||||
|
value(color) {
|
||||||
|
color = parseColor(color);
|
||||||
|
return (color >> 8) & 0xFF;
|
||||||
|
},
|
||||||
|
enumerable: true,
|
||||||
|
},
|
||||||
|
blue: {
|
||||||
|
value(color) {
|
||||||
|
color = parseColor(color);
|
||||||
|
return color & 0xFF;
|
||||||
|
},
|
||||||
|
enumerable: true,
|
||||||
|
},
|
||||||
|
isSimilar: {
|
||||||
|
value(c1, c2, threshold, algorithm) {
|
||||||
|
c1 = parseColor(c1);
|
||||||
|
c2 = parseColor(c2);
|
||||||
|
threshold = threshold === undefined ? 4 : threshold;
|
||||||
|
algorithm = algorithm === undefined ? 'diff' : algorithm;
|
||||||
|
let colorDetector = getColorDetector(c1, algorithm, threshold);
|
||||||
|
return colorDetector.detectsColor(colors.red(c2), colors.green(c2), colors.blue(c2));
|
||||||
|
},
|
||||||
|
enumerable: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const images = () => void 0;
|
||||||
|
|
||||||
|
images.requestScreenCapture = function (landscape) {
|
||||||
|
let ScreenCapturer = com.stardust.autojs.core.image.capture.ScreenCapturer;
|
||||||
|
let orientation = ScreenCapturer.ORIENTATION_AUTO;
|
||||||
|
if (landscape === true) {
|
||||||
|
orientation = ScreenCapturer.ORIENTATION_LANDSCAPE;
|
||||||
|
}
|
||||||
|
if (landscape === false) {
|
||||||
|
orientation = ScreenCapturer.ORIENTATION_PORTRAIT;
|
||||||
|
}
|
||||||
|
return ResultAdapter.wait(rtImages.requestScreenCapture(orientation));
|
||||||
|
};
|
||||||
|
|
||||||
|
images.save = function (img, path, format, quality) {
|
||||||
|
format = format || 'png';
|
||||||
|
quality = quality === undefined ? 100 : quality;
|
||||||
|
return rtImages.save(img, path, format, quality);
|
||||||
|
};
|
||||||
|
|
||||||
|
images.saveImage = function (img, path, format, quality) {
|
||||||
|
return images.save(img, path, format, quality);
|
||||||
|
};
|
||||||
|
|
||||||
|
images.grayscale = function (img, dstCn) {
|
||||||
|
return images.cvtColor(img, 'BGR2GRAY', dstCn);
|
||||||
|
};
|
||||||
|
|
||||||
|
images.threshold = function (img, threshold, maxVal, type) {
|
||||||
|
initIfNeeded();
|
||||||
|
let mat = new Mat();
|
||||||
|
type = type || 'BINARY';
|
||||||
|
type = Imgproc['THRESH_' + type];
|
||||||
|
Imgproc.threshold(img.mat, mat, threshold, maxVal, type);
|
||||||
|
return images.matToImage(mat);
|
||||||
|
};
|
||||||
|
|
||||||
|
images.inRange = function (img, lowerBound, upperBound) {
|
||||||
|
initIfNeeded();
|
||||||
|
let lb = new Scalar(colors.red(lowerBound), colors.green(lowerBound),
|
||||||
|
colors.blue(lowerBound), colors.alpha(lowerBound));
|
||||||
|
let ub = new Scalar(colors.red(upperBound), colors.green(upperBound),
|
||||||
|
colors.blue(upperBound), colors.alpha(lowerBound));
|
||||||
|
let bi = new Mat();
|
||||||
|
Core.inRange(img.mat, lb, ub, bi);
|
||||||
|
return images.matToImage(bi);
|
||||||
|
};
|
||||||
|
|
||||||
|
images.interval = function (img, color, threshold) {
|
||||||
|
initIfNeeded();
|
||||||
|
let lb = new Scalar(colors.red(color) - threshold, colors.green(color) - threshold,
|
||||||
|
colors.blue(color) - threshold, colors.alpha(color));
|
||||||
|
let ub = new Scalar(colors.red(color) + threshold, colors.green(color) + threshold,
|
||||||
|
colors.blue(color) + threshold, colors.alpha(color));
|
||||||
|
let bi = new Mat();
|
||||||
|
Core.inRange(img.mat, lb, ub, bi);
|
||||||
|
return images.matToImage(bi);
|
||||||
|
};
|
||||||
|
|
||||||
|
images.adaptiveThreshold = function (img, maxValue, adaptiveMethod, thresholdType, blockSize, C) {
|
||||||
|
initIfNeeded();
|
||||||
|
let mat = new Mat();
|
||||||
|
adaptiveMethod = Imgproc['ADAPTIVE_THRESH_' + adaptiveMethod];
|
||||||
|
thresholdType = Imgproc['THRESH_' + thresholdType];
|
||||||
|
Imgproc.adaptiveThreshold(img.mat, mat, maxValue, adaptiveMethod, thresholdType, blockSize, C);
|
||||||
|
return images.matToImage(mat);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
images.blur = function (img, size, point, type) {
|
||||||
|
initIfNeeded();
|
||||||
|
let mat = new Mat();
|
||||||
|
size = newSize(size);
|
||||||
|
type = Core['BORDER_' + (type || 'DEFAULT')];
|
||||||
|
if (point === undefined) {
|
||||||
|
Imgproc.blur(img.mat, mat, size);
|
||||||
|
} else {
|
||||||
|
Imgproc.blur(img.mat, mat, size, new Point(point[0], point[1]), type);
|
||||||
|
}
|
||||||
|
return images.matToImage(mat);
|
||||||
|
};
|
||||||
|
|
||||||
|
images.medianBlur = function (img, size) {
|
||||||
|
initIfNeeded();
|
||||||
|
let mat = new Mat();
|
||||||
|
Imgproc.medianBlur(img.mat, mat, size);
|
||||||
|
return images.matToImage(mat);
|
||||||
|
};
|
||||||
|
|
||||||
|
images.gaussianBlur = function (img, size, sigmaX, sigmaY, type) {
|
||||||
|
initIfNeeded();
|
||||||
|
let mat = new Mat();
|
||||||
|
size = newSize(size);
|
||||||
|
sigmaX = sigmaX === undefined ? 0 : sigmaX;
|
||||||
|
sigmaY = sigmaY === undefined ? 0 : sigmaY;
|
||||||
|
type = Core['BORDER_' + (type || 'DEFAULT')];
|
||||||
|
Imgproc.GaussianBlur(img.mat, mat, size, sigmaX, sigmaY, type);
|
||||||
|
return images.matToImage(mat);
|
||||||
|
};
|
||||||
|
|
||||||
|
images.cvtColor = function (img, code, dstCn) {
|
||||||
|
initIfNeeded();
|
||||||
|
let mat = new Mat();
|
||||||
|
code = Imgproc['COLOR_' + code];
|
||||||
|
if (dstCn === undefined) {
|
||||||
|
Imgproc.cvtColor(img.mat, mat, code);
|
||||||
|
} else {
|
||||||
|
Imgproc.cvtColor(img.mat, mat, code, dstCn);
|
||||||
|
}
|
||||||
|
return images.matToImage(mat);
|
||||||
|
};
|
||||||
|
|
||||||
|
images.findCircles = function (grayImg, options) {
|
||||||
|
initIfNeeded();
|
||||||
|
options = options || {};
|
||||||
|
let mat = options.region === undefined ? grayImg.mat : new Mat(grayImg.mat, buildRegion(options.region, grayImg));
|
||||||
|
let resultMat = new Mat();
|
||||||
|
let dp = options.dp === undefined ? 1 : options.dp;
|
||||||
|
let minDst = options.minDst === undefined ? grayImg.height / 8 : options.minDst;
|
||||||
|
let param1 = options.param1 === undefined ? 100 : options.param1;
|
||||||
|
let param2 = options.param2 === undefined ? 100 : options.param2;
|
||||||
|
let minRadius = options.minRadius === undefined ? 0 : options.minRadius;
|
||||||
|
let maxRadius = options.maxRadius === undefined ? 0 : options.maxRadius;
|
||||||
|
Imgproc.HoughCircles(mat, resultMat, Imgproc.CV_HOUGH_GRADIENT, dp, minDst, param1, param2, minRadius, maxRadius);
|
||||||
|
let result = [];
|
||||||
|
for (let i = 0; i < resultMat.rows(); i++) {
|
||||||
|
for (let j = 0; j < resultMat.cols(); j++) {
|
||||||
|
let d = resultMat.get(i, j);
|
||||||
|
result.push({
|
||||||
|
x: d[0],
|
||||||
|
y: d[1],
|
||||||
|
radius: d[2],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (landscape === false) {
|
|
||||||
orientation = ScreenCapturer.ORIENTATION_PORTRAIT;
|
|
||||||
}
|
|
||||||
return ResultAdapter.wait(javaImages.requestScreenCapture(orientation));
|
|
||||||
}
|
}
|
||||||
|
if (options.region !== undefined) {
|
||||||
images.save = function (img, path, format, quality) {
|
mat.release();
|
||||||
format = format || "png";
|
|
||||||
quality = quality == undefined ? 100 : quality;
|
|
||||||
return javaImages.save(img, path, format, quality);
|
|
||||||
}
|
}
|
||||||
|
resultMat.release();
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
images.saveImage = images.save;
|
images.resize = function (img, size, interpolation) {
|
||||||
|
initIfNeeded();
|
||||||
|
let mat = new Mat();
|
||||||
|
interpolation = Imgproc['INTER_' + (interpolation || 'LINEAR')];
|
||||||
|
Imgproc.resize(img.mat, mat, newSize(size), 0, 0, interpolation);
|
||||||
|
return images.matToImage(mat);
|
||||||
|
};
|
||||||
|
|
||||||
images.grayscale = function (img, dstCn) {
|
images.scale = function (img, fx, fy, interpolation) {
|
||||||
return images.cvtColor(img, "BGR2GRAY", dstCn);
|
initIfNeeded();
|
||||||
|
let mat = new Mat();
|
||||||
|
interpolation = Imgproc['INTER_' + (interpolation || 'LINEAR')];
|
||||||
|
Imgproc.resize(img.mat, mat, newSize([0, 0]), fx, fy, interpolation);
|
||||||
|
return images.matToImage(mat);
|
||||||
|
};
|
||||||
|
|
||||||
|
images.rotate = function (img, degree, x, y) {
|
||||||
|
initIfNeeded();
|
||||||
|
if (x === undefined) {
|
||||||
|
x = img.width / 2;
|
||||||
}
|
}
|
||||||
|
if (y === undefined) {
|
||||||
images.threshold = function (img, threshold, maxVal, type) {
|
y = img.height / 2;
|
||||||
initIfNeeded();
|
|
||||||
var mat = new Mat();
|
|
||||||
type = type || "BINARY";
|
|
||||||
type = Imgproc["THRESH_" + type];
|
|
||||||
Imgproc.threshold(img.mat, mat, threshold, maxVal, type);
|
|
||||||
return images.matToImage(mat);
|
|
||||||
}
|
}
|
||||||
|
return rtImages.rotate(img, x, y, degree);
|
||||||
|
};
|
||||||
|
|
||||||
images.inRange = function (img, lowerBound, upperBound) {
|
images.concat = function (img1, img2, direction) {
|
||||||
initIfNeeded();
|
initIfNeeded();
|
||||||
var lb = new Scalar(colors.red(lowerBound), colors.green(lowerBound),
|
direction = direction || 'right';
|
||||||
colors.blue(lowerBound), colors.alpha(lowerBound));
|
return Images.concat(img1, img2, android.view.Gravity[direction.toUpperCase()]);
|
||||||
var ub = new Scalar(colors.red(upperBound), colors.green(upperBound),
|
};
|
||||||
colors.blue(upperBound), colors.alpha(lowerBound))
|
|
||||||
var bi = new Mat();
|
|
||||||
Core.inRange(img.mat, lb, ub, bi);
|
|
||||||
return images.matToImage(bi);
|
|
||||||
}
|
|
||||||
|
|
||||||
images.interval = function (img, color, threshold) {
|
images.detectsColor = function (img, color, x, y, threshold, algorithm) {
|
||||||
initIfNeeded();
|
initIfNeeded();
|
||||||
var lb = new Scalar(colors.red(color) - threshold, colors.green(color) - threshold,
|
color = parseColor(color);
|
||||||
colors.blue(color) - threshold, colors.alpha(color));
|
algorithm = algorithm || 'diff';
|
||||||
var ub = new Scalar(colors.red(color) + threshold, colors.green(color) + threshold,
|
threshold = threshold || DEF_COLOR_THRESHOLD;
|
||||||
colors.blue(color) + threshold, colors.alpha(color));
|
let colorDetector = getColorDetector(color, algorithm, threshold);
|
||||||
var bi = new Mat();
|
let pixel = images.pixel(img, x, y);
|
||||||
Core.inRange(img.mat, lb, ub, bi);
|
return colorDetector.detectsColor(colors.red(pixel), colors.green(pixel), colors.blue(pixel));
|
||||||
return images.matToImage(bi);
|
};
|
||||||
}
|
|
||||||
|
|
||||||
images.adaptiveThreshold = function (img, maxValue, adaptiveMethod, thresholdType, blockSize, C) {
|
images.findColor = function (img, color, options) {
|
||||||
initIfNeeded();
|
initIfNeeded();
|
||||||
var mat = new Mat();
|
color = parseColor(color);
|
||||||
adaptiveMethod = Imgproc["ADAPTIVE_THRESH_" + adaptiveMethod];
|
options = options || {};
|
||||||
thresholdType = Imgproc["THRESH_" + thresholdType];
|
let region = options.region || [];
|
||||||
Imgproc.adaptiveThreshold(img.mat, mat, maxValue, adaptiveMethod, thresholdType, blockSize, C);
|
let threshold = function $iiFe() {
|
||||||
return images.matToImage(mat);
|
|
||||||
|
|
||||||
}
|
|
||||||
images.blur = function (img, size, point, type) {
|
|
||||||
initIfNeeded();
|
|
||||||
var mat = new Mat();
|
|
||||||
size = newSize(size);
|
|
||||||
type = Core["BORDER_" + (type || "DEFAULT")];
|
|
||||||
if (point == undefined) {
|
|
||||||
Imgproc.blur(img.mat, mat, size);
|
|
||||||
} else {
|
|
||||||
Imgproc.blur(img.mat, mat, size, new Point(point[0], point[1]), type);
|
|
||||||
}
|
|
||||||
return images.matToImage(mat);
|
|
||||||
}
|
|
||||||
|
|
||||||
images.medianBlur = function (img, size) {
|
|
||||||
initIfNeeded();
|
|
||||||
var mat = new Mat();
|
|
||||||
Imgproc.medianBlur(img.mat, mat, size);
|
|
||||||
return images.matToImage(mat);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
images.gaussianBlur = function (img, size, sigmaX, sigmaY, type) {
|
|
||||||
initIfNeeded();
|
|
||||||
var mat = new Mat();
|
|
||||||
size = newSize(size);
|
|
||||||
sigmaX = sigmaX == undefined ? 0 : sigmaX;
|
|
||||||
sigmaY = sigmaY == undefined ? 0 : sigmaY;
|
|
||||||
type = Core["BORDER_" + (type || "DEFAULT")];
|
|
||||||
Imgproc.GaussianBlur(img.mat, mat, size, sigmaX, sigmaY, type);
|
|
||||||
return images.matToImage(mat);
|
|
||||||
}
|
|
||||||
|
|
||||||
images.cvtColor = function (img, code, dstCn) {
|
|
||||||
initIfNeeded();
|
|
||||||
var mat = new Mat();
|
|
||||||
code = Imgproc["COLOR_" + code];
|
|
||||||
if (dstCn == undefined) {
|
|
||||||
Imgproc.cvtColor(img.mat, mat, code);
|
|
||||||
} else {
|
|
||||||
Imgproc.cvtColor(img.mat, mat, code, dstCn);
|
|
||||||
}
|
|
||||||
return images.matToImage(mat);
|
|
||||||
}
|
|
||||||
|
|
||||||
images.findCircles = function (grayImg, options) {
|
|
||||||
initIfNeeded();
|
|
||||||
options = options || {};
|
|
||||||
var mat = options.region == undefined ? grayImg.mat : new Mat(grayImg.mat, buildRegion(options.region, grayImg));
|
|
||||||
var resultMat = new Mat()
|
|
||||||
var dp = options.dp == undefined ? 1 : options.dp;
|
|
||||||
var minDst = options.minDst == undefined ? grayImg.height / 8 : options.minDst;
|
|
||||||
var param1 = options.param1 == undefined ? 100 : options.param1;
|
|
||||||
var param2 = options.param2 == undefined ? 100 : options.param2;
|
|
||||||
var minRadius = options.minRadius == undefined ? 0 : options.minRadius;
|
|
||||||
var maxRadius = options.maxRadius == undefined ? 0 : options.maxRadius;
|
|
||||||
Imgproc.HoughCircles(mat, resultMat, Imgproc.CV_HOUGH_GRADIENT, dp, minDst, param1, param2, minRadius, maxRadius);
|
|
||||||
var result = [];
|
|
||||||
for (var i = 0; i < resultMat.rows(); i++) {
|
|
||||||
for (var j = 0; j < resultMat.cols(); j++) {
|
|
||||||
var d = resultMat.get(i, j);
|
|
||||||
result.push({
|
|
||||||
x: d[0],
|
|
||||||
y: d[1],
|
|
||||||
radius: d[2]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (options.region != undefined) {
|
|
||||||
mat.release();
|
|
||||||
}
|
|
||||||
resultMat.release();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
images.resize = function (img, size, interpolation) {
|
|
||||||
initIfNeeded();
|
|
||||||
var mat = new Mat();
|
|
||||||
interpolation = Imgproc["INTER_" + (interpolation || "LINEAR")];
|
|
||||||
Imgproc.resize(img.mat, mat, newSize(size), 0, 0, interpolation);
|
|
||||||
return images.matToImage(mat);
|
|
||||||
}
|
|
||||||
|
|
||||||
images.scale = function (img, fx, fy, interpolation) {
|
|
||||||
initIfNeeded();
|
|
||||||
var mat = new Mat();
|
|
||||||
interpolation = Imgproc["INTER_" + (interpolation || "LINEAR")];
|
|
||||||
Imgproc.resize(img.mat, mat, newSize([0, 0]), fx, fy, interpolation);
|
|
||||||
return images.matToImage(mat);
|
|
||||||
}
|
|
||||||
|
|
||||||
images.rotate = function (img, degree, x, y) {
|
|
||||||
initIfNeeded();
|
|
||||||
if (x == undefined) {
|
|
||||||
x = img.width / 2;
|
|
||||||
}
|
|
||||||
if (y == undefined) {
|
|
||||||
y = img.height / 2;
|
|
||||||
}
|
|
||||||
return javaImages.rotate(img, x, y, degree);
|
|
||||||
}
|
|
||||||
|
|
||||||
images.concat = function (img1, img2, direction) {
|
|
||||||
initIfNeeded();
|
|
||||||
direction = direction || "right";
|
|
||||||
return javaImages.concat(img1, img2, android.view.Gravity[direction.toUpperCase()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
images.detectsColor = function (img, color, x, y, threshold, algorithm) {
|
|
||||||
initIfNeeded();
|
|
||||||
color = parseColor(color);
|
|
||||||
algorithm = algorithm || "diff";
|
|
||||||
threshold = threshold || defaultColorThreshold;
|
|
||||||
var colorDetector = getColorDetector(color, algorithm, threshold);
|
|
||||||
var pixel = images.pixel(img, x, y);
|
|
||||||
return colorDetector.detectsColor(colors.red(pixel), colors.green(pixel), colors.blue(pixel));
|
|
||||||
}
|
|
||||||
|
|
||||||
images.findColor = function (img, color, options) {
|
|
||||||
initIfNeeded();
|
|
||||||
color = parseColor(color);
|
|
||||||
options = options || {};
|
|
||||||
var region = options.region || [];
|
|
||||||
if (options.similarity) {
|
if (options.similarity) {
|
||||||
var threshold = parseInt(255 * (1 - options.similarity));
|
return parseInt(255 * (1 - options.similarity));
|
||||||
} else {
|
|
||||||
var threshold = options.threshold || defaultColorThreshold;
|
|
||||||
}
|
}
|
||||||
if (options.region) {
|
return options.threshold || DEF_COLOR_THRESHOLD;
|
||||||
return colorFinder.findColor(img, color, threshold, buildRegion(options.region, img));
|
}();
|
||||||
} else {
|
|
||||||
return colorFinder.findColor(img, color, threshold, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
images.findColorInRegion = function (img, color, x, y, width, height, threshold) {
|
if (options.region) {
|
||||||
return findColor(img, color, {
|
return colorFinder.findColor(img, color, threshold, buildRegion(region, img));
|
||||||
region: [x, y, width, height],
|
} else {
|
||||||
threshold: threshold
|
return colorFinder.findColor(img, color, threshold, null);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
images.findColorEquals = function (img, color, x, y, width, height) {
|
images.findColorInRegion = function (img, color, x, y, width, height, threshold) {
|
||||||
return findColor(img, color, {
|
return findColor(img, color, {
|
||||||
region: [x, y, width, height],
|
region: [x, y, width, height],
|
||||||
threshold: 0
|
threshold: threshold,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
images.findAllPointsForColor = function (img, color, options) {
|
images.findColorEquals = function (img, color, x, y, width, height) {
|
||||||
initIfNeeded();
|
return findColor(img, color, {
|
||||||
color = parseColor(color);
|
region: [x, y, width, height],
|
||||||
options = options || {};
|
threshold: 0,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
images.findAllPointsForColor = function (img, color, options) {
|
||||||
|
initIfNeeded();
|
||||||
|
color = parseColor(color);
|
||||||
|
options = options || {};
|
||||||
|
let threshold = function $iiFe() {
|
||||||
if (options.similarity) {
|
if (options.similarity) {
|
||||||
var threshold = parseInt(255 * (1 - options.similarity));
|
return parseInt(255 * (1 - options.similarity));
|
||||||
} else {
|
|
||||||
var threshold = options.threshold || defaultColorThreshold;
|
|
||||||
}
|
|
||||||
if (options.region) {
|
|
||||||
return toPointArray(colorFinder.findAllPointsForColor(img, color, threshold, buildRegion(options.region, img)));
|
|
||||||
} else {
|
|
||||||
return toPointArray(colorFinder.findAllPointsForColor(img, color, threshold, null));
|
|
||||||
}
|
}
|
||||||
|
return options.threshold || DEF_COLOR_THRESHOLD;
|
||||||
|
}();
|
||||||
|
if (options.region) {
|
||||||
|
return toPointArray(colorFinder.findAllPointsForColor(img, color, threshold, buildRegion(options.region, img)));
|
||||||
|
} else {
|
||||||
|
return toPointArray(colorFinder.findAllPointsForColor(img, color, threshold, null));
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
images.findMultiColors = function (img, firstColor, paths, options) {
|
images.findMultiColors = function (img, firstColor, paths, options) {
|
||||||
initIfNeeded();
|
initIfNeeded();
|
||||||
options = options || {};
|
options = options || {};
|
||||||
firstColor = parseColor(firstColor);
|
firstColor = parseColor(firstColor);
|
||||||
var list = java.lang.reflect.Array.newInstance(java.lang.Integer.TYPE, paths.length * 3);
|
let list = java.lang.reflect.Array.newInstance(java.lang.Integer.TYPE, paths.length * 3);
|
||||||
for (var i = 0; i < paths.length; i++) {
|
for (let i = 0; i < paths.length; i++) {
|
||||||
var p = paths[i];
|
let p = paths[i];
|
||||||
list[i * 3] = p[0];
|
list[i * 3] = p[0];
|
||||||
list[i * 3 + 1] = p[1];
|
list[i * 3 + 1] = p[1];
|
||||||
list[i * 3 + 2] = parseColor(p[2]);
|
list[i * 3 + 2] = parseColor(p[2]);
|
||||||
}
|
|
||||||
var region = options.region ? buildRegion(options.region, img) : null;
|
|
||||||
var threshold = options.threshold === undefined ? defaultColorThreshold : options.threshold;
|
|
||||||
return colorFinder.findMultiColors(img, firstColor, threshold, region, list);
|
|
||||||
}
|
}
|
||||||
|
let region = options.region ? buildRegion(options.region, img) : null;
|
||||||
|
let threshold = options.threshold === undefined ? DEF_COLOR_THRESHOLD : options.threshold;
|
||||||
|
return colorFinder.findMultiColors(img, firstColor, threshold, region, list);
|
||||||
|
};
|
||||||
|
|
||||||
images.findImage = function (img, template, options) {
|
images.findImage = function (img, template, options) {
|
||||||
initIfNeeded();
|
initIfNeeded();
|
||||||
options = options || {};
|
options = options || {};
|
||||||
var threshold = options.threshold || 0.9;
|
let threshold = options.threshold || 0.9;
|
||||||
var maxLevel = -1;
|
let maxLevel = -1;
|
||||||
if (typeof (options.level) == 'number') {
|
if (typeof options.level === 'number') {
|
||||||
maxLevel = options.level;
|
maxLevel = options.level;
|
||||||
}
|
|
||||||
var weakThreshold = options.weakThreshold || 0.6;
|
|
||||||
if (options.region) {
|
|
||||||
return javaImages.findImage(img, template, weakThreshold, threshold, buildRegion(options.region, img), maxLevel);
|
|
||||||
} else {
|
|
||||||
return javaImages.findImage(img, template, weakThreshold, threshold, null, maxLevel);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
let weakThreshold = options.weakThreshold || 0.6;
|
||||||
images.matchTemplate = function (img, template, options) {
|
if (options.region) {
|
||||||
initIfNeeded();
|
return rtImages.findImage(img, template, weakThreshold, threshold, buildRegion(options.region, img), maxLevel);
|
||||||
options = options || {};
|
} else {
|
||||||
var threshold = options.threshold || 0.9;
|
return rtImages.findImage(img, template, weakThreshold, threshold, null, maxLevel);
|
||||||
var maxLevel = -1;
|
|
||||||
if (typeof (options.level) == 'number') {
|
|
||||||
maxLevel = options.level;
|
|
||||||
}
|
|
||||||
var max = options.max || 5;
|
|
||||||
var weakThreshold = options.weakThreshold || 0.6;
|
|
||||||
var result;
|
|
||||||
if (options.region) {
|
|
||||||
result = javaImages.matchTemplate(img, template, weakThreshold, threshold, buildRegion(options.region, img), maxLevel, max);
|
|
||||||
} else {
|
|
||||||
result = javaImages.matchTemplate(img, template, weakThreshold, threshold, null, maxLevel, max);
|
|
||||||
}
|
|
||||||
return new MatchingResult(result);
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
images.matchTemplate = function (img, template, options) {
|
||||||
|
initIfNeeded();
|
||||||
images.findImageInRegion = function (img, template, x, y, width, height, threshold) {
|
options = options || {};
|
||||||
return images.findImage(img, template, {
|
let threshold = options.threshold || 0.9;
|
||||||
region: [x, y, width, height],
|
let maxLevel = -1;
|
||||||
threshold: threshold
|
if (typeof options.level === 'number') {
|
||||||
});
|
maxLevel = options.level;
|
||||||
}
|
}
|
||||||
|
let max = options.max || 5;
|
||||||
images.fromBase64 = function (base64) {
|
let weakThreshold = options.weakThreshold || 0.6;
|
||||||
return javaImages.fromBase64(base64);
|
let result;
|
||||||
|
if (options.region) {
|
||||||
|
result = rtImages.matchTemplate(img, template, weakThreshold, threshold, buildRegion(options.region, img), maxLevel, max);
|
||||||
|
} else {
|
||||||
|
result = rtImages.matchTemplate(img, template, weakThreshold, threshold, null, maxLevel, max);
|
||||||
}
|
}
|
||||||
|
return new MatchingResult(result);
|
||||||
|
};
|
||||||
|
|
||||||
images.toBase64 = function (img, format, quality) {
|
images.findImageInRegion = function (img, template, x, y, width, height, threshold) {
|
||||||
format = format || "png";
|
return images.findImage(img, template, {
|
||||||
quality = quality == undefined ? 100 : quality;
|
region: [x, y, width, height],
|
||||||
return javaImages.toBase64(img, format, quality);
|
threshold: threshold,
|
||||||
}
|
});
|
||||||
|
};
|
||||||
|
|
||||||
images.fromBytes = function (bytes) {
|
images.fromBase64 = function (base64) {
|
||||||
return javaImages.fromBytes(bytes);
|
return rtImages.fromBase64(base64);
|
||||||
}
|
};
|
||||||
|
|
||||||
images.toBytes = function (img, format, quality) {
|
images.toBase64 = function (img, format, quality) {
|
||||||
format = format || "png";
|
format = format || 'png';
|
||||||
quality = quality == undefined ? 100 : quality;
|
quality = quality === undefined ? 100 : quality;
|
||||||
return javaImages.toBytes(img, format, quality);
|
return rtImages.toBase64(img, format, quality);
|
||||||
}
|
};
|
||||||
|
|
||||||
images.readPixels = function (path) {
|
images.fromBytes = function (bytes) {
|
||||||
var img = images.read(path);
|
return rtImages.fromBytes(bytes);
|
||||||
var bitmap = img.getBitmap();
|
};
|
||||||
var w = bitmap.getWidth();
|
|
||||||
var h = bitmap.getHeight();
|
|
||||||
var pixels = util.java.array("int", w * h);
|
|
||||||
bitmap.getPixels(pixels, 0, w, 0, 0, w, h);
|
|
||||||
img.recycle();
|
|
||||||
return {
|
|
||||||
data: pixels,
|
|
||||||
width: w,
|
|
||||||
height: h
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
images.matToImage = function (img) {
|
images.toBytes = function (img, format, quality) {
|
||||||
initIfNeeded();
|
format = format || 'png';
|
||||||
return Image.ofMat(img);
|
quality = quality === undefined ? 100 : quality;
|
||||||
}
|
return rtImages.toBytes(img, format, quality);
|
||||||
|
};
|
||||||
|
|
||||||
|
images.readPixels = function (path) {
|
||||||
|
let img = images.read(path);
|
||||||
|
let bitmap = img.getBitmap();
|
||||||
|
let w = bitmap.getWidth();
|
||||||
|
let h = bitmap.getHeight();
|
||||||
|
let pixels = util.java.array('int', w * h);
|
||||||
|
bitmap.getPixels(pixels, 0, w, 0, 0, w, h);
|
||||||
|
img.recycle();
|
||||||
|
return {
|
||||||
|
data: pixels,
|
||||||
|
width: w,
|
||||||
|
height: h,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
images.matToImage = function (img) {
|
||||||
|
initIfNeeded();
|
||||||
|
return Image.ofMat(img);
|
||||||
|
};
|
||||||
|
|
||||||
|
util.__assignFunctions__(rtImages, images, ['captureScreen', 'read', 'copy', 'load', 'clip', 'pixel']);
|
||||||
|
|
||||||
|
scope.__asGlobal__(images, ['requestScreenCapture', 'captureScreen', 'findImage', 'findImageInRegion', 'findColor', 'findColorInRegion', 'findColorEquals', 'findMultiColors']);
|
||||||
|
|
||||||
function getColorDetector(color, algorithm, threshold) {
|
scope.colors = colors;
|
||||||
switch (algorithm) {
|
|
||||||
case "rgb":
|
|
||||||
return new com.stardust.autojs.core.image.ColorDetector.RGBDistanceDetector(color, threshold);
|
|
||||||
case "equal":
|
|
||||||
return new com.stardust.autojs.core.image.ColorDetector.EqualityDetector(color);
|
|
||||||
case "diff":
|
|
||||||
return new com.stardust.autojs.core.image.ColorDetector.DifferenceDetector(color, threshold);
|
|
||||||
case "rgb+":
|
|
||||||
return new com.stardust.autojs.core.image.ColorDetector.WeightedRGBDistanceDetector(color, threshold);
|
|
||||||
case "hs":
|
|
||||||
return new com.stardust.autojs.core.image.ColorDetector.HSDistanceDetector(color, threshold);
|
|
||||||
}
|
|
||||||
throw new Error("Unknown algorithm: " + algorithm);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return images;
|
||||||
|
|
||||||
function toPointArray(points) {
|
};
|
||||||
var arr = [];
|
|
||||||
for (var i = 0; i < points.length; i++) {
|
|
||||||
arr.push(points[i]);
|
|
||||||
}
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildRegion(region, img) {
|
|
||||||
if (region == undefined) {
|
|
||||||
region = [];
|
|
||||||
}
|
|
||||||
var x = region[0] === undefined ? 0 : region[0];
|
|
||||||
var y = region[1] === undefined ? 0 : region[1];
|
|
||||||
var width = region[2] === undefined ? img.getWidth() - x : region[2];
|
|
||||||
var height = region[3] === undefined ? (img.getHeight() - y) : region[3];
|
|
||||||
var r = new org.opencv.core.Rect(x, y, width, height);
|
|
||||||
if (x < 0 || y < 0 || x + width > img.width || y + height > img.height) {
|
|
||||||
throw new Error("out of region: region = [" + [x, y, width, height] + "], image.size = [" + [img.width, img.height] + "]");
|
|
||||||
}
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseColor(color) {
|
|
||||||
if (typeof (color) == 'string') {
|
|
||||||
color = colors.parseColor(color);
|
|
||||||
}
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
|
|
||||||
function newSize(size) {
|
|
||||||
if (!Array.isArray(size)) {
|
|
||||||
size = [size, size];
|
|
||||||
}
|
|
||||||
if (size.length == 1) {
|
|
||||||
size = [size[0], size[0]];
|
|
||||||
}
|
|
||||||
return new Size(size[0], size[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function initIfNeeded() {
|
|
||||||
javaImages.initOpenCvIfNeeded();
|
|
||||||
}
|
|
||||||
|
|
||||||
scope.__asGlobal__(images, ['requestScreenCapture', 'captureScreen', 'findImage', 'findImageInRegion', 'findColor', 'findColorInRegion', 'findColorEquals', 'findMultiColors']);
|
|
||||||
|
|
||||||
scope.colors = colors;
|
|
||||||
|
|
||||||
return images;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"appVersionCode": 693,
|
"appVersionCode": 694,
|
||||||
"appVersionName": "6.0.1",
|
"appVersionName": "6.0.1",
|
||||||
"appSinceDate": "Jan 1, 2022",
|
"appSinceDate": "Jan 1, 2022",
|
||||||
"target": 28,
|
"target": 28,
|
||||||
|
|||||||
Reference in New Issue
Block a user