開門見山!先上效果圖:


字符稍微密集了一點,不過放大來看大家應(yīng)該能夠看到確確實實是字符畫。
那我們在安卓端是如何實現(xiàn)?
Android開發(fā)中對圖片的操作,顯示一般都是通過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值。
在安卓開發(fā)中要獲取一個圖片的每一個像素值其實很簡單:
//按照參數(shù)范圍獲取像素數(shù)組
bitmap.getPixels(...);
//或者獲取單個位置像素
bitmap.getPixel(x,y);
當(dāng)我們獲取到了像素值,轉(zhuǎn)換成ARGB值后,我們獲取帶了RGB三個值,要如何判斷什么顏色用什么字?要知道調(diào)色輪盤的顏色數(shù)不勝數(shù):

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

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