Skip to content

Commit c8d6ef9

Browse files
author
Ven
committed
feat: 图像识别、协程步骤优化
1 parent 97ce5b6 commit c8d6ef9

25 files changed

+520
-186
lines changed

assists/src/main/AndroidManifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
android:resource="@xml/assists_provider_paths" />
1313
</provider>
1414
<service
15-
android:name="com.ven.assists.ScreenService"
15+
android:name="com.ven.assists.ScreenCaptureService"
1616
android:enabled="true"
1717
android:exported="false"
1818
android:foregroundServiceType="mediaProjection" />

assists/src/main/java/com/ven/assists/Assists.kt

Lines changed: 50 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,16 @@ import com.blankj.utilcode.util.ActivityUtils
2424
import com.blankj.utilcode.util.LogUtils
2525
import com.blankj.utilcode.util.ScreenUtils
2626
import com.blankj.utilcode.util.ThreadUtils
27+
import com.ven.assists.stepper.ScreenCaptureAutoEnable
28+
import com.ven.assists.stepper.Step
29+
import com.ven.assists.stepper.StepCollector
2730
import com.ven.assists.stepper.StepManager
31+
import kotlinx.coroutines.CompletableDeferred
2832
import kotlinx.coroutines.CoroutineScope
2933
import kotlinx.coroutines.Dispatchers
3034
import kotlinx.coroutines.Job
35+
import kotlinx.coroutines.delay
36+
import kotlinx.coroutines.withContext
3137

