Android字符串相機

字符畫面.jpg

很早就看到過這種場景嗅义,用字符來展示圖片甚至播放視頻族阅,可以說是黑客炫(zhuang)技(b)神器东跪。當(dāng)然有了一定的技術(shù)之后躁染,就明白其實實現(xiàn)挺簡單鸣哀。

相機預(yù)覽

首先是相機預(yù)覽的實現(xiàn),因為不是這里的重點吞彤,所以直接在Github上找到成熟的代碼我衬。Google官方的Demo當(dāng)然是最好的:
https://github.com/googlesamples/android-Camera2Basic
這個項目演示了Camera2 API的基本使用,并在一個TextureView上展示了相機實時畫面饰恕。

轉(zhuǎn)換算法一(RGB轉(zhuǎn)換)

有了TextureView挠羔,就能通過getBitmap()方法拿到bitmap,接下來就是把bitmap轉(zhuǎn)換成字符串懂盐,相關(guān)算法這里有一份:
https://github.com/idevelop/ascii-camera/blob/master/script/ascii.js
雖然是JavaScript的褥赊,但是簡單看一下就知道原理:

  1. 把bitmap中像素點的RGB值轉(zhuǎn)換成灰度
  2. 用一個字符數(shù)組表示不同的灰度,如ascii字符串" .,:;i1tfLCG08@"莉恼,越往后表示灰度越高拌喉,也就是顏色越深。當(dāng)然也可以中文" 一十大木本米菜數(shù)簇龍龘"俐银。
  3. 采樣像素點灰度轉(zhuǎn)換成字符尿背,每行成一個字符串,不同行用換行符連接成一個總的字符串捶惜,展示到TextView上田藐。

算法 Utils.java

public class Utils {

    public static void startConvert(final TextureView textureView, final TextView textView) {
        Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                if(textureView != null) {
                    Bitmap bitmap = textureView.getBitmap();
                    if(bitmap != null) {
                        String s = Utils.bitmap2string(bitmap);
                        textView.setText(s);
                    }
                }
                sendEmptyMessageDelayed(0, 20);
            }
        };
        handler.sendEmptyMessage(0);
    }

    public static Bitmap imageReader2Bitmap(ImageReader imageReader) {
        Image image = null;
        ByteBuffer buffer = null;

        try {
            image = imageReader.acquireNextImage();
            buffer = image.getPlanes()[0].getBuffer();
            byte[] bytes = new byte[buffer.remaining()];
            buffer.get(bytes);
            return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(buffer != null) {
                buffer.clear();
            }
            if(image != null) {
                image.close();
            }
        }
        return null;
    }

    public static String bitmap2string(Bitmap bitmap) {
        StringBuilder sb = new StringBuilder();
        int w = bitmap.getWidth();
        int h = bitmap.getHeight();
        for(int j = 0; j < h; j+=20) {
            for(int i = 0; i < w; i+=15) {
                int pixel = bitmap.getPixel(i, j);
                sb.append(color2char(pixel));
            }
            sb.append("\r\n");
        }
        return sb.toString();
    }

//    private static char[] sChars = " .,:;i1tfLCG08@".toCharArray();
    private static char[] sChars = " 一十大木本米菜數(shù)簇龍龘".toCharArray();

    public static Character color2char(int color) {
        int red = Color.red(color);
        int green = Color.green(color);
        int blue = Color.blue(color);
        int brightness = Math.round(0.299f * red + 0.587f * green + 0.114f * blue);
        return sChars[brightness * (sChars.length - 1) / 255];
    }
}

在原項目的Camera2BasicFragment的onViewCreated()方法中添加一行代碼啟動即可

    @Override
    public void onViewCreated(final View view, Bundle savedInstanceState) {
        view.findViewById(R.id.picture).setOnClickListener(this);
        view.findViewById(R.id.info).setOnClickListener(this);
        mTextureView = (AutoFitTextureView) view.findViewById(R.id.texture);
        Utils.startConvert(mTextureView, (TextView) view.findViewById(R.id.text));
    }

轉(zhuǎn)換算法二(YUV轉(zhuǎn)換)

上面雖然實現(xiàn)了圖像到字符串的轉(zhuǎn)換, 但是有一些問題:

  • TextureView上面還在顯示視頻畫面, 而我們只需要TextView顯示的字符串, 這是一種浪費, 可是TextureView不顯示就拿不到Bitmap
  • 很多視頻播放器是SurfaceView的封裝, 也是沒法直接獲取到Bitmap的
  • 從Bitmap中取得像素的RGB值, 轉(zhuǎn)換成灰度, 再轉(zhuǎn)換成字符串, 需要一定的計算量, 是否有更簡單的方式?

使用ImageReader可以解決以上問題. ImageReader是Android API 19后提供的工具類, 它內(nèi)部有一個Surface, 可以加載和讀取圖像, 但是不需要直接顯示在界面上. 就相當(dāng)于一個沒有界面的后臺播放器, 我們需要時可以從里面獲取當(dāng)前"播放"的圖像數(shù)據(jù).

ImageReader還能設(shè)置圖像的格式, 除了RGB外, 另一種常用的格式是YUV. 它也是用像素點的分量來表示圖像, 不同的是, 它的Y分量代表亮度, U和V兩個分量代表顏色. 這樣表示的好處是彩色與黑白畫面的轉(zhuǎn)換很方便, 去掉UV就是黑白的, 也就是灰度; 并且Y分量可以做一定的壓縮, 比如每兩個或四個像素點取一個Y分量, 以節(jié)省空間, 這就產(chǎn)生了不同格式的YUV, 如下圖

YUV.jpg

YUV格式的詳細介紹可以看這篇文章
http://www.cnblogs.com/azraelly/archive/2013/01/01/2841269.html

代碼實現(xiàn)
之前初始化相機的時候傳入一個TextureView顯示預(yù)覽, 現(xiàn)在傳入一個ImageReader可以嗎? 其實相機依賴的不是TextureView而是Surface, ImageReader.getSurface()方法可以獲得它內(nèi)部的Surface.
ImageReader.OnImageAvailableListener回調(diào)中可以獲取ImageReader中的圖像.
我這里給ImageReader設(shè)置的格式是ImageFormat.YUV_420_888, 這種格式可以直接獲得圖像的Y分量也就是灰度.

    private ImageReader mImageReader = ImageReader.newInstance(MAX_PREVIEW_WIDTH, MAX_PREVIEW_HEIGHT,
                        ImageFormat.YUV_420_888, /*maxImages*/2);
    mImageReader.setOnImageAvailableListener(
                        mOnImageAvailableListener, mBackgroundHandler);

    private void createCameraPreviewSession() {
        try {

            // We set up a CaptureRequest.Builder with the output Surface.
            mPreviewRequestBuilder
                    = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            mPreviewRequestBuilder.addTarget(mImageReader.getSurface());

            // Here, we create a CameraCaptureSession for camera preview.
            mCameraDevice.createCaptureSession(Arrays.asList(mImageReader.getSurface()),

轉(zhuǎn)換算法如下, 從ImageReader中取得Image, Image中有幾個平面Image.Plane[], 其中第一個平面就是Y分量數(shù)組. 它是一維數(shù)組, 通過逐行掃描將二維圖像保存成一維, 我們獲取圖像寬度后進行相反的操作就能轉(zhuǎn)換成二維. 數(shù)組中保存的灰度值范圍是-128~127. 轉(zhuǎn)換一下就能映射成字符串了.

public static String yuv2string(ImageReader imageReader) {
        Image image = null;
        ByteBuffer buffer = null;

        try {
            image = imageReader.acquireNextImage();
            Image.Plane[] planes = image.getPlanes();
            buffer = planes[0].getBuffer();
            byte[] bytes = new byte[buffer.remaining()];
            buffer.get(bytes);
            int w = image.getWidth();
            int h = image.getHeight();
//            Log.e("chao", "planes " + planes.length + " " + w + "," + h);
            StringBuilder sb = new StringBuilder();
            for(int j = 0; j < h; j+=6) {
                for (int i = 0; i < w; i+=6) {
//                    int y = bytes[i * w + j] + 128;
                    int y = bytes[j * w + i] + 128;
                    char c = sChars[y * (sChars.length - 1) / 255];
                    sb.append(c);
                }
                sb.append("\r\n");
            }
            return sb.toString();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(buffer != null) {
                buffer.clear();
            }
            if(image != null) {
                image.close();
            }
        }
        return null;
    }

最終的展示效果與RGB轉(zhuǎn)換后相似, 但是YUV轉(zhuǎn)換通用性更好, 效率更高, 它也是圖像處理中經(jīng)常用到的格式.

Github地址

https://github.com/rome753/StringCamera

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子汽久,更是在濱河造成了極大的恐慌鹤竭,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件景醇,死亡現(xiàn)場離奇詭異臀稚,居然都是意外死亡,警方通過查閱死者的電腦和手機三痰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門吧寺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人散劫,你說我怎么就攤上這事稚机。” “怎么了获搏?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長谋币。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么早芭? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任彼城,我火速辦了婚禮,結(jié)果婚禮上退个,老公的妹妹穿的比我還像新娘。我一直安慰自己语盈,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布刀荒。 她就那樣靜靜地躺著代嗤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪缠借。 梳的紋絲不亂的頭發(fā)上干毅,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天泼返,我揣著相機與錄音,去河邊找鬼渠鸽。 笑死叫乌,一個胖子當(dāng)著我的面吹牛徽缚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播猎拨,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼额各!你這毒婦竟也來了吧恃?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤痕寓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后硬毕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡吐咳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年韭脊,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沪羔。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡象浑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出融柬,到底是詐尸還是另有隱情,我是刑警寧澤粒氧,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站摘盆,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏孩擂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一狈邑、第九天 我趴在偏房一處隱蔽的房頂上張望蚤认。 院中可真熱鬧米苹,春花似錦砰琢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽况增。三九已至训挡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間舍哄,已是汗流浹背誊锭。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蟆沫,地道東北人温治。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像熬荆,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 今天小壯放學(xué)后要去上英語的試聽課,孩子走出校門若债,都開始起大風(fēng),下大雨蠢琳。我們匆匆趕來教室讓孩子上課前先把作業(yè)寫一部分...
    開心的壯媽閱讀 190評論 0 0
  • 好書推薦:《小小小小的火》 昨天,鄭州的天氣霧霾指數(shù)爆表蓝牲,灰蒙蒙的天空有點像災(zāi)難電影里面的世界末日,既然不能出門就...
    燦爛jx閱讀 2,032評論 2 20
  • 圖文/子諾不離 小時候搞旭,家住農(nóng)村菇绵。 夏天,當(dāng)滾燙的太陽終于落山咬最,夜幕降臨,辛勤勞作一天的村民永乌,便會三三兩兩來到村頭...
    子諾不離閱讀 2,107評論 21 67