Android不规则封闭区域填充色彩的实例代码

2024-03-19 13:12

本文主要是介绍Android不规则封闭区域填充色彩的实例代码,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、概述

在上一篇的叙述中,我们通过图层的方式完成了图片颜色的填充(详情请戳:Android不规则图像填充颜色小游戏),不过在着色游戏中更多的还是基于边界的图像的填充。本篇博客将详细描述。

图像的填充有2种经典算法。

一种是种子填充法。

种子填充法理论上能够填充任意区域和图形,但是这种算法存在大量的反复入栈和大规模的递归,降低了填充效率。

另一种是扫描线填充法。

注意:实际上图像填充的算法还是很多的,有兴趣可以去Google学术上去搜一搜。

ok,下面先看看今天的效果图:

img

ok,可以看到这样的颜色填充比上一篇的基于层的在素材的准备上要easy 很多~~~

二、原理分析

首先我们简述下原理,我们在点击的时候拿到点击点的”颜色”,然后按照我们选择的算法进行填色即可。

算法1:种子填充法,四联通/八联通

算法简介:假设要将某个区域填充成红色。

从用户点击点的像素开始,上下左右(八联通还有左上,左下,右上,右下)去判断颜色,如果四个方向上的颜色与当前点击点的像素一致,则改变颜色至目标色。然后继续上述这个过程。

ok,可以看到这是一个递归的过程,1个点到4个,4个到16个不断的去延伸。如果按照这种算法,你会写出类似这样的代码:

