Android实现图片转字符画效果
作者:kinton
来源:https://www.jianshu.com/p/16ef3bf9ac5c
开门见山!先上效果图:
原图
转换字符画
字符稍微密集了一点,不过放大来看大家应该能够看到确确实实是字符画。
那我们在安卓端是如何实现?
Android开发中对图片的操作,显示一般都是通过Bitmap进行的,我们可以通过图片路径获取Bitmap对象:
1static public Bitmap getBitmapByUri(Context context, Uri uri) {
2 Bitmap bit = null;
3 try {
4 bit = BitmapFactory.decodeStream(context.getContentResolver().openInputStream(uri));
5 } catch (Exception ex) {
6 Log.i("utils", "" + ex.getMessage());
7 }
8 return bit;
9 }
一个图片的每一个像素其实都是一个值,这个值代表着这个像素的颜色,我们可以通过位运算来获取这个像素的ARGB值。
在安卓开发中要获取一个图片的每一个像素值其实很简单:
1//按照参数范围获取像素数组
2bitmap.getPixels(...);
3//或者获取单个位置像素
4bitmap.getPixel(x,y);
当我们获取到了像素值,转换成ARGB值后,我们获取带了RGB三个值,要如何判断什么颜色用什么字?要知道调色轮盘的颜色数不胜数:
截取自iconfont的调色板
这么多的颜色我们应该用什么样的标准给这么多颜色归类?灰度值是个很好的办法,什么是灰度值?灰度值的范围只有0到255,计算方式一般是RGB三个值的平均值(也可以通过对RGB值进行加权计算不同的灰度),在很多图像处理里面的图片灰度化步骤用的就是这种方法。
灰度化示例(转自百度百科图片)
原理跟思路清楚了,我们实现下把Bitmap转化成灰度值数组的方法:
1 static public int[][] getBitmap2GaryArray(Bitmap bitmap) {
2 int width = bitmap.getWidth(); //获取位图的宽
3 int height = bitmap.getHeight(); //获取位图的高
4 int[][] datas = new int[width][height]; //通过位图的大小创建像素点数组
5 //也可以使用getPixels方法来获取像素数组
6 //bitmap.getPixels(datas, 0, width, 0, 0, width, height);
7 int alpha = 0xFF << 24;
8 bitmap.getPixels();
9 for (int i = 0; i < width; i++) {
10 for (int j = 0; j < height; j++) {
11 int grey = bitmap.getPixel(i, j);
12 int red = (grey & 0x00ff0000) >> 16; //取高两位
13 int green = (grey & 0x0000ff00) >> 8; //取中两位
14 int blue = grey & 0x000000ff; //取低两位
15
16 grey = (int) ((float) red * 0.4 + (float) green * 0.3 + (float) blue * 0.3);
17 datas[i][j] = grey;
18 }
19 }
20 return datas;
21 }
在获取像素前我们还需要多做一步,为了防止图片过大(类似2K图/4K图),我们需要在获取像素前做一次统一标准化的压缩,我设置为宽为200,高等比例压缩。
1...
2//宽为200时,计算压缩比例是多少
3float xScale = (float) 200 / bitmap.getWidth();
4bitmap = BitmapUtils.compressBitmap(bitmap, xScale, xScale);
5...
6
7static public Bitmap compressBitmap(Bitmap bitmap, float sx, float sy) {
8 Matrix matrix = new Matrix();
9 matrix.setScale(sx, sy);
10 Log.i("utils_compressBitmap", "" + sx + "," + sy);
11 Bitmap bit = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
12 bitmap.getHeight(), matrix, true);
13
14 Log.i("utils_compressBitmap", "" + bit.getWidth() + "," + bit.getHeight());
15 //记得把不用的bitmap进行回收,以防止OOM
16 bitmap.recycle();
17 return bit;
18 }
当我们通过压缩好的图片获取到了它的灰度值数组,现在我们就可以根据灰度值转换为对应的文字了,我给了灰度值15个等级,根据颜色的深度给对应的中文字:(0是黑色,255是白色)
1static String[] arr = {"餮", "淼", "圆", "困", "品", "回", "田", "凸", "口", "王", "天", "干", "工", "十", "一"};
我们制定好字符等级,那么要怎么根据数组制作图片呢?
上面说过图片的操作在Android中一般都在Bitmap进行的,所以我们要想绘制一张新的图片,那么就创建一个新的Bitmap对象,绘制的事情交给万能的画布就好了,画布带有文字绘制接口完美的符合我们需求:
1static public Bitmap array2Bitmap(int[][] garyDatas, int width, int height) {
2 //绘制一个字对应一个像素,所以新绘制的Bitmap的大小应该乘上字体大小
3 Bitmap whiteBgBitmap = Bitmap.createBitmap(width * 6 + 20, height * 6 + 20, Bitmap.Config.ARGB_8888);
4 //在Bitmap上创建画布
5 Canvas canvas = new Canvas(whiteBgBitmap);
6 //绘制白色背景
7 canvas.drawARGB(255, 255, 255, 255);
8 //初始化画笔
9 Paint mPaint = new Paint();
10 mPaint.setStrokeWidth(1);
11 mPaint.setColor(Color.BLACK);
12 mPaint.setTextSize(6);
13
14 int x = 0;
15 //遍历灰度值数组
16 for (int xIndex = 10; x < width; xIndex += 6) {
17 int y = 0;
18 for (int yIndex = 10; y < height; yIndex += 6) {
19 //获取灰度值对应的字符
20 int charIndex = garyDatas[x][y] / 18;
21 String _char = arr[charIndex];
22 //在对应的坐标绘制字符
23 canvas.drawText(_char, xIndex, yIndex, mPaint);
24 y++;
25 }
26 x++;
27 }
28 return whiteBgBitmap;
29 }
绘制完成后输出Bitmap,下一步是把Bitmap保存为本地图片,关键代码如下:
1...
2File photo = new File(Environment.getExternalStorageDirectory() + "/" + dirName, String.format("CharPic_%d.jpg",System.currentTimeMillis()));
3
4File dir = new File(photo.getParent());
5if(!dir.exists()){
6 dir.mkdirs();
7 }
8photo.createNewFile();
9saveBitmapToJPG(bmp, photo);
10...
11
12static private void saveBitmapToJPG(Bitmap bitmap, File photo) throws IOException {
13 OutputStream stream = new FileOutputStream(photo);
14 bitmap.compress(Bitmap.CompressFormat.JPEG, 80, stream);
15 stream.close();
16 bitmap.recycle();
17 }
下一步我们为了在系统相册更好的找到我们的图片,我们可以把图片发送一个广播来通知系统相册:
1Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
2Uri contentUri = Uri.fromFile(photo);
3mediaScanIntent.setData(contentUri);
4context.sendBroadcast(mediaScanIntent);
以上就是图片转成字符画的全部代码与讲解。可能有的人会问这样的功能,除了酷炫,有趣,牛逼之外,做出来有什么用?我只能问得好!乍一看好像用处不大,但是基于这个功能我们可以做短视频转换字符画视频。下一篇我将会讲一下如何把视频转换成字符画视频,本篇的内容到此为止,如有问题,欢迎提出,如有错误,欢迎指正,谢谢。
奉上完整的源码(已完成视频转换跟图片转换功能),觉得有趣的请star一下呗。
完整项目源码地址:https://github.com/452kinton/CharacterDance
• 耗时2年,Android进阶三部曲第三部《Android进阶指北》出版!
• 『BATcoder』做了多年安卓还没编译过源码?一个视频带你玩转!
• 重生!进阶三部曲第一部《Android进阶之光》第2版 出版!
BATcoder技术群,让一部分人先进大厂
大家好,我是刘望舒,腾讯TVP,著有三本业内知名畅销书,连续四年蝉联电子工业出版社年度优秀作者,百度百科收录的资深技术专家。
想要加入 BATcoder技术群,公号回复
BAT
即可。
为了防止失联,欢迎关注我的小号
微信改了推送机制,真爱请星标本公号👇