首页 > 解决方案 > 将绘图限制为轮廓

问题描述

我正在尝试创建一个绘制复活节彩蛋的应用程序。每次绘制后,用户绘制的画布被一个带有白色背景和透明填充(在鸡蛋内部具有轮廓)的鸡蛋的位图踩踏。到目前为止,一切都很好。但我希望如果用户开始在鸡蛋内的某个区域进行绘画,他将无法摆脱它。我尝试了一个想法,通过使用 floodFill 算法确定它可以绘制的位置,但它花了很长时间,所以我使用了 AsyncTask,但它产生了很多问题并且没有工作。任何人有想法,任何信息都将受到欢迎。或者也许非常快的洪水填充。总之非常感谢。

具有内部轮廓的鸡蛋 (由于黑色背景而有点难以看到,但在鸡蛋中它是透明的)

我正在使用 Kotlin,但 java 也可以,这是我的 MainActivity:

package evyoreben.app.paint

import android.R.attr.pivotX
import android.R.attr.pivotY
import android.graphics.*
import android.os.Bundle
import android.view.View
import android.view.animation.Animation
import android.view.animation.RotateAnimation
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*


class MainActivity : AppCompatActivity() {
    private val partitionPosition = ArrayList<Point>()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)


        partitionPosition.add(Point(287, 448))

        val shaderBTMListener = View.OnClickListener { v ->
            paintView.patternBTM = when(findViewById<Button>(v.id).text.toString()) {
                "1" -> BitmapShader(BitmapFactory.decodeResource(resources,R.drawable.p_1), Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)
                "2" -> BitmapShader(BitmapFactory.decodeResource(resources,R.drawable.p_2), Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)
                "3" -> BitmapShader(BitmapFactory.decodeResource(resources,R.drawable.p_3), Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)
                "4" -> BitmapShader(BitmapFactory.decodeResource(resources,R.drawable.p_4), Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)
                else -> null
            }
        }
        val reUnDoListener  = View.OnClickListener { v ->
            when (findViewById<Button>(v.id).text.toString()) {
                "Undo" -> paintView.undo()
                "Redo" -> paintView.redo()
            }
        }
        p1.setOnClickListener(shaderBTMListener)
        p2.setOnClickListener(shaderBTMListener)
        p3.setOnClickListener(shaderBTMListener)
        p4.setOnClickListener(shaderBTMListener)
        undo.setOnClickListener(reUnDoListener)
        redo.setOnClickListener(reUnDoListener)
    }



    override fun onStart() {
        paintView.post(Runnable {
            var bmp = BitmapFactory.decodeResource(resources,R.drawable.egg_test)
            var width = bmp.width.toDouble() / 200
            var height = bmp.height.toDouble() / 200
            while (width * 1.1 < paintView.width && height * 1.1 < paintView.height) {
                width *= 1.1
                height *= 1.1
            }
            bmp = Bitmap.createScaledBitmap(bmp, width.toInt(), height.toInt(), true)
            paintView.initialize(Bitmap.createScaledBitmap(bmp, width.toInt(), height.toInt(), true),width.toInt(),height.toInt())
            val asyncSetPartitions = AsyncFoodFill(bmp,partitionPosition,paintView)
            asyncSetPartitions.execute()
        })
        super.onStart()
    }




}

这是 PaintView 类:

package evyoreben.app.paint

import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_main.view.*


class PaintView(context: Context, attributes: AttributeSet) : View(context,attributes), AsyncFoodFill.CompleteFloodCallBack  {

    var strokeWidth = 150f
    var currentColor = Color.RED
    var patternBTM: BitmapShader? = null
    var backGroundColor = Color.WHITE
    var TOUCH_TOLERANCE = 4


    private var mPaint = Paint()
    private val paths = ArrayList<Bitmap>()
    private var mX: Float = 0F
    private var mY: Float = 0F
    private var mPath = Path()
    private val eggPaint = Paint()
    private var tmpCanvas: Canvas = Canvas()
    private val undo = ArrayList<Bitmap>()

