Android實(shí)現(xiàn)圖片轉(zhuǎn)字符畫效果
作者:kinton
來(lái)源:https://www.jianshu.com/p/16ef3bf9ac5c
開門見山!先上效果圖:

原圖

轉(zhuǎn)換字符畫
字符稍微密集了一點(diǎn),不過放大來(lái)看大家應(yīng)該能夠看到確確實(shí)實(shí)是字符畫。
那我們?cè)诎沧慷耸侨绾螌?shí)現(xiàn)?
Android開發(fā)中對(duì)圖片的操作,顯示一般都是通過Bitmap進(jìn)行的,我們可以通過圖片路徑獲取Bitmap對(duì)象:
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????}
一個(gè)圖片的每一個(gè)像素其實(shí)都是一個(gè)值,這個(gè)值代表著這個(gè)像素的顏色,我們可以通過位運(yùn)算來(lái)獲取這個(gè)像素的ARGB值。
在安卓開發(fā)中要獲取一個(gè)圖片的每一個(gè)像素值其實(shí)很簡(jiǎn)單:
1//按照參數(shù)范圍獲取像素?cái)?shù)組
2bitmap.getPixels(...);
3//或者獲取單個(gè)位置像素
4bitmap.getPixel(x,y);
當(dāng)我們獲取到了像素值,轉(zhuǎn)換成ARGB值后,我們獲取帶了RGB三個(gè)值,要如何判斷什么顏色用什么字?要知道調(diào)色輪盤的顏色數(shù)不勝數(shù):

截取自iconfont的調(diào)色板
這么多的顏色我們應(yīng)該用什么樣的標(biāo)準(zhǔn)給這么多顏色歸類?灰度值是個(gè)很好的辦法,什么是灰度值?灰度值的范圍只有0到255,計(jì)算方式一般是RGB三個(gè)值的平均值(也可以通過對(duì)RGB值進(jìn)行加權(quán)計(jì)算不同的灰度),在很多圖像處理里面的圖片灰度化步驟用的就是這種方法。

灰度化示例(轉(zhuǎn)自百度百科圖片)
原理跟思路清楚了,我們實(shí)現(xiàn)下把Bitmap轉(zhuǎn)化成灰度值數(shù)組的方法:
1?static?public?int[][]?getBitmap2GaryArray(Bitmap?bitmap)?{
2????????int?width?=?bitmap.getWidth();????????????//獲取位圖的寬
3????????int?height?=?bitmap.getHeight();????????//獲取位圖的高
4????????int[][]?datas?=?new?int[width][height];????//通過位圖的大小創(chuàng)建像素點(diǎn)數(shù)組
5????????//也可以使用getPixels方法來(lái)獲取像素?cái)?shù)組
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圖),我們需要在獲取像素前做一次統(tǒng)一標(biāo)準(zhǔn)化的壓縮,我設(shè)置為寬為200,高等比例壓縮。
1...
2//寬為200時(shí),計(jì)算壓縮比例是多少
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進(jìn)行回收,以防止OOM
16????????bitmap.recycle();
17????????return?bit;
18????}
當(dāng)我們通過壓縮好的圖片獲取到了它的灰度值數(shù)組,現(xiàn)在我們就可以根據(jù)灰度值轉(zhuǎn)換為對(duì)應(yīng)的文字了,我給了灰度值15個(gè)等級(jí),根據(jù)顏色的深度給對(duì)應(yīng)的中文字:(0是黑色,255是白色)
1static?String[]?arr?=?{"餮",?"淼",?"圓",?"困",?"品",?"回",?"田",?"凸",?"口",?"王",?"天",?"干",?"工",?"十",?"一"};
我們制定好字符等級(jí),那么要怎么根據(jù)數(shù)組制作圖片呢?
上面說(shuō)過圖片的操作在Android中一般都在Bitmap進(jìn)行的,所以我們要想繪制一張新的圖片,那么就創(chuàng)建一個(gè)新的Bitmap對(duì)象,繪制的事情交給萬(wàn)能的畫布就好了,畫布帶有文字繪制接口完美的符合我們需求:
1static?public?Bitmap?array2Bitmap(int[][]?garyDatas,?int?width,?int?height)?{
2????????//繪制一個(gè)字對(duì)應(yīng)一個(gè)像素,所以新繪制的Bitmap的大小應(yīng)該乘上字體大小
3????????Bitmap?whiteBgBitmap?=?Bitmap.createBitmap(width?*?6?+?20,?height?*?6?+?20,?Bitmap.Config.ARGB_8888);
4????????//在Bitmap上創(chuàng)建畫布
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?????????//遍歷灰度值數(shù)組
16????????for?(int?xIndex?=?10;?x?6)?{
17????????????int?y?=?0;
18????????????for?(int?yIndex?=?10;?y?6)?{
19????????????????//獲取灰度值對(duì)應(yīng)的字符
20????????????????int?charIndex?=?garyDatas[x][y]?/?18;
21????????????????String?_char?=?arr[charIndex];
22????????????????//在對(duì)應(yīng)的坐標(biāo)繪制字符
23????????????????canvas.drawText(_char,?xIndex,?yIndex,?mPaint);
24????????????????y++;
25????????????}
26????????????x++;
27????????}
28????????return?whiteBgBitmap;
29????}
繪制完成后輸出Bitmap,下一步是把Bitmap保存為本地圖片,關(guān)鍵代碼如下:
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????}
下一步我們?yōu)榱嗽谙到y(tǒng)相冊(cè)更好的找到我們的圖片,我們可以把圖片發(fā)送一個(gè)廣播來(lái)通知系統(tǒng)相冊(cè):
1Intent?mediaScanIntent?=?new?Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
2Uri?contentUri?=?Uri.fromFile(photo);
3mediaScanIntent.setData(contentUri);
4context.sendBroadcast(mediaScanIntent);
以上就是圖片轉(zhuǎn)成字符畫的全部代碼與講解??赡苡械娜藭?huì)問這樣的功能,除了酷炫,有趣,牛逼之外,做出來(lái)有什么用?我只能問得好!乍一看好像用處不大,但是基于這個(gè)功能我們可以做短視頻轉(zhuǎn)換字符畫視頻。下一篇我將會(huì)講一下如何把視頻轉(zhuǎn)換成字符畫視頻,本篇的內(nèi)容到此為止,如有問題,歡迎提出,如有錯(cuò)誤,歡迎指正,謝謝。
奉上完整的源碼(已完成視頻轉(zhuǎn)換跟圖片轉(zhuǎn)換功能),覺得有趣的請(qǐng)star一下唄。
完整項(xiàng)目源碼地址:https://github.com/452kinton/CharacterDance

? 耗時(shí)2年,Android進(jìn)階三部曲第三部《Android進(jìn)階指北》出版!
? 『BATcoder』做了多年安卓還沒編譯過源碼?一個(gè)視頻帶你玩轉(zhuǎn)!
? 重生!進(jìn)階三部曲第一部《Android進(jìn)階之光》第2版 出版!
?BATcoder技術(shù)群,讓一部分人先進(jìn)大廠
大家好,我是劉望舒,騰訊TVP,著有三本業(yè)內(nèi)知名暢銷書,連續(xù)四年蟬聯(lián)電子工業(yè)出版社年度優(yōu)秀作者,百度百科收錄的資深技術(shù)專家。
想要加入?BATcoder技術(shù)群,公號(hào)回復(fù)
BAT?即可。
為了防止失聯(lián),歡迎關(guān)注我的小號(hào)
??微信改了推送機(jī)制,真愛請(qǐng)星標(biāo)本公號(hào)??
