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
)
}
}
Результат