6.7.0 - Alpha - 修复 images 模块相关方法出现异常时 oneShot 标记功能失效的问题 (issue #372)

This commit is contained in:
SuperMonster003
2025-06-10 19:35:15 +08:00
parent 06b8fa5b5c
commit 3677c6755f
7 changed files with 885 additions and 563 deletions

View File

@@ -1,13 +1,14 @@
{ {
"$data": { "$data": {
"v6.7.0": { "v6.7.0": {
"released_date": "2025/06/08", "released_date": "2025/06/10",
"feature": [ "feature": [
"zip 模块, 用于文件压缩与解压缩操作 (Ref to [Auto.js Pro](https://g.pro.autojs.org/)) (参阅 项目文档 > [Zip](https://docs.autojs6.com/#/zip))" "zip 模块, 用于文件压缩与解压缩操作 (Ref to [Auto.js Pro](https://g.pro.autojs.org/)) (参阅 项目文档 > [Zip](https://docs.autojs6.com/#/zip))"
], ],
"fix": [ "fix": [
"使用 XML 语法将 JavaScript 表达式作为属性值时, this 对象可能出现指向错误的问题", "使用 XML 语法将 JavaScript 表达式作为属性值时, this 对象可能出现指向错误的问题",
"调用 images.requestScreenCapture 时用户取消授权可能导致应用崩溃的问题", "调用 images.requestScreenCapture 时用户取消授权可能导致应用崩溃的问题",
"images 部分相关方法出现异常时 oneShot 标记功能失效的问题 _[`issue #372`](http://issues.autojs6.com/372)_",
"版本历史页面部分系统因字体差别导致统计数据显示不完整的问题" "版本历史页面部分系统因字体差别导致统计数据显示不完整的问题"
], ],
"improvement": [], "improvement": [],

View File

@@ -32,7 +32,7 @@ import java.util.concurrent.atomic.AtomicLong
* Transformed by SuperMonster003 on May 16, 2023. * Transformed by SuperMonster003 on May 16, 2023.
*/ */
// @Reference to Auto.js Pro 9.3.11 by SuperMonster003 on Dec 20, 2023. // @Reference to Auto.js Pro 9.3.11 by SuperMonster003 on Dec 20, 2023.
open class ImageWrapper : Recyclable, MonitorResource { open class ImageWrapper : Shootable<ImageWrapper>, MonitorResource {
private var mScriptRuntime: ScriptRuntime private var mScriptRuntime: ScriptRuntime
@@ -62,36 +62,44 @@ open class ImageWrapper : Recyclable, MonitorResource {
val size val size
get() = Size(mWidth.toDouble(), mHeight.toDouble()).also { ensureNotRecycled() } get() = Size(mWidth.toDouble(), mHeight.toDouble()).also { ensureNotRecycled() }
val bitmap by lazy { val bitmap: Bitmap
ensureNotRecycled() get() {
if (mBitmap == null) { ensureNotRecycled()
if (mMat != null) { if (mBitmap == null) {
mBitmap = createBitmap(mMat!!.width(), mMat!!.height()) val mat = mMat
Utils.matToBitmap(mMat, mBitmap) if (mat != null) {
} else { val bitmap = createBitmap(mat.width(), mat.height()).also { mBitmap = it }
mBitmap = mediaImage?.let { toBitmap(it) } Utils.matToBitmap(mat, bitmap)
} else {
val mediaImage = mediaImage
if (mediaImage != null) {
mBitmap = toBitmap(mediaImage)
}
}
} }
return mBitmap ?: throw Exception("Bitmap of ImageWrapper should never be null")
} }
return@lazy mBitmap ?: throw Exception("Bitmap of ImageWrapper should never be null")
}
val mat by lazy { val mat: Mat
ensureNotRecycled() get() {
if (mMat != null) { ensureNotRecycled()
return@lazy mMat!! val mat = mMat
if (mat != null) {
return mat
}
val bitmap = mBitmap
if (bitmap != null) {
val newMat = Mat().also { mMat = it }
Utils.bitmapToMat(bitmap, newMat)
return newMat
}
if (mediaImage != null) {
val plane = plane ?: throw AssertionError("Image plain is null")
plane.buffer.position(0)
return Mat(mHeight, mWidth, CvType.CV_8UC4, plane.buffer, plane.rowStride.toLong()).also { mMat = it }
}
throw AssertionError("Both bitmap and image are null")
} }
if (mBitmap != null) {
mMat = Mat()
Utils.bitmapToMat(mBitmap, mMat)
return@lazy mMat!!
}
if (mediaImage != null) {
val plane = plane ?: throw AssertionError("Image plain is null")
plane.buffer.position(0)
return@lazy Mat(mHeight, mWidth, CvType.CV_8UC4, plane.buffer, plane.rowStride.toLong()).also { mMat = it }
}
throw AssertionError("Both bitmap and image are null")
}
val bgrMat val bgrMat
get() = Mat().also { get() = Mat().also {
@@ -308,7 +316,7 @@ open class ImageWrapper : Recyclable, MonitorResource {
fun recycleAll() { fun recycleAll() {
imageList.forEach { imageList.forEach {
when (val o = it.get()) { when (val o = it.get()) {
is Recyclable -> o.recycle() is Shootable<*> -> o.recycle()
is Bitmap -> o.recycle() is Bitmap -> o.recycle()
is org.opencv.core.Mat -> OpenCVHelper.release(o) is org.opencv.core.Mat -> OpenCVHelper.release(o)
is Image -> o.close() is Image -> o.close()

View File

@@ -1,14 +1,14 @@
package org.autojs.autojs.core.image; package org.autojs.autojs.core.image;
public interface Recyclable { public interface Shootable<T> {
void recycle(); void recycle();
boolean isRecycled(); boolean isRecycled();
ImageWrapper setOneShot(boolean b); T setOneShot(boolean b);
default ImageWrapper oneShot() { default T oneShot() {
return setOneShot(true); return setOneShot(true);
} }

View File

@@ -23,6 +23,7 @@ import org.autojs.autojs.annotation.ScriptVariable;
import org.autojs.autojs.concurrent.VolatileDispose; import org.autojs.autojs.concurrent.VolatileDispose;
import org.autojs.autojs.core.image.CapturedImage; import org.autojs.autojs.core.image.CapturedImage;
import org.autojs.autojs.core.image.ImageWrapper; import org.autojs.autojs.core.image.ImageWrapper;
import org.autojs.autojs.core.image.Shootable;
import org.autojs.autojs.core.image.RhinoColorFinder; import org.autojs.autojs.core.image.RhinoColorFinder;
import org.autojs.autojs.core.image.TemplateMatching; import org.autojs.autojs.core.image.TemplateMatching;
import org.autojs.autojs.core.image.capture.ScreenCaptureRequester; import org.autojs.autojs.core.image.capture.ScreenCaptureRequester;
@@ -56,6 +57,7 @@ import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Objects;
import static android.app.Activity.RESULT_OK; import static android.app.Activity.RESULT_OK;
import static org.autojs.autojs.util.RhinoUtils.isMainThread; import static org.autojs.autojs.util.RhinoUtils.isMainThread;
@@ -153,12 +155,22 @@ public class Images {
if (image == null) { if (image == null) {
throw new NullPointerException(str(R.string.error_method_called_with_null_argument, "Images.pixel", "image")); throw new NullPointerException(str(R.string.error_method_called_with_null_argument, "Images.pixel", "image"));
} }
int pixel = image.pixel(x, y); try {
image.shoot(); return image.pixel(x, y);
return pixel; } finally {
shoot(image);
}
} }
public static ImageWrapper concat(ScriptRuntime scriptRuntime, ImageWrapper imgA, ImageWrapper imgB, int direction) { public static ImageWrapper concat(ScriptRuntime scriptRuntime, ImageWrapper imgA, ImageWrapper imgB, int direction) {
try {
return concatInternal(scriptRuntime, imgA, imgB, direction);
} finally {
shoot(imgA, imgB);
}
}
private static ImageWrapper concatInternal(ScriptRuntime scriptRuntime, ImageWrapper imgA, ImageWrapper imgB, int direction) {
if (!Arrays.asList(Gravity.START, Gravity.END, Gravity.TOP, Gravity.BOTTOM).contains(direction)) { if (!Arrays.asList(Gravity.START, Gravity.END, Gravity.TOP, Gravity.BOTTOM).contains(direction)) {
throw new IllegalArgumentException(str(R.string.error_illegal_argument, "direction", direction)); throw new IllegalArgumentException(str(R.string.error_illegal_argument, "direction", direction));
} }
@@ -186,8 +198,6 @@ public class Images {
canvas.drawBitmap(imgA.getBitmap(), (float) (width - imgA.getWidth()) / 2, 0, paint); canvas.drawBitmap(imgA.getBitmap(), (float) (width - imgA.getWidth()) / 2, 0, paint);
canvas.drawBitmap(imgB.getBitmap(), (float) (width - imgB.getWidth()) / 2, imgA.getHeight(), paint); canvas.drawBitmap(imgB.getBitmap(), (float) (width - imgB.getWidth()) / 2, imgA.getHeight(), paint);
} }
imgA.shoot();
imgB.shoot();
return ImageWrapper.ofBitmap(scriptRuntime, bitmap); return ImageWrapper.ofBitmap(scriptRuntime, bitmap);
} }
@@ -279,12 +289,22 @@ public class Images {
} }
public ImageWrapper copy(@NonNull ImageWrapper image) { public ImageWrapper copy(@NonNull ImageWrapper image) {
ImageWrapper imageWrapper = image.clone(); try {
image.shoot(); return image.clone();
return imageWrapper; } finally {
shoot(image);
}
} }
public boolean save(@NonNull ImageWrapper image, @NonNull String path, @NonNull String format, int quality) throws IOException { public boolean save(@NonNull ImageWrapper image, @NonNull String path, @NonNull String format, int quality) throws IOException {
try {
return saveInternal(image, path, format, quality);
} finally {
shoot(image);
}
}
private boolean saveInternal(@NonNull ImageWrapper image, @NonNull String path, @NonNull String format, int quality) throws IOException {
Bitmap bitmap = image.getBitmap(); Bitmap bitmap = image.getBitmap();
Bitmap.CompressFormat compressFormat = parseImageFormat(format); Bitmap.CompressFormat compressFormat = parseImageFormat(format);
@@ -306,19 +326,24 @@ public class Images {
} }
try (FileOutputStream fos = new FileOutputStream(path)) { try (FileOutputStream fos = new FileOutputStream(path)) {
boolean b = bitmap.compress(compressFormat, quality, fos); return bitmap.compress(compressFormat, quality, fos);
image.shoot();
return b;
} }
} }
public byte[] compressToBytes(@NotNull ImageWrapper image, @NotNull String format, int quality) { public byte[] compressToBytes(@NotNull ImageWrapper image, @NotNull String format, int quality) {
try {
return compressToBytesInternal(image, format, quality);
} finally {
shoot(image);
}
}
private byte[] compressToBytesInternal(@NotNull ImageWrapper image, @NotNull String format, int quality) {
Bitmap bitmap = image.getBitmap(); Bitmap bitmap = image.getBitmap();
Bitmap.CompressFormat compressFormat = parseImageFormat(format); Bitmap.CompressFormat compressFormat = parseImageFormat(format);
if (compressFormat == Bitmap.CompressFormat.PNG && quality != 100) { if (compressFormat == Bitmap.CompressFormat.PNG && quality != 100) {
byte[] compressed = PngQuantBridge.quantize(bitmap, quality); byte[] compressed = PngQuantBridge.quantize(bitmap, quality);
image.shoot();
if (compressed != null) { if (compressed != null) {
return compressed; return compressed;
} }
@@ -327,14 +352,12 @@ public class Images {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (compressFormat == Bitmap.CompressFormat.WEBP_LOSSLESS && quality != 100) { if (compressFormat == Bitmap.CompressFormat.WEBP_LOSSLESS && quality != 100) {
image.shoot();
throw new IllegalArgumentException(mContext.getString(R.string.error_webp_lossless_quality_not_supported)); throw new IllegalArgumentException(mContext.getString(R.string.error_webp_lossless_quality_not_supported));
} }
} }
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
bitmap.compress(compressFormat, quality, outputStream); bitmap.compress(compressFormat, quality, outputStream);
image.shoot();
return outputStream.toByteArray(); return outputStream.toByteArray();
} }
@@ -352,17 +375,31 @@ public class Images {
releaseScreenCapturer(); releaseScreenCapturer();
} }
public ImageWrapper rotate(@NonNull ImageWrapper img, float x, float y, float degree) { public ImageWrapper rotate(@NonNull ImageWrapper image, float x, float y, float degree) {
try {
return rotateInternal(image, x, y, degree);
} finally {
shoot(image);
}
}
private ImageWrapper rotateInternal(@NonNull ImageWrapper image, float x, float y, float degree) {
Matrix matrix = new Matrix(); Matrix matrix = new Matrix();
matrix.postRotate(degree, x, y); matrix.postRotate(degree, x, y);
ImageWrapper imageWrapper = ImageWrapper.ofBitmap(mScriptRuntime, Bitmap.createBitmap(img.getBitmap(), 0, 0, img.getWidth(), img.getHeight(), matrix, true)); return ImageWrapper.ofBitmap(mScriptRuntime, Bitmap.createBitmap(image.getBitmap(), 0, 0, image.getWidth(), image.getHeight(), matrix, true));
img.shoot();
return imageWrapper;
} }
@ScriptInterface @ScriptInterface
public ImageWrapper flip(@NonNull ImageWrapper img, boolean horizontal, boolean vertical) { public ImageWrapper flip(@NonNull ImageWrapper image, boolean horizontal, boolean vertical) {
Bitmap original = img.getBitmap(); try {
return flipInternal(image, horizontal, vertical);
} finally {
shoot(image);
}
}
private ImageWrapper flipInternal(@NonNull ImageWrapper image, boolean horizontal, boolean vertical) {
Bitmap original = image.getBitmap();
Matrix matrix = new Matrix(); Matrix matrix = new Matrix();
// Set scaling ratio according to input parameters. // Set scaling ratio according to input parameters.
@@ -380,14 +417,19 @@ public class Images {
if (vertical) matrix.postTranslate(0, original.getHeight()); if (vertical) matrix.postTranslate(0, original.getHeight());
Bitmap flipped = Bitmap.createBitmap(original, 0, 0, original.getWidth(), original.getHeight(), matrix, true); Bitmap flipped = Bitmap.createBitmap(original, 0, 0, original.getWidth(), original.getHeight(), matrix, true);
img.shoot();
return ImageWrapper.ofBitmap(mScriptRuntime, flipped); return ImageWrapper.ofBitmap(mScriptRuntime, flipped);
} }
public ImageWrapper clip(@NonNull ImageWrapper img, int x, int y, int w, int h) { public ImageWrapper clip(@NonNull ImageWrapper image, int x, int y, int w, int h) {
ImageWrapper imageWrapper = ImageWrapper.ofBitmap(mScriptRuntime, Bitmap.createBitmap(img.getBitmap(), x, y, w, h)); try {
img.shoot(); return clipInternal(image, x, y, w, h);
return imageWrapper; } finally {
shoot(image);
}
}
private ImageWrapper clipInternal(@NonNull ImageWrapper image, int x, int y, int w, int h) {
return ImageWrapper.ofBitmap(mScriptRuntime, Bitmap.createBitmap(image.getBitmap(), x, y, w, h));
} }
public ImageWrapper read(String path) { public ImageWrapper read(String path) {
@@ -416,25 +458,32 @@ public class Images {
return ImageWrapper.ofBitmap(mScriptRuntime, Drawables.loadBase64Data(data)); return ImageWrapper.ofBitmap(mScriptRuntime, Drawables.loadBase64Data(data));
} }
public String toBase64(ImageWrapper img, String format, int quality) { public String toBase64(ImageWrapper image, String format, int quality) {
byte[] input = toBytes(img, format, quality); try {
img.shoot(); byte[] input = toBytes(image, format, quality);
return Base64.encodeToString(input, Base64.NO_WRAP); return Base64.encodeToString(input, Base64.NO_WRAP);
} finally {
shoot(image);
}
} }
public byte[] toBytes(@NonNull ImageWrapper img, String format, int quality) { public byte[] toBytes(@NonNull ImageWrapper image, String format, int quality) {
Bitmap.CompressFormat compressFormat = parseImageFormat(format); try {
Bitmap bitmap = img.getBitmap(); Bitmap.CompressFormat compressFormat = parseImageFormat(format);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); Bitmap bitmap = image.getBitmap();
bitmap.compress(compressFormat, quality, outputStream); ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
img.shoot(); bitmap.compress(compressFormat, quality, outputStream);
return outputStream.toByteArray(); return outputStream.toByteArray();
} finally {
shoot(image);
}
} }
public ImageWrapper fromBytes(byte[] bytes) throws BitmapUtils.DecodeException { public ImageWrapper fromBytes(byte[] bytes) throws BitmapUtils.DecodeException {
return ImageWrapper.ofBitmap(mScriptRuntime, BitmapUtils.bitmapFromByteArrayOrThrow(bytes)); return ImageWrapper.ofBitmap(mScriptRuntime, BitmapUtils.bitmapFromByteArrayOrThrow(bytes));
} }
/** @noinspection deprecation */
private Bitmap.CompressFormat parseImageFormat(String format) { private Bitmap.CompressFormat parseImageFormat(String format) {
return switch (format.toLowerCase(Language.getPrefLanguage().getLocale())) { return switch (format.toLowerCase(Language.getPrefLanguage().getLocale())) {
case "png" -> Bitmap.CompressFormat.PNG; case "png" -> Bitmap.CompressFormat.PNG;
@@ -468,6 +517,14 @@ public class Images {
} }
public ImageWrapper invert(@NonNull ImageWrapper image) { public ImageWrapper invert(@NonNull ImageWrapper image) {
try {
return invertInternal(image);
} finally {
shoot(image);
}
}
private ImageWrapper invertInternal(@NonNull ImageWrapper image) {
initOpenCvIfNeeded(); initOpenCvIfNeeded();
Bitmap originalBitmap = image.getBitmap(); Bitmap originalBitmap = image.getBitmap();
@@ -503,8 +560,6 @@ public class Images {
Bitmap invertedBitmap = Bitmap.createBitmap(originalWidth, originalHeight, Bitmap.Config.ARGB_8888); Bitmap invertedBitmap = Bitmap.createBitmap(originalWidth, originalHeight, Bitmap.Config.ARGB_8888);
Utils.matToBitmap(destMat, invertedBitmap); Utils.matToBitmap(destMat, invertedBitmap);
image.shoot();
return ImageWrapper.ofBitmap(mScriptRuntime, invertedBitmap); return ImageWrapper.ofBitmap(mScriptRuntime, invertedBitmap);
} }
@@ -557,6 +612,14 @@ public class Images {
@Nullable @Nullable
@ScriptInterface @ScriptInterface
public Point findImage(ImageWrapper image, ImageWrapper template, float weakThreshold, float strictThreshold, Rect rect, int maxLevel) throws Exception { public Point findImage(ImageWrapper image, ImageWrapper template, float weakThreshold, float strictThreshold, Rect rect, int maxLevel) throws Exception {
try {
return findImageInternal(image, template, weakThreshold, strictThreshold, rect, maxLevel);
} finally {
shoot(image, template);
}
}
private Point findImageInternal(ImageWrapper image, ImageWrapper template, float weakThreshold, float strictThreshold, Rect rect, int maxLevel) throws Exception {
initOpenCvIfNeeded(); initOpenCvIfNeeded();
if (image == null) { if (image == null) {
throw new NullPointerException(mContext.getString(R.string.error_method_called_with_null_argument, "Images.findImage", "image")); throw new NullPointerException(mContext.getString(R.string.error_method_called_with_null_argument, "Images.findImage", "image"));
@@ -588,8 +651,6 @@ public class Images {
if (shouldReleaseMat) { if (shouldReleaseMat) {
OpenCVHelper.release(src); OpenCVHelper.release(src);
} }
image.shoot();
template.shoot();
} }
if (point != null) { if (point != null) {
if (rect != null) { if (rect != null) {
@@ -603,6 +664,14 @@ public class Images {
} }
public List<TemplateMatching.Match> matchTemplate(ImageWrapper image, ImageWrapper template, float weakThreshold, float strictThreshold, Rect rect, int maxLevel, int limit, boolean useTransparentMask) { public List<TemplateMatching.Match> matchTemplate(ImageWrapper image, ImageWrapper template, float weakThreshold, float strictThreshold, Rect rect, int maxLevel, int limit, boolean useTransparentMask) {
try {
return matchTemplateInternal(image, template, weakThreshold, strictThreshold, rect, maxLevel, limit, useTransparentMask);
} finally {
shoot(image, template);
}
}
private List<TemplateMatching.Match> matchTemplateInternal(ImageWrapper image, ImageWrapper template, float weakThreshold, float strictThreshold, Rect rect, int maxLevel, int limit, boolean useTransparentMask) {
initOpenCvIfNeeded(); initOpenCvIfNeeded();
if (image == null) { if (image == null) {
throw new NullPointerException(mContext.getString(R.string.error_method_called_with_null_argument, "Images.matchTemplate", "image")); throw new NullPointerException(mContext.getString(R.string.error_method_called_with_null_argument, "Images.matchTemplate", "image"));
@@ -623,8 +692,6 @@ public class Images {
if (src != image.getMat()) { if (src != image.getMat()) {
OpenCVHelper.release(src); OpenCVHelper.release(src);
} }
image.shoot();
template.shoot();
for (TemplateMatching.Match match : result) { for (TemplateMatching.Match match : result) {
Point point = match.point; Point point = match.point;
@@ -669,4 +736,8 @@ public class Images {
} }
} }
public static void shoot(Shootable<?>... shootableArgs) {
Arrays.stream(shootableArgs).filter(Objects::nonNull).forEach(Shootable::shoot);
}
} }

View File

@@ -1,5 +1,6 @@
package org.autojs.autojs.runtime.api.augment.images package org.autojs.autojs.runtime.api.augment.images
import org.autojs.autojs.core.image.Shootable
import org.autojs.autojs.runtime.api.ImageFeatureMatching import org.autojs.autojs.runtime.api.ImageFeatureMatching
import org.autojs.autojs.util.RhinoUtils import org.autojs.autojs.util.RhinoUtils
import org.mozilla.javascript.Undefined import org.mozilla.javascript.Undefined
@@ -9,15 +10,28 @@ class ImageFeatures(
@JvmField var javaObject: ImageFeatureMatching.FeatureMatchingDescriptor, @JvmField var javaObject: ImageFeatureMatching.FeatureMatchingDescriptor,
@JvmField var scale: Float, @JvmField var scale: Float,
@JvmField var region: Rect, @JvmField var region: Rect,
) { ) : Shootable<ImageFeatures> {
private var mIsOneShot = false
@JvmField @JvmField
var recycled = false var recycled = false
override fun isRecycled() = recycled
override fun setOneShot(b: Boolean): ImageFeatures {
mIsOneShot = b
return this
}
override fun shoot() {
if (mIsOneShot) recycle()
}
@JvmField @JvmField
var onRecycled: ((ImageFeatures) -> Undefined) = { _: ImageFeatures -> RhinoUtils.UNDEFINED } var onRecycled: ((ImageFeatures) -> Undefined) = { _: ImageFeatures -> RhinoUtils.UNDEFINED }
fun recycle() { override fun recycle() {
if (!recycled) { if (!recycled) {
javaObject.release() javaObject.release()
onRecycled.invoke(this) onRecycled.invoke(this)

View File

@@ -1,5 +1,5 @@
#Mon Jun 09 19:43:01 CST 2025 #Tue Jun 10 18:30:41 CST 2025
BUILD_TIME=1749469381134 BUILD_TIME=1749551441206
COMPILE_SDK_VERSION=35 COMPILE_SDK_VERSION=35
IMAGE_QUANT_CMAKE_VERSION=3.22.1 IMAGE_QUANT_CMAKE_VERSION=3.22.1
IMAGE_QUANT_NDK_VERSION=26.1.10909125 IMAGE_QUANT_NDK_VERSION=26.1.10909125
@@ -19,6 +19,6 @@ RAPID_OCR_OPENCV_MOBILE_LABEL_VERSION=13
RAPID_OCR_OPENCV_MOBILE_VERSION=4.5.3 RAPID_OCR_OPENCV_MOBILE_VERSION=4.5.3
TARGET_SDK_VERSION=35 TARGET_SDK_VERSION=35
TARGET_SDK_VERSION_INRT=29 TARGET_SDK_VERSION_INRT=29
VERSION_BUILD=3286 VERSION_BUILD=3289
VERSION_NAME=6.7.0 Alpha VERSION_NAME=6.7.0 Alpha
VSCODE_EXT_REQUIRED_VERSION=1.0.8 VSCODE_EXT_REQUIRED_VERSION=1.0.8