Android手绘效果实现

效果图

原理

大概介绍一下实现原理。首先你得有一张图(废话~),接下来就是把这张图的轮廓提取出来,轮廓提取算法有很多,本人不是搞图像处理的,对图像处理感兴趣的童鞋可以查看相关资料。如果你有好的轮廓提取算法,也可以把源码中的算法替换掉,我们采用的轮廓提取算法是Sobel边缘检测。网上的实现有很多,我懒得去实现一遍,github有开源的实现,直接下载了:GraphicLib.本文不具体介绍轮廓提取算法。得到轮廓图以后,接下来要做的是,按照线条的走势,一个一个像素点绘制出来。注意,一定要按照线条的走势显示对应的像素点,如果用两个嵌套的for循环,动画会像网速不好时浏览器显示图片一样。难点就在于此,如何把像素点按照线条方向绘制。接下来我们一起研究。

代码实现

如何让绘制的点不会跳远太远,使之连贯起来?首先,对于一个刚绘制完成的点,接下来要绘制的点肯定要选择离它最近的点,这样肯定是最佳的下一个绘制点。因此,只要我们找到最近的点就可以,寻找最近的点,可以通过以圆的方式不断改变半径的大小进行探测。但是用圆的话需要各种三角函数运算,影响效率。我们可以换一种方法:根据当前的点可以轻松得到每一层以该点为中心的正方形,一层层遍历,直到找到需要的点就是我们要的点。遍历的方法很简单,就是比较对应的正方形的上下左右四条边上面的像素点。如下图所示:

接下来看看如何用代码去实现寻找最近的点:

//获取离指定点最近的一个未绘制过的点private Point getNearestPoint(Point p) {if (p == null) return null;//以点p为中心,向外扩大搜索范围,每次搜索的是与p点相距add的正方形for (int add = 1; add < mSrcBmWidth && add < mSrcBmHeight; add++) {//int beginX = (p.x - add) >= 0 ? (p.x - add) : 0;int endX = (p.x + add) < mSrcBmWidth ? (p.x + add) : mSrcBmWidth - 1;int beginY = (p.y - add) >= 0 ? (p.y - add) : 0;int endY = (p.y + add) < mSrcBmHeight ? (p.y + add) : mSrcBmHeight - 1;//搜索正方形的上下边for (int x = beginX; x <= endX; x++) {if (mArray[x][beginY]) {//标记当前点已经访问过mArray[x][beginY] = false;return new Point(x, beginY);}if (mArray[x][endY]) {//标记当前点已经访问过mArray[x][endY] = false;return new Point(x, endY);}}//搜索正方形的左右边for (int y = beginY + 1; y <= endY - 1; y++) {if (mArray[beginX][y]) {//标记当前点已经访问过mArray[beginX][beginY] = false;return new Point(beginX, beginY);}if (mArray[endX][y]) {//标记当前点已经访问过mArray[endX][y] = false;return new Point(endX, y);}}}return null;}

任何一个点,只要还存在没有绘制过的点,就一定能找得到与它最近的点,如果找不到,说明所有的点已经绘制完毕。为了防止查找到重复的点,需要把访问过的点做上记号(即设为false)。我们需要把整张图中每一个像素点位置作好记号,标记哪些点是需要绘制,哪些点是不需要绘制或者是已经绘制过。用一个boolean[][]型数组保存。还需要记录最后一次访问的点,以便继续下一次的绘制。根据最后一次访问的点继续寻找最近点,反复迭代,把所有的点绘制完成后,整张图就出来了。程序开始时,将最后一次访问的点初始化为左上角的点。

   private Point mLastPoint = new Point(0, 0);
//获取下一个需要绘制的点private Point getNextPoint() {mLastPoint = getNearestPoint(mLastPoint);return mLastPoint;}

接下来是将点绘制到Bitmap上,在将Bitmap绘制到SurfaceView的Canvas上。这里这么做的目的是,SurfaceView内部使用了双缓存,直接绘制到SurfaceView的Canvas可能会闪屏。

