我正在尝试创建绘画复活节彩蛋的应用程序。在每幅绘图之后,用户都会通过一个带有白色背景和透明填充物(在鸡蛋内部具有轮廓)的鸡蛋的位图踩踏画布。到现在为止还挺好。但是我希望,如果用户开始在鸡蛋内部的某个区域绘画,他将无法摆脱。我尝试了一个想法,使用FloodFill算法确定可以绘制的位置,但是花了很长时间,所以我使用了AsyncTask,但是它产生了很多问题,因此无法正常工作。 任何人都有想法,任何信息都将受到欢迎。也许是非常快的洪水填满。无论如何,非常感谢。
Egg with internal contours (由于 黑色背景 但在鸡蛋中透明)
我正在使用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
}
}
我的慢 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>
打印(“非常感谢您的帮助”)