/*** @param pixels 像素数组* @param w 宽度* @param h 高度* @param pixel 当前点的颜色* @param newColor 填充色* @param i 横坐标* @param j 纵坐标*/private void fillColor01(int[] pixels, int w, int h, int pixel, int newColor, int i, int j){int index = j * w + i;if (pixels[index] != pixel || i  = w || i < 0 || j < 0 || j  = h)return;pixels[index] = newColor;//上fillColor01(pixels, w, h, pixel, newColor, i, j - 1);//右fillColor01(pixels, w, h, pixel, newColor, i + 1, j);//下fillColor01(pixels, w, h, pixel, newColor, i, j + 1);//左fillColor01(pixels, w, h, pixel, newColor, i - 1, j);}

代码很简单,但是如果你去运行,会发生StackOverflowException异常,这个异常主要是因为大量的递归造成的。虽然简单,但是在移动设备上使用该方法不行。

于是,我就想,这个方法不是递归深度过多么,那么我可以使用一个Stack去存像素点,减少递归的深度和次数,于是我把代码改成如下的方式:

/*** @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){mStacks.push(new Point(i, j));while (!mStacks.isEmpty()){Point seed = mStacks.pop();Log.e("TAG", "seed = " + seed.x + " , seed = " + seed.y);int index = seed.y * w + seed.x;pixels[index] = newColor;if (seed.y   0){int top = index - w;if (pixels[top] == pixel){mStacks.push(new Point(seed.x, seed.y - 1));}}if (seed.y < h - 1){int bottom = index + w;if (pixels[bottom] == pixel){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]:

初始化一个空的栈用于存放种子点,将种子点(x, y)入栈; 判断栈是否为空,如果栈为空则结束算法,否则取出栈顶元素作为当前扫描线的种子点(x, y),y是当前的扫描线; 从种子点(x, y)出发,沿当前扫描线向左、右两个方向填充,直到边界。分别标记区段的左、右端点坐标为xLeft和xRight; 分别检查与当前扫描线相邻的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<Point  mStacks = new Stack<Point ();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();}@Overrideprotected 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一样大小的bitmapBitmapDrawable drawable = (BitmapDrawable) getDrawable();Bitmap bm = drawable.getBitmap();mBitmap = Bitmap.createScaledBitmap(bm, getMeasuredWidth(), getMeasuredHeight(), false);}

可以看到我们选择的是继承ImageView,这样只需要将图片设为src即可。 构造方法中获取我们的自定义边界颜色,当然可以不设置~~ 重写测量的目的是为了获取一个和View一样大小的Bitmap便于我们操作。

接下来就是点击啦~

(二)onTouchEvent

@Overridepublic 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);//重新设置bitmapbm.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;}private boolean needFillPixel(int[] pixels, int pixel, int index){if (hasBorderColor){return pixels[index] != mBorderColor;} else{return pixels[index] == pixel;}}/*** 返回一个随机颜色** @return*/private int randomColor(){Random random = new Random();int color = Color.argb(255, random.nextInt(256), random.nextInt(256), random.nextInt(256));return color;}

ok,到此,代码就介绍完毕了~~~

最后贴下布局文件~~

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"xmlns:zhy="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"android:paddingLeft="@dimen/activity_horizontal_margin"android:paddingRight="@dimen/activity_horizontal_margin"android:paddingTop="@dimen/activity_vertical_margin"android:paddingBottom="@dimen/activity_vertical_margin"tools:context=".MainActivity" <com.zhy.colour_app_01.ColourImageViewzhy:border_color="#FF000000"android:src="@drawable/image_007"android:background="#33ff0000"android:layout_width="match_parent"android:layout_centerInParent="true"android:layout_height="match_parent"/ </RelativeLayout <?xml version="1.0" encoding="utf-8"? 
<resources <declare-styleable name="ColourImageView" <attr name="border_color" format="color|reference" </attr </declare-styleable 
</resources 

参考链接

扫描线种子填充算法的解析

图像处理之泛洪填充算法(Flood Fill Algorithm)

递归种子填充算法

扫描线种子填充算法

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对ZaLou.Cn的支持。

更多Android进阶指南 可以扫码 解锁 《Android十大板块文档》

1.Android车载应用开发系统学习指南(附项目实战)

2.Android Framework学习指南,助力成为系统级开发高手

3.2024最新Android中高级面试题汇总+解析,告别零offer

4.企业级Android音视频开发学习路线+项目实战(附源码)

5.Android Jetpack从入门到精通,构建高质量UI界面

6.Flutter技术解析与实战,跨平台首要之选

7.Kotlin从入门到实战,全方面提升架构基础

8.高级Android插件化与组件化(含实战教程和源码)

9.Android 性能优化实战+360°全方面性能调优

10.Android零基础入门到精通,高手进阶之路

敲代码不易,关注一下吧。ღ( ´・ᴗ・` ) 🤔

这篇关于Android不规则封闭区域填充色彩的实例代码的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/826107

相关文章

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

poj 1258 Agri-Net(最小生成树模板代码)

感觉用这题来当模板更适合。 题意就是给你邻接矩阵求最小生成树啦。~ prim代码:效率很高。172k...0ms。 #include<stdio.h>#include<algorithm>using namespace std;const int MaxN = 101;const int INF = 0x3f3f3f3f;int g[MaxN][MaxN];int n

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

计算机毕业设计 大学志愿填报系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

🍊作者:计算机编程-吉哥 🍊简介:专业从事JavaWeb程序开发,微信小程序开发,定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事,生活就是快乐的。 🍊心愿:点赞 👍 收藏 ⭐评论 📝 🍅 文末获取源码联系 👇🏻 精彩专栏推荐订阅 👇🏻 不然下次找不到哟~Java毕业设计项目~热门选题推荐《1000套》 目录 1.技术选型 2.开发工具 3.功能

代码随想录冲冲冲 Day39 动态规划Part7

198. 打家劫舍 dp数组的意义是在第i位的时候偷的最大钱数是多少 如果nums的size为0 总价值当然就是0 如果nums的size为1 总价值是nums[0] 遍历顺序就是从小到大遍历 之后是递推公式 对于dp[i]的最大价值来说有两种可能 1.偷第i个 那么最大价值就是dp[i-2]+nums[i] 2.不偷第i个 那么价值就是dp[i-1] 之后取这两个的最大值就是d

android-opencv-jni

//------------------start opencv--------------------@Override public void onResume(){ super.onResume(); //通过OpenCV引擎服务加载并初始化OpenCV类库,所谓OpenCV引擎服务即是 //OpenCV_2.4.3.2_Manager_2.4_*.apk程序包,存

pip-tools:打造可重复、可控的 Python 开发环境,解决依赖关系,让代码更稳定

在 Python 开发中,管理依赖关系是一项繁琐且容易出错的任务。手动更新依赖版本、处理冲突、确保一致性等等,都可能让开发者感到头疼。而 pip-tools 为开发者提供了一套稳定可靠的解决方案。 什么是 pip-tools? pip-tools 是一组命令行工具,旨在简化 Python 依赖关系的管理,确保项目环境的稳定性和可重复性。它主要包含两个核心工具:pip-compile 和 pip

C++操作符重载实例(独立函数)

C++操作符重载实例,我们把坐标值CVector的加法进行重载,计算c3=c1+c2时,也就是计算x3=x1+x2,y3=y1+y2,今天我们以独立函数的方式重载操作符+(加号),以下是C++代码: c1802.cpp源代码: D:\YcjWork\CppTour>vim c1802.cpp #include <iostream>using namespace std;/*** 以独立函数