Получить видимость изображения холста в композиции

У меня есть холст, на котором я рисую два изображения одинакового размера, и я реализовал сенсорный прослушиватель, где я «стираю» одно из них, и я хотел бы знать, есть ли возможность узнать% видимости одного что я "стираю".

val overlayImageLoaded = rememberAsyncImagePainter(
        model = overlayImage,
    )
    val baseImageLoaded = rememberAsyncImagePainter(
        model = baseImage
    )
    Canvas(modifier = modifier
        .size(220.dp)
        .clip(RoundedCornerShape(size = 16.dp))
        .pointerInteropFilter {
            when (it.action) {
                MotionEvent.ACTION_DOWN -> {
                    currentPath.moveTo(it.x, it.y)
                }

                MotionEvent.ACTION_MOVE -> {
                    onMovedOffset(it.x, it.y)
                }
            }
            true
        }) {

        with(overlayImageLoaded) {
            draw(size = Size(size.width, size.height))

        }

        movedOffset?.let {
            currentPath.addOval(oval = Rect(it, currentPathThickness))
        }

        clipPath(path = currentPath, clipOp = ClipOp.Intersect) {
            with(baseImageLoaded) {
                draw(size = Size(size.width, size.height))
            }
        }
    }

У меня есть некоторые идеи:

Поскольку я хочу знать, было ли изображение стерто не менее чем на 70%, скажем, я подумал о том, чтобы сохранить onMovedOffset в список, и поскольку я знаю размер моего холста и толщину пути, я могу сделать расчет того, что пользователь видел, но, возможно, это немного перебор.

Также я подумал о том, чтобы рисовать холст как растровое изображение каждый раз, когда пользователь перемещается, а затем иметь метод, который сравнивает растровое изображение с растровым изображением и проверяет% равенства.

Цель состоит в том, чтобы узнать, стер ли пользователь не менее 70% изображения, и оно больше не видно.

Шаблоны Angular PrimeNg
Шаблоны Angular PrimeNg
Как привнести проверку типов в наши шаблоны Angular, использующие компоненты библиотеки PrimeNg, и настроить их отображение с помощью встроенной...
Создайте ползком, похожим на звездные войны, с помощью CSS и Javascript
Создайте ползком, похожим на звездные войны, с помощью CSS и Javascript
Если вы веб-разработчик (или хотите им стать), то вы наверняка гик и вам нравятся "Звездные войны". А как бы вы хотели, чтобы фоном для вашего...
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
Начала с розового дизайна
Начала с розового дизайна
Pink Design - это система дизайна Appwrite с открытым исходным кодом для создания последовательных и многократно используемых пользовательских...
Шлюз в PHP
Шлюз в PHP
API-шлюз (AG) - это сервер, который действует как единая точка входа для набора микросервисов.
14 Задание: Типы данных и структуры данных Python для DevOps
14 Задание: Типы данных и структуры данных Python для DevOps
проверить тип данных используемой переменной, мы можем просто написать: your_variable=100
3
0
223
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Ответ на этот вопрос довольно сложен и включает в себя много слоев. Может быть некоторая оптимизация многопоточности для сравнения пикселей в фоновом потоке.

Шаг первый: создайте копию оригинального ImageBitmap, чтобы она помещалась в Composable на экране. Поскольку мы стираем пиксели на экране, мы должны использовать sc

// Pixels of scaled bitmap, we scale it to composable size because we will erase
// from Composable on screen
val originalPixels: IntArray = remember {
    val buffer = IntArray(imageWidth * imageHeight)
    Bitmap.createScaledBitmap(imageBitmap.asAndroidBitmap(), imageWidth, imageHeight, false)
        .asImageBitmap()
        .readPixels(
            buffer = buffer,
            startX = 0,
            startY = 0,
            width = imageWidth,
            height = imageHeight
        )

    buffer
}

val erasedBitmap: ImageBitmap = remember {
    Bitmap.createBitmap(imageWidth, imageHeight, Bitmap.Config.ARGB_8888).asImageBitmap()
}

Второй шаг — создать androidx.compose.ui.graphics.Canvas(imageBitmap) для применения изменений к imageBitmap как в этом ответе. Ознакомьтесь с этим, чтобы ознакомиться с тем, как манипулировать растровым изображением, которое рисуется в пустое растровое изображение.

val canvas: Canvas = remember {
    Canvas(erasedBitmap)
}

Третий шаг: создайте краски для стирания с растрового изображения.

val paint = remember {
    Paint()
}

val erasePaint = remember {
    Paint().apply {
        blendMode = BlendMode.Clear
        this.style = PaintingStyle.Stroke
        strokeWidth = 30f
    }
}

