Android 实现 图片 转 字符画 效果

开门见山!先上效果图:

  

  

  

字符稍微密集了一点,不过放大来看大家应该能够看到确确实实是 字符画

Android 端实现

Android开发中对图片的操作,显示一般都是通过Bitmap进行的,我们可以通过图片路径获取Bitmap对象:

static public Bitmap getBitmapByUri(Context context, Uri uri) {
        Bitmap bit = null;
        try {
            bit = BitmapFactory.decodeStream(context.getContentResolver().openInputStream(uri));
        } catch (Exception ex) {
            Log.i("utils", "" + ex.getMessage());
        }
        return bit;
    }

一个图片的每一个像素其实都是一个值,这个值代表着这个像素的颜色,我们可以通过位运算来获取这个像素的ARGB值。

在安卓开发中要获取一个图片的每一个像素值其实很简单:

//按照参数范围获取像素数组
bitmap.getPixels(...);
//或者获取单个位置像素
bitmap.getPixel(x,y);

当我们获取到了像素值,转换成ARGB值后,我们获取带了RGB三个值,要如何判断什么颜色用什么字?要知道调色轮盘的颜色数不胜数:

  

这么多的颜色我们应该用什么样的标准给这么多颜色归类?

灰度值获取

灰度值是个很好的办法,什么是灰度值?

灰度值的范围只有0到255,计算方式一般是RGB三个值的平均值(也可以通过对RGB值进行加权计算不同的灰度),在很多图像处理里面的图片灰度化步骤用的就是这种方法。

  

原理跟思路清楚了,我们实现下把Bitmap转化成灰度值数组的方法:

 static public int[][] getBitmap2GaryArray(Bitmap bitmap) {
        int width = bitmap.getWidth();            //获取位图的宽
        int height = bitmap.getHeight();        //获取位图的高
        int[][] datas = new int[width][height];    //通过位图的大小创建像素点数组
        //也可以使用getPixels方法来获取像素数组
        //bitmap.getPixels(datas, 0, width, 0, 0, width, height);
        int alpha = 0xFF << 24;
        bitmap.getPixels();
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                int grey = bitmap.getPixel(i, j);
                int red = (grey & 0x00ff0000) >> 16; //取高两位
                int green = (grey & 0x0000ff00) >> 8; //取中两位
                int blue = grey & 0x000000ff; //取低两位

                grey = (int) ((float) red * 0.4 + (float) green * 0.3 + (float) blue * 0.3);
                datas[i][j] = grey;
            }
        }
        return datas;
    }

在获取像素前我们还需要多做一步,为了防止图片过大(类似2K图/4K图),我们需要在获取像素前做一次统一标准化的压缩,我设置为宽为200,高等比例压缩。

...
//宽为200时,计算压缩比例是多少
float xScale = (float) 200 / bitmap.getWidth();
bitmap = BitmapUtils.compressBitmap(bitmap, xScale, xScale);
...

static public Bitmap compressBitmap(Bitmap bitmap, float sx, float sy) {
        Matrix matrix = new Matrix();
        matrix.setScale(sx, sy);
        Log.i("utils_compressBitmap", "" + sx + "," + sy);
        Bitmap bit = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
                bitmap.getHeight(), matrix, true);

        Log.i("utils_compressBitmap", "" + bit.getWidth() + "," + bit.getHeight());
        //记得把不用的bitmap进行回收,以防止OOM
        bitmap.recycle();
        return bit;
    }

当我们通过压缩好的图片获取到了它的灰度值数组,现在我们就可以根据灰度值转换为对应的文字了,我给了灰度值15个等级,根据颜色的深度给对应的中文字:(0是黑色,255是白色)

static String[] arr = {"餮", "淼", "圆", "困", "品", "回", "田", "凸", "口", "王", "天", "干", "工", "十", "一"};

我们制定好字符等级,那么要怎么根据数组制作图片呢?

根据 字符 绘制 Bitmap

上面说过图片的操作在Android中一般都在Bitmap进行的,所以我们要想绘制一张新的图片,那么就创建一个新的Bitmap对象,绘制的事情交给万能的画布就好了,画布带有文字绘制接口完美的符合我们需求:

static public Bitmap array2Bitmap(int[][] garyDatas, int width, int height) {
        //绘制一个字对应一个像素,所以新绘制的Bitmap的大小应该乘上字体大小
        Bitmap whiteBgBitmap = Bitmap.createBitmap(width * 6 + 20, height * 6 + 20, Bitmap.Config.ARGB_8888);
        //在Bitmap上创建画布
        Canvas canvas = new Canvas(whiteBgBitmap);
        //绘制白色背景
        canvas.drawARGB(255, 255, 255, 255);
        //初始化画笔
        Paint mPaint = new Paint();
        mPaint.setStrokeWidth(1);
        mPaint.setColor(Color.BLACK);
        mPaint.setTextSize(6);

        int x = 0;
         //遍历灰度值数组
        for (int xIndex = 10; x < width; xIndex += 6) {
            int y = 0;
            for (int yIndex = 10; y < height; yIndex += 6) {
                //获取灰度值对应的字符
                int charIndex = garyDatas[x][y] / 18;
                String _char = arr[charIndex];
                //在对应的坐标绘制字符
                canvas.drawText(_char, xIndex, yIndex, mPaint);
                y++;
            }
            x++;
        }
        return whiteBgBitmap;
    }

绘制完成后输出Bitmap,下一步是把Bitmap保存为本地图片,关键代码如下:

...
File photo = new File(Environment.getExternalStorageDirectory() + "/" + dirName, String.format("CharPic_%d.jpg",System.currentTimeMillis()));

File dir = new File(photo.getParent());
if(!dir.exists()){
      dir.mkdirs();
 }
photo.createNewFile();
saveBitmapToJPG(bmp, photo);
...

static private void saveBitmapToJPG(Bitmap bitmap, File photo) throws IOException {
        OutputStream stream = new FileOutputStream(photo);
        bitmap.compress(Bitmap.CompressFormat.JPEG, 80, stream);
        stream.close();
        bitmap.recycle();
    }

下一步我们为了在系统相册更好的找到我们的图片,我们可以把图片发送一个广播来通知系统相册:

Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri contentUri = Uri.fromFile(photo);
mediaScanIntent.setData(contentUri);
context.sendBroadcast(mediaScanIntent);

以上就是图片转成字符画的全部代码与讲解。可能有的人会问这样的功能,除了酷炫,有趣,牛逼之外,做出来有什么用?我只能问得好!乍一看好像用处不大,但是基于这个功能我们可以做短视频转换字符画视频。

奉上完整的源码,觉得有趣的请star一下呗。

完整项目源码地址:

  • https://github.com/452kinton/CharacterDance

作者:Kinton

来源:https://www.jianshu.com/p/16ef3bf9ac5c


技术交流,欢迎加我微信:ezglumes ,拉你入技术交流群。

推荐阅读:

音视频面试基础题

OpenGL ES 学习资源分享

一文读懂 YUV 的采样与格式

OpenGL 之 GPUImage 源码分析

推荐几个堪称教科书级别的 Android 音视频入门项目

觉得不错,点个在看呗~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值