Jetpack Compose androidx.compose.ui.graphics.Canvas не обновляется правильно для обрезки растрового изображения

С androidx.compose.foundation.Canvas, холст по умолчанию для Jetpack Compose

      @Composable
fun Canvas(modifier: Modifier, onDraw: DrawScope.() -> Unit) =
    Spacer(modifier.drawBehind(onDraw

правильно обновляет рисунок на холсте при изменении mutableState

      var offset by remember {
    mutableStateOf(Offset(bitmapWidth / 2f, bitmapHeight / 2f))
}  

Canvas(modifier = canvasModifier.fillMaxSize()) {
        val canvasWidth = size.width.roundToInt()
        val canvasHeight = size.height.roundToInt()
    
        drawImage(
            image = dstBitmap,
            srcSize = IntSize(dstBitmap.width, dstBitmap.height),
            dstSize = IntSize(canvasWidth, canvasHeight)
        )
    
        drawCircle(
            center = offset,
            color = Color.Red,
            radius = canvasHeight.coerceAtMost(canvasWidth) / 8f,
        )
    }

С androidx.compose.ui.graphics.Canvas я добавляю полную реализацию, чтобы легко проверить это и очень ценю, если вы найдете решение.

      @Composable
fun NativeCanvasSample2(imageBitmap: ImageBitmap, modifier: Modifier) {
    
    BoxWithConstraints(modifier) {

        val imageWidth = constraints.maxWidth
        val imageHeight = constraints.maxHeight

        val bitmapWidth = imageBitmap.width
        val bitmapHeight = imageBitmap.height

        var offset by remember {
            mutableStateOf(Offset(bitmapWidth / 2f, bitmapHeight / 2f))
        }


        val canvasModifier = Modifier.pointerMotionEvents(
            Unit,
            onDown = {
                val position = it.position
                val offsetX = position.x * bitmapWidth / imageWidth
                val offsetY = position.y * bitmapHeight / imageHeight
                offset = Offset(offsetX, offsetY)
                it.consumeDownChange()
            },
            onMove = {
                val position = it.position
                val offsetX = position.x * bitmapWidth / imageWidth
                val offsetY = position.y * bitmapHeight / imageHeight
                offset = Offset(offsetX, offsetY)
                it.consumePositionChange()
            },
            delayAfterDownInMillis = 20
        )

        val canvas: androidx.compose.ui.graphics.Canvas = Canvas(imageBitmap)
        

        val paint1 = remember {
            Paint().apply {
                color = Color.Red
            }
        }
        canvas.apply {
            val nativeCanvas = this.nativeCanvas
            val canvasWidth = nativeCanvas.width.toFloat()
            val canvasHeight = nativeCanvas.height.toFloat()

            drawCircle(
                center = offset,
                radius = canvasHeight.coerceAtMost(canvasWidth) / 8,
                paint = paint1
            )
        }


        Image(
            modifier = canvasModifier,
            bitmap = imageBitmap,
            contentDescription = null,
            contentScale = ContentScale.FillBounds
        )

        Text(
            "Offset: $offset",
            modifier = Modifier.align(Alignment.BottomEnd),
            color = Color.White,
            fontSize = 16.sp
        )
    }
}

Первая проблема, он никогда не обновляет Canvas без Textили что-то другое чтение Offset.

Вторая проблема, как на изображении ниже. Это не очищает предыдущий рисунок на изображении, я пробовал все возможные решения в этой ветке вопросов , но ни одно из них не сработало.

Я пробовал рисовать изображения с помощью BlendMode, drawColor(Color.TRANSPARENT,Mode.Multiply) с собственным холстом, и многие комбинации все еще не могут дать такой же результат с Jetpack Compose Canvas.

          val erasePaint = remember {
        Paint().apply {
            color = Color.Transparent
            blendMode = BlendMode.Clear
        }
    }

with(canvas.nativeCanvas) {
    val checkPoint = saveLayer(null, null)

    drawImage(imageBitmap, topLeftOffset = Offset.Zero, erasePaint)
    drawCircle(
        center = offset,
        radius = canvasHeight.coerceAtMost(canvasWidth) / 8,
        paint = paint1
    )
    
    restoreToCount(checkPoint)
}

мне нужно использовать androidx.compose.ui.graphics.Canvasкак вы можете видеть, операции на холсте отражаются в растровом изображении, и, используя это, я планирую создать основу для обрезки растрового изображения.

1 ответ

Я, наконец, через 6 месяцев понял, как это можно сделать и как можно изменить экземпляр Bitmap с помощьюandroidx.compose.ui.graphics.Canvas

Сначала создайте пустое изменяемое растровое изображение с теми же размерами, что и исходное растровое изображение. Это то, что мы будем рисовать. Хитрость здесь заключается не в отправке реального растрового изображения, а пустого растрового изображения.

      val bitmapWidth = imageBitmap.width
val bitmapHeight = imageBitmap.height

val bmp: Bitmap = remember {
    Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888)
}

Затем, поскольку мы ничего не рисуем в основании, мы можем использоватьdrawColor(android.graphics.Color.TRANSPARENT, PorterDuff.Mode.CLEAR)

чтобы очистить каждый рисунок, затем нарисуйте изображение и примените любой режим наложения с помощью Paint

      val paint = remember {
    Paint()
}

val erasePaint = remember {
    Paint().apply {
        color = Color.Red
        blendMode = BlendMode.SrcIn
    }
}

canvas.apply {
    val nativeCanvas = this.nativeCanvas
    val canvasWidth = nativeCanvas.width.toFloat()
    val canvasHeight = nativeCanvas.height.toFloat()

    with(canvas.nativeCanvas) {
       drawColor(android.graphics.Color.TRANSPARENT, PorterDuff.Mode.CLEAR)

        drawCircle(
            center = offset,
            radius = 400f,
            paint = paint
        )

        drawImageRect(
            image = imageBitmap,
            dstSize = IntSize(canvasWidth.toInt(), canvasHeight.toInt()),
            paint = erasePaint
        )
    }
}

Наконец, нарисуйте растровое изображение, которое мы использовали в Canvas, в Image Composable, используя

      Image(
    modifier = canvasModifier.border(2.dp, Color.Green),
    bitmap = bmp.asImageBitmap(),
    contentDescription = null,
    contentScale = ContentScale.FillBounds
)

или вы можете сохранить этот измененный ImageBitmap с водяным знаком или любым наложением, которое вы рисуете на холсте.

Полная реализация

      @Composable
fun NativeCanvasSample2(imageBitmap: ImageBitmap, modifier: Modifier) {

    BoxWithConstraints(modifier) {

        val imageWidth = constraints.maxWidth
        val imageHeight = constraints.maxHeight

        val bitmapWidth = imageBitmap.width
        val bitmapHeight = imageBitmap.height

        var offset by remember {
            mutableStateOf(Offset(bitmapWidth / 2f, bitmapHeight / 2f))
        }

        val bmp: Bitmap = remember {
            Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888)
        }

        val canvas: Canvas = remember {
            Canvas(bmp.asImageBitmap())
        }

        val paint = remember {
            Paint()
        }

        val erasePaint = remember {
            Paint().apply {
                color = Color.Red
                blendMode = BlendMode.SrcIn
            }
        }

        canvas.apply {
            val nativeCanvas = this.nativeCanvas
            val canvasWidth = nativeCanvas.width.toFloat()
            val canvasHeight = nativeCanvas.height.toFloat()

            with(canvas.nativeCanvas) {
                drawColor(android.graphics.Color.TRANSPARENT, PorterDuff.Mode.CLEAR)

                drawCircle(
                    center = offset,
                    radius = 400f,
                    paint = paint
                )

                drawImageRect(
                    image = imageBitmap,
                    dstSize = IntSize(canvasWidth.toInt(), canvasHeight.toInt()),
                    paint = erasePaint
                )
            }
        }
        
        val canvasModifier = Modifier.pointerMotionEvents(
            Unit,
            onDown = {
                val position = it.position
                val offsetX = position.x * bitmapWidth / imageWidth
                val offsetY = position.y * bitmapHeight / imageHeight
                offset = Offset(offsetX, offsetY)
                it.consume()
            },
            onMove = {
                val position = it.position
                val offsetX = position.x * bitmapWidth / imageWidth
                val offsetY = position.y * bitmapHeight / imageHeight
                offset = Offset(offsetX, offsetY)
                it.consume()
            },
            delayAfterDownInMillis = 20
        )

        Image(
            modifier = canvasModifier.border(2.dp, Color.Green),
            bitmap = bmp.asImageBitmap(),
            contentDescription = null,
            contentScale = ContentScale.FillBounds
        )
    }
}

Результат

Другие вопросы по тегам