3238
object Assists {
3339

@@ -40,6 +46,8 @@ object Assists {
4046
val screenRequestLaunchers: HashMap<Activity, ActivityResultLauncher<Intent>> = hashMapOf()
4147

4248
private var job = Job()
49+
50+
//协程域
4351
var coroutine: CoroutineScope = CoroutineScope(job + Dispatchers.IO)
4452
private set
4553
get() {
@@ -117,15 +125,16 @@ object Assists {
117125
.createScreenCaptureIntent()
118126
)
119127
if (isAutoEnable && service != null) {
128+
StepManager.execute(ScreenCaptureAutoEnable::class.java, 1)
120129
}
121130
}
122131
}
123132

124133
/**
125134
* 是否拥有录屏权限
126135
*/
127-
fun isOwnScreenCapture(): Boolean {
128-
return screenCaptureService == null
136+
fun isEnableScreenCapture(): Boolean {
137+
return screenCaptureService != null
129138
}
130139

131140
fun init(application: Application) {
@@ -193,6 +202,20 @@ object Assists {
193202
return arrayListOf()
194203
}
195204

205+
/**
206+
* 判断元素是否包含指定的文本
207+
*/
208+
fun AccessibilityNodeInfo?.containsText(text: String): Boolean {
209+
if (this == null) return false
210+
getText()?.let {
211+
if (it.contains(text)) return true
212+
}
213+
contentDescription?.let {
214+
if (it.contains(text)) return true
215+
}
216+
return false
217+
}
218+
196219

197220
/**
198221
* 根据类型查找元素
@@ -285,17 +308,17 @@ object Assists {
285308
* @return 执行手势动作总耗时
286309
*/
287310
@JvmStatic
288-
fun gesture(
311+
suspend fun gesture(
289312
startLocation: FloatArray,
290313
endLocation: FloatArray,
291314
startTime: Long,
292315
duration: Long,
293-
): Long {
316+
) {
294317
gestureListeners.forEach { it.onGestureBegin(startLocation, endLocation) }
295318
val path = Path()
296319
path.moveTo(startLocation[0], startLocation[1])
297320
path.lineTo(endLocation[0], endLocation[1])
298-
return gesture(path, startTime, duration)
321+
gesture(path, startTime, duration)
299322
}
300323

301324
/**
@@ -306,34 +329,35 @@ object Assists {
306329
* @return 执行手势动作总耗时
307330
*/
308331
@JvmStatic
309-
fun gesture(
332+
suspend fun gesture(
310333
path: Path,
311334
startTime: Long,
312335
duration: Long,
313-
): Long {
336+
) {
314337
val builder = GestureDescription.Builder()
315338
val strokeDescription = GestureDescription.StrokeDescription(path, startTime, duration)
316339
val gestureDescription = builder.addStroke(strokeDescription).build()
317-
object : AccessibilityService.GestureResultCallback() {
340+
val deferred = CompletableDeferred<Int>()
341+
service?.dispatchGesture(gestureDescription, object : AccessibilityService.GestureResultCallback() {
318342
override fun onCompleted(gestureDescription: GestureDescription) {
319-
gestureListeners.forEach { it.onGestureCompleted() }
320-
gestureListeners.forEach { it.onGestureEnd() }
343+
deferred.complete(1)
321344
}
322345

323346
override fun onCancelled(gestureDescription: GestureDescription) {
347+
deferred.complete(0)
324348
gestureListeners.forEach { it.onGestureCancelled() }
325349
gestureListeners.forEach { it.onGestureEnd() }
326350
}
327-
}.apply {
328-
if (gestureBeginDelay == 0L) {
329-
service?.dispatchGesture(gestureDescription, this, null)
330-
} else {
331-
ThreadUtils.runOnUiThreadDelayed({
332-
service?.dispatchGesture(gestureDescription, this, null)
333-
}, gestureBeginDelay)
334-
}
351+
}, null) ?: let {
352+
deferred.complete(0)
353+
}
354+
val result = deferred.await()
355+
if (result == 1) {
356+
gestureListeners.forEach { it.onGestureCompleted() }
357+
} else {
358+
gestureListeners.forEach { it.onGestureCancelled() }
335359
}
336-
return gestureBeginDelay + startTime + duration
360+
gestureListeners.forEach { it.onGestureEnd() }
337361
}
338362

339363
/**
@@ -348,9 +372,9 @@ object Assists {
348372
/**
349373
* 手势点击元素所处的位置
350374
*/
351-
fun AccessibilityNodeInfo.gestureClick(): Long {
375+
suspend fun AccessibilityNodeInfo.gestureClick() {
352376
val rect = getBoundsInScreen()
353-
return gestureClick(rect.left + 15F, rect.top + 15F, 10)
377+
gestureClick(rect.left + 15F, rect.top + 15F, 10)
354378
}
355379

356380
/**
@@ -363,21 +387,21 @@ object Assists {
363387
/**
364388
* 手势长按元素所处的位置
365389
*/
366-
fun AccessibilityNodeInfo.gestureLongClick(): Long {
390+
suspend fun AccessibilityNodeInfo.gestureLongClick() {
367391
val rect = getBoundsInScreen()
368-
return gestureClick(rect.left + 15F, rect.top + 15F, 1000)
392+
gestureClick(rect.left + 15F, rect.top + 15F, 1000)
369393
}
370394

371395
/**
372396
* 点击屏幕指定位置
373397
* @return 执行手势动作总耗时
374398
*/
375-
fun gestureClick(
399+
suspend fun gestureClick(
376400
x: Float,
377401
y: Float,
378402
duration: Long = 10
379-
): Long {
380-
return gesture(
403+
) {
404+
gesture(
381405
floatArrayOf(x, y), floatArrayOf(x, y),
382406
0,
383407
duration,

assists/src/main/java/com/ven/assists/AssistsWindowLayout.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ class AssistsWindowLayout @JvmOverloads constructor(
8585

8686
init {
8787

88-
setBackgroundColor(Color.parseColor("#80000000"))
88+
setBackgroundColor(Color.parseColor("#4D000000"))
8989

9090
assistsWindowLayoutWrapperBinding = AssistsWindowLayoutWrapperBinding.inflate(LayoutInflater.from(getContext()), this).apply {
9191
ivMove.setOnTouchListener(onTouchMoveListener)

assists/src/main/java/com/ven/assists/AssistsWindowManager.kt

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import android.view.View
99
import android.view.ViewGroup
1010
import android.view.WindowManager
1111
import androidx.core.view.isInvisible
12+
import androidx.core.view.isVisible
1213
import java.util.Collections
1314

1415
object AssistsWindowManager {
@@ -48,13 +49,22 @@ object AssistsWindowManager {
4849
}
4950
}
5051

52+
fun showLastView() {
53+
viewList.lastOrNull()?.let {
54+
it.view.isVisible = true
55+
}
56+
switchTouchableAll()
57+
}
58+
5159

52-
fun addView(view: View?, params: ViewGroup.LayoutParams) {
60+
fun addView(view: View?, params: ViewGroup.LayoutParams, isStack: Boolean = false) {
5361
view ?: return
54-
viewList.forEach {
55-
it.view.isInvisible = true
56-
if (it.view is AssistsWindowLayout) {
57-
it.view.switchNotTouchable()
62+
if (!isStack) {
63+
viewList.forEach {
64+
it.view.isInvisible = true
65+
if (it.view is AssistsWindowLayout) {
66+
it.view.switchNotTouchable()
67+
}
5868
}
5969
}
6070

assists/src/main/java/com/ven/assists/OpencvWrapper.kt

Lines changed: 111 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
1-
package com.ven.assists
1+
package com.ven.assists
22

33
import android.graphics.Bitmap
44
import android.graphics.BitmapFactory
5+
import android.util.Log
56
import com.blankj.utilcode.util.ActivityUtils
67
import com.blankj.utilcode.util.LogUtils
7-
import kotlinx.coroutines.Dispatchers
8-
import kotlinx.coroutines.GlobalScope
8+
import com.blankj.utilcode.util.PathUtils
99
import kotlinx.coroutines.launch
10-
import kotlinx.coroutines.withContext
1110
import org.opencv.android.OpenCVLoader
1211
import org.opencv.android.Utils
1312
import org.opencv.core.Core
13+
import org.opencv.core.CvType
1414
import org.opencv.core.Mat
1515
import org.opencv.core.Point
16+
import org.opencv.core.Rect
17+
import org.opencv.core.Scalar
1618
import org.opencv.imgproc.Imgproc
19+
import java.io.FileOutputStream
1720
import java.io.IOException
1821
import java.io.InputStream
22+
import kotlin.math.abs
23+
1924

2025
object OpencvWrapper {
2126

@@ -29,22 +34,111 @@ object OpencvWrapper {
2934
}
3035
}
3136

32-
fun matchTemplateFromScreen(image: Mat, template: Mat): Point {
37+
fun matchTemplate(image: Mat?, template: Mat?, mask: Mat? = null): Mat? {
38+
image ?: return null
39+
template ?: return null
40+
val resultCols: Int = image.cols() - template.cols() + 1
41+
val resultRows: Int = image.rows() - template.rows() + 1
42+
val result = Mat(resultRows, resultCols, CvType.CV_32FC1)
43+
val method = Imgproc.TM_CCORR_NORMED
44+
if (mask == null) {
45+
Imgproc.matchTemplate(image, template, result, method)
46+
47+
} else {
48+
Imgproc.matchTemplate(image, template, result, method, mask)
49+
50+
}
51+
return result
52+
}
53+
54+
fun getResultWithThreshold(
55+
result: Mat,
56+
threshold: Double,
57+
ignoreX: Double = -1.0,
58+
ignoreY: Double = -1.0,
59+
): ArrayList<Point> {
60+
val resultList = arrayListOf<Point>()
61+
for (y in 0 until result.rows()) {
62+
for (x in 0 until result.cols()) {
63+
val matchValue = result[y, x]
64+
if (matchValue[0] >= threshold) {
65+
val point = Point(x.toDouble(), y.toDouble())
66+
if (resultList.isEmpty() || (ignoreX == -1.0 && ignoreY == -1.0)) {
67+
resultList.add(point)
68+
} else {
69+
var ignore = false
70+
for (value in resultList) {
71+
val ignoreValueX = abs(point.x - value.x)
72+
val ignoreValueY = abs(point.y - value.y)
73+
if (ignoreX != -1.0 && ignoreValueX < ignoreX) {
74+
ignore = true
75+
break
76+
}
77+
if (ignoreY != -1.0 && ignoreValueY < ignoreY) {
78+
ignore = true
79+
break
80+
}
81+
}
82+
if (!ignore) {
83+
resultList.add(point)
84+
}
85+
}
86+
}
87+
}
88+
}
89+
return resultList
90+
}
91+
92+
fun matchTemplateFromScreenToMinMaxLoc(image: Mat?, template: Mat?, mask: Mat? = null): Core.MinMaxLocResult? {
93+
image ?: return null
94+
template ?: return null
3395
val result = Mat()
34-
val method = Imgproc.TM_CCOEFF_NORMED
35-
Imgproc.matchTemplate(image, template, result, method)
96+
val method = Imgproc.TM_CCORR_NORMED
97+
Imgproc.matchTemplate(image, template, result, method, mask)
3698
val minMaxLocResult = Core.minMaxLoc(result)
37-
return minMaxLocResult.maxLoc
99+
return minMaxLocResult
38100
}
39101

40-
fun getScreen() {
102+
103+
/**
104+
* 创建掩膜
105+
*/
106+
fun createMask(
107+
source: Mat,
108+
lowerGreen: Scalar,
109+
upperGreen: Scalar,
110+
requisiteExtraRectList: List<Rect> = arrayListOf(),
111+
redundantExtraRectList: List<Rect> = arrayListOf()
112+
): Mat {
113+
val hsvImage = Mat()
114+
Imgproc.cvtColor(source, hsvImage, Imgproc.COLOR_BGR2HSV)
115+
val mask = Mat()
116+
Core.inRange(hsvImage, lowerGreen, upperGreen, mask)
117+
requisiteExtraRectList.forEach {
118+
Imgproc.rectangle(mask, it, Scalar(0.0), -1)
119+
}
120+
redundantExtraRectList.forEach {
121+
Imgproc.rectangle(mask, it, Scalar(255.0), -1)
122+
123+
}
124+
return mask
125+
}
126+
127+
/**
128+
* 获取屏幕图像
129+
*/
130+
fun getScreen(): Mat {
41131
val screenBitmap = Assists.screenCaptureService?.toBitmap()
42132
val screenMat = Mat()
43133
Utils.bitmapToMat(screenBitmap, screenMat)
44134
Imgproc.cvtColor(screenMat, screenMat, Imgproc.COLOR_RGBA2BGR)
135+
return screenMat
45136
}
46137

47-
fun getTemplateFromAsset(assetPath: String): Mat? {
138+
/**
139+
* 从Assets获取图像
140+
*/
141+
fun getTemplateFromAssets(assetPath: String): Mat? {
48142
val bitmap = getBitmapFromAsset(assetPath)
49143
bitmap ?: return null
50144
val mat = Mat()
@@ -73,5 +167,12 @@ object OpencvWrapper {
73167
}
74168
}
75169

170+
class MinMaxLocResultWrapper(val minMaxLocResult: Core.MinMaxLocResult, val targetMat: Mat?) {
76171

172+
}
173+
174+
175+
class ResultWrapper(val result: Mat, val targetMat: Mat?) {
176+
177+
}
77178
}

0 commit comments

Comments
 (0)