Четвертый шаг - стереть из растрового изображения с помощью жеста и вычислить разницу исходных пикселей с текущим стертым растровым изображением.

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


    when (motionEvent) {

        MotionEvent.Down -> {
            erasePath.moveTo(currentPosition.x, currentPosition.y)
            previousPosition = currentPosition

        }
        MotionEvent.Move -> {

            erasePath.quadraticBezierTo(
                previousPosition.x,
                previousPosition.y,
                (previousPosition.x + currentPosition.x) / 2,
                (previousPosition.y + currentPosition.y) / 2

            )
            previousPosition = currentPosition
        }

        MotionEvent.Up -> {
            erasePath.lineTo(currentPosition.x, currentPosition.y)
            currentPosition = Offset.Unspecified
            previousPosition = currentPosition
            motionEvent = MotionEvent.Idle

            matchPercent = compareBitmaps(
                originalPixels,
                erasedBitmap,
                imageWidth,
                imageHeight
            )
        }
        else -> Unit
    }

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

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

        drawPath(
            path = erasePath,
            paint = erasePaint
        )
    }
}

Пятый шаг — сравнить исходные пиксели со стертым растровым изображением, чтобы определить, в каком проценте они совпадают.

private fun compareBitmaps(
    originalPixels: IntArray,
    erasedBitmap: ImageBitmap,
    imageWidth: Int,
    imageHeight: Int
): Float {

    var match = 0f

    val size = imageWidth * imageHeight
    val erasedBitmapPixels = IntArray(size)

    erasedBitmap.readPixels(
        buffer = erasedBitmapPixels,
        startX = 0,
        startY = 0,
        width = imageWidth,
        height = imageHeight
    )

    erasedBitmapPixels.forEachIndexed { index, pixel: Int ->
        if (originalPixels[index] == pixel) {
            match++
        }
    }

    return 100f * match / size
}

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

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


    var matchPercent by remember {
        mutableStateOf(100f)
    }

    BoxWithConstraints(modifier) {

        // Path used for erasing. In this example erasing is faked by drawing with canvas color
        // above draw path.
        val erasePath = remember { Path() }

        var motionEvent by remember { mutableStateOf(MotionEvent.Idle) }
        // This is our motion event we get from touch motion
        var currentPosition by remember { mutableStateOf(Offset.Unspecified) }
        // This is previous motion event before next touch is saved into this current position
        var previousPosition by remember { mutableStateOf(Offset.Unspecified) }

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


        val drawImageBitmap = remember {
            Bitmap.createScaledBitmap(imageBitmap.asAndroidBitmap(), imageWidth, imageHeight, false)
                .asImageBitmap()
        }

        // Pixels of scaled bitmap, we scale it to composable size because we will erase
        // from Composable on screen
        val originalPixels: IntArray = remember {
            val buffer = IntArray(imageWidth * imageHeight)
            drawImageBitmap
                .readPixels(
                    buffer = buffer,
                    startX = 0,
                    startY = 0,
                    width = imageWidth,
                    height = imageHeight
                )

            buffer
        }

        val erasedBitmap: ImageBitmap = remember {
            Bitmap.createBitmap(imageWidth, imageHeight, Bitmap.Config.ARGB_8888).asImageBitmap()
        }

        val canvas: Canvas = remember {
            Canvas(erasedBitmap)
        }

        val paint = remember {
            Paint()
        }

        val erasePaint = remember {
            Paint().apply {
                blendMode = BlendMode.Clear
                this.style = PaintingStyle.Stroke
                strokeWidth = 30f
            }
        }


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


            when (motionEvent) {

                MotionEvent.Down -> {
                    erasePath.moveTo(currentPosition.x, currentPosition.y)
                    previousPosition = currentPosition

                }
                MotionEvent.Move -> {

                    erasePath.quadraticBezierTo(
                        previousPosition.x,
                        previousPosition.y,
                        (previousPosition.x + currentPosition.x) / 2,
                        (previousPosition.y + currentPosition.y) / 2

                    )
                    previousPosition = currentPosition
                }

                MotionEvent.Up -> {
                    erasePath.lineTo(currentPosition.x, currentPosition.y)
                    currentPosition = Offset.Unspecified
                    previousPosition = currentPosition
                    motionEvent = MotionEvent.Idle

                    matchPercent = compareBitmaps(
                        originalPixels,
                        erasedBitmap,
                        imageWidth,
                        imageHeight
                    )
                }
                else -> Unit
            }

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



                drawImageRect(
                    image = drawImageBitmap,
                    dstSize = IntSize(canvasWidth.toInt(), canvasHeight.toInt()),
                    paint = paint
                )

                drawPath(
                    path = erasePath,
                    paint = erasePaint
                )
            }
        }

        val canvasModifier = Modifier.pointerMotionEvents(
            Unit,
            onDown = { pointerInputChange ->
                motionEvent = MotionEvent.Down
                currentPosition = pointerInputChange.position
                pointerInputChange.consume()
            },
            onMove = { pointerInputChange ->
                motionEvent = MotionEvent.Move
                currentPosition = pointerInputChange.position
                pointerInputChange.consume()
            },
            onUp = { pointerInputChange ->
                motionEvent = MotionEvent.Up
                pointerInputChange.consume()
            },
            delayAfterDownInMillis = 20
        )

        Image(
            modifier = canvasModifier
                .clipToBounds()
                .drawBehind {
                    val width = this.size.width
                    val height = this.size.height

                    val checkerWidth = 10.dp.toPx()
                    val checkerHeight = 10.dp.toPx()

                    val horizontalSteps = (width / checkerWidth).toInt()
                    val verticalSteps = (height / checkerHeight).toInt()

                    for (y in 0..verticalSteps) {
                        for (x in 0..horizontalSteps) {
                            val isGrayTile = ((x + y) % 2 == 1)
                            drawRect(
                                color = if (isGrayTile) Color.LightGray else Color.White,
                                topLeft = Offset(x * checkerWidth, y * checkerHeight),
                                size = Size(checkerWidth, checkerHeight)
                            )
                        }
                    }
                }
                .matchParentSize()
                .border(2.dp, Color.Green),
            bitmap = erasedBitmap,
            contentDescription = null,
            contentScale = ContentScale.FillBounds
        )
    }

    Text("Original Bitmap")

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

    Text("Bitmap match $matchPercent", color = Color.Red, fontSize = 22.sp)

}