    private var currentVisiblePositions: ArrayList<Point>? = null
    private var partitionsVisiblePositions: ArrayList<ArrayList<Point>>? = null


    private var bitmapEgg: Bitmap? = null



//    private var partitionsPos : ArrayList<Point>? = null


    init {
        mPaint = Paint()
        mPaint!!.isAntiAlias = true
        mPaint!!.isDither = true
        mPaint!!.color = currentColor
        mPaint!!.style = Paint.Style.STROKE
        mPaint!!.strokeJoin = Paint.Join.ROUND
        mPaint!!.strokeCap = Paint.Cap.ROUND
        mPaint!!.xfermode = null
        mPaint!!.alpha = 0xff
        mPaint!!.shader = patternBTM

    }


    fun initialize(bit: Bitmap,width: Int,height : Int) {
        this.bitmapEgg = bit
        val layoutParams: ViewGroup.LayoutParams = this.layoutParams
        layoutParams.width = width
        layoutParams.height = height
        this.layoutParams = layoutParams

    }


    override fun onFloodComplete(result: ArrayList<ArrayList<Point>>) {
        partitionsVisiblePositions = result
        postInvalidate()
    }

    override fun onDraw(canvas: Canvas) {
        if(partitionsVisiblePositions == null) return
        canvas.save()

        canvas.drawColor(backGroundColor)
        for (path in paths) {

            canvas.drawBitmap(path, matrix, null)

        }

        canvas.drawBitmap(bitmapEgg!!, matrix, eggPaint)
        canvas.restore()

    }


    override fun onTouchEvent(event: MotionEvent?): Boolean {
        if(partitionsVisiblePositions == null) return false
        val x = event!!.x
        val y = event!!.y

        when (event?.action) {
            MotionEvent.ACTION_DOWN -> {
                paths.add(Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888))
                tmpCanvas = Canvas(paths[paths.size - 1])
                mPaint.color = currentColor
                mPaint.shader = patternBTM
                mPaint.strokeWidth = strokeWidth

                mPath = Path()
                mPath!!.reset()
                mPath!!.moveTo(x, y)
                mX = x
                mY = y
                tmpCanvas.drawPath(mPath, mPaint)
                findPartition(Point(x.toInt(), y.toInt()))
                paths[paths.size -1] = applyVisiblePositions(paths[paths.size -1])
                postInvalidate()


            }
            MotionEvent.ACTION_UP -> {
                mPath!!.lineTo(mX, mY)
                tmpCanvas.drawPath(mPath, mPaint)
                paths[paths.size -1] = applyVisiblePositions(paths[paths.size -1])
                postInvalidate()

            }
            MotionEvent.ACTION_MOVE -> {
                val dx = Math.abs(x - mX)
                val dy = Math.abs(y - mY)
                if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
                    mPath!!.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2)
                    mPath.lineTo(x, y)
                    mX = x
                    mY = y
                }

                tmpCanvas.drawPath(mPath, mPaint)
                paths[paths.size -1] = applyVisiblePositions(paths[paths.size -1])
                postInvalidate()
            }

        }
        return true
    }

    fun undo() {
        if (paths.size > 0) {
            undo.add(paths.removeAt(paths.size - 1))
            invalidate() // add
        } else {
            Toast.makeText(context, "Nothing to undo", Toast.LENGTH_SHORT).show()
        }
    }

    fun redo() {
        if (undo.size > 0) {
            paths.add(undo.removeAt(undo.size - 1))
            invalidate() // add
        } else {
            Toast.makeText(context, "Nothing to redo", Toast.LENGTH_SHORT).show()
        }
    }



    //********************************************************************************************


    private fun findPartition(pt: Point){
        for(partition in partitionsVisiblePositions!!){
            if(partition.contains(pt)) {
                currentVisiblePositions = partition
            }
        }
        if (currentVisiblePositions == null)
            currentVisiblePositions = ArrayList<Point>()
    }
    fun applyVisiblePositions(bit: Bitmap): Bitmap{
        val toReBitmap = Bitmap.createBitmap(width,height,Bitmap.Config.ARGB_8888)
        for(pos in currentVisiblePositions!!){
            toReBitmap.setPixel(pos.x,pos.y,bit.getPixel(pos.x,pos.y))
        }
        return toReBitmap
    }


}

