Android 不规则封闭区域填充 手指秒变油漆桶

{

mStacks.push(new Point(seed.x, seed.y + 1));

}

}

if (seed.x > 0)

{

int left = index - 1;

if (pixels[left] == pixel)

{

mStacks.push(new Point(seed.x - 1, seed.y));

}

}

if (seed.x < w - 1)

{

int right = index + 1;

if (pixels[right] == pixel)

{

mStacks.push(new Point(seed.x + 1, seed.y));

}

}

}

}

方法的思想也比较简单,将当前像素点入栈,然后出栈着色,接下来分别判断四个方向的,如果符合条件也进行入栈(只要栈不为空持续运行)。ok,这个方法我也尝试跑了下,恩,这次不会报错了,但是速度特别的慢~~~~慢得我是不可接受的。(有兴趣可以尝试,记得如果ANR,点击等待)。

这样来看,第一种算法,我们是不考虑了,没有办法使用,主要原因是假设对于矩形同色区域,都是需要填充的,而算法一依然是各种入栈。于是考虑第二种算法

扫描线填充法

详细可参考 扫描线种子填充算法的解析扫描线种子填充算法

算法思想[4]:

  1. 初始化一个空的栈用于存放种子点,将种子点(x, y)入栈;
  1. 判断栈是否为空,如果栈为空则结束算法,否则取出栈顶元素作为当前扫描线的种子点(x, y),y是当前的扫描线;
  1. 从种子点(x, y)出发,沿当前扫描线向左、右两个方向填充,直到边界。分别标记区段的左、右端点坐标为xLeft和xRight;
  1. 分别检查与当前扫描线相邻的y - 1和y + 1两条扫描线在区间[xLeft, xRight]中的像素,从xRight开始向xLeft方向搜索,假设扫描的区间为AAABAAC(A为种子点颜色),那么将B和C前面的A作为种子点压入栈中,然后返回第(2)步;

上述参考自参考文献[4],做了些修改,文章[4]中描述算法,测试有一点问题,所以做了修改.

可以看到该算法,基本上是一行一行着色的,这样的话在大块需要着色区域的效率比算法一要高很多。

ok,关于算法的步骤大家目前觉得模糊,一会可以参照我们的代码。选定了算法以后,接下来就开始编码了。


三、编码实现

我们代码中引入了一个边界颜色,如果设置的话,着色的边界参考为该边界颜色,否则会只要与种子颜色不一致为边界。

(一)构造方法与测量

public class ColourImageView extends ImageView

{

private Bitmap mBitmap;

/**

  • 边界的颜色

*/

private int mBorderColor = -1;

private boolean hasBorderColor = false;

private Stack mStacks = new Stack();

public ColourImageView(Context context, AttributeSet attrs)

{

super(context, attrs);

TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ColourImageView);

mBorderColor = ta.getColor(R.styleable.ColourImageView_border_color, -1);

hasBorderColor = (mBorderColor != -1);

L.e("hasBorderColor = " + hasBorderColor + " , mBorderColor = " + mBorderColor);

ta.recycle();

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)

{

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

int viewWidth = getMeasuredWidth();

int viewHeight = getMeasuredHeight();

//以宽度为标准,等比例缩放view的高度

setMeasuredDimension(viewWidth,

getDrawable().getIntrinsicHeight() * viewWidth / getDrawable().getIntrinsicWidth());

L.e("view’s width = " + getMeasuredWidth() + " , view’s height = " + getMeasuredHeight());

//根据drawable,去得到一个和view一样大小的bitmap

BitmapDrawable drawable = (BitmapDrawable) getDrawable();

Bitmap bm = drawable.getBitmap();

mBitmap = Bitmap.createScaledBitmap(bm, getMeasuredWidth(), getMeasuredHeight(), false);

}

可以看到我们选择的是继承ImageView,这样只需要将图片设为src即可。

构造方法中获取我们的自定义边界颜色,当然可以不设置~~

重写测量的目的是为了获取一个和View一样大小的Bitmap便于我们操作。

接下来就是点击啦~

(二)onTouchEvent

@Override

public boolean onTouchEvent(MotionEvent event)

{

final int x = (int) event.getX();

final int y = (int) event.getY();

if (event.getAction() == MotionEvent.ACTION_DOWN)

{

//填色

fillColorToSameArea(x, y);

}

return super.onTouchEvent(event);

}

/**

  • 根据x,y获得改点颜色,进行填充

  • @param x

  • @param y

*/

private void fillColorToSameArea(int x, int y)

{

Bitmap bm = mBitmap;

int pixel = bm.getPixel(x, y);

if (pixel == Color.TRANSPARENT || (hasBorderColor && mBorderColor == pixel))

{

return;

}

int newColor = randomColor();

int w = bm.getWidth();

int h = bm.getHeight();

//拿到该bitmap的颜色数组

int[] pixels = new int[w * h];

bm.getPixels(pixels, 0, w, 0, 0, w, h);

//填色

fillColor(pixels, w, h, pixel, newColor, x, y);

//重新设置bitmap

bm.setPixels(pixels, 0, w, 0, 0, w, h);

setImageDrawable(new BitmapDrawable(bm));

}

可以看到,我们在onTouchEvent中获取(x,y),然后拿到改点坐标:

  • 获得点击点颜色,获得整个bitmap的像素数组

  • 改变这个数组中的颜色

  • 然后重新设置给bitmap,重新设置给ImageView

重点就是通过fillColor去改变数组中的颜色

/**

  • @param pixels 像素数组

  • @param w 宽度

  • @param h 高度

  • @param pixel 当前点的颜色

  • @param newColor 填充色

  • @param i 横坐标

  • @param j 纵坐标

*/

