Android 不规则封闭区域填充 手指秒变油漆桶,手把手教你5G时代Webview的正确使用姿势-程序员宅基地

技术标签: 程序员  5G  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)

最后:学习总结——Android框架体系架构知识脑图(纯手绘xmind文档)

学完之后,若是想验收效果如何,其实最好的方法就是可自己去总结一下。比如我就会在学习完一个东西之后自己去手绘一份xmind文件的知识梳理大纲脑图,这样也可方便后续的复习,且都是自己的理解,相信随便瞟几眼就能迅速过完整个知识,脑补回来。

下方即为我手绘的Android框架体系架构知识脑图,由于是xmind文件,不好上传,所以小编将其以图片形式导出来传在此处,细节方面不是特别清晰。但可给感兴趣的朋友提供完整的Android框架体系架构知识脑图原件(包括上方的面试解析xmind文档)

除此之外,前文所提及的Alibaba珍藏版 Android框架体系架构 手写文档以及一本 《大话数据结构》 书籍等等相关的学习笔记文档,也皆可分享给认可的朋友!

——感谢大家伙的认可支持,请注意:点赞+点赞+点赞!!!

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

新!**

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

最后:学习总结——Android框架体系架构知识脑图(纯手绘xmind文档)

学完之后,若是想验收效果如何,其实最好的方法就是可自己去总结一下。比如我就会在学习完一个东西之后自己去手绘一份xmind文件的知识梳理大纲脑图,这样也可方便后续的复习,且都是自己的理解,相信随便瞟几眼就能迅速过完整个知识,脑补回来。

下方即为我手绘的Android框架体系架构知识脑图,由于是xmind文件,不好上传,所以小编将其以图片形式导出来传在此处,细节方面不是特别清晰。但可给感兴趣的朋友提供完整的Android框架体系架构知识脑图原件(包括上方的面试解析xmind文档)
[外链图片转存中…(img-2xK0seg4-1712346849598)]

除此之外,前文所提及的Alibaba珍藏版 Android框架体系架构 手写文档以及一本 《大话数据结构》 书籍等等相关的学习笔记文档,也皆可分享给认可的朋友!

——感谢大家伙的认可支持,请注意:点赞+点赞+点赞!!!

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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/2401_84152223/article/details/137415796

智能推荐

while循环&CPU占用率高问题深入分析与解决方案_main函数使用while(1)循环cpu占用99-程序员宅基地

文章浏览阅读3.8k次,点赞9次,收藏28次。直接上一个工作中碰到的问题,另外一个系统开启多线程调用我这边的接口,然后我这边会开启多线程批量查询第三方接口并且返回给调用方。使用的是两三年前别人遗留下来的方法,放到线上后发现确实是可以正常取到结果,但是一旦调用,CPU占用就直接100%(部署环境是win server服务器)。因此查看了下相关的老代码并使用JProfiler查看发现是在某个while循环的时候有问题。具体项目代码就不贴了,类似于下面这段代码。​​​​​​while(flag) {//your code;}这里的flag._main函数使用while(1)循环cpu占用99

【无标题】jetbrains idea shift f6不生效_idea shift +f6快捷键不生效-程序员宅基地

文章浏览阅读347次。idea shift f6 快捷键无效_idea shift +f6快捷键不生效

node.js学习笔记之Node中的核心模块_node模块中有很多核心模块,以下不属于核心模块,使用时需下载的是-程序员宅基地

文章浏览阅读135次。Ecmacript 中没有DOM 和 BOM核心模块Node为JavaScript提供了很多服务器级别,这些API绝大多数都被包装到了一个具名和核心模块中了,例如文件操作的 fs 核心模块 ,http服务构建的http 模块 path 路径操作模块 os 操作系统信息模块// 用来获取机器信息的var os = require('os')// 用来操作路径的var path = require('path')// 获取当前机器的 CPU 信息console.log(os.cpus._node模块中有很多核心模块,以下不属于核心模块,使用时需下载的是

数学建模【SPSS 下载-安装、方差分析与回归分析的SPSS实现(软件概述、方差分析、回归分析)】_化工数学模型数据回归软件-程序员宅基地

文章浏览阅读10w+次,点赞435次,收藏3.4k次。SPSS 22 下载安装过程7.6 方差分析与回归分析的SPSS实现7.6.1 SPSS软件概述1 SPSS版本与安装2 SPSS界面3 SPSS特点4 SPSS数据7.6.2 SPSS与方差分析1 单因素方差分析2 双因素方差分析7.6.3 SPSS与回归分析SPSS回归分析过程牙膏价格问题的回归分析_化工数学模型数据回归软件

利用hutool实现邮件发送功能_hutool发送邮件-程序员宅基地

文章浏览阅读7.5k次。如何利用hutool工具包实现邮件发送功能呢?1、首先引入hutool依赖<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.19</version></dependency>2、编写邮件发送工具类package com.pc.c..._hutool发送邮件

docker安装elasticsearch,elasticsearch-head,kibana,ik分词器_docker安装kibana连接elasticsearch并且elasticsearch有密码-程序员宅基地

文章浏览阅读867次,点赞2次,收藏2次。docker安装elasticsearch,elasticsearch-head,kibana,ik分词器安装方式基本有两种,一种是pull的方式,一种是Dockerfile的方式,由于pull的方式pull下来后还需配置许多东西且不便于复用,个人比较喜欢使用Dockerfile的方式所有docker支持的镜像基本都在https://hub.docker.com/docker的官网上能找到合..._docker安装kibana连接elasticsearch并且elasticsearch有密码

随便推点

Python 攻克移动开发失败!_beeware-程序员宅基地

文章浏览阅读1.3w次,点赞57次,收藏92次。整理 | 郑丽媛出品 | CSDN(ID:CSDNnews)近年来,随着机器学习的兴起,有一门编程语言逐渐变得火热——Python。得益于其针对机器学习提供了大量开源框架和第三方模块,内置..._beeware

Swift4.0_Timer 的基本使用_swift timer 暂停-程序员宅基地

文章浏览阅读7.9k次。//// ViewController.swift// Day_10_Timer//// Created by dongqiangfei on 2018/10/15.// Copyright 2018年 飞飞. All rights reserved.//import UIKitclass ViewController: UIViewController { ..._swift timer 暂停

元素三大等待-程序员宅基地

文章浏览阅读986次,点赞2次,收藏2次。1.硬性等待让当前线程暂停执行,应用场景:代码执行速度太快了,但是UI元素没有立马加载出来,造成两者不同步,这时候就可以让代码等待一下,再去执行找元素的动作线程休眠,强制等待 Thread.sleep(long mills)package com.example.demo;import org.junit.jupiter.api.Test;import org.openqa.selenium.By;import org.openqa.selenium.firefox.Firefox.._元素三大等待

Java软件工程师职位分析_java岗位分析-程序员宅基地

文章浏览阅读3k次,点赞4次,收藏14次。Java软件工程师职位分析_java岗位分析

Java:Unreachable code的解决方法_java unreachable code-程序员宅基地

文章浏览阅读2k次。Java:Unreachable code的解决方法_java unreachable code

标签data-*自定义属性值和根据data属性值查找对应标签_如何根据data-*属性获取对应的标签对象-程序员宅基地

文章浏览阅读1w次。1、html中设置标签data-*的值 标题 11111 222222、点击获取当前标签的data-url的值$('dd').on('click', function() { var urlVal = $(this).data('ur_如何根据data-*属性获取对应的标签对象

推荐文章

热门文章

相关标签