我的SLOW floodFill 作为 AsyncTask (:

package evyoreben.app.paint

import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.Point
import android.os.AsyncTask
import java.util.*
import kotlin.collections.ArrayList

class AsyncFoodFill(val bitmap: Bitmap,val partitionsPoints : ArrayList<Point>, val callBackListener: AsyncFoodFill.CompleteFloodCallBack): AsyncTask<String,Void,String>(){

    private val toRePartitionsPositions = ArrayList<ArrayList<Point>>()
    interface CompleteFloodCallBack{
        fun onFloodComplete(result: ArrayList<ArrayList<Point>>)
    }



    override fun onPostExecute(result: String?) {
        callBackListener.onFloodComplete(toRePartitionsPositions)
        super.onPostExecute(result)
    }

    override fun doInBackground(vararg params: String?): String {
        val toRePartitionVisiblePositions = ArrayList<ArrayList<Point>>()
        for (partitionPos in partitionsPoints) {
            toRePartitionVisiblePositions.add(
                FloodFill(
                    bitmap,
                    partitionPos,
                    Color.TRANSPARENT,
                    Color.WHITE
                )
            )
        }
        return ""

    }
    private fun FloodFill(
        bmp: Bitmap,
        pt: Point,
        targetColor: Int,
        replacementColor: Int


    ): ArrayList<Point> {
        val q: Queue<Point> = LinkedList()
        val visiblePositions = ArrayList<Point>()
        q.add(pt)
        while (q.size > 0) {
            val n = q.poll()
            if (bmp.getPixel(n.x, n.y) == Color.BLACK) continue
            val e = Point(n.x + 1, n.y)
            while (n.x > 0 && bmp.getPixel(n.x, n.y) == targetColor) {
                bmp.setPixel(n.x, n.y, replacementColor)
                visiblePositions.add(Point(n.x, n.y))
                if (n.y > 0 && bmp.getPixel(n.x, n.y - 1) == targetColor) q.add(
                    Point(
                        n.x,
                        n.y - 1
                    )
                )
                if (n.y < bmp.height - 1
                    && bmp.getPixel(n.x, n.y + 1) == targetColor
                ) q.add(Point(n.x, n.y + 1))
                n.x--
            }
            while (e.x < bmp.width - 1
                && bmp.getPixel(e.x, e.y) == targetColor
            ) {
                bmp.setPixel(e.x, e.y, replacementColor)
                visiblePositions.add(Point(e.x, e.y))
                if (e.y > 0 && bmp.getPixel(e.x, e.y - 1) == targetColor) q.add(
                    Point(
                        e.x,
                        e.y - 1
                    )
                )
                if (e.y < bmp.height - 1
                    && bmp.getPixel(e.x, e.y + 1) == targetColor
                ) q.add(Point(e.x, e.y + 1))
                e.x++
            }
        }
        return visiblePositions
    }

}

最后是我的布局:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <evyoreben.app.paint.PaintView
        android:id="@+id/paintView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_alignParentEnd="true"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        app:layout_constraintBottom_toTopOf="@+id/guideline"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.0" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.86" />

    <LinearLayout
        android:id="@+id/LinearLayout"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        android:orientation="horizontal"
        android:weightSum="5"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline">

        <Button
            android:id="@+id/p1"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:text="1" />

        <Button
            android:id="@+id/p2"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:text="2" />

        <Button
            android:id="@+id/p3"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:text="3" />

        <Button
            android:id="@+id/p4"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="4" />

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:orientation="vertical">

            <Button
                android:id="@+id/undo"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="Undo" />

            <Button
                android:id="@+id/redo"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="Redo" />
        </LinearLayout>

    </LinearLayout>


</androidx.constraintlayout.widget.ConstraintLayout>

print("非常感谢您的帮助")

标签: javaandroidkotlincanvas

解决方案


推荐阅读