Результат

Фрагмент выше я использую для определения цвета точки касания в этом разделе. github.com/SmartToolFactory/…

Thracian 25.11.2022 20:43

Спасибо за ответ, дело в том, что я следую этому примеру, и я ищу обратный вызов, когда, скажем, видно 70% базового изображения, поэтому я могу удалить наложенное и показать базовый

StuartDTO 28.11.2022 16:45

К сожалению, нет такого обратного вызова, чтобы показать, какой процент стирается. Тот пример, который у вас есть, это не то, как вы его решаете. Вы можете проверить мой ответ здесь, это то же самое, что вы манипулируете пикселями на холсте. stackoverflow.com/a/73025165/5457853. То, что вы на самом деле делаете, это то, что я разместил выше, изменяя растровые пиксели и сравнивая их с исходным изображением.

Thracian 28.11.2022 16:49

Наконец-то я понял, как успешно изменить пиксели на растровом изображении, как в этой ссылке. stackoverflow.com/questions/72168588/… Я опубликую ответ на свой вопрос, а затем на ваш.

Thracian 28.11.2022 16:50

@StuartDTO, наконец, закончил этот ответ и добавил немного вкуса, чтобы нарисовать фон шашки, чтобы показать, что он стирается. Я, наконец, понял мой вопрос, а через 6 месяцев. С помощью Canvas (растрового изображения) вы можете рисовать и изменять пиксели растрового изображения.

Thracian 29.11.2022 16:15

Эй, @Thracian, я проверю, соответствует ли это моим потребностям! отличный ответ, хотя

StuartDTO 01.12.2022 11:14

Привет, фракиец, я собираюсь принять ваш ответ как правильный, потому что осталось меньше часа, и я хочу, чтобы вы получили награду, но кое-чего не хватает, не могли бы вы отредактировать свой ответ на: у меня есть два изображения одно над другим и вместо «стирания» я показываю изображение ниже, и эти изображения загружаются с помощью rememberAsyncImagePainter, потому что есть строка, и как только я достигну% процента, можно удалить тот, который я стираю, чтобы показать только правильный?

StuartDTO 01.12.2022 17:23

Привет @StuartDTO, мне нужно проверить, возвращает ли RememberAsyncImagePainter растровое изображение, потому что вам определенно нужно растровое изображение для управления или сравнения пикселей. Я могу проверить, можете ли вы получить растровое изображение из Coil Painter, но это невозможно с помощью обычного Painter, возможно, у Coil Painter есть способ выставить его. Вы можете удалить тексты и второе изображение, выходящие за пределы BoxWihtConstraints. Просто добавьте Image над изображением, которое отображает стертый ImageBitmap, чтобы отобразить изображение ниже, которое выцарапывается, это очень просто.

Thracian 01.12.2022 17:44

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

StuartDTO 02.12.2022 19:21

Да, завтра посмотрю. Вам просто нужно поместить изображение, которое не следует стирать, и одно, которое нужно стереть, в тот же Box() {Image(notErasedImageBitmap) Image(ErasedImageBitmap)} после получения ImageBitmap или Bitmap из Coil. И удалите модификатор drawBehind с изображения, которое я стираю в приведенном выше примере. Текст и изображение за пределами BoxWithConstraints предназначены только для демонстрации

Thracian 03.12.2022 12:29

В катушке есть функция, которая создает растровое изображение с заданными размерами. val state = painter.state if (state — AsyncImagePainter.State.Success) { // Выполняем анимацию перехода. state.result.drawable.toBitmap() }

Thracian 03.12.2022 12:33

Здравствуйте, @Thracian, не могли бы вы изучить это и проверить, что мне не хватает?paste.ofcode.org ZvPXBfEpZueU3ZkUJKqzty Я пытался создать два изображения из строкового URL-адреса, а затем передать его в Composable, но изображение не отображается. ... Дело в том, что я хочу вместо стирания с помощью серых и белых плиток нарисовать его прозрачным, чтобы показать изображение ниже, и как только% будет моим желаемым, удалите изображение, которое я стираю

StuartDTO 04.12.2022 19:15

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