Browse Source

拖动编辑目标的轮廓、长、宽。

Ulric 1 tuần trước cách đây
mục cha
commit
043252b22e

+ 30 - 11
app/src/main/java/com/ys/imageProcess/ui/work/CanvasView.kt

@@ -17,9 +17,9 @@ import com.ys.imageProcess.ui.theme.ImageProcTheme
 @Composable
 fun CanvasView(
     modifier: Modifier = Modifier,
-    contour: List<PointF> = listOf(),
-    length: List<PointF> = listOf(),
-    width: List<PointF> = listOf(),
+    contour: List<Offset> = listOf(),
+    length: List<Offset> = listOf(),
+    width: List<Offset> = listOf(),
     fillColor: Color = Color.Transparent,
     scale: Float = 1f,
     state: ImageState = ImageState()
@@ -27,19 +27,38 @@ fun CanvasView(
     Canvas(modifier.fillMaxSize()) {
         val strokeWidth = 5 / scale
         if (state.showContour) {
-            drawPolygon(contour, fillColor, Color.Green, strokeWidth, state.editMode,true)
+            drawPolygon(
+                contour,
+                fillColor,
+                Color.Green,
+                strokeWidth,
+                state.editMode,
+                true
+            )
         }
         if (state.showWidth) {
-            drawPolygon(width, fillColor, Color.Blue, strokeWidth, state.editMode)
+            drawPolygon(
+                width,
+                fillColor,
+                Color.Blue,
+                strokeWidth,
+                state.editMode
+            )
         }
         if (state.showLength) {
-            drawPolygon(length, fillColor, Color.Red, strokeWidth, state.editMode)
+            drawPolygon(
+                length,
+                fillColor,
+                Color.Red,
+                strokeWidth,
+                state.editMode
+            )
         }
     }
 }
 
 private fun DrawScope.drawPolygon(
-    points: List<PointF>,
+    points: List<Offset>,
     fillColor: Color,
     strokeColor: Color,
     strokeWidth: Float,
@@ -90,10 +109,10 @@ fun DefaultPreview() {
     ImageProcTheme {
         CanvasView(
             contour = listOf(
-                PointF(50f, 50f),
-                PointF(200f, 50f),
-                PointF(200f, 200f),
-                PointF(50f, 200f)
+                Offset(50f, 50f),
+                Offset(200f, 50f),
+                Offset(200f, 200f),
+                Offset(50f, 200f)
             )
         )
     }

+ 35 - 16
app/src/main/java/com/ys/imageProcess/ui/work/CentralImageView.kt

@@ -1,26 +1,25 @@
 package com.ys.imageProcess.ui.work
 
-import android.content.ContentValues.TAG
 import android.graphics.BitmapFactory
-import android.nfc.Tag
-import android.util.Log
+import android.graphics.PointF
 import androidx.compose.foundation.Image
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.scale
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.geometry.center
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.ImageBitmap
 import androidx.compose.ui.graphics.asImageBitmap
 import androidx.compose.ui.layout.ContentScale
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.tooling.preview.Preview
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import androidx.lifecycle.viewmodel.compose.viewModel
 import com.ys.imageProcess.R
 import com.ys.imageProcess.viewModel.CentralImageViewModel
@@ -32,21 +31,43 @@ fun CentralImageView(
     bitmap: ImageBitmap? = null,
     canvasSize: Size = Size(0f, 0f),
     data: ProcessedData = ProcessedData(),
+    contour: List<Offset> = listOf(),
+    width: List<Offset> = listOf(),
+    length: List<Offset> = listOf(),
     imageState: ImageState = ImageState(),
     scale: Float = 1f,
+    offset: Offset = Offset(0f, 0f),
     viewModel: CentralImageViewModel = viewModel(),
 ) {
     LaunchedEffect(data) {
-        Log.i(TAG, "set data")
-        viewModel.setData(canvasSize, data)
+        //        Log.i(TAG, "set data")
+        //        viewModel.setData(canvasSize, data)
     }
 
     Box(
         modifier.background(Color.White)
     ) {
-        val contour by viewModel.contour.collectAsStateWithLifecycle()
-        val width by viewModel.width.collectAsStateWithLifecycle()
-        val length by viewModel.length.collectAsStateWithLifecycle()
+        //        val contour by viewModel.contour.collectAsStateWithLifecycle()
+        //        val width by viewModel.width.collectAsStateWithLifecycle()
+        //        val length by viewModel.length.collectAsStateWithLifecycle()
+
+        val w = mutableListOf<Offset>()
+        val center = canvasSize.center
+
+        for (origin in width) {
+            val originToCenter = origin - center
+            val finalToCenter = originToCenter * scale + offset
+            val final = finalToCenter + center
+
+            val rect = Rect(center * 0.5f, center * 1.5f)
+            if (rect.contains(final)) {
+                val np = Offset(origin.x + 10, origin.y)
+                w.add(np)
+            } else {
+                w.add(origin)
+            }
+        }
+
 
         bitmap?.let {
             Image(
@@ -60,7 +81,7 @@ fun CentralImageView(
 
             CanvasView(
                 contour = contour,
-                width = width,
+                width = w,
                 length = length,
                 state = imageState,
                 scale = scale
@@ -72,10 +93,8 @@ fun CentralImageView(
 @Preview(device = "id:pixel_tablet")
 @Composable
 private fun ImagePreview() {
-    val bitmap =
-        BitmapFactory.decodeResource(
-            LocalContext.current.resources,
-            R.drawable.img_example
-        )
+    val bitmap = BitmapFactory.decodeResource(
+        LocalContext.current.resources, R.drawable.img_example
+    )
     CentralImageView(bitmap = bitmap.asImageBitmap())
 }

+ 21 - 2
app/src/main/java/com/ys/imageProcess/ui/work/WorkScreen.kt

@@ -51,7 +51,7 @@ fun WorkScreen(
 
     LaunchedEffect(projId) {
         viewModel.setProject(projId, context)
-        viewModel.getProcessedData()
+        viewModel.processData()
     }
 
     val imageData by viewModel.imageData.collectAsStateWithLifecycle()
@@ -62,6 +62,10 @@ fun WorkScreen(
     val offset by viewModel.offset.collectAsStateWithLifecycle()
     val visibleArea by viewModel.visibleArea.collectAsStateWithLifecycle()
 
+    val contour by viewModel.contourPath.collectAsStateWithLifecycle()
+    val width by viewModel.widthPath.collectAsStateWithLifecycle()
+    val length by viewModel.lengthPath.collectAsStateWithLifecycle()
+
     val density = LocalDensity.current.density
 
     Row(modifier.fillMaxSize()) {
@@ -93,6 +97,10 @@ fun WorkScreen(
                         viewModel.setCanvasSize(canvasSize)
                     }
 
+                    LaunchedEffect(canvasSize, processedData) {
+                        viewModel.transformPoints()
+                    }
+
                     val aniOffset by animateOffsetAsState(
                         targetValue = offset,
                         label = ""
@@ -111,8 +119,12 @@ fun WorkScreen(
                         it,
                         canvasSize,
                         processedData,
+                        contour,
+                        width,
+                        length,
                         imageState,
-                        scale
+                        scale,
+                        offset
                     )
 
                     AnchorLayer(
@@ -128,6 +140,13 @@ fun WorkScreen(
                             .align(Alignment.TopEnd)
                             .padding(20.dp)
                     )
+
+                    Spacer(
+                        modifier = Modifier
+                            .fillMaxSize(0.5f)
+                            .align(Alignment.Center)
+                            .background(Color(0x33000000))
+                    )
                 }
                 ViewToolBar(
                     Modifier

+ 160 - 7
app/src/main/java/com/ys/imageProcess/viewModel/WorkViewModel.kt

@@ -10,6 +10,7 @@ import android.util.Log
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.geometry.center
 import androidx.compose.ui.graphics.ImageBitmap
 import androidx.compose.ui.graphics.asImageBitmap
 import androidx.lifecycle.ViewModel
@@ -44,10 +45,18 @@ class WorkViewModel : ViewModel() {
 
     val processedData = MutableStateFlow(ProcessedData())
 
+    val contourPath = MutableStateFlow(listOf<Offset>())
+    val lengthPath = MutableStateFlow(listOf<Offset>())
+    val widthPath = MutableStateFlow(listOf<Offset>())
+
     val mapData = MutableStateFlow(mapOf<String, String>())
 
     val imageState = MutableStateFlow(ImageState())
 
+    private var isEditingVertex = false // 正在编辑目标信息
+    private var editingPath = -1 // 正在编辑的路径,0:轮廓,1:长,2:宽
+    private var editingPoint = -1 // 正在编辑的顶点坐标的索引
+
     private var isAlgRunning: Boolean = false
 
     private var canvasSize = Size(1f, 1f)
@@ -107,7 +116,7 @@ class WorkViewModel : ViewModel() {
         imageState.value = ImageState(zoom = zoom)
     }
 
-    fun getProcessedData() {
+    fun processData() {
         processScope.launch {
             val original = imageData.value
             val name = original.name.substringBeforeLast(".")
@@ -130,7 +139,7 @@ class WorkViewModel : ViewModel() {
                     }
                 }
 
-                Log.i(TAG, "--- $scale, $topLeft")
+                Log.i(TAG, "scale: $scale, topLeft: $topLeft")
 
                 val store = ProcessedData(bitmap)
                 store.setMap(storedMap)
@@ -171,6 +180,7 @@ class WorkViewModel : ViewModel() {
             final?.let {
                 processedData.value = it
                 updateInfoMap()
+                Log.i(TAG, "update processedData")
             }
         }
     }
@@ -196,6 +206,71 @@ class WorkViewModel : ViewModel() {
         mapData.value = map
     }
 
+    fun transformPoints() {
+        val sizeValid = canvasSize.width * canvasSize.height > 1
+        val hasData = processedData.value.bitmap != null
+        Log.i(TAG, "size valid:$sizeValid, has data: $hasData")
+        if (sizeValid && hasData) {
+            Log.i(TAG, "image:$imageSize, canvas:$canvasSize")
+            convertContour()
+            convertWidth()
+            convertLength()
+        }
+    }
+
+    /**
+     * 给定屏幕上一个位置,查找目标身上处于这个位置的点在数组中的索引
+     * 本函数会依次遍历轮廓、长、宽各个数组,找到数组中处于该位置的点
+     * @param position 屏幕上点的坐标,这里是相对于画布左上角的位移
+     * @return Pair<Int, Int>,第一个 Int 是数组的标记,0:轮廓,1:长,2:宽;第二个 Int 表示点在数组中的索引。
+     * @return Pair(-1, -1),表示没有找到目标点
+     */
+    fun findVertexAt(position: Offset): Pair<Int, Int> {
+
+        /**
+         * 给定屏幕上一个位置,在数组中查找此坐标附近的点
+         * 由于画布经过了缩放、平移等操作,所以需要把数组中的坐标经过相同的变换映射到新的坐标
+         * @param list 画布上点的数组,这里指轮廓,长或宽的数组
+         * @return 点在画布上的位置,-1 表示查无此点
+         */
+        fun findVertex(list: List<Offset>): Int {
+            val center = canvasSize.center
+            val distance = 20f
+
+            for (i in list.indices) {
+                val p = list[i]
+                val pToCenter = p - center
+                val p1ToCenter = pToCenter * scale.value + offset.value
+                val p1 = p1ToCenter + center
+
+                val rect = Rect(position, distance)
+                if (rect.contains(p1)) {
+                    return i
+                }
+            }
+            return -1
+        }
+
+        var pathIndex = -1
+        var pointIndex = -1
+
+        val paths: List<List<Offset>> = listOf(
+            contourPath.value,
+            lengthPath.value,
+            widthPath.value
+        )
+
+        for (i in paths.indices) {
+            pointIndex = findVertex(paths[i])
+            if (pointIndex >= 0) {
+                pathIndex = i
+                break
+            }
+        }
+
+        return Pair(pathIndex, pointIndex)
+    }
+
     fun executeImageAction(action: ViewAction) {
         when (action) {
             ViewAction.Back -> {}
@@ -224,22 +299,66 @@ class WorkViewModel : ViewModel() {
 
     fun onDrag(position: Offset) {
 
-//        Log.i(TAG, "drag $position")
+        //        Log.i(TAG, "drag $position")
 
+        // 开始拖动
         if (dragStartPos == null) {
             dragStartPos = position
+
+            if (imageState.value.editMode) {
+                val (pathIndex, pointIndex) = findVertexAt(position)
+                isEditingVertex = pathIndex >= 0 && pointIndex >= 0
+                if (isEditingVertex) {
+                    editingPath = pathIndex
+                    editingPoint = pointIndex
+                }
+            }
+
+            Log.i(
+                TAG, "editing:$isEditingVertex, path: $editingPath, " +
+                        "point:$editingPoint"
+            )
         }
 
-        offset.value = lastOffset + position - dragStartPos!!
+        if (isEditingVertex) {
+            // 拖动目标
+            val newVertex =
+                (position - offset.value - canvasSize.center) / scale
+                    .value + canvasSize.center
+
+            if (editingPath == 0) {
+                val newPath = contourPath.value.toMutableList()
+                newPath[editingPoint] = newVertex
+                contourPath.value = newPath
+            } else if (editingPath == 1) {
+                val newPath = lengthPath.value.toMutableList()
+                newPath[editingPoint] = newVertex
+                lengthPath.value = newPath
+            } else if (editingPath == 2) {
+                val newPath = widthPath.value.toMutableList()
+                newPath[editingPoint] = newVertex
+                widthPath.value = newPath
+            }
+        } else {
+            // 拖动图像
+            offset.value = lastOffset + position - dragStartPos!!
+        }
     }
 
+    // 结束拖动
     fun onDragEnd() {
         dragStartPos = null
         dragEndPos = null
-        lastOffset = offset.value
 
-        backToVisibleArea()
-        updateVisibleArea()
+        if (isEditingVertex) {
+            isEditingVertex = false
+            // 拖动目标结束
+        } else {
+            // 拖动图像结束
+            lastOffset = offset.value
+            backToVisibleArea()
+            updateVisibleArea()
+        }
     }
 
     private fun zoomIn() {
@@ -338,6 +457,40 @@ class WorkViewModel : ViewModel() {
 
         visibleArea.value = Rect(Offset(px1, py1), Offset(px2, py2))
     }
+
+    private fun convertContour() {
+        val list = mutableListOf<Offset>()
+        processedData.value.contour.forEach { p: PointF ->
+            list.add(imageToCanvas(Offset(p.x, p.y)))
+        }
+        contourPath.value = list
+    }
+
+    private fun convertLength() {
+        val list = mutableListOf<Offset>()
+        processedData.value.lengthLine.forEach { p: PointF ->
+            list.add(imageToCanvas(Offset(p.x, p.y)))
+        }
+        lengthPath.value = list
+    }
+
+    private fun convertWidth() {
+        val list = mutableListOf<Offset>()
+        processedData.value.widthLine.forEach { p: PointF ->
+            list.add(imageToCanvas(Offset(p.x, p.y)))
+        }
+        widthPath.value = list
+    }
+
+    /**
+     * 使用 Fit 模式填充画布时,将图像上点的坐标转换为画布上点的坐标
+     */
+    private fun imageToCanvas(origin: Offset): Offset {
+        val zoom = zoomToFit(canvasSize, imageSize)
+        val offsetToImageCenter = origin - imageSize.center
+        val final = offsetToImageCenter * zoom + canvasSize.center
+        return final
+    }
 }
 
 data class ImageData(