You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android绘图工具Kotlin版Flood Fill(泛洪填充)实现求助

解决Android绘图工具的漫水填充(Flood Fill)问题

我明白你现在的困境——Path.FillType确实不是用来实现「油漆桶式」漫水填充的,它只负责闭合路径内部的填充规则,和你需要的基于像素颜色的区域填充完全是两回事。下面我给你提供Kotlin版本的Flood Fill实现,直接适配你的DrawingView代码。

第一步:修改DrawingView的成员变量

首先添加填充模式的标识和填充颜色的存储:

class DrawingView(context: Context, attrs: AttributeSet) : View(context, attrs) {
    // ... 原有成员变量 ...
    private var isFillMode = false // 标记是否处于填充模式
    private var fillColor = Color.BLACK // 用户选择的填充颜色
    // ... 原有代码 ...
}

第二步:实现核心漫水填充算法

这里用迭代式队列实现(避免递归导致的栈溢出问题),处理Bitmap的像素替换:

// 漫水填充核心方法
private fun floodFill(x: Int, y: Int, targetColor: Int, replacementColor: Int) {
    val bitmap = mCanvasBitMap ?: return
    // 确保Bitmap是可修改的,不可修改则复制一份
    val mutableBitmap = if (bitmap.isMutable) bitmap else bitmap.copy(Bitmap.Config.ARGB_8888, true)
    val width = mutableBitmap.width
    val height = mutableBitmap.height

    // 边界检查:点击点超出Bitmap范围,或目标颜色和填充色一致则直接返回
    if (x < 0 || x >= width || y < 0 || y >= height) return
    if (targetColor == replacementColor) return

    // 用队列存储待处理的像素点
    val pixelQueue = LinkedList<Pair<Int, Int>>()
    pixelQueue.add(Pair(x, y))
    mutableBitmap.setPixel(x, y, replacementColor)

    // 遍历队列,处理每个像素的上下左右四个方向
    while (pixelQueue.isNotEmpty()) {
        val (currentX, currentY) = pixelQueue.removeFirst()

        // 上方像素
        if (currentY > 0 && mutableBitmap.getPixel(currentX, currentY - 1) == targetColor) {
            mutableBitmap.setPixel(currentX, currentY - 1, replacementColor)
            pixelQueue.add(Pair(currentX, currentY - 1))
        }
        // 下方像素
        if (currentY < height - 1 && mutableBitmap.getPixel(currentX, currentY + 1) == targetColor) {
            mutableBitmap.setPixel(currentX, currentY + 1, replacementColor)
            pixelQueue.add(Pair(currentX, currentY + 1))
        }
        // 左方像素
        if (currentX > 0 && mutableBitmap.getPixel(currentX - 1, currentY) == targetColor) {
            mutableBitmap.setPixel(currentX - 1, currentY, replacementColor)
            pixelQueue.add(Pair(currentX - 1, currentY))
        }
        // 右方像素
        if (currentX < width - 1 && mutableBitmap.getPixel(currentX + 1, currentY) == targetColor) {
            mutableBitmap.setPixel(currentX + 1, currentY, replacementColor)
            pixelQueue.add(Pair(currentX + 1, currentY))
        }
    }

    // 更新画布Bitmap并刷新视图
    mCanvasBitMap = mutableBitmap
    invalidate()
}

第三步:修改触摸事件逻辑

在触摸事件中判断是否处于填充模式,点击时执行填充:

override fun onTouchEvent(event: MotionEvent?): Boolean {
    val touchX = event?.x ?: return false
    val touchY = event?.y ?: return false

    // 如果是填充模式,处理点击填充
    if (isFillMode) {
        if (event.action == MotionEvent.ACTION_UP) {
            // 将View触摸坐标转换为Bitmap像素坐标(适配View和Bitmap尺寸差异)
            val scaleX = mCanvasBitMap?.width?.toFloat() ?: width.toFloat() / width.toFloat()
            val scaleY = mCanvasBitMap?.height?.toFloat() ?: height.toFloat() / height.toFloat()
            val bitmapX = (touchX * scaleX).toInt()
            val bitmapY = (touchY * scaleY).toInt()
            
            // 获取点击点的原始颜色,执行填充
            val targetColor = mCanvasBitMap?.getPixel(bitmapX, bitmapY) ?: Color.TRANSPARENT
            floodFill(bitmapX, bitmapY, targetColor, fillColor)
        }
        return true
    }

    // 原有绘图逻辑保持不变
    when(event.action){
        MotionEvent.ACTION_DOWN -> {
            mDrawPath!!.color = color
            mDrawPath!!.brushThickness = mBrushSize
            mDrawPath!!.reset()
            mDrawPath!!.moveTo(touchX, touchY)
        }
        MotionEvent.ACTION_MOVE -> {
            mDrawPath!!.lineTo(touchX, touchY)
        }
        MotionEvent.ACTION_UP -> {
            mPaths.add(mDrawPath!!)
            mDrawPath = CustomPath(color, mBrushSize)
        }
        else -> return false
    }
    invalidate()
    return true
}

第四步:添加模式切换和初始化优化

添加切换填充模式的方法,同时确保初始化时Bitmap是可修改的:

// 外部调用:切换填充模式,传入是否启用和填充颜色
fun toggleFillMode(enable: Boolean, selectedColor: Int) {
    isFillMode = enable
    fillColor = selectedColor
}

// 重写onSizeChanged,初始化可修改的画布Bitmap
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
    super.onSizeChanged(w, h, oldw, oldh)
    mCanvasBitMap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
    canvas = Canvas(mCanvasBitMap!!)
}

注意事项

  • 坐标适配:如果你的View存在缩放、裁剪等情况,需要调整坐标转换逻辑,确保触摸点准确对应Bitmap的像素位置。
  • 颜色精度:如果Bitmap使用带alpha通道的格式(如ARGB_8888),getPixel会返回包含alpha的完整颜色值,判断时要完整比较ARGB,避免半透明颜色的误判。
  • 性能优化:大尺寸Bitmap的填充可能耗时,建议在子线程执行填充逻辑,完成后回到主线程刷新视图(注意Bitmap的线程安全)。

内容的提问来源于stack exchange,提问作者Zomb991

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 11:32:53