6.6.3 - Alpha5 - 新增 images.compressToBytes/downsample; 修复 images.compress; ImageWrapper#saveTo 支持相对路径
This commit is contained in:
@@ -1,13 +1,15 @@
|
|||||||
{
|
{
|
||||||
"$data": {
|
"$data": {
|
||||||
"v6.6.3": {
|
"v6.6.3": {
|
||||||
"released_date": "2025/05/14",
|
"released_date": "2025/05/21",
|
||||||
"feature": [
|
"feature": [
|
||||||
"版本历史功能, 可查看发行版本历史更新记录 (多语言) 与统计数据",
|
"版本历史功能, 可查看发行版本历史更新记录 (多语言) 与统计数据",
|
||||||
"timers.keepAlive 方法 (已全局化), 用于保持脚本活跃状态",
|
"timers.keepAlive 方法 (已全局化), 用于保持脚本活跃状态",
|
||||||
"engines.on('start/stop/error', callback) 等事件监听方法, 用于监听脚本引擎全局事件",
|
"engines.on('start/stop/error', callback) 等事件监听方法, 用于监听脚本引擎全局事件",
|
||||||
"images.detectMultiColors 方法, 用于多点颜色校验 _[`issue #374`](http://issues.autojs6.com/374)_",
|
"images.detectMultiColors 方法, 用于多点颜色校验 _[`issue #374`](http://issues.autojs6.com/374)_",
|
||||||
"images.matchFeatures/detectAndComputeFeatures 方法, 支持全分辨率找图 (Ref to [Auto.js Pro](https://g.pro.autojs.org/)) _[`issue #366`](http://issues.autojs6.com/366)_"
|
"images.matchFeatures/detectAndComputeFeatures 方法, 支持全分辨率找图 (Ref to [Auto.js Pro](https://g.pro.autojs.org/)) _[`issue #366`](http://issues.autojs6.com/366)_",
|
||||||
|
"images.compressToBytes 方法, 用于压缩图像并生成字节数组",
|
||||||
|
"images.downsample 方法, 用于像素降采样并生成新的 ImageWrapper"
|
||||||
],
|
],
|
||||||
"fix": [
|
"fix": [
|
||||||
"主页文档标签显示在线文档时部分内容被系统导航栏遮挡的问题",
|
"主页文档标签显示在线文档时部分内容被系统导航栏遮挡的问题",
|
||||||
@@ -21,6 +23,8 @@
|
|||||||
"使用 JavaAdapter 时导致 ClassLoader 调用栈溢出的问题 _[`issue #376`](http://issues.autojs6.com/376)_",
|
"使用 JavaAdapter 时导致 ClassLoader 调用栈溢出的问题 _[`issue #376`](http://issues.autojs6.com/376)_",
|
||||||
"console.setContentTextColor 方法导致日志字体颜色丢失默认值的问题 _[`issue #346`](http://issues.autojs6.com/346)_",
|
"console.setContentTextColor 方法导致日志字体颜色丢失默认值的问题 _[`issue #346`](http://issues.autojs6.com/346)_",
|
||||||
"console.setContentBackgroundColor 方法无法接受颜色名称参数的问题 _[`issue #384`](http://issues.autojs6.com/384)_",
|
"console.setContentBackgroundColor 方法无法接受颜色名称参数的问题 _[`issue #384`](http://issues.autojs6.com/384)_",
|
||||||
|
"images.compress 方法实现原理由像素降采样修正为编码质量变化",
|
||||||
|
"images.resize 方法无法正常使用的问题",
|
||||||
"README.md 中部分语言日期格式不正确的问题",
|
"README.md 中部分语言日期格式不正确的问题",
|
||||||
"Gradle 构建脚本可能因获取到无效库档案文件长度而导致构建失败的问题 _[`issue #389`](http://issues.autojs6.com/389)_"
|
"Gradle 构建脚本可能因获取到无效库档案文件长度而导致构建失败的问题 _[`issue #389`](http://issues.autojs6.com/389)_"
|
||||||
],
|
],
|
||||||
@@ -34,6 +38,7 @@
|
|||||||
"文件管理器浮动按钮展开后点击其他区域可自动隐藏",
|
"文件管理器浮动按钮展开后点击其他区域可自动隐藏",
|
||||||
"打包单文件时自动读取并勾选已安装应用的声明权限 _[`issue #362`](http://issues.autojs6.com/362)_",
|
"打包单文件时自动读取并勾选已安装应用的声明权限 _[`issue #362`](http://issues.autojs6.com/362)_",
|
||||||
"意图相关操作 (编辑/查看/安装/发送/播放等) 增加操作异常提示",
|
"意图相关操作 (编辑/查看/安装/发送/播放等) 增加操作异常提示",
|
||||||
|
"ImageWrapper#saveTo 方法的路径参数支持相对路径",
|
||||||
"images.save 方法使用 quality 参数时支持 png 格式的文件体积压缩 _[`issue #367`](http://issues.autojs6.com/367)_",
|
"images.save 方法使用 quality 参数时支持 png 格式的文件体积压缩 _[`issue #367`](http://issues.autojs6.com/367)_",
|
||||||
"已忽略更新记录及客户端模式连接地址记录支持清空操作",
|
"已忽略更新记录及客户端模式连接地址记录支持清空操作",
|
||||||
"版本更新信息支持多语言显示 (与当前显示语言同步)",
|
"版本更新信息支持多语言显示 (与当前显示语言同步)",
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ class SimpleActionAutomator(private val accessibilityBridge: AccessibilityBridge
|
|||||||
val bitmap = hardwareBuffer.copy(Bitmap.Config.ARGB_8888, true)
|
val bitmap = hardwareBuffer.copy(Bitmap.Config.ARGB_8888, true)
|
||||||
|
|
||||||
hardwareBuffer.recycle()
|
hardwareBuffer.recycle()
|
||||||
promiseAdapter.resolve(ImageWrapper.ofBitmap(bitmap))
|
promiseAdapter.resolve(ImageWrapper.ofBitmap(scriptRuntime, bitmap))
|
||||||
}
|
}
|
||||||
mPromiseAdapter = null
|
mPromiseAdapter = null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,10 @@ import android.graphics.PorterDuff;
|
|||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.RectF;
|
import android.graphics.RectF;
|
||||||
import android.graphics.Region;
|
import android.graphics.Region;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.autojs.autojs.core.image.ImageWrapper;
|
import org.autojs.autojs.core.image.ImageWrapper;
|
||||||
|
import org.autojs.autojs.runtime.ScriptRuntime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Stardust on Mar 22, 2018.
|
* Created by Stardust on Mar 22, 2018.
|
||||||
@@ -23,24 +22,27 @@ import org.autojs.autojs.core.image.ImageWrapper;
|
|||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class ScriptCanvas {
|
public class ScriptCanvas {
|
||||||
|
|
||||||
|
private final ScriptRuntime mScriptRuntime;
|
||||||
|
|
||||||
private Canvas mCanvas;
|
private Canvas mCanvas;
|
||||||
private Bitmap mBitmap;
|
private Bitmap mBitmap;
|
||||||
|
|
||||||
public ScriptCanvas(int width, int height) {
|
public ScriptCanvas(ScriptRuntime scriptRuntime, int width, int height) {
|
||||||
this(Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888));
|
this(scriptRuntime, Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ScriptCanvas(@NonNull Bitmap bitmap) {
|
public ScriptCanvas(ScriptRuntime scriptRuntime, @NonNull Bitmap bitmap) {
|
||||||
|
mScriptRuntime = scriptRuntime;
|
||||||
mCanvas = new Canvas(bitmap);
|
mCanvas = new Canvas(bitmap);
|
||||||
mBitmap = bitmap;
|
mBitmap = bitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ScriptCanvas(@NonNull ImageWrapper image) {
|
public ScriptCanvas(ScriptRuntime scriptRuntime, @NonNull ImageWrapper image) {
|
||||||
this(image.getBitmap().copy(image.getBitmap().getConfig(), true));
|
this(scriptRuntime, image.getBitmap().copy(image.getBitmap().getConfig(), true));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ScriptCanvas() {
|
public ScriptCanvas(ScriptRuntime scriptRuntime) {
|
||||||
/* Empty body. */
|
mScriptRuntime = scriptRuntime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Canvas getAndroidCanvas() {
|
public Canvas getAndroidCanvas() {
|
||||||
@@ -52,7 +54,7 @@ public class ScriptCanvas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ImageWrapper toImage() {
|
public ImageWrapper toImage() {
|
||||||
return ImageWrapper.ofBitmap(mBitmap.copy(mBitmap.getConfig(), true));
|
return ImageWrapper.ofBitmap(mScriptRuntime, mBitmap.copy(mBitmap.getConfig(), true));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isHardwareAccelerated() {
|
public boolean isHardwareAccelerated() {
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
package org.autojs.autojs.core.image
|
package org.autojs.autojs.core.image
|
||||||
|
|
||||||
import android.media.Image
|
import android.media.Image
|
||||||
|
import org.autojs.autojs.runtime.ScriptRuntime
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by SuperMonster003 on Dec 15, 2023.
|
* Created by SuperMonster003 on Dec 15, 2023.
|
||||||
|
* Modified by SuperMonster003 as of May 20, 2025.
|
||||||
*/
|
*/
|
||||||
// @Reference to Auto.js Pro 9.3.11 by SuperMonster003 on Dec 15, 2023.
|
// @Reference to Auto.js Pro 9.3.11 by SuperMonster003 on Dec 15, 2023.
|
||||||
class CapturedImage(image: Image) : ImageWrapper(image) {
|
class CapturedImage(scriptRuntime: ScriptRuntime, image: Image) : ImageWrapper(scriptRuntime, image) {
|
||||||
|
|
||||||
override fun recycle() {
|
override fun recycle() {
|
||||||
/* Doing nothing to suppress default recycle method. */
|
/* Doing nothing to suppress default recycle method. */
|
||||||
|
|||||||
@@ -4,12 +4,15 @@ import android.graphics.Bitmap
|
|||||||
import android.graphics.Bitmap.CompressFormat
|
import android.graphics.Bitmap.CompressFormat
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.media.Image
|
import android.media.Image
|
||||||
|
import androidx.core.graphics.createBitmap
|
||||||
|
import androidx.core.graphics.get
|
||||||
import org.autojs.autojs.annotation.ScriptInterface
|
import org.autojs.autojs.annotation.ScriptInterface
|
||||||
import org.autojs.autojs.core.opencv.Mat
|
import org.autojs.autojs.core.opencv.Mat
|
||||||
import org.autojs.autojs.core.opencv.OpenCVHelper
|
import org.autojs.autojs.core.opencv.OpenCVHelper
|
||||||
import org.autojs.autojs.core.ref.MonitorResource
|
import org.autojs.autojs.core.ref.MonitorResource
|
||||||
import org.autojs.autojs.core.ref.NativeObjectReference
|
import org.autojs.autojs.core.ref.NativeObjectReference
|
||||||
import org.autojs.autojs.pio.UncheckedIOException
|
import org.autojs.autojs.pio.UncheckedIOException
|
||||||
|
import org.autojs.autojs.runtime.ScriptRuntime
|
||||||
import org.autojs.autojs.runtime.api.Images
|
import org.autojs.autojs.runtime.api.Images
|
||||||
import org.autojs.autojs.util.StringUtils.str
|
import org.autojs.autojs.util.StringUtils.str
|
||||||
import org.autojs.autojs6.R
|
import org.autojs.autojs6.R
|
||||||
@@ -31,6 +34,8 @@ import java.util.concurrent.atomic.AtomicLong
|
|||||||
// @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 : Recyclable, MonitorResource {
|
||||||
|
|
||||||
|
private var mScriptRuntime: ScriptRuntime
|
||||||
|
|
||||||
private var mMat: Mat? = null
|
private var mMat: Mat? = null
|
||||||
private var mBgrMat: Mat? = null
|
private var mBgrMat: Mat? = null
|
||||||
private var mBitmap: Bitmap? = null
|
private var mBitmap: Bitmap? = null
|
||||||
@@ -61,7 +66,7 @@ open class ImageWrapper : Recyclable, MonitorResource {
|
|||||||
ensureNotRecycled()
|
ensureNotRecycled()
|
||||||
if (mBitmap == null) {
|
if (mBitmap == null) {
|
||||||
if (mMat != null) {
|
if (mMat != null) {
|
||||||
mBitmap = Bitmap.createBitmap(mMat!!.width(), mMat!!.height(), Bitmap.Config.ARGB_8888)
|
mBitmap = createBitmap(mMat!!.width(), mMat!!.height())
|
||||||
Utils.matToBitmap(mMat, mBitmap)
|
Utils.matToBitmap(mMat, mBitmap)
|
||||||
} else {
|
} else {
|
||||||
mBitmap = mediaImage?.let { toBitmap(it) }
|
mBitmap = mediaImage?.let { toBitmap(it) }
|
||||||
@@ -100,23 +105,26 @@ open class ImageWrapper : Recyclable, MonitorResource {
|
|||||||
}
|
}
|
||||||
get() = mPlane ?: mediaImage?.planes?.get(0)
|
get() = mPlane ?: mediaImage?.planes?.get(0)
|
||||||
|
|
||||||
constructor(width: Int, height: Int) : this(Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888))
|
constructor(scriptRuntime: ScriptRuntime, width: Int, height: Int) : this(scriptRuntime, createBitmap(width, height))
|
||||||
|
|
||||||
constructor(bitmap: Bitmap) {
|
constructor(scriptRuntime: ScriptRuntime, bitmap: Bitmap) {
|
||||||
|
mScriptRuntime = scriptRuntime
|
||||||
mId = mNextId.incrementAndGet()
|
mId = mNextId.incrementAndGet()
|
||||||
mBitmap = bitmap.also { addToList(it) }
|
mBitmap = bitmap.also { addToList(it) }
|
||||||
mWidth = bitmap.width
|
mWidth = bitmap.width
|
||||||
mHeight = bitmap.height
|
mHeight = bitmap.height
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(mat: Mat) {
|
constructor(scriptRuntime: ScriptRuntime, mat: Mat) {
|
||||||
|
mScriptRuntime = scriptRuntime
|
||||||
mId = mNextId.incrementAndGet()
|
mId = mNextId.incrementAndGet()
|
||||||
mMat = mat.also { addToList(it) }
|
mMat = mat.also { addToList(it) }
|
||||||
mWidth = mat.cols()
|
mWidth = mat.cols()
|
||||||
mHeight = mat.rows()
|
mHeight = mat.rows()
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(mat: org.opencv.core.Mat) {
|
constructor(scriptRuntime: ScriptRuntime, mat: org.opencv.core.Mat) {
|
||||||
|
mScriptRuntime = scriptRuntime
|
||||||
mId = mNextId.incrementAndGet()
|
mId = mNextId.incrementAndGet()
|
||||||
mMat = when (mat.nativeObj != 0L) {
|
mMat = when (mat.nativeObj != 0L) {
|
||||||
true -> Mat(mat.nativeObj)
|
true -> Mat(mat.nativeObj)
|
||||||
@@ -126,7 +134,8 @@ open class ImageWrapper : Recyclable, MonitorResource {
|
|||||||
mHeight = mat.rows()
|
mHeight = mat.rows()
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(bitmap: Bitmap, mat: Mat?) {
|
constructor(scriptRuntime: ScriptRuntime, bitmap: Bitmap, mat: Mat?) {
|
||||||
|
mScriptRuntime = scriptRuntime
|
||||||
mId = mNextId.incrementAndGet()
|
mId = mNextId.incrementAndGet()
|
||||||
mMat = mat?.also { addToList(it) }
|
mMat = mat?.also { addToList(it) }
|
||||||
mBitmap = bitmap.also { addToList(it) }
|
mBitmap = bitmap.also { addToList(it) }
|
||||||
@@ -134,7 +143,8 @@ open class ImageWrapper : Recyclable, MonitorResource {
|
|||||||
mHeight = bitmap.height
|
mHeight = bitmap.height
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(mediaImage: Image) {
|
constructor(scriptRuntime: ScriptRuntime, mediaImage: Image) {
|
||||||
|
mScriptRuntime = scriptRuntime
|
||||||
mId = mNextId.incrementAndGet()
|
mId = mNextId.incrementAndGet()
|
||||||
this.mediaImage = mediaImage.also { addToList(it) }
|
this.mediaImage = mediaImage.also { addToList(it) }
|
||||||
mWidth = mediaImage.width
|
mWidth = mediaImage.width
|
||||||
@@ -149,27 +159,23 @@ open class ImageWrapper : Recyclable, MonitorResource {
|
|||||||
imageList.add(WeakReference(image))
|
imageList.add(WeakReference(image))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveTo(path: String?): Boolean {
|
fun saveTo(path: String): Boolean {
|
||||||
ensureNotRecycled()
|
ensureNotRecycled()
|
||||||
path ?: return false
|
val fullPath = mScriptRuntime.files.nonNullPath(path)
|
||||||
if (mBitmap == null) {
|
if (mBitmap == null) {
|
||||||
if (mMat != null) {
|
if (mMat != null) {
|
||||||
return Imgcodecs.imwrite(path, mMat)
|
return Imgcodecs.imwrite(fullPath, mMat)
|
||||||
}
|
}
|
||||||
bitmap /* Getter, for initializing `mBitmap`. */
|
bitmap /* Getter, for initializing `mBitmap`. */
|
||||||
}
|
}
|
||||||
return try {
|
return runCatching { saveWithBitmap(fullPath) }.isSuccess
|
||||||
true.also { saveWithBitmap(path) }
|
|
||||||
} catch (_: Exception) {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveWithBitmap(path: String?) {
|
private fun saveWithBitmap(fullPath: String?) {
|
||||||
try {
|
try {
|
||||||
path ?: throw Exception("Argument \"path\" cannot be null")
|
fullPath ?: throw Exception("Argument \"path\" cannot be null")
|
||||||
mBitmap ?: throw Exception("Member \"bitmap\" cannot be null")
|
mBitmap ?: throw Exception("Member \"bitmap\" cannot be null")
|
||||||
mBitmap!!.compress(CompressFormat.PNG, 100, FileOutputStream(path))
|
mBitmap!!.compress(CompressFormat.PNG, 100, FileOutputStream(fullPath))
|
||||||
} catch (e: FileNotFoundException) {
|
} catch (e: FileNotFoundException) {
|
||||||
throw UncheckedIOException(e)
|
throw UncheckedIOException(e)
|
||||||
}
|
}
|
||||||
@@ -181,7 +187,7 @@ open class ImageWrapper : Recyclable, MonitorResource {
|
|||||||
throw ArrayIndexOutOfBoundsException("Point ($x, $y) out of bounds of $this")
|
throw ArrayIndexOutOfBoundsException("Point ($x, $y) out of bounds of $this")
|
||||||
}
|
}
|
||||||
mBitmap?.let { oBitmap ->
|
mBitmap?.let { oBitmap ->
|
||||||
return oBitmap.getPixel(x, y)
|
return oBitmap[x, y]
|
||||||
}
|
}
|
||||||
mMat?.let { oMat ->
|
mMat?.let { oMat ->
|
||||||
|
|
||||||
@@ -282,8 +288,8 @@ open class ImageWrapper : Recyclable, MonitorResource {
|
|||||||
fun clone(): ImageWrapper {
|
fun clone(): ImageWrapper {
|
||||||
ensureNotRecycled()
|
ensureNotRecycled()
|
||||||
return when (val bitmap = mBitmap) {
|
return when (val bitmap = mBitmap) {
|
||||||
null -> ofMat(mat.clone())
|
null -> ofMat(mScriptRuntime, mat.clone())
|
||||||
else -> ofBitmap(bitmap.copy(bitmap.config ?: Bitmap.Config.ARGB_8888, true))
|
else -> ofBitmap(mScriptRuntime, bitmap.copy(bitmap.config ?: Bitmap.Config.ARGB_8888, true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,16 +318,16 @@ open class ImageWrapper : Recyclable, MonitorResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun ofImage(image: Image) = ImageWrapper(image)
|
fun ofImage(scriptRuntime: ScriptRuntime, image: Image) = ImageWrapper(scriptRuntime, image)
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun ofMat(mat: Mat) = ImageWrapper(mat)
|
fun ofMat(scriptRuntime: ScriptRuntime, mat: Mat) = ImageWrapper(scriptRuntime, mat)
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun ofMat(mat: org.opencv.core.Mat) = ImageWrapper(mat)
|
fun ofMat(scriptRuntime: ScriptRuntime, mat: org.opencv.core.Mat) = ImageWrapper(scriptRuntime, mat)
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun ofBitmap(bitmap: Bitmap) = ImageWrapper(bitmap)
|
fun ofBitmap(scriptRuntime: ScriptRuntime, bitmap: Bitmap) = ImageWrapper(scriptRuntime, bitmap)
|
||||||
|
|
||||||
@ScriptInterface
|
@ScriptInterface
|
||||||
fun toBitmap(image: Image): Bitmap {
|
fun toBitmap(image: Image): Bitmap {
|
||||||
@@ -329,11 +335,7 @@ open class ImageWrapper : Recyclable, MonitorResource {
|
|||||||
val buffer = plane.buffer.apply { position(0) }
|
val buffer = plane.buffer.apply { position(0) }
|
||||||
val pixelStride = plane.pixelStride
|
val pixelStride = plane.pixelStride
|
||||||
val rowPadding = plane.rowStride - pixelStride * image.width
|
val rowPadding = plane.rowStride - pixelStride * image.width
|
||||||
val bitmap = Bitmap.createBitmap(
|
val bitmap = createBitmap(image.width + rowPadding / pixelStride, image.height).apply { copyPixelsFromBuffer(buffer) }
|
||||||
image.width + rowPadding / pixelStride,
|
|
||||||
image.height,
|
|
||||||
Bitmap.Config.ARGB_8888,
|
|
||||||
).apply { copyPixelsFromBuffer(buffer) }
|
|
||||||
return when (rowPadding == 0) {
|
return when (rowPadding == 0) {
|
||||||
true -> bitmap
|
true -> bitmap
|
||||||
else -> Bitmap.createBitmap(bitmap, 0, 0, image.width, image.height)
|
else -> Bitmap.createBitmap(bitmap, 0, 0, image.width, image.height)
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ class JsCanvasView : TextureView, TextureView.SurfaceTextureListener {
|
|||||||
execute {
|
execute {
|
||||||
var canvas: Canvas? = null
|
var canvas: Canvas? = null
|
||||||
var time = SystemClock.uptimeMillis()
|
var time = SystemClock.uptimeMillis()
|
||||||
val scriptCanvas = ScriptCanvas()
|
val scriptCanvas = ScriptCanvas(mScriptRuntime)
|
||||||
try {
|
try {
|
||||||
while (mDrawing) {
|
while (mDrawing) {
|
||||||
canvas = lockCanvas()
|
canvas = lockCanvas()
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.autojs.autojs.extension
|
package org.autojs.autojs.extension
|
||||||
|
|
||||||
|
import androidx.core.net.toUri
|
||||||
import java.text.Normalizer
|
import java.text.Normalizer
|
||||||
|
|
||||||
object StringExtensions {
|
object StringExtensions {
|
||||||
@@ -59,4 +60,12 @@ object StringExtensions {
|
|||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun String.isUri(): Boolean {
|
||||||
|
if (isBlank()) return false
|
||||||
|
val uri = runCatching { this.toUri() }.getOrNull() ?: return false
|
||||||
|
uri.scheme?.lowercase() ?: return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,9 +62,37 @@ import static org.autojs.autojs.util.RhinoUtils.isMainThread;
|
|||||||
import static org.autojs.autojs.util.StringUtils.str;
|
import static org.autojs.autojs.util.StringUtils.str;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Quick Reference – Image Size / Quality Helpers<br>
|
||||||
|
* zh-CN: 快速参考 – 图像尺寸 / 质量相关辅助方法
|
||||||
|
* <hr>
|
||||||
|
* <b>scale</b><br>
|
||||||
|
* Multiply both width and height of an <b>already-decoded</b> Bitmap by
|
||||||
|
* the given scale factor(s).<br>
|
||||||
|
* zh-CN: 在 <b>已解码</b> 的 Bitmap 上, 按照给定比例同时缩放宽高.
|
||||||
|
* <p>
|
||||||
|
* <b>resize</b><br>
|
||||||
|
* Force an <b>already-decoded</b> Bitmap to the specified width/height.
|
||||||
|
* Aspect ratio may be kept or ignored depending on the overload.<br>
|
||||||
|
* zh-CN: 将 <b>已解码</b> Bitmap 调整为指定宽高; 可选择保持或忽略纵横比.
|
||||||
|
* <p>
|
||||||
|
* <b>downsample</b><br>
|
||||||
|
* Shrink <b>before decoding</b> by setting <code>inSampleSize</code>; pixels are
|
||||||
|
* skipped while reading, dramatically reducing memory.<br>
|
||||||
|
* zh-CN: <b>解码前</b> 通过设置 <code>inSampleSize</code> 跳读像素, 显著降低解码分辨率与内存.
|
||||||
|
* <p>
|
||||||
|
* <b>compress</b><br>
|
||||||
|
* Re-encode an existing Bitmap (or byte stream) into JPEG/PNG/WebP
|
||||||
|
* with the given format & quality to reduce <b>disk</b> footprint.<br>
|
||||||
|
* zh-CN: 使用指定格式与质量重新编码 Bitmap (或字节流), 以减小 <b>磁盘</b> 体积.
|
||||||
|
* <p>
|
||||||
|
* <b>save</b><br>
|
||||||
|
* Convenience wrapper that internally invokes <code>compress</code> (with
|
||||||
|
* default or user-supplied options) and then writes to storage.<br>
|
||||||
|
* zh-CN: 便捷封装, 内部调用 <code>compress</code> (默认或自定义参数) 后写入存储.
|
||||||
|
* <hr>
|
||||||
* Created by Stardust on May 20, 2017.
|
* Created by Stardust on May 20, 2017.
|
||||||
|
* Modified by SuperMonster003 as of Dec 1, 2021.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public class Images {
|
public class Images {
|
||||||
|
|
||||||
private static final String TAG = Images.class.getSimpleName();
|
private static final String TAG = Images.class.getSimpleName();
|
||||||
@@ -130,7 +158,7 @@ public class Images {
|
|||||||
return pixel;
|
return pixel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ImageWrapper concat(ImageWrapper imgA, ImageWrapper imgB, int direction) {
|
public static ImageWrapper concat(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));
|
||||||
}
|
}
|
||||||
@@ -160,7 +188,7 @@ public class Images {
|
|||||||
}
|
}
|
||||||
imgA.shoot();
|
imgA.shoot();
|
||||||
imgB.shoot();
|
imgB.shoot();
|
||||||
return ImageWrapper.ofBitmap(bitmap);
|
return ImageWrapper.ofBitmap(scriptRuntime, bitmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void saveBitmap(@NonNull Bitmap bitmap, String path) {
|
public static void saveBitmap(@NonNull Bitmap bitmap, String path) {
|
||||||
@@ -184,8 +212,8 @@ public class Images {
|
|||||||
return Bitmap.createBitmap(origin, 0, 0, width, height, matrix, false);
|
return Bitmap.createBitmap(origin, 0, 0, width, height, matrix, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void setImageCaptureCallback(OnScreenCaptureAvailableListener onScreenCaptureAvailableListener, Image image) {
|
protected static void setImageCaptureCallback(ScriptRuntime scriptRuntime, OnScreenCaptureAvailableListener onScreenCaptureAvailableListener, Image image) {
|
||||||
ImageWrapper ofImage = ImageWrapper.ofImage(image);
|
ImageWrapper ofImage = ImageWrapper.ofImage(scriptRuntime, image);
|
||||||
onScreenCaptureAvailableListener.onCaptureAvailable(ofImage);
|
onScreenCaptureAvailableListener.onCaptureAvailable(ofImage);
|
||||||
// ofImage.recycle();
|
// ofImage.recycle();
|
||||||
}
|
}
|
||||||
@@ -238,7 +266,7 @@ public class Images {
|
|||||||
mPreCaptureImage.recycleInternal();
|
mPreCaptureImage.recycleInternal();
|
||||||
}
|
}
|
||||||
if (capture != null) {
|
if (capture != null) {
|
||||||
mPreCaptureImage = new CapturedImage(capture);
|
mPreCaptureImage = new CapturedImage(mScriptRuntime, capture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return mPreCaptureImage;
|
return mPreCaptureImage;
|
||||||
@@ -247,7 +275,7 @@ public class Images {
|
|||||||
|
|
||||||
public boolean captureScreen(String path) {
|
public boolean captureScreen(String path) {
|
||||||
ImageWrapper image = captureScreen();
|
ImageWrapper image = captureScreen();
|
||||||
return image != null && image.saveTo(mScriptRuntime.files.nonNullPath(path));
|
return image != null && image.saveTo(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImageWrapper copy(@NonNull ImageWrapper image) {
|
public ImageWrapper copy(@NonNull ImageWrapper image) {
|
||||||
@@ -256,18 +284,12 @@ public class Images {
|
|||||||
return imageWrapper;
|
return imageWrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean save(@NonNull ImageWrapper image,
|
public boolean save(@NonNull ImageWrapper image, @NonNull String path, @NonNull String format, int quality) throws IOException {
|
||||||
@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);
|
||||||
|
|
||||||
if (compressFormat == Bitmap.CompressFormat.PNG && quality != 100) {
|
if (compressFormat == Bitmap.CompressFormat.PNG && quality != 100) {
|
||||||
// ARGB_8888 -> RGBA[]
|
byte[] compressed = PngQuantBridge.quantize(bitmap, quality);
|
||||||
byte[] rgba = BitmapUtils.bitmapToRgba(bitmap);
|
|
||||||
byte[] compressed = PngQuantBridge.quantize(rgba, bitmap.getWidth(), bitmap.getHeight(), quality);
|
|
||||||
if (compressed != null) {
|
if (compressed != null) {
|
||||||
try (FileOutputStream fos = new FileOutputStream(path)) {
|
try (FileOutputStream fos = new FileOutputStream(path)) {
|
||||||
fos.write(compressed);
|
fos.write(compressed);
|
||||||
@@ -290,6 +312,36 @@ public class Images {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] compressToBytes(@NotNull ImageWrapper image, @NotNull String format, int quality) {
|
||||||
|
Bitmap bitmap = image.getBitmap();
|
||||||
|
Bitmap.CompressFormat compressFormat = parseImageFormat(format);
|
||||||
|
|
||||||
|
if (compressFormat == Bitmap.CompressFormat.PNG && quality != 100) {
|
||||||
|
byte[] compressed = PngQuantBridge.quantize(bitmap, quality);
|
||||||
|
image.shoot();
|
||||||
|
if (compressed != null) {
|
||||||
|
return compressed;
|
||||||
|
}
|
||||||
|
throw new WrappedRuntimeException("PNG quantization failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
if (compressFormat == Bitmap.CompressFormat.WEBP_LOSSLESS && quality != 100) {
|
||||||
|
image.shoot();
|
||||||
|
throw new IllegalArgumentException(mContext.getString(R.string.error_webp_lossless_quality_not_supported));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
bitmap.compress(compressFormat, quality, outputStream);
|
||||||
|
image.shoot();
|
||||||
|
return outputStream.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageWrapper compress(@NotNull ImageWrapper image, @NotNull String format, int quality) throws BitmapUtils.DecodeException {
|
||||||
|
return fromBytes(compressToBytes(image, format, quality));
|
||||||
|
}
|
||||||
|
|
||||||
// public EventEmitter select() {
|
// public EventEmitter select() {
|
||||||
// EventEmitter eventEmitter = new EventEmitter(scriptRuntime.bridges, scriptRuntime.timers.getTimerForCurrentThread());
|
// EventEmitter eventEmitter = new EventEmitter(scriptRuntime.bridges, scriptRuntime.timers.getTimerForCurrentThread());
|
||||||
// StartForResultActivity.start(mContext, new SelectImageCallback(this, eventEmitter, mContext, mContext.getString(R.string.text_select_image)));
|
// StartForResultActivity.start(mContext, new SelectImageCallback(this, eventEmitter, mContext, mContext.getString(R.string.text_select_image)));
|
||||||
@@ -303,7 +355,7 @@ public class Images {
|
|||||||
public ImageWrapper rotate(@NonNull ImageWrapper img, float x, float y, float degree) {
|
public ImageWrapper rotate(@NonNull ImageWrapper img, 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(Bitmap.createBitmap(img.getBitmap(), 0, 0, img.getWidth(), img.getHeight(), matrix, true));
|
ImageWrapper imageWrapper = ImageWrapper.ofBitmap(mScriptRuntime, Bitmap.createBitmap(img.getBitmap(), 0, 0, img.getWidth(), img.getHeight(), matrix, true));
|
||||||
img.shoot();
|
img.shoot();
|
||||||
return imageWrapper;
|
return imageWrapper;
|
||||||
}
|
}
|
||||||
@@ -313,23 +365,27 @@ public class Images {
|
|||||||
Bitmap original = img.getBitmap();
|
Bitmap original = img.getBitmap();
|
||||||
Matrix matrix = new Matrix();
|
Matrix matrix = new Matrix();
|
||||||
|
|
||||||
// 根据传入参数设置缩放比例
|
// Set scaling ratio according to input parameters.
|
||||||
|
// zh-CN: 根据传入参数设置缩放比例.
|
||||||
float sx = horizontal ? -1.0f : 1.0f;
|
float sx = horizontal ? -1.0f : 1.0f;
|
||||||
float sy = vertical ? -1.0f : 1.0f;
|
float sy = vertical ? -1.0f : 1.0f;
|
||||||
matrix.preScale(sx, sy);
|
matrix.preScale(sx, sy);
|
||||||
|
|
||||||
// 水平方向翻转后, 平移到图像宽度的位置
|
// After horizontal flip, translate to image width position.
|
||||||
|
// zh-CN: 水平方向翻转后, 平移到图像宽度的位置.
|
||||||
if (horizontal) matrix.postTranslate(original.getWidth(), 0);
|
if (horizontal) matrix.postTranslate(original.getWidth(), 0);
|
||||||
// 垂直方向翻转后, 平移到图像高度的位置
|
|
||||||
|
// After vertical flip, translate to image height position.
|
||||||
|
// zh-CN: 垂直方向翻转后, 平移到图像高度的位置.
|
||||||
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();
|
img.shoot();
|
||||||
return ImageWrapper.ofBitmap(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 img, int x, int y, int w, int h) {
|
||||||
ImageWrapper imageWrapper = ImageWrapper.ofBitmap(Bitmap.createBitmap(img.getBitmap(), x, y, w, h));
|
ImageWrapper imageWrapper = ImageWrapper.ofBitmap(mScriptRuntime, Bitmap.createBitmap(img.getBitmap(), x, y, w, h));
|
||||||
img.shoot();
|
img.shoot();
|
||||||
return imageWrapper;
|
return imageWrapper;
|
||||||
}
|
}
|
||||||
@@ -346,7 +402,7 @@ public class Images {
|
|||||||
if (!isStrict) return null;
|
if (!isStrict) return null;
|
||||||
throw new RuntimeException(mContext.getString(R.string.error_file_in_path_does_not_exist, fullPath));
|
throw new RuntimeException(mContext.getString(R.string.error_file_in_path_does_not_exist, fullPath));
|
||||||
}
|
}
|
||||||
return ImageWrapper.ofBitmap(bitmap);
|
return ImageWrapper.ofBitmap(mScriptRuntime, bitmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@@ -357,7 +413,7 @@ public class Images {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ImageWrapper fromBase64(String data) {
|
public ImageWrapper fromBase64(String data) {
|
||||||
return ImageWrapper.ofBitmap(Drawables.loadBase64Data(data));
|
return ImageWrapper.ofBitmap(mScriptRuntime, Drawables.loadBase64Data(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toBase64(ImageWrapper img, String format, int quality) {
|
public String toBase64(ImageWrapper img, String format, int quality) {
|
||||||
@@ -375,8 +431,8 @@ public class Images {
|
|||||||
return outputStream.toByteArray();
|
return outputStream.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImageWrapper fromBytes(byte[] bytes) {
|
public ImageWrapper fromBytes(byte[] bytes) throws BitmapUtils.DecodeException {
|
||||||
return ImageWrapper.ofBitmap(BitmapFactory.decodeByteArray(bytes, 0, bytes.length));
|
return ImageWrapper.ofBitmap(mScriptRuntime, BitmapUtils.bitmapFromByteArrayOrThrow(bytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Bitmap.CompressFormat parseImageFormat(String format) {
|
private Bitmap.CompressFormat parseImageFormat(String format) {
|
||||||
@@ -405,7 +461,7 @@ public class Images {
|
|||||||
HttpURLConnection connection = (HttpURLConnection) new URL(src).openConnection();
|
HttpURLConnection connection = (HttpURLConnection) new URL(src).openConnection();
|
||||||
connection.setDoInput(true);
|
connection.setDoInput(true);
|
||||||
connection.connect();
|
connection.connect();
|
||||||
return ImageWrapper.ofBitmap(BitmapFactory.decodeStream(connection.getInputStream()));
|
return ImageWrapper.ofBitmap(mScriptRuntime, BitmapFactory.decodeStream(connection.getInputStream()));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -441,7 +497,7 @@ public class Images {
|
|||||||
channels.add(3, alphaChannel); // 将原来的 alpha 通道放回最后一个位置
|
channels.add(3, alphaChannel); // 将原来的 alpha 通道放回最后一个位置
|
||||||
|
|
||||||
org.opencv.core.Mat destMat = new org.opencv.core.Mat();
|
org.opencv.core.Mat destMat = new org.opencv.core.Mat();
|
||||||
Core.merge(channels, destMat); // 合并 4 个通道(BGR + 原始 alpha)
|
Core.merge(channels, destMat); // 合并 4 个通道 (BGR + 原始 alpha)
|
||||||
|
|
||||||
// 转换反色后的 Mat 回 Bitmap
|
// 转换反色后的 Mat 回 Bitmap
|
||||||
Bitmap invertedBitmap = Bitmap.createBitmap(originalWidth, originalHeight, Bitmap.Config.ARGB_8888);
|
Bitmap invertedBitmap = Bitmap.createBitmap(originalWidth, originalHeight, Bitmap.Config.ARGB_8888);
|
||||||
@@ -449,7 +505,7 @@ public class Images {
|
|||||||
|
|
||||||
image.shoot();
|
image.shoot();
|
||||||
|
|
||||||
return ImageWrapper.ofBitmap(invertedBitmap);
|
return ImageWrapper.ofBitmap(mScriptRuntime, invertedBitmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void releaseScreenCapturer() {
|
public void releaseScreenCapturer() {
|
||||||
@@ -521,18 +577,20 @@ public class Images {
|
|||||||
shouldReleaseMat = true;
|
shouldReleaseMat = true;
|
||||||
}
|
}
|
||||||
@Nullable
|
@Nullable
|
||||||
org.opencv.core.Point point = TemplateMatching.singleTemplateMatching(
|
org.opencv.core.Point point;
|
||||||
src,
|
try {
|
||||||
template.getMat(),
|
point = TemplateMatching.singleTemplateMatching(
|
||||||
new TemplateMatching.Options(-1, weakThreshold, strictThreshold, maxLevel)
|
src,
|
||||||
);
|
template.getMat(),
|
||||||
|
new TemplateMatching.Options(-1, weakThreshold, strictThreshold, maxLevel)
|
||||||
if (shouldReleaseMat) {
|
);
|
||||||
OpenCVHelper.release(src);
|
} finally {
|
||||||
|
if (shouldReleaseMat) {
|
||||||
|
OpenCVHelper.release(src);
|
||||||
|
}
|
||||||
|
image.shoot();
|
||||||
|
template.shoot();
|
||||||
}
|
}
|
||||||
image.shoot();
|
|
||||||
template.shoot();
|
|
||||||
|
|
||||||
if (point != null) {
|
if (point != null) {
|
||||||
if (rect != null) {
|
if (rect != null) {
|
||||||
point.x += rect.x;
|
point.x += rect.x;
|
||||||
@@ -605,7 +663,7 @@ public class Images {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setImageCaptureCallback(OnScreenCaptureAvailableListener onScreenCaptureAvailableListener) {
|
public void setImageCaptureCallback(OnScreenCaptureAvailableListener onScreenCaptureAvailableListener) {
|
||||||
mOnScreenCaptureAvailableListener = new ScreenCaptureAvailableHandler(onScreenCaptureAvailableListener);
|
mOnScreenCaptureAvailableListener = new ScreenCaptureAvailableHandler(mScriptRuntime, onScreenCaptureAvailableListener);
|
||||||
if (mScreenCapturer != null) {
|
if (mScreenCapturer != null) {
|
||||||
mScreenCapturer.setImageCaptureCallback(mOnScreenCaptureAvailableListener);
|
mScreenCapturer.setImageCaptureCallback(mOnScreenCaptureAvailableListener);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package org.autojs.autojs.runtime.api;
|
package org.autojs.autojs.runtime.api;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
|
||||||
public class PngQuantBridge {
|
public class PngQuantBridge {
|
||||||
|
|
||||||
static {
|
static {
|
||||||
@@ -35,12 +37,8 @@ public class PngQuantBridge {
|
|||||||
* <li>PNG 规范不支持在压缩阶段引入有损处理, 因此不会像 JPEG 那样再出现马赛克等失真.</li>
|
* <li>PNG 规范不支持在压缩阶段引入有损处理, 因此不会像 JPEG 那样再出现马赛克等失真.</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @param rgba contiguous RGBA8888 buffer; length must equal {@code width * height * 4}
|
|
||||||
* @param width image width in pixels
|
|
||||||
* @param height image height in pixels
|
|
||||||
* @param quality libimagequant quality (0–100, lower = smaller file & potentially higher error)
|
|
||||||
* @return palette-based PNG bytes (may contain transparency)
|
* @return palette-based PNG bytes (may contain transparency)
|
||||||
*/
|
*/
|
||||||
public static native byte[] quantize(byte[] rgba, int width, int height, int quality);
|
public static native byte[] quantize(Bitmap bitmap, int quality);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -4,18 +4,17 @@ import android.content.Context;
|
|||||||
import android.media.Image;
|
import android.media.Image;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.autojs.autojs.core.floaty.BaseResizableFloatyWindow;
|
import org.autojs.autojs.core.floaty.BaseResizableFloatyWindow;
|
||||||
import org.autojs.autojs.core.image.capture.ScreenCapturer;
|
import org.autojs.autojs.core.image.capture.ScreenCapturer;
|
||||||
|
import org.autojs.autojs.runtime.ScriptRuntime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by SuperMonster003 on Dec 18, 2023.
|
* Created by SuperMonster003 on Dec 18, 2023.
|
||||||
*/
|
*/
|
||||||
// @Reference to Auto.js Pro 9.3.11 by SuperMonster003 on Dec 18, 2023.
|
// @Reference to Auto.js Pro 9.3.11 by SuperMonster003 on Dec 18, 2023.
|
||||||
public record ScreenCaptureAvailableHandler(Images.OnScreenCaptureAvailableListener listener) implements BaseResizableFloatyWindow.ViewSupplier, ScreenCapturer.OnScreenCaptureAvailableListener {
|
public record ScreenCaptureAvailableHandler(ScriptRuntime scriptRuntime, Images.OnScreenCaptureAvailableListener listener) implements BaseResizableFloatyWindow.ViewSupplier, ScreenCapturer.OnScreenCaptureAvailableListener {
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
@@ -25,6 +24,6 @@ public record ScreenCaptureAvailableHandler(Images.OnScreenCaptureAvailableListe
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCaptureAvailable(Image image) {
|
public void onCaptureAvailable(Image image) {
|
||||||
Images.setImageCaptureCallback(listener, image);
|
Images.setImageCaptureCallback(scriptRuntime, listener, image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ package org.autojs.autojs.runtime.api.augment.images
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
|
import android.net.Uri
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
|
import androidx.core.net.toUri
|
||||||
import org.autojs.autojs.annotation.RhinoRuntimeFunctionInterface
|
import org.autojs.autojs.annotation.RhinoRuntimeFunctionInterface
|
||||||
import org.autojs.autojs.core.image.ColorDetector
|
import org.autojs.autojs.core.image.ColorDetector
|
||||||
import org.autojs.autojs.core.image.ImageWrapper
|
import org.autojs.autojs.core.image.ImageWrapper
|
||||||
@@ -18,6 +20,7 @@ import org.autojs.autojs.extension.ArrayExtensions.toNativeObject
|
|||||||
import org.autojs.autojs.extension.ScriptableExtensions.hasProp
|
import org.autojs.autojs.extension.ScriptableExtensions.hasProp
|
||||||
import org.autojs.autojs.extension.ScriptableExtensions.prop
|
import org.autojs.autojs.extension.ScriptableExtensions.prop
|
||||||
import org.autojs.autojs.extension.ScriptableObjectExtensions.inquire
|
import org.autojs.autojs.extension.ScriptableObjectExtensions.inquire
|
||||||
|
import org.autojs.autojs.extension.StringExtensions.isUri
|
||||||
import org.autojs.autojs.runtime.ScriptRuntime
|
import org.autojs.autojs.runtime.ScriptRuntime
|
||||||
import org.autojs.autojs.runtime.api.ImageFeatureMatching
|
import org.autojs.autojs.runtime.api.ImageFeatureMatching
|
||||||
import org.autojs.autojs.runtime.api.ImageSimilarity
|
import org.autojs.autojs.runtime.api.ImageSimilarity
|
||||||
@@ -30,6 +33,8 @@ import org.autojs.autojs.runtime.api.augment.colors.Colors
|
|||||||
import org.autojs.autojs.runtime.api.augment.s13n.S13n
|
import org.autojs.autojs.runtime.api.augment.s13n.S13n
|
||||||
import org.autojs.autojs.runtime.exception.ShouldNeverHappenException
|
import org.autojs.autojs.runtime.exception.ShouldNeverHappenException
|
||||||
import org.autojs.autojs.runtime.exception.WrappedIllegalArgumentException
|
import org.autojs.autojs.runtime.exception.WrappedIllegalArgumentException
|
||||||
|
import org.autojs.autojs.runtime.exception.WrappedRuntimeException
|
||||||
|
import org.autojs.autojs.util.BitmapUtils
|
||||||
import org.autojs.autojs.util.RhinoUtils.callFunction
|
import org.autojs.autojs.util.RhinoUtils.callFunction
|
||||||
import org.autojs.autojs.util.RhinoUtils.coerceBoolean
|
import org.autojs.autojs.util.RhinoUtils.coerceBoolean
|
||||||
import org.autojs.autojs.util.RhinoUtils.coerceFloatNumber
|
import org.autojs.autojs.util.RhinoUtils.coerceFloatNumber
|
||||||
@@ -47,16 +52,9 @@ import org.mozilla.javascript.ScriptableObject
|
|||||||
import org.opencv.core.CvType
|
import org.opencv.core.CvType
|
||||||
import org.opencv.features2d.DescriptorMatcher
|
import org.opencv.features2d.DescriptorMatcher
|
||||||
import org.opencv.imgproc.Imgproc
|
import org.opencv.imgproc.Imgproc
|
||||||
import java.io.ByteArrayOutputStream
|
import java.net.URL
|
||||||
import kotlin.collections.component1
|
|
||||||
import kotlin.collections.component2
|
|
||||||
import kotlin.collections.component3
|
|
||||||
import kotlin.collections.contains
|
|
||||||
import kotlin.math.floor
|
|
||||||
import kotlin.math.ln
|
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlin.math.pow
|
|
||||||
import kotlin.math.round
|
import kotlin.math.round
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
import kotlin.math.sqrt
|
import kotlin.math.sqrt
|
||||||
@@ -101,7 +99,7 @@ class Images(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime), AsEmitt
|
|||||||
::bilateralFilter.name,
|
::bilateralFilter.name,
|
||||||
::cvtColor.name,
|
::cvtColor.name,
|
||||||
::findCircles.name,
|
::findCircles.name,
|
||||||
::resizeInternal.name,
|
::resize.name,
|
||||||
::scale.name,
|
::scale.name,
|
||||||
::rotate.name,
|
::rotate.name,
|
||||||
::flip.name,
|
::flip.name,
|
||||||
@@ -135,6 +133,8 @@ class Images(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime), AsEmitt
|
|||||||
::isRecycled.name,
|
::isRecycled.name,
|
||||||
::recycle.name,
|
::recycle.name,
|
||||||
::compress.name,
|
::compress.name,
|
||||||
|
::compressToBytes.name,
|
||||||
|
::downsample.name,
|
||||||
::getSize.name,
|
::getSize.name,
|
||||||
::getWidth.name,
|
::getWidth.name,
|
||||||
::getHeight.name,
|
::getHeight.name,
|
||||||
@@ -160,6 +160,9 @@ class Images(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime), AsEmitt
|
|||||||
@JvmField
|
@JvmField
|
||||||
val DEFAULT_IMAGE_SAVE_QUALITY = 100
|
val DEFAULT_IMAGE_SAVE_QUALITY = 100
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
val DEFAULT_IMAGE_COMPRESS_QUALITY = 60
|
||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
val DEFAULT_IMAGE_TO_BYTES_QUALITY = 100
|
val DEFAULT_IMAGE_TO_BYTES_QUALITY = 100
|
||||||
|
|
||||||
@@ -308,7 +311,12 @@ class Images(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime), AsEmitt
|
|||||||
val image = if (o is String) read(scriptRuntime, arrayOf<Any>(o, true)) else o
|
val image = if (o is String) read(scriptRuntime, arrayOf<Any>(o, true)) else o
|
||||||
require(image is ImageWrapper) { "Argument image for images.save must be a ImageWrapper" }
|
require(image is ImageWrapper) { "Argument image for images.save must be a ImageWrapper" }
|
||||||
require(!path.isJsNullish()) { "Argument path for images.save must be non-nullish" }
|
require(!path.isJsNullish()) { "Argument path for images.save must be non-nullish" }
|
||||||
scriptRuntime.images.save(image, scriptRuntime.files.nonNullPath(coerceString(path)), parseImageFormat(format), parseQuality(quality, DEFAULT_IMAGE_SAVE_QUALITY))
|
scriptRuntime.images.save(
|
||||||
|
/* image = */ image,
|
||||||
|
/* path = */ scriptRuntime.files.nonNullPath(coerceString(path)),
|
||||||
|
/* format = */ parseImageFormat(format),
|
||||||
|
/* quality = */ parseQuality(quality, DEFAULT_IMAGE_SAVE_QUALITY),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@@ -318,7 +326,12 @@ class Images(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime), AsEmitt
|
|||||||
val image = if (o is String) read(scriptRuntime, arrayOf<Any>(o, true)) else o
|
val image = if (o is String) read(scriptRuntime, arrayOf<Any>(o, true)) else o
|
||||||
require(image is ImageWrapper) { "Argument image for images.saveImage must be a ImageWrapper" }
|
require(image is ImageWrapper) { "Argument image for images.saveImage must be a ImageWrapper" }
|
||||||
require(!path.isJsNullish()) { "Argument path for images.saveImage must be non-nullish" }
|
require(!path.isJsNullish()) { "Argument path for images.saveImage must be non-nullish" }
|
||||||
scriptRuntime.images.save(image, scriptRuntime.files.nonNullPath(coerceString(path)), parseImageFormat(format), parseQuality(quality, DEFAULT_IMAGE_SAVE_QUALITY))
|
scriptRuntime.images.save(
|
||||||
|
/* image = */ image,
|
||||||
|
/* path = */ scriptRuntime.files.nonNullPath(coerceString(path)),
|
||||||
|
/* format = */ parseImageFormat(format),
|
||||||
|
/* quality = */ parseQuality(quality, DEFAULT_IMAGE_SAVE_QUALITY),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@@ -658,7 +671,7 @@ class Images(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime), AsEmitt
|
|||||||
val imageB = if (oB is String) read(scriptRuntime, arrayOf<Any>(oB, true)) else oB
|
val imageB = if (oB is String) read(scriptRuntime, arrayOf<Any>(oB, true)) else oB
|
||||||
require(imageB is ImageWrapper) { "Argument imageB for images.concat must be a ImageWrapper" }
|
require(imageB is ImageWrapper) { "Argument imageB for images.concat must be a ImageWrapper" }
|
||||||
initOpenCvIfNeeded()
|
initOpenCvIfNeeded()
|
||||||
ApiImages.concat(imageA, imageB, directionToGravityToConcat(direction))
|
ApiImages.concat(scriptRuntime, imageA, imageB, directionToGravityToConcat(direction))
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@@ -988,8 +1001,8 @@ class Images(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime), AsEmitt
|
|||||||
fun matToImage(scriptRuntime: ScriptRuntime, args: Array<out Any?>): ImageWrapper = ensureArgumentsOnlyOne(args) {
|
fun matToImage(scriptRuntime: ScriptRuntime, args: Array<out Any?>): ImageWrapper = ensureArgumentsOnlyOne(args) {
|
||||||
initOpenCvIfNeeded()
|
initOpenCvIfNeeded()
|
||||||
when (it) {
|
when (it) {
|
||||||
is AutoJsMat -> ImageWrapper.ofMat(it)
|
is AutoJsMat -> ImageWrapper.ofMat(scriptRuntime, it)
|
||||||
is OpencvMat -> ImageWrapper.ofMat(it)
|
is OpencvMat -> ImageWrapper.ofMat(scriptRuntime, it)
|
||||||
else -> throw WrappedIllegalArgumentException("Argument mat ${it.jsBrief()} is invalid for images.matToImage()")
|
else -> throw WrappedIllegalArgumentException("Argument mat ${it.jsBrief()} is invalid for images.matToImage()")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1077,18 +1090,71 @@ class Images(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime), AsEmitt
|
|||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@RhinoRuntimeFunctionInterface
|
@RhinoRuntimeFunctionInterface
|
||||||
fun compress(scriptRuntime: ScriptRuntime, args: Array<out Any?>): ImageWrapper = ensureArgumentsLength(args, 2) {
|
fun compress(scriptRuntime: ScriptRuntime, args: Array<out Any?>): ImageWrapper = ensureArgumentsLengthInRange(args, 1..3) {
|
||||||
val (o, compressLevelArg) = it
|
|
||||||
|
// @Comment by SuperMonster003 on May 19, 2025.
|
||||||
|
// ! Option `inSampleSize` performs pixel downsampling during decoding
|
||||||
|
// ! by discarding pixels to reduce resolution, rather than changing encoding quality.
|
||||||
|
// ! zh-CN:
|
||||||
|
// ! 选项 `inSampleSize` 在解码阶段做像素降采样,
|
||||||
|
// ! 通过降低分辨率丢弃像素的方式来实现文件压缩, 而非改变编码质量.
|
||||||
|
// !
|
||||||
|
// # val (o, compressLevelArg) = it
|
||||||
|
// # val image = if (o is String) read(scriptRuntime, arrayOf<Any>(o, true)) else o
|
||||||
|
// # require(image is ImageWrapper) { "Argument image for images.compress must be a ImageWrapper" }
|
||||||
|
// # val compressLevel = coerceNumber(compressLevelArg, 1.0)
|
||||||
|
// # val level = 2.0.pow(floor(ln(compressLevel.coerceAtLeast(1.0)) / ln(2.0))).toInt()
|
||||||
|
// # val outputStream = ByteArrayOutputStream()
|
||||||
|
// # image.bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
|
||||||
|
// # val byteArray = outputStream.toByteArray()
|
||||||
|
// # val options = BitmapFactory.Options().apply { inSampleSize = level }
|
||||||
|
// # val bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size, options)
|
||||||
|
// # ImageWrapper.ofBitmap(bitmap).also { image.shoot() }
|
||||||
|
|
||||||
|
val (o, format, quality) = it
|
||||||
val image = if (o is String) read(scriptRuntime, arrayOf<Any>(o, true)) else o
|
val image = if (o is String) read(scriptRuntime, arrayOf<Any>(o, true)) else o
|
||||||
require(image is ImageWrapper) { "Argument image for images.compress must be a ImageWrapper" }
|
require(image is ImageWrapper) { "Argument image for images.compress must be a ImageWrapper" }
|
||||||
val compressLevel = coerceNumber(compressLevelArg, 1.0)
|
scriptRuntime.images.compress(
|
||||||
val level = 2.0.pow(floor(ln(compressLevel.coerceAtLeast(1.0)) / ln(2.0))).toInt()
|
/* image = */ image,
|
||||||
val outputStream = ByteArrayOutputStream()
|
/* format = */ parseImageFormat(format),
|
||||||
image.bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
|
/* quality = */ parseQuality(quality, DEFAULT_IMAGE_COMPRESS_QUALITY),
|
||||||
val byteArray = outputStream.toByteArray()
|
)
|
||||||
val options = BitmapFactory.Options().apply { inSampleSize = level }
|
}
|
||||||
val bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size, options)
|
|
||||||
ImageWrapper.ofBitmap(bitmap).also { image.shoot() }
|
@JvmStatic
|
||||||
|
@RhinoRuntimeFunctionInterface
|
||||||
|
fun compressToBytes(scriptRuntime: ScriptRuntime, args: Array<out Any?>): ByteArray = ensureArgumentsLengthInRange(args, 1..3) {
|
||||||
|
val (o, format, quality) = it
|
||||||
|
val image = if (o is String) read(scriptRuntime, arrayOf<Any>(o, true)) else o
|
||||||
|
require(image is ImageWrapper) { "Argument image for images.compressToBytes must be a ImageWrapper" }
|
||||||
|
scriptRuntime.images.compressToBytes(
|
||||||
|
/* image = */ image,
|
||||||
|
/* format = */ parseImageFormat(format),
|
||||||
|
/* quality = */ parseQuality(quality, DEFAULT_IMAGE_COMPRESS_QUALITY),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@RhinoRuntimeFunctionInterface
|
||||||
|
fun downsample(scriptRuntime: ScriptRuntime, args: Array<out Any?>): ImageWrapper = ensureArgumentsLengthInRange(args, 3..4) { argList ->
|
||||||
|
val (src, reqWidthArg, reqHeightArg, withAlphaArg) = argList
|
||||||
|
val reqWidth = coerceIntNumber(reqWidthArg)
|
||||||
|
val reqHeight = coerceIntNumber(reqHeightArg)
|
||||||
|
val withAlpha = coerceBoolean(withAlphaArg, true)
|
||||||
|
when (src) {
|
||||||
|
is ByteArray -> BitmapUtils.downsample(src, reqWidth, reqHeight, withAlpha)
|
||||||
|
is String -> when {
|
||||||
|
src.isUri() -> BitmapUtils.downsample(scriptRuntime.uiHandler.applicationContext, src.toUri(), reqWidth, reqHeight, withAlpha)
|
||||||
|
else -> BitmapUtils.downsample(scriptRuntime.files.nonNullPath(src), reqWidth, reqHeight, withAlpha)
|
||||||
|
}
|
||||||
|
is URL -> BitmapUtils.downsample(src, reqWidth, reqHeight, withAlpha)
|
||||||
|
is Uri -> BitmapUtils.downsample(scriptRuntime.uiHandler.applicationContext, src, reqWidth, reqHeight, withAlpha)
|
||||||
|
is Bitmap -> BitmapUtils.downsample(src, reqWidth, reqHeight, withAlpha)
|
||||||
|
is ImageWrapper -> BitmapUtils.downsample(src.bitmap, reqWidth, reqHeight, withAlpha).also { src.shoot() }
|
||||||
|
else -> throw WrappedIllegalArgumentException("Argument src ${src.jsBrief()} is invalid for images.downsample()")
|
||||||
|
}?.let {
|
||||||
|
ImageWrapper.ofBitmap(scriptRuntime, it)
|
||||||
|
} ?: throw WrappedRuntimeException("Failed to downsample image from ${src.jsBrief()}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
package org.autojs.autojs.util
|
package org.autojs.autojs.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import android.graphics.Matrix
|
import android.graphics.Matrix
|
||||||
import android.graphics.drawable.BitmapDrawable
|
import android.graphics.drawable.BitmapDrawable
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.net.Uri
|
||||||
import androidx.core.graphics.createBitmap
|
import androidx.core.graphics.createBitmap
|
||||||
|
import androidx.core.graphics.scale
|
||||||
|
import java.net.URL
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Stardust on Apr 22, 2017.
|
* Created by Stardust on Apr 22, 2017.
|
||||||
|
* Modified by SuperMonster003 as of Jan 21, 2023.
|
||||||
*/
|
*/
|
||||||
object BitmapUtils {
|
object BitmapUtils {
|
||||||
|
|
||||||
@@ -58,4 +64,164 @@ object BitmapUtils {
|
|||||||
return rgba
|
return rgba
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@Throws(DecodeException::class)
|
||||||
|
fun bitmapFromByteArrayOrThrow(bytes: ByteArray): Bitmap {
|
||||||
|
return bitmapFromByteArray(bytes) ?: throw DecodeException("Failed to decode bitmap from bytes")
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun bitmapFromByteArray(bytes: ByteArray): Bitmap? {
|
||||||
|
return BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode and downsample based on inSampleSize (zh-CN: 解码并按 inSampleSize 降采样)
|
||||||
|
*
|
||||||
|
* @param data Compressed image byte stream (zh-CN: 压缩格式图片字节流)
|
||||||
|
* @param reqWidth Target max width (zh-CN: 目标最大宽)
|
||||||
|
* @param reqHeight Target max height (zh-CN: 目标最大高)
|
||||||
|
* @param withAlpha Whether to keep alpha channel; default true=ARGB_8888, false=RGB_565.<br>
|
||||||
|
* zh-CN: 是否保留透明通道; 默认 true=ARGB_8888, false=RGB_565.
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
@JvmOverloads
|
||||||
|
fun downsample(
|
||||||
|
data: ByteArray,
|
||||||
|
reqWidth: Int,
|
||||||
|
reqHeight: Int,
|
||||||
|
withAlpha: Boolean = true,
|
||||||
|
): Bitmap? {
|
||||||
|
val opts = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
||||||
|
BitmapFactory.decodeByteArray(data, 0, data.size, opts)
|
||||||
|
|
||||||
|
opts.inSampleSize = computeSampleSize(opts.outWidth, opts.outHeight, reqWidth, reqHeight)
|
||||||
|
opts.inJustDecodeBounds = false
|
||||||
|
opts.inPreferredConfig = if (withAlpha) Bitmap.Config.ARGB_8888 else Bitmap.Config.RGB_565
|
||||||
|
|
||||||
|
val bmp = BitmapFactory.decodeByteArray(data, 0, data.size, opts)
|
||||||
|
if (!withAlpha) bmp?.setHasAlpha(false)
|
||||||
|
return bmp
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@JvmOverloads
|
||||||
|
fun downsample(
|
||||||
|
filePath: String,
|
||||||
|
reqWidth: Int,
|
||||||
|
reqHeight: Int,
|
||||||
|
withAlpha: Boolean = true,
|
||||||
|
): Bitmap? {
|
||||||
|
val opts = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
||||||
|
BitmapFactory.decodeFile(filePath, opts)
|
||||||
|
|
||||||
|
opts.inSampleSize = computeSampleSize(opts.outWidth, opts.outHeight, reqWidth, reqHeight)
|
||||||
|
opts.inJustDecodeBounds = false
|
||||||
|
opts.inPreferredConfig = if (withAlpha) Bitmap.Config.ARGB_8888 else Bitmap.Config.RGB_565
|
||||||
|
|
||||||
|
val bmp = BitmapFactory.decodeFile(filePath, opts)
|
||||||
|
if (!withAlpha) bmp?.setHasAlpha(false)
|
||||||
|
return bmp
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@JvmOverloads
|
||||||
|
fun downsample(
|
||||||
|
url: URL,
|
||||||
|
reqWidth: Int,
|
||||||
|
reqHeight: Int,
|
||||||
|
withAlpha: Boolean = true,
|
||||||
|
): Bitmap? = runCatching {
|
||||||
|
// Need to run in coroutine or thread in production to avoid blocking main thread.
|
||||||
|
// zh-CN: 生产环境需放到协程或线程中, 避免阻塞主线程.
|
||||||
|
val bytes = url.openStream().use { it.readBytes() }
|
||||||
|
downsample(bytes, reqWidth, reqHeight, withAlpha)
|
||||||
|
}.getOrNull()
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@JvmOverloads
|
||||||
|
fun downsample(
|
||||||
|
src: Bitmap,
|
||||||
|
reqWidth: Int,
|
||||||
|
reqHeight: Int,
|
||||||
|
withAlpha: Boolean = true,
|
||||||
|
): Bitmap {
|
||||||
|
// Already a Bitmap, cannot use inSampleSize, can only scale proportionally.
|
||||||
|
// zh-CN: 已经是 Bitmap, 无法再用 inSampleSize, 只能等比缩放.
|
||||||
|
val scale = calcScale(src.width, src.height, reqWidth, reqHeight)
|
||||||
|
val dstW = (src.width / scale).coerceAtLeast(1)
|
||||||
|
val dstH = (src.height / scale).coerceAtLeast(1)
|
||||||
|
|
||||||
|
val scaled = src.scale(dstW, dstH)
|
||||||
|
|
||||||
|
// If need to adjust pixel format / alpha channel.
|
||||||
|
// zh-CN: 如需调整像素格式 / 透明通道.
|
||||||
|
return when (withAlpha) {
|
||||||
|
scaled.hasAlpha() -> scaled
|
||||||
|
else -> scaled.copy(
|
||||||
|
if (withAlpha) Bitmap.Config.ARGB_8888 else Bitmap.Config.RGB_565,
|
||||||
|
/* isMutable = */ false,
|
||||||
|
).apply { if (!withAlpha) setHasAlpha(false) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@JvmOverloads
|
||||||
|
fun downsample(
|
||||||
|
context: Context,
|
||||||
|
uri: Uri,
|
||||||
|
reqWidth: Int,
|
||||||
|
reqHeight: Int,
|
||||||
|
withAlpha: Boolean = true,
|
||||||
|
): Bitmap? = runCatching {
|
||||||
|
val opts = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
||||||
|
context.contentResolver.openInputStream(uri)?.use {
|
||||||
|
BitmapFactory.decodeStream(it, null, opts)
|
||||||
|
} ?: return@runCatching null
|
||||||
|
|
||||||
|
opts.inSampleSize = computeSampleSize(opts.outWidth, opts.outHeight, reqWidth, reqHeight)
|
||||||
|
opts.inJustDecodeBounds = false
|
||||||
|
opts.inPreferredConfig = if (withAlpha) Bitmap.Config.ARGB_8888 else Bitmap.Config.RGB_565
|
||||||
|
|
||||||
|
val bmp = context.contentResolver.openInputStream(uri)?.use {
|
||||||
|
BitmapFactory.decodeStream(it, null, opts)
|
||||||
|
}
|
||||||
|
if (!withAlpha) bmp?.setHasAlpha(false)
|
||||||
|
bmp
|
||||||
|
}.getOrNull()
|
||||||
|
|
||||||
|
private fun calcScale(
|
||||||
|
srcW: Int, srcH: Int,
|
||||||
|
dstW: Int, dstH: Int,
|
||||||
|
): Int {
|
||||||
|
var scale = 1
|
||||||
|
if (srcH > dstH || srcW > dstW) {
|
||||||
|
while ((srcH / scale) > dstH || (srcW / scale) > dstW) {
|
||||||
|
scale++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return scale
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun computeSampleSize(outWidth: Int, outHeight: Int, reqWidth: Int, reqHeight: Int): Int {
|
||||||
|
|
||||||
|
var inSampleSize = 1
|
||||||
|
|
||||||
|
if (outHeight > reqHeight || outWidth > reqWidth) {
|
||||||
|
val halfHeight = outHeight / 2
|
||||||
|
val halfWidth = outWidth / 2
|
||||||
|
|
||||||
|
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
|
||||||
|
// height and width larger than the requested height and width.
|
||||||
|
// zh-CN: 计算满足所需高度和宽度的2的幂次方的最大采样率.
|
||||||
|
while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
|
||||||
|
inSampleSize *= 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return inSampleSize
|
||||||
|
}
|
||||||
|
|
||||||
|
class DecodeException(message: String) : Exception(message)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.autojs.autojs.util
|
package org.autojs.autojs.util
|
||||||
|
|
||||||
import android.graphics.BitmapFactory
|
|
||||||
import org.opencv.core.Core
|
import org.opencv.core.Core
|
||||||
import org.opencv.core.CvType
|
import org.opencv.core.CvType
|
||||||
import org.opencv.core.Mat
|
import org.opencv.core.Mat
|
||||||
@@ -8,29 +7,6 @@ import org.opencv.imgproc.Imgproc
|
|||||||
|
|
||||||
object ImageUtils {
|
object ImageUtils {
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
|
|
||||||
|
|
||||||
// Raw height and width of image (zh-CN: 图像的原始高度和宽度)
|
|
||||||
val height = options.outHeight
|
|
||||||
val width = options.outWidth
|
|
||||||
|
|
||||||
var inSampleSize = 1
|
|
||||||
|
|
||||||
if (height > reqHeight || width > reqWidth) {
|
|
||||||
val halfHeight = height / 2
|
|
||||||
val halfWidth = width / 2
|
|
||||||
|
|
||||||
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
|
|
||||||
// height and width larger than the requested height and width.
|
|
||||||
// zh-CN: 计算满足所需高度和宽度的2的幂次方的最大采样率.
|
|
||||||
while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
|
|
||||||
inSampleSize *= 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return inSampleSize
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun Mat.to8UC3(): Mat {
|
fun Mat.to8UC3(): Mat {
|
||||||
val src = this
|
val src = this
|
||||||
|
|||||||
@@ -1,110 +1,156 @@
|
|||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
|
#include <android/bitmap.h>
|
||||||
|
#include <android/log.h>
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <setjmp.h>
|
||||||
#include "libimagequant.h"
|
#include "libimagequant.h"
|
||||||
#include "png.h"
|
#include "png.h"
|
||||||
#include <android/log.h>
|
|
||||||
|
|
||||||
#define ALOGE(fmt, ...) __android_log_print(ANDROID_LOG_ERROR, "PNGQ", fmt, ##__VA_ARGS__)
|
#define ALOGE(fmt, ...) __android_log_print(ANDROID_LOG_ERROR, "PNGQ", fmt, ##__VA_ARGS__)
|
||||||
|
|
||||||
|
/* RAII: 保证函数结束时一定会 unlockPixels(). */
|
||||||
|
class BitmapLocker {
|
||||||
|
public:
|
||||||
|
BitmapLocker(JNIEnv *env, jobject bmp)
|
||||||
|
: mEnv(env), mBmp(bmp), mPixels(nullptr), mLocked(false) {}
|
||||||
|
|
||||||
|
bool lock() {
|
||||||
|
if (AndroidBitmap_lockPixels(mEnv, mBmp, &mPixels) != 0 || !mPixels) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mLocked = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *pixels() const { return mPixels; }
|
||||||
|
|
||||||
|
~BitmapLocker() {
|
||||||
|
if (mLocked) {
|
||||||
|
AndroidBitmap_unlockPixels(mEnv, mBmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
JNIEnv *mEnv;
|
||||||
|
jobject mBmp;
|
||||||
|
void *mPixels;
|
||||||
|
bool mLocked;
|
||||||
|
};
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT jbyteArray JNICALL
|
JNIEXPORT jbyteArray
|
||||||
|
|
||||||
|
JNICALL
|
||||||
Java_org_autojs_autojs_runtime_api_PngQuantBridge_quantize(
|
Java_org_autojs_autojs_runtime_api_PngQuantBridge_quantize(
|
||||||
JNIEnv *env, jclass, jbyteArray srcRgba,
|
JNIEnv *env, jclass /*cls*/, jobject jbitmap, jint quality) {
|
||||||
jint width, jint height, jint quality) {
|
|
||||||
|
|
||||||
/* 读取像素. */
|
/* 参数检查 & Bitmap 信息. */
|
||||||
|
if (!jbitmap) {
|
||||||
|
env->ThrowNew(env->FindClass("java/lang/IllegalArgumentException"), "Bitmap is null");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
AndroidBitmapInfo info{};
|
||||||
|
if (AndroidBitmap_getInfo(env, jbitmap, &info) != 0 ||
|
||||||
|
info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
|
||||||
|
env->ThrowNew(env->FindClass("java/lang/IllegalArgumentException"),
|
||||||
|
"Bitmap must be RGBA_8888");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
const int width = static_cast<int>(info.width);
|
||||||
|
const int height = static_cast<int>(info.height);
|
||||||
|
|
||||||
jsize len = env->GetArrayLength(srcRgba);
|
/* 提前声明所有资源. */
|
||||||
std::vector<uint8_t> rgba(len);
|
BitmapLocker locker(env, jbitmap);
|
||||||
env->GetByteArrayRegion(srcRgba, 0, len, reinterpret_cast<jbyte *>(rgba.data()));
|
liq_attr *attr = nullptr;
|
||||||
|
liq_image *image = nullptr;
|
||||||
|
liq_result *res = nullptr;
|
||||||
|
std::vector <uint8_t> indexed; // 后面 resize()
|
||||||
|
const liq_palette *pal = nullptr;
|
||||||
|
std::vector <uint8_t> compressed; // 输出 PNG
|
||||||
|
|
||||||
if (len != width * height * 4) {
|
/* 锁定像素. */
|
||||||
ALOGE("rgba length mismatch, len=%d, expect=%d", len, width * height * 4);
|
if (!locker.lock()) {
|
||||||
|
ALOGE("AndroidBitmap_lockPixels failed");
|
||||||
|
env->ThrowNew(env->FindClass("java/io/IOException"),
|
||||||
|
"AndroidBitmap_lockPixels failed");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* libimagequant 量化. */
|
/* libimagequant. */
|
||||||
|
attr = liq_attr_create();
|
||||||
liq_attr *attr = liq_attr_create();
|
|
||||||
if (!attr) {
|
if (!attr) {
|
||||||
ALOGE("liq_attr_create failed");
|
ALOGE("liq_attr_create failed");
|
||||||
return nullptr;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
if (quality < 0) quality = 0;
|
||||||
|
if (quality > 100) quality = 100;
|
||||||
liq_set_speed(attr, 8);
|
liq_set_speed(attr, 8);
|
||||||
liq_set_quality(attr, quality, quality);
|
liq_set_quality(attr, quality, quality);
|
||||||
liq_set_max_colors(attr, 256);
|
liq_set_max_colors(attr, 256);
|
||||||
|
|
||||||
liq_image *image = liq_image_create_rgba(attr, rgba.data(), width, height, 0);
|
image = liq_image_create_rgba(attr,
|
||||||
liq_result *res;
|
static_cast<uint8_t *>(locker.pixels()),
|
||||||
|
width, height, 0);
|
||||||
liq_error err = liq_image_quantize(image, attr, &res);
|
if (!image) {
|
||||||
if (err != LIQ_OK) {
|
ALOGE("liq_image_create_rgba failed");
|
||||||
ALOGE("liq_image_quantize failed, code=%d", err);
|
goto fail;
|
||||||
liq_image_destroy(image);
|
}
|
||||||
liq_attr_destroy(attr);
|
if (liq_image_quantize(image, attr, &res) != LIQ_OK || !res) {
|
||||||
return nullptr;
|
ALOGE("liq_image_quantize failed");
|
||||||
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t outSize = width * height;
|
/* Remap. */
|
||||||
std::vector<uint8_t> indexed(outSize);
|
indexed.resize(static_cast<size_t>(width) * height);
|
||||||
liq_write_remapped_image(res, image, indexed.data(), outSize);
|
liq_write_remapped_image(res, image, indexed.data(), indexed.size());
|
||||||
const liq_palette *pal = liq_get_palette(res);
|
|
||||||
|
|
||||||
/* 写索引色 PNG 到内存. */
|
pal = liq_get_palette(res);
|
||||||
|
if (!pal || pal->count == 0 || pal->count > 256) {
|
||||||
|
ALOGE("invalid palette, count=%d", pal ? pal->count : -1);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> compressed;
|
/* libpng 写 PNG 到内存. */
|
||||||
do {
|
{
|
||||||
/* palette 检查. */
|
|
||||||
if (!pal || pal->count == 0 || pal->count > 256) {
|
|
||||||
ALOGE("invalid palette count=%d", pal ? pal->count : -1);
|
|
||||||
goto png_fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* libpng 结构体. */
|
|
||||||
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
|
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
|
||||||
nullptr,
|
nullptr,
|
||||||
[](png_structp png_ptr, png_const_charp msg) {
|
[](png_structp png_ptr, png_const_charp msg) {
|
||||||
ALOGE("libpng error: %s", msg);
|
__android_log_print(ANDROID_LOG_ERROR, "PNGQ", "libpng error: %s", msg);
|
||||||
longjmp(png_jmpbuf(png_ptr), 1);
|
longjmp(png_jmpbuf(png_ptr), 1);
|
||||||
},
|
},
|
||||||
nullptr);
|
nullptr);
|
||||||
if (!png_ptr) {
|
if (!png_ptr) {
|
||||||
ALOGE("png_create_write_struct failed");
|
ALOGE("png_create_write_struct failed");
|
||||||
goto png_fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
png_infop info_ptr = png_create_info_struct(png_ptr);
|
png_infop info_ptr = png_create_info_struct(png_ptr);
|
||||||
if (!info_ptr) {
|
if (!info_ptr) {
|
||||||
png_destroy_write_struct(&png_ptr, nullptr);
|
png_destroy_write_struct(&png_ptr, nullptr);
|
||||||
goto png_fail;
|
ALOGE("png_create_info_struct failed");
|
||||||
|
goto fail;
|
||||||
}
|
}
|
||||||
|
if (setjmp(png_jmpbuf(png_ptr))) { // libpng error
|
||||||
/* longjmp error handler. */
|
|
||||||
if (setjmp(png_jmpbuf(png_ptr))) {
|
|
||||||
png_destroy_write_struct(&png_ptr, &info_ptr);
|
png_destroy_write_struct(&png_ptr, &info_ptr);
|
||||||
goto png_fail;
|
ALOGE("libpng longjmp error");
|
||||||
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* custom mem writer. */
|
/* 自定义写函数 => vector. */
|
||||||
struct Writer {
|
struct Writer {
|
||||||
static void PNGAPI write(png_structp png_ptr, png_bytep data, png_size_t len) {
|
static void PNGAPI write(png_structp png_ptr, png_bytep data, png_size_t len) {
|
||||||
auto *vec = static_cast<std::vector<uint8_t> *>(png_get_io_ptr(png_ptr));
|
auto *vec = static_cast<std::vector <uint8_t> *>(png_get_io_ptr(png_ptr));
|
||||||
vec->insert(vec->end(), data, data + len);
|
vec->insert(vec->end(), data, data + len);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void PNGAPI flush(png_structp) {}
|
static void PNGAPI flush(png_structp) {}
|
||||||
};
|
};
|
||||||
std::vector<uint8_t> tmpBuf;
|
png_set_write_fn(png_ptr, &compressed, Writer::write, Writer::flush);
|
||||||
png_set_write_fn(png_ptr, &tmpBuf, Writer::write, Writer::flush);
|
|
||||||
|
|
||||||
/* IHDR. */
|
/* IHDR. */
|
||||||
png_set_IHDR(png_ptr, info_ptr,
|
png_set_IHDR(png_ptr, info_ptr, width, height,
|
||||||
static_cast<png_uint_32>(width),
|
8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE,
|
||||||
static_cast<png_uint_32>(height),
|
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
||||||
8, // bit depth
|
|
||||||
PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE,
|
|
||||||
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
|
||||||
|
|
||||||
png_set_compression_level(png_ptr, 3);
|
png_set_compression_level(png_ptr, 3);
|
||||||
png_set_filter(png_ptr, 0, PNG_FILTER_NONE);
|
png_set_filter(png_ptr, 0, PNG_FILTER_NONE);
|
||||||
|
|
||||||
@@ -124,36 +170,38 @@ Java_org_autojs_autojs_runtime_api_PngQuantBridge_quantize(
|
|||||||
|
|
||||||
png_write_info(png_ptr, info_ptr);
|
png_write_info(png_ptr, info_ptr);
|
||||||
|
|
||||||
/* rows. */
|
/* Rows. */
|
||||||
std::vector<png_bytep> rows(height);
|
std::vector <png_bytep> rows(height);
|
||||||
for (int y = 0; y < height; ++y) {
|
for (int y = 0; y < height; ++y) {
|
||||||
rows[y] = indexed.data() + y * width;
|
rows[y] = indexed.data() + y * width;
|
||||||
}
|
}
|
||||||
png_write_image(png_ptr, rows.data());
|
png_write_image(png_ptr, rows.data());
|
||||||
|
|
||||||
png_write_end(png_ptr, info_ptr);
|
png_write_end(png_ptr, info_ptr);
|
||||||
compressed.swap(tmpBuf);
|
|
||||||
|
|
||||||
/* 无论是否成功, 均销毁. */
|
|
||||||
png_destroy_write_struct(&png_ptr, &info_ptr);
|
png_destroy_write_struct(&png_ptr, &info_ptr);
|
||||||
} while (false);
|
|
||||||
|
|
||||||
png_fail:
|
|
||||||
if (compressed.empty()) {
|
|
||||||
ALOGE("compress failed, return null to Java");
|
|
||||||
env->ThrowNew(env->FindClass("java/io/IOException"),
|
|
||||||
"pngquant native: compress failed");
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 清理. */
|
/* 成功: 回传 Java. */
|
||||||
liq_result_destroy(res);
|
if (compressed.empty()) {
|
||||||
liq_image_destroy(image);
|
ALOGE("compression produced empty data");
|
||||||
liq_attr_destroy(attr);
|
goto fail;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
jbyteArray out = env->NewByteArray(static_cast<jsize>(compressed.size()));
|
||||||
|
env->SetByteArrayRegion(out, 0, static_cast<jsize>(compressed.size()),
|
||||||
|
reinterpret_cast<const jbyte *>(compressed.data()));
|
||||||
|
/* 释放 libimagequant 资源. */
|
||||||
|
liq_result_destroy(res);
|
||||||
|
liq_image_destroy(image);
|
||||||
|
liq_attr_destroy(attr);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
/* 回 JNI. */
|
fail:
|
||||||
jbyteArray out = env->NewByteArray(compressed.size());
|
/* 统一错误处理. */
|
||||||
env->SetByteArrayRegion(out, 0, compressed.size(),
|
if (res) liq_result_destroy(res);
|
||||||
reinterpret_cast<jbyte *>(compressed.data()));
|
if (image) liq_image_destroy(image);
|
||||||
return out;
|
if (attr) liq_attr_destroy(attr);
|
||||||
|
env->ThrowNew(env->FindClass("java/io/IOException"), "pngquant native failed");
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -40,6 +40,7 @@ android {
|
|||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
cppFlags "-std=c++17"
|
cppFlags "-std=c++17"
|
||||||
|
cFlags "-ljnigraphics"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -204,8 +204,8 @@ pluginManagement {
|
|||||||
/* Print concerned info by `System.getProperties()`. */
|
/* Print concerned info by `System.getProperties()`. */
|
||||||
val isShowConcernedSystemProperties = true
|
val isShowConcernedSystemProperties = true
|
||||||
|
|
||||||
val isCleanupPaddleOcr = true
|
val isCleanupPaddleOcr = false
|
||||||
val isCleanupRapidOcr = true
|
val isCleanupRapidOcr = false
|
||||||
|
|
||||||
val fallbackAgpVersion = "7.4.2"
|
val fallbackAgpVersion = "7.4.2"
|
||||||
val fallbackKotlinVersion = "1.7.10"
|
val fallbackKotlinVersion = "1.7.10"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#Sun May 18 20:20:48 CST 2025
|
#Tue May 20 22:30:54 CST 2025
|
||||||
BUILD_TIME=1747570848298
|
BUILD_TIME=1747751454685
|
||||||
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=3234
|
VERSION_BUILD=3238
|
||||||
VERSION_NAME=6.6.3 Alpha5
|
VERSION_NAME=6.6.3 Alpha5
|
||||||
VSCODE_EXT_REQUIRED_VERSION=1.0.8
|
VSCODE_EXT_REQUIRED_VERSION=1.0.8
|
||||||
|
|||||||
Reference in New Issue
Block a user