一、前语

在本篇之前,很多博客现已完成过图片文本化,可是因为烘托方法的不合理,大部分设计都做不到尽可能实时播映,本篇将在已有的基础上进行一些优化,使得视频文字化具备必定的实时性。

下图总体上视频和文字化的画面基本是实时的,上面是SurfaceView,下面是咱们自定义的View类,通过实时抓帧然后实时转bitmap,做到了基本同步。

Android 视频画面实时文字化图画

二、现状

目前很多盛行的方法是修正像素的色值,这个功能距离太大,导致卡顿非常严峻,无法做到实时性。当然也有通过open gl mask完成的,可是在贴图这一块咱们知道,open gl只支撑制作三角、点和线,因而“文字”纹路生成还得利用Canvas完成。

Android 视频画面实时文字化图画

但关于对帧率要求不高的需求,是不是有更好的方案呢?

三、优化方案

优化点1: 运用Shader

网上很多博客都是利用Bitmap#getPixel和Bitmap#setPixel进行,这个核算量明显太大了,就算运用open gl 也未必好,因而首要解决的问题便是运用Shader上色。

优化点2: 提前核算好单个文字所占的最大空间

明显这个原因是更加整齐的摆放文字,其次也能够做到下降核算量和进步灵敏度

优化点3:运用行列

关于了编解码的开发而言,运用行列不仅能够复用buffer,而且还能进步制作功能,别的必要时能够丢帧。

基于以上三点,基本能够做到实时字符化画面,当然,咱们这儿是彩色的,关于灰度图的需求,可通过设置Paint的ColorMatrix完成,总之,要防止遍历修正像素了RGB。

四、要害代码