private void fillColor(int[] pixels, int w, int h, int pixel, int newColor, int i, int j)

{

//步骤1:将种子点(x, y)入栈;

mStacks.push(new Point(i, j));

//步骤2:判断栈是否为空,

// 如果栈为空则结束算法,否则取出栈顶元素作为当前扫描线的种子点(x, y),

// y是当前的扫描线;

while (!mStacks.isEmpty())

{

/**

  • 步骤3:从种子点(x, y)出发,沿当前扫描线向左、右两个方向填充,

  • 直到边界。分别标记区段的左、右端点坐标为xLeft和xRight;

*/

Point seed = mStacks.pop();

//L.e("seed = " + seed.x + " , seed = " + seed.y);

int count = fillLineLeft(pixels, pixel, w, h, newColor, seed.x, seed.y);

int left = seed.x - count + 1;

count = fillLineRight(pixels, pixel, w, h, newColor, seed.x + 1, seed.y);

int right = seed.x + count;

/**

  • 步骤4:

  • 分别检查与当前扫描线相邻的y - 1和y + 1两条扫描线在区间[xLeft, xRight]中的像素,

  • 从xRight开始向xLeft方向搜索,假设扫描的区间为AAABAAC(A为种子点颜色),

  • 那么将B和C前面的A作为种子点压入栈中,然后返回第(2)步;

*/

//从y-1找种子

if (seed.y - 1 >= 0)

findSeedInNewLine(pixels, pixel, w, h, seed.y - 1, left, right);

//从y+1找种子

if (seed.y + 1 < h)

findSeedInNewLine(pixels, pixel, w, h, seed.y + 1, left, right);

}

}

可以看到我已经很清楚的将该算法的四个步骤标识到该方法中。好了,最后就是一些依赖的细节上的方法:

/**

  • 在新行找种子节点

  • @param pixels

  • @param pixel

  • @param w

  • @param h

  • @param i

  • @param left

  • @param right

*/

private void findSeedInNewLine(int[] pixels, int pixel, int w, int h, int i, int left, int right)

{

/**

  • 获得该行的开始索引

*/

int begin = i * w + left;

/**

  • 获得该行的结束索引

*/

int end = i * w + right;

boolean hasSeed = false;

int rx = -1, ry = -1;

ry = i;

/**

  • 从end到begin,找到种子节点入栈(AAABAAAB,则B前的A为种子节点)

*/

while (end >= begin)

{

if (pixels[end] == pixel)

{

if (!hasSeed)

{

rx = end % w;

mStacks.push(new Point(rx, ry));

hasSeed = true;

}

} else

{

hasSeed = false;

}

end–;

}

}

/**

  • 往右填色,返回填充的个数

  • @return

*/

private int fillLineRight(int[] pixels, int pixel, int w, int h, int newColor, int x, int y)

{

int count = 0;

while (x < w)

{

//拿到索引

int index = y * w + x;

if (needFillPixel(pixels, pixel, index))

{

pixels[index] = newColor;

count++;

x++;

} else

{

break;

}

}

return count;

}

/**

  • 往左填色,返回填色的数量值

  • @return

*/

private int fillLineLeft(int[] pixels, int pixel, int w, int h, int newColor, int x, int y)

{

int count = 0;

while (x >= 0)

{

//计算出索引

int index = y * w + x;

if (needFillPixel(pixels, pixel, index))

{

pixels[index] = newColor;

count++;

x–;

} else

{

break;

}

}

return count;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

学习分享

在当下这个信息共享的时代,很多资源都可以在网络上找到,只取决于你愿不愿意找或是找的方法对不对了

很多朋友不是没有资料,大多都是有几十上百个G,但是杂乱无章,不知道怎么看从哪看起,甚至是看后就忘

如果大家觉得自己在网上找的资料非常杂乱、不成体系的话,我也分享一套给大家,比较系统,我平常自己也会经常研读。

2021最新上万页的大厂面试真题

七大模块学习资料:如NDK模块开发、Android框架体系架构…

只有系统,有方向的学习,才能在段时间内迅速提高自己的技术。

这份体系学习笔记,适应人群:
**第一,**学习知识比较碎片化,没有合理的学习路线与进阶方向。
**第二,**开发几年,不知道如何进阶更进一步,比较迷茫。
**第三,**到了合适的年纪,后续不知道该如何发展,转型管理,还是加强技术研究。

由于文章内容比较多,篇幅不允许,部分未展示内容以截图方式展示 。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

c=“https://i-blog.csdnimg.cn/blog_migrate/7f3c4dbd0de72124326e0edd1ae541af.jpeg” />

学习分享

在当下这个信息共享的时代,很多资源都可以在网络上找到,只取决于你愿不愿意找或是找的方法对不对了

很多朋友不是没有资料,大多都是有几十上百个G,但是杂乱无章,不知道怎么看从哪看起,甚至是看后就忘

如果大家觉得自己在网上找的资料非常杂乱、不成体系的话,我也分享一套给大家,比较系统,我平常自己也会经常研读。

2021最新上万页的大厂面试真题

[外链图片转存中…(img-iM70SfgW-1712620572129)]

七大模块学习资料:如NDK模块开发、Android框架体系架构…

[外链图片转存中…(img-PwKPfCRB-1712620572129)]

只有系统,有方向的学习,才能在段时间内迅速提高自己的技术。

这份体系学习笔记,适应人群:
**第一,**学习知识比较碎片化,没有合理的学习路线与进阶方向。
**第二,**开发几年,不知道如何进阶更进一步,比较迷茫。
**第三,**到了合适的年纪,后续不知道该如何发展,转型管理,还是加强技术研究。

由于文章内容比较多,篇幅不允许,部分未展示内容以截图方式展示 。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 13
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值