/*** //绘制* return :false 表示绘制完成,true表示还需要继续绘制*/private boolean draw() {mPaint.setStyle(Paint.Style.STROKE);mPaint.setColor(Color.BLACK);//获取count个点后,一次性绘制到bitmap在把bitmap绘制到SurfaceViewint count = 100;Point p = null;while (count-- > 0) {p = getNextPoint();if (p == null) {//如果p为空,说明所有的点已经绘制完成return false;}mTmpCanvas.drawPoint(p.x, p.y + offsetY, mPaint);}//将bitmap绘制到SurfaceView中Canvas canvas = mSurfaceHolder.lockCanvas();canvas.drawBitmap(mTmpBm, 0, 0, mPaint);if (p != null)canvas.drawBitmap(mPaintBm, p.x, p.y - mPaintBm.getHeight() + offsetY, mPaint);mSurfaceHolder.unlockCanvasAndPost(canvas);return true;}

基本上绘制算完成了,附上完整的代码:

package com.hc.myoutline;import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;/*** Package com.hc.myoutline* Created by HuaChao on 2016/5/27.*/
public class DrawOutlineView extends SurfaceView implements SurfaceHolder.Callback {private SurfaceHolder mSurfaceHolder;private Bitmap mTmpBm;private Canvas mTmpCanvas;private int mWidth;private int mHeight;private Paint mPaint;private int mSrcBmWidth;private int mSrcBmHeight;private boolean[][] mArray;private int offsetY = 100;private Bitmap mPaintBm;private Point mLastPoint = new Point(0, 0);public DrawOutlineView(Context context) {super(context);init();}public DrawOutlineView(Context context, AttributeSet attrs) {super(context, attrs);init();}private void init() {mSurfaceHolder = getHolder();mSurfaceHolder.addCallback(this);mPaint = new Paint();mPaint.setColor(Color.BLACK);}//设置画笔图片public void setPaintBm(Bitmap paintBm) {mPaintBm = paintBm;}//获取离指定点最近的一个未绘制过的点private Point getNearestPoint(Point p) {if (p == null) return null;//以点p为中心,向外扩大搜索范围,每次搜索的是与p点相距add的正方形for (int add = 1; add < mSrcBmWidth && add < mSrcBmHeight; add++) {//int beginX = (p.x - add) >= 0 ? (p.x - add) : 0;int endX = (p.x + add) < mSrcBmWidth ? (p.x + add) : mSrcBmWidth - 1;int beginY = (p.y - add) >= 0 ? (p.y - add) : 0;int endY = (p.y + add) < mSrcBmHeight ? (p.y + add) : mSrcBmHeight - 1;//搜索正方形的上下边for (int x = beginX; x <= endX; x++) {if (mArray[x][beginY]) {mArray[x][beginY] = false;return new Point(x, beginY);}if (mArray[x][endY]) {mArray[x][endY] = false;return new Point(x, endY);}}//搜索正方形的左右边for (int y = beginY + 1; y <= endY - 1; y++) {if (mArray[beginX][y]) {mArray[beginX][beginY] = false;return new Point(beginX, beginY);}if (mArray[endX][y]) {mArray[endX][y] = false;return new Point(endX, y);}}}return null;}//获取下一个需要绘制的点private Point getNextPoint() {mLastPoint = getNearestPoint(mLastPoint);return mLastPoint;}/*** //绘制* return :false 表示绘制完成,true表示还需要继续绘制*/private boolean draw() {mPaint.setStyle(Paint.Style.STROKE);mPaint.setColor(Color.BLACK);//获取count个点后,一次性绘制到bitmap在把bitmap绘制到SurfaceViewint count = 100;Point p = null;while (count-- > 0) {p = getNextPoint();if (p == null) {//如果p为空,说明所有的点已经绘制完成return false;}mTmpCanvas.drawPoint(p.x, p.y + offsetY, mPaint);}//将bitmap绘制到SurfaceView中Canvas canvas = mSurfaceHolder.lockCanvas();canvas.drawBitmap(mTmpBm, 0, 0, mPaint);if (p != null)canvas.drawBitmap(mPaintBm, p.x, p.y - mPaintBm.getHeight() + offsetY, mPaint);mSurfaceHolder.unlockCanvasAndPost(canvas);return true;}//重画public void reDraw(boolean[][] array) {if (isDrawing) return;mTmpBm = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);mTmpCanvas = new Canvas(mTmpBm);mPaint.setColor(Color.WHITE);mPaint.setStyle(Paint.Style.FILL);mTmpCanvas.drawRect(0, 0, mWidth, mHeight, mPaint);mLastPoint = new Point(0, 0);beginDraw(array);}private boolean isDrawing = false;public void beginDraw(boolean[][] array) {if (isDrawing) return;this.mArray = array;mSrcBmWidth = array.length;mSrcBmHeight = array[0].length;new Thread() {@Overridepublic void run() {while (true) {isDrawing = true;boolean rs = draw();if (!rs) break;try {sleep(20);} catch (InterruptedException e) {e.printStackTrace();}}isDrawing = false;}}.start();}@Overridepublic void surfaceCreated(SurfaceHolder holder) {}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {this.mWidth = width;this.mHeight = height;mTmpBm = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);mTmpCanvas = new Canvas(mTmpBm);mPaint.setColor(Color.WHITE);mPaint.setStyle(Paint.Style.FILL);mTmpCanvas.drawRect(0, 0, mWidth, mHeight, mPaint);Canvas canvas = holder.lockCanvas();canvas.drawBitmap(mTmpBm, 0, 0, mPaint);holder.unlockCanvasAndPost(canvas);mPaint.setStyle(Paint.Style.STROKE);}@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {}
}

接下来是在MainActivity里面传入参数过来,附上MainActivity的代码:

package com.hc.myoutline;import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.MotionEvent;public class MainActivity extends AppCompatActivity {private DrawOutlineView drawOutlineView;private Bitmap sobelBm;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//将Bitmap压缩处理,防止OOMBitmap bm = CommenUtils.getRatioBitmap(this, R.drawable.test, 100, 100);//返回的是处理过的BitmapsobelBm = SobelUtils.Sobel(bm);drawOutlineView = (DrawOutlineView) findViewById(R.id.outline);Bitmap paintBm = CommenUtils.getRatioBitmap(this, R.drawable.paint, 10, 20);drawOutlineView.setPaintBm(paintBm);}//根据Bitmap信息,获取每个位置的像素点是否需要绘制//使用boolean数组而不是int[][]主要是考虑到内存的消耗private boolean[][] getArray(Bitmap bitmap) {boolean[][] b = new boolean[bitmap.getWidth()][bitmap.getHeight()];for (int i = 0; i < bitmap.getWidth(); i++) {for (int j = 0; j < bitmap.getHeight(); j++) {if (bitmap.getPixel(i, j) != Color.WHITE)b[i][j] = true;elseb[i][j] = false;}}return b;}boolean first = true;//点击时开始绘制@Overridepublic boolean onTouchEvent(MotionEvent event) {if (first) {first = false;drawOutlineView.beginDraw(getArray(sobelBm));} elsedrawOutlineView.reDraw(getArray(sobelBm));return true;}
}

关于轮廓提取的具体实现代码这里不粘出,可以去GitHub查看:GraphicLib 或者是下载我的源代码查看。所有内容已经结束,赶紧下载源码运行一下你的照片去秀一下你的逼格吧!

最后的最后:请注意,源码中,轮廓的提取是运行在主线程中,如果图片比较复杂,可能会导致ANR,建议另开线程处理。别问为什么我不去改,一个字:懒!另外,接下来一篇文章中,我将介绍提高运算速度相关内容,提升轮廓提取速度,敬请期待~

源码地址:Android自动手绘,圆你儿时画家梦

参考链接

Android自动手绘,圆你儿时画家梦! - huachao1001的专栏 - 博客频道 - CSDN.NET

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/494046.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

干货|120页精华PPT详解工业机器人本体设计运算及仿真

来源&#xff1a;哈尔滨工业大学摘要&#xff1a;120页精华PPT详解工业机器人本体设计运算及仿真未来智能实验室是人工智能学家与科学院相关机构联合成立的人工智能&#xff0c;互联网和脑科学交叉研究机构。未来智能实验室的主要工作包括&#xff1a;建立AI智能系统智商评测体…

环信SDK集成

利用环信SDK可以实现即时通讯&#xff0c;但在集成的过程中碰到了不少的坑。 注意 选择项目路径&#xff0c;这里以最新版环信demo为例 注意&#xff1a;环信的ChatDemoUI这个demo里边因为研发的同事为了照顾老版本的AndroidStudio使用者&#xff0c;已经用eclipse生成了bui…

协作机器人先驱宣布倒闭!累计融资10.3亿元,贝佐斯投资八轮

来源&#xff1a;量子位一家机器人领域的头部玩家&#xff0c;还不是说倒下就倒下了。上个月&#xff0c;协作机器人的先驱Rethink Robotics刚刚宣布出售第2500个机器人产品&#xff0c;而昨天&#xff0c;就又宣布关门大吉了。CEO Scott Eckert在接受外媒The Robot Report采访…

YOLOv8改进 | 2023注意力篇 | MSDA多尺度空洞注意力(附多位置添加教程)

一、本文介绍 本文给大家带来的改进机制是MSDA&#xff08;多尺度空洞注意力&#xff09;发表于今年的中科院一区(算是国内计算机领域的最高期刊了)&#xff0c;其全称是"DilateFormer: Multi-Scale Dilated Transformer for Visual Recognition"。MSDA的主要思想是…

贝索斯专访:亚马逊帝国大规模业务转型的秘诀

来源&#xff1a;机器之能摘要&#xff1a;这篇文章简明、清晰地揭示出这家似乎没有边界、无拘束公司成功「转身」背后的逻辑与秘诀。与美国的其它科技业巨头不同&#xff0c;亚马逊并没有一个总的企业园区。在其全球的 57.5 万名雇员中&#xff0c;有 4.5 万名员工和管理人员位…

2018及过去20年诺贝尔化学奖获奖者及其贡献!

来源&#xff1a;科学网摘要&#xff1a;2018及过去20年诺贝尔化学奖获奖者及其贡献&#xff01;2018年诺贝尔化学获的获得者为美国科学家Frances H. Arnold, George P. Smith和英国科学家George P.Winter&#xff0c;他们利用进化的力量为人类造福。获奖的内容分别是研究酶的定…

RxJava学习入门

RxJava是什么 一个词&#xff1a;异步。 RxJava 在 GitHub 主页上的自我介绍是 “a library for composing asynchronous and event-based programs using observable sequences for the Java VM”&#xff08;一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序…

解读2018年诺贝尔化学奖成果:用进化的力量解决化学问题

来源&#xff1a;新华网摘要&#xff1a;新华社斯德哥尔摩&#xff11;&#xff10;月&#xff13;日电 科普&#xff1a;用进化的力量解决化学问题——解读&#xff12;&#xff10;&#xff11;&#xff18;年诺贝尔化学奖成果地球的生命经过长期进化最终获得强大的适应力&am…

在win8下安装使用java及在win8下部署java环境变量-图文

为了反编译APK&#xff0c;不得不安装一些Androidfby、apktool1.4.1、dex2jar-0.0.9.9等&#xff0c;甚至连DW也安装了&#xff0c;但是我的电脑是win8X64的&#xff0c;也就是64位的win8系统&#xff0c;这就有点头疼了&#xff0c;出现了&#xff1a; 不是内部或外部命令&…

国际运营商智慧城市探索与实践

来源&#xff1a;中国信息通信研究院CAICT摘要&#xff1a;AT&T、SKT、沃达丰在智慧城市领域探索的经验。2008年11月&#xff0c;IBM提出“智慧地球” 理念引发产业界热议&#xff0c;2010年&#xff0c;该公司进一步提出“智慧城市”愿景作为“智慧地球”在城市运营中的具…

Retrofit学习入门

Retrofit的使用 设置权限与添加依赖 定义请求接口通过创建一个retrofit生成一个接口的实现类(动态代理)调用接口请求数据 设置权限与添加依赖 权限&#xff1a;首先确保在AndroidManifest.xml中请求了网络权限 &#xff1a; <uses-permission android:name"android…

autotools入门笔记(一)

GNU autotools作用&#xff1a;收集系统配置信息并自动生成Makefile文件。 GNU autotools主要包括三个工具&#xff1a;autoconf、automake、libtool&#xff0c;还有很多辅助的工具&#xff0c;包括&#xff1a;autoheader、aclocal、autoscan。 ● autoscan检测源文件生成con…

本田、大众宣布智能路口研究新进展 以安全为重点

编译&#xff1a;网易智能摘要&#xff1a;每年&#xff0c;在十字路口发生的交通事故约占交通事故死亡人数的20%&#xff0c;这就是为什么汽车制造商和供应商都下定决心要创造出智能的十字路口&#xff0c;利用尖端技术来减少&#xff08;或者最好是能够避免&#xff09;在十字…

智能连接:5G、AI和IoT的组合如何改变美洲

来源&#xff1a;199IT互联网数据中心摘要&#xff1a;GSMA发布了新报告“智能连接&#xff1a;5G、AI和IoT的组合如何改变美洲”&#xff0c;强调了该地区如何从这些科技中受益。GSMA Intelligence预测&#xff0c;到2025年全球5G连接数量将达到13亿&#xff0c;覆盖全球40%的…

刚刚,生物学横扫诺贝尔3大奖,两名女性获奖!化学奖授予试管中的“进化论”...

来源&#xff1a;Deeptech深科技北京时间 10 月 3 日下午 5 点 45 分&#xff0c;2018 诺贝尔化学奖揭晓——诺贝尔委员会宣布&#xff0c;将此奖项一半颁给女科学家Frances H. Arnold&#xff0c;另一半则由George P. Smith、 Gregory P. Winter两人共享。此次诺贝尔化学奖表彰…

MACIOS Socket编程

转自 https://github.com/kejinlu/objc-doc/blob/master/Socket%E7%BC%96%E7%A8%8B.md 大纲 一.Socket简介二.BSD Socket编程准备 1.地址2.端口3.网络字节序4.半相关与全相关5.网络编程模型三.socket接口编程示例四.使用select五.使用kqueue六.使用流注:文档中设计涉及的代码也…

Android缓存学习入门

本文主要包括以下内容 利用LruCache实现内存缓存 利用DiskLruCache实现磁盘缓存 LruCache与DiskLruCache结合实例 利用了缓存机制的瀑布流实例 内存缓存的实现 public class PhotoWallAdapter extends ArrayAdapter<String> implements OnScrollListener {/*** 记录…

2018年人工智能之自动驾驶研究报告

来源&#xff1a;AMiner未来智能实验室是人工智能学家与科学院相关机构联合成立的人工智能&#xff0c;互联网和脑科学交叉研究机构。未来智能实验室的主要工作包括&#xff1a;建立AI智能系统智商评测体系&#xff0c;开展世界人工智能智商评测&#xff1b;开展互联网&#xf…

MyEclipse+Tomcat 启动时出现 configuration error occured during startup错误的解决方法

配置好Tomcat server&#xff0c;启动Tomcat&#xff0c;报如下错误&#xff1a; 解决方法如下&#xff1a; 选中Tomcat 6中的JDK一项&#xff0c;这里要特别注意&#xff0c;默认的是JRE的运行环境&#xff0c;这里要设定成JDK的&#xff0c;否则&#xff0c;MyEclipse无法正常…

硅谷首场AI硬件峰会干货报告:AI芯片井喷期即将到来

来源&#xff1a;智东西摘要&#xff1a;英特尔和英伟达对AI芯片的角逐&#xff0c;AI芯片行业的创业者机会。AI芯片不仅是未来十年半导体行业中最有希望的增长机会之一&#xff0c;而且还是有可能破坏传统计算市场的力量。现今 99&#xff05;的AI软件尚未编写&#xff0c;只有…