Android实现图片转字符画效果

刘望舒

共 3284字,需浏览 7分钟

 ·

2021-11-18 07:56


作者: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 < widthi++) {
10            for (int j = 0; j < heightj++) {
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 = (float200 / 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, 00, 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(255255255255);
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』做了多年安卓还没编译过源码?一个视频带你玩转!

『BATcoder』我去!安装Ubuntu还有坑?

重生!进阶三部曲第一部《Android进阶之光》第2版 出版!


 BATcoder技术群,让一部分人先进大厂

大家,我是刘望舒,腾讯TVP,著有三本业内知名畅销书,连续四年蝉联电子工业出版社年度优秀作者,百度百科收录的资深技术专家。


想要加入 BATcoder技术群,公号回复BAT 即可。

为了防止失联,欢迎关注我的小号

  微信改了推送机制,真爱请星标本公号👇
浏览 52
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报