运用shader上色

 this.bitmapShader = new BitmapShader(inputBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
 this.mCharPaint.setShader(bitmapShader);
//用下面方法清空bitmap
 boardBitmap.bitmap.eraseColor(Color.TRANSPARENT);

核算字符size

    private Rect computeMaxCharWidth(TextPaint drawerPaint, String text) {
        if (TextUtils.isEmpty(text)) {
            return null;
        }
        Rect result = new Rect(); // 文字所在区域的矩形
        Rect md = new Rect();
        for (int i = 0; i < text.length(); i++) {
            String s = text.charAt(i) + "";
            if(TextUtils.isEmpty(s)) {
                continue;
            }
            drawerPaint.getTextBounds(s, 0, 1, md);
            if (md.width() > result.width()) {
                result.right = md.width();
            }
            if (md.height() > result.height()) {
                result.bottom = md.height();
            }
        }
        return result;
    }

定义双行列,完成操控和享元机制

    private BitmapPool bitmapPool = new BitmapPool();
    private BitmapPool recyclePool = new BitmapPool();
    static class BitmapPool {
        int width;
        int height;
        private LinkedBlockingQueue<BitmapItem> linkedBlockingQueue = new LinkedBlockingQueue<>(5);
    }
    static class BitmapItem{
        Bitmap bitmap;
        boolean isUsed = false;
    }

完整代码

public class WordBitmapView extends View {
    private final DisplayMetrics mDM;
    private TextPaint mCharPaint;
    private TextPaint mDrawerPaint = null;
    private Bitmap inputBitmap;
    private Rect charMxWidth = null ;
    private String text = "a1b2c3d4e5f6h7j8k9l0";
    private float textBaseline;
    private BitmapShader bitmapShader;
    public WordBitmapView(Context context) {
        this(context, null);
    }
    public WordBitmapView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public WordBitmapView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mDM = getResources().getDisplayMetrics();
        initPaint();
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        if (widthMode != MeasureSpec.EXACTLY) {
            widthSize = mDM.widthPixels / 2;
        }
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        if (heightMode != MeasureSpec.EXACTLY) {
            heightSize = widthSize / 2;
        }
        setMeasuredDimension(widthSize, heightSize);
        textBaseline = getTextPaintBaseline(mDrawerPaint);
    }
    public float dp2px(float dp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, mDM);
    }
    public float sp2px(float dp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, dp, mDM);
    }
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        recyclePool.clear();
        bitmapPool.clear();
    }
    Matrix matrix = new Matrix();
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int width = getWidth();
        int height = getHeight();
        if (width <= 1 || height <= 1) {
            return;
        }
        BitmapItem bitmapItem = bitmapPool.linkedBlockingQueue.poll();
        if (bitmapItem == null || inputBitmap == null) {
            return;
        }
        if(!bitmapItem.isUsed){
            return;
        }
        canvas.drawBitmap(bitmapItem.bitmap,matrix,mDrawerPaint);
        bitmapItem.isUsed = false;
        try {
            recyclePool.linkedBlockingQueue.offer(bitmapItem,16,TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static float getTextPaintBaseline(Paint p) {
        Paint.FontMetrics fontMetrics = p.getFontMetrics();
        return (fontMetrics.descent - fontMetrics.ascent) / 2 - fontMetrics.descent;
    }
    private Rect computeMaxCharWidth(TextPaint drawerPaint, String text) {
        if (TextUtils.isEmpty(text)) {
            return null;
        }
        Rect result = new Rect(); // 文字所在区域的矩形
        Rect md = new Rect();
        for (int i = 0; i < text.length(); i++) {
            String s = text.charAt(i) + "";
            if(TextUtils.isEmpty(s)) {
                continue;
            }
            drawerPaint.getTextBounds(s, 0, 1, md);
            if (md.width() > result.width()) {
                result.right = md.width();
            }
            if (md.height() > result.height()) {
                result.bottom = md.height();
            }
        }
        return result;
    }
    private BitmapPool bitmapPool = new BitmapPool();
    private BitmapPool recyclePool = new BitmapPool();
    static class BitmapPool {
        int width;
        int height;
        private LinkedBlockingQueue<BitmapItem> linkedBlockingQueue = new LinkedBlockingQueue<>(5);
        public void clear(){
            Iterator<BitmapItem> iterator = linkedBlockingQueue.iterator();
            do{
                if(!iterator.hasNext()) break;
                BitmapItem next = iterator.next();
                if(!next.bitmap.isRecycled()) {
                    next.bitmap.recycle();
                }
                iterator.remove();
            }while (true);
        }
        public int getWidth() {
            return width;
        }
        public int getHeight() {
            return height;
        }
        public void setHeight(int height) {
            this.height = height;
        }
        public void setWidth(int width) {
            this.width = width;
        }
    }
    class BitmapItem{
        Bitmap bitmap;
        boolean isUsed = false;
    }
   //视频图片入队
    public void queueInputBitmap(Bitmap inputBitmap) {
        this.inputBitmap = inputBitmap;
        if(charMxWidth  == null){
            charMxWidth = computeMaxCharWidth(mDrawerPaint,text);
        }
        if(charMxWidth == null || charMxWidth.width() == 0){
            return;
        }
        if(this.bitmapPool != null && this.inputBitmap != null){
            if(this.bitmapPool.getWidth() != this.inputBitmap.getWidth()){
                bitmapPool.clear();
                recyclePool.clear();
            }else if(this.bitmapPool.getHeight() != this.inputBitmap.getHeight()){
                bitmapPool.clear();
                recyclePool.clear();
            }
        }
        bitmapPool.setWidth(inputBitmap.getWidth());
        bitmapPool.setHeight(inputBitmap.getHeight());
        recyclePool.setWidth(inputBitmap.getWidth());
        recyclePool.setHeight(inputBitmap.getHeight());
        BitmapItem boardBitmap = recyclePool.linkedBlockingQueue.poll();
        if (boardBitmap == null && inputBitmap != null) {
            boardBitmap = new BitmapItem();
            boardBitmap.bitmap = Bitmap.createBitmap(inputBitmap.getWidth(), inputBitmap.getHeight(), Bitmap.Config.ARGB_8888);
        }
        boardBitmap.isUsed = true;
        int bitmapWidth = inputBitmap.getWidth();
        int bitmapHeight = inputBitmap.getHeight();
        int unitWidth = (int) (charMxWidth.width()  *1.5);
        int unitHeight = charMxWidth.height()  + 2;
        int centerY = charMxWidth.centerY();
        float hLineCharNum = bitmapWidth * 1F / unitWidth;
        float vLineCharNum = bitmapHeight * 1F / unitHeight;
        this.bitmapShader = new BitmapShader(inputBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        this.mCharPaint.setShader(bitmapShader);
        boardBitmap.bitmap.eraseColor(Color.TRANSPARENT);
        Canvas drawCanvas = new Canvas(boardBitmap.bitmap);
        int k = (int) (Math.random() * text.length());
        for (int i = 0; i < vLineCharNum; i++) {
            for (int j = 0; j < hLineCharNum; j++) {
                int length = text.length();
                int x = unitWidth * j;
                int y = centerY + i * unitHeight;
                String c = text.charAt(k % length) + "";
                drawCanvas.drawText(c, x, y + textBaseline, mCharPaint);
                k++;
            }
        }
        try {
            bitmapPool.linkedBlockingQueue.offer(boardBitmap,16, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        postInvalidate();
    }
    private void initPaint() {
        // 实例化画笔并打开抗锯齿
        mCharPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
        mCharPaint.setAntiAlias(true);
        mCharPaint.setStyle(Paint.Style.FILL);
        mCharPaint.setStrokeCap(Paint.Cap.ROUND);
        mDrawerPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
        mDrawerPaint.setAntiAlias(true);
        mDrawerPaint.setStyle(Paint.Style.FILL);
        mDrawerPaint.setStrokeCap(Paint.Cap.ROUND);
    }
}

五、总结

Android中Shader是非常重要的东西,咱们无需单独修正像素的情况下就能完成快速烘托字符,得意与Shader超卓的烘托能力。别的因为时间原因,这儿对字符的制作并没有做到很准确,仅仅选了一些比较中规中列的摆放,后续再持续完善吧。