ColorTextView(随视频帧变化颜色的TextView)

来源:互联网 发布:怎么下载ppt软件 编辑:程序博客网 时间:2024/06/05 04:27

要实现的效果
这里写图片描述

图片颜色提取

1.一开始打算先做个静态的:
我需要两样东西,一张素材图片,一张透明背景文字图片,
素材图片这里写图片描述
进行合成:最后出现的效果图
这里写图片描述
,然后将合成。那具体要怎么合成呢,Android提供现成的合成方案。
PorterDuffXferMode,他合成效果有一个经典的图片
这里写图片描述
简单的说下这个的原理,她合成是符合一系列计算公式的
就举例SRC_OVER和来说,
SRC_OVER:[Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc]
分别从透明度和颜色说起:
透明又分2种情况
(1)在SRC不透明的地方,Sa + (1 - Sa)*Da =》 Sa,也就是遵循SRC图的透明度
(2)在SRC透明的地方,Sa + (1 - Sa)*Da =》 Da,也就是遵守DC的透明度
而颜色又分为以下几种情况
Src(蓝色图)有颜色的地方:
(1)没有重合的部分Sc + (1 - Sa)*Dc =》 Sc (2)和蓝色图(Sc)和黄色图(Dc)重合部分则Rc = Sc + (1 - Sa)*Dc =》 Rc = Sc;也就是不管重合不重合都是以Sc为准,就和他的名字一样,SrcOver就是源图(蓝色图)在上层
Src没有颜色的地方:Sc + (1 - Sa)*Dc =》Dc,就是按照Dc(蓝色图的颜色)
其他地方也一样

CLEAR[0, 0]SRC:[Sa, Sc]DST:[Da, Dc]SRC_OVER:[Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc]DST_OVER:[Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc]SRC_IN:[Sa * Da, Sc * Da]DST_IN:[Sa * Da, Sa * Dc]SRC_OUT:[Sa * (1 - Da), Sc * (1 - Da)]DST_OUT:[Da * (1 - Sa), Dc * (1 - Sa)]SRC_ATOP:[Da, Sc * Da + (1 - Sa) * Dc]DST_ATOP:[Sa, Sa * Dc + Sc * (1 - Da)]XOR:[Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc]DARKEN:[Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)]LIGHTEN:[Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)]MULTIPLY:[Sa * Da, Sc * Dc]SCREEN:[Sa + Da - Sa * Da, Sc + Dc - Sc * Dc]ADD:Saturate(S + D)OVERLAY:Saturate(S + D)

具体使用方法也比较简单,可以分析下,加入背景图是SRC,文字是Dst,那么就应该用SRCOVer

//待完善super.onDraw(canvas);            canvas.drawBitmap(r, r, r, paint);            //使用CLEAR作为PorterDuffXfermode绘制蓝色的矩形            paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));            paint.setColor(0xFF66AAFF);            canvas.drawRect(r, r, r * 2.7f, r * 2.7f, paint);            //最后将画笔去除Xfermode            paint.setXfermode(null);        canvas.restoreToCount(layerId);

这样的话每次我们需要拿到视频图片,然后,和文字图片合成一下就可以了,然而这样的做的缺点没次换字都要适配一个新的图片字
2.回去重做。。。然后就textView来做是比较好的方案,然后因为字体颜色要改变,那么就要在Paint上动手脚,而且要改变的颜色,那么就可能会联想到Shader渲染器(着色器)。而着色器主要以下几个子类
这里写图片描述
我们需要用到BitmapShader,ComposeShader,LinearGradient,还有ComposeShader,之所以要有前面的是因为会比如不加个白底话,即使模糊了,也会出现跟视频画面高度吻合情况。

//白色渲染器mWhiteShader = new LinearGradient(0, 0, ColorfulTextView.this.getWidth(), ColorfulTextView.this.getWidth(), 0xffffffff, 0xffffffff, Shader.TileMode.REPEAT);//素材底色渲染器mBitmapShader = new BitmapShader(mBackGroundBitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);//组合渲染器 mComposeShader = new ComposeShader(mBitmapShader, mWhiteShader, PorterDuff.Mode.DST_OVER);

然后在绘制前

protected void onDraw(Canvas canvas) {        Paint paint = getPaint();        paint.setColorFilter(mColorFilter);        paint.setShader(mComposeShader);        super.onDraw(canvas);    }

这里有个坑,就是ComposeShader只能用于不同的渲染器,,如果BitmapShader和BitmapShader混合是没有效果的。

图片的模糊

模糊方案,这里用到的主要是高斯模糊,所谓的模糊,就是去周围像素点的平均值,这里写图片描述
比如这个图,图片会取到中间2会变成1。
先看下FastApi

//三个参数分别代表需要模糊的图片,模糊半径,是否复用图片public static Bitmap blur(Bitmap sentBitmap, int radius, boolean canReuseInBitmap)

那这里的模糊半径是什么意思呢,举个生动的栗子:
这里写图片描述
箭头所指脸蛋的部分,取得周围像素进行取平均值,加入半径比较大,把头发黑色也平均进去了,那不就变成黑脸了嘛,意思就是它不是简单取周围半径内所有像素平均,而是有个占重比的,越靠近的占重比越高,而这个占重到底多高,就由我们吊炸天的高斯函数解决了===》所以就为什么叫高斯模糊了
这里写图片描述,就类似这样的正太分布函数。
说了辣么多高斯模糊主要主要有几种方案

1.FastBlur
2.RenderScript(api18以后出现,18以下可用他的兼容包)
3.先缩小,模糊后再放大。也就是减少模糊的成本
自己有个比较疑惑也比较傻的问题,话说他到底兼容包是什么原理,为什么不把所有的新特性都加入兼容包,这样开发就再也不会有版本问题?
自问自答:其实兼容包就和自己引入第三方库差不多一个意思,有些不能加入功能可能是因为当前低版本系统不支持

方案一:FastBlur ,就是对每个点进行高斯模糊运算,用纯java实现,但显然速度是比较慢的,大概257行代码,他就是最直接暴力对每个像素利用高斯运算取值
方案二:RenderScript
根据Android官方网站的介绍:RenderScript是Android平台上用于运行计算密集任务的框架。RenderScript主要是面向数据并行计算,当然了,RenderScript中使用串行计算效率也很好。RenderScript是充分利用GPU,CPU的计算能力,由于不同的硬件对应的并行执行不同,RenderScript会编译2次,首先是我们的PC编译器编译到apk中,然后在apk安装的时候,再编译一次。这样的好处是,可以充分利用不同的硬件,我们编写的代码无需关心具体的硬件的不同,都能写出高性能的代码,总之很牛逼。

加入我们需要对一张图片所有的像素都取反(和对我们所有的像素都取高斯计算一样的场景,这里我方便理解将高斯计算简略为对每个像素取反)一般来说,我们需要编写一个脚本文件类似这样的

#pragma version(1)#pragma rs java_package_name(com.hc.renderscript)uchar4 __attribute__((kernel)) invert(uchar4 in){  uchar4 out = in;  out.r =255- in.r;//这段脚本的意思是对rgb各个颜色取反  out.g = 255-in.g;  out.b = 255-in.b;  return out;}

在java调用。

        //伪代码,省去        RenderScript rs = RenderScript.create(this);        mScript = new ScriptC_hello(rs);//可以理解为读取脚本创建java实例         //模糊的话mBlurScript = ScriptIntrinsicBlur.create(mRs, Element.U8_4(mRs));        Allocation aIn = Allocation.createFromBitmap(rs, mInBitmap);//输入容器        Allocation aOut = Allocation.createFromBitmap(rs, mOutBitmap);//输出容器        mScript.forEach_invert(aIn, aOut);//这一步就是执行我们脚本中计算,这一句其实就是核心        aOut.copyTo(mOutBitmap);//最后将计算完的图片拷贝出来为mOutBitmap就可以了

说白了就是在脚本文件写好计算公式,然后把脚本文件加载进去变成java实例,然后就执行,剩下的就交给RenderScript底层机制,可能是因为模糊比较常用,android 平台已经帮我们封装好了,叫做ScriptIntrinsicBlur,我们直接调用就好了。就将上面注释那一部分换成

方案三:Render Script其实已经够快了,但是我们可以对他进行优化,就是在处理之前先把图片先缩小,丢失一些像素点,然后模糊化后再对他进行放大。,对图片缩小可以

Bitmap smallBitmap = Bitmap.createBitmap(bmp,0,0,bmp.getWidth,bmp.getHeight,matrix,true);

获得小图后模糊,然后模糊,然后在放大,这样就可以计算量

视频帧的提取

提取方案
1.MediaMetadataRetriever+mediaPlayer+surfaceView
3.TextureView +Mediaplayer
4.GlSurfaceView +Mediaplayer(opengl解决方案)

方案一:mediaPlayer+surfaceView,是用来播放视频的,MediaMetadataRetriever,正如名字差不多,对视频数据进行遍历,取得对应时间所在帧,不断在子线程取视频帧,代码如下,

MediaMetadataRetriever metadataRetriever=new MediaMetadataRetriever();        metadataRetriever.setDataSource(this,Uri.parse(path));        while (true){       mBitmap=metadataRetriever.getFrameAtTime(mediaPlayer.getCurrentPosition()*1000,MediaMetadataRetriever.OPTION_CLOSEST);        }

OPTION_CLOSEST 在给定的时间,检索最近一个帧,这个帧不一定是关键帧
OPTION_CLOSEST_SYNC 在给定的时间,检索与当前时间最近的关键帧
OPTION_NEXT_SYNC 在给定时间之后检索一个关键帧
OPTION_PREVIOUS_SYNC 在给定时间之前检索一个关键帧
缺点:获取速度慢且不实时,具体多少没测,但肉眼明显辨认出大概延迟了1s~2s
适用:适用于那些取缩略图,比如视频封面,第一帧画面等实时性要求不高的情况

方案二:
将播放视频的surfaceView改成TextureView,播放,因为他有一个取帧的方法getBitmap就可以获得当前播放视频的当前帧

TextureView mTextureView = new TextureView();Bitmap videoBmp = mTextureView.getBitmap();

直接一句话变可以获得视频帧。
缺点:1.无法准确估算视频帧,只能最快速度去取帧,有可能会取得视频同一帧,2.5.0以下getBitmap必须在主线程调用,否则直接崩溃,据我了解是因为5.0以后加入渲染线程后,TexureView把工作都拿到渲染线程去做,所以可以在子线程读取视频帧,而5.0以下不行

方案三
视频帧本身获取是在gl线程,虽然如果获取时间过长会卡gl线程,导致视频卡顿,但不会卡主线程。取帧湿度和textureView差不多,在美图M8上20~30ms左右,在三星j5 60ms。但在小米四上高达100ms,

优化

1.糟糕的刷新方案
我最开始使用比较蠢的刷新方案是:
这里写图片描述

2.加快取帧速度glReadPixels(),速度太慢。
Pbo加速
先介绍两个概念FBO和PBO
FBO:即Frame Buffer Object 帧缓冲对象://待完善

PBO:即Pixel Buffer Object也是用于GPU的扩展,这里的缓存当然就是GPU的缓存, PBO的主要优点是可以通过DMA (Direct Memory Access) 快速地在显卡上传递像素数据,而不影响CPU的时钟周期(中断)。另一个优势是它还具备异步DMA传输。让我们对比使用PBO前后的纹理传送方法。左侧图是从图像文件或视频中加载纹理。首先,资源被加载到系统内存(Client)中,然后使用glTexImage2D()函数从系统内存复制到OpenGL纹理对象中(Client->Server)。这两次数据传输(加载和复制)完全由CPU执行他的加速原理如下图
这里写图片描述
使用PBO的纹理加载,和上图相反,原图像可以直接加载到PBO中,而PBO是由OpenGL控制的。虽然CPU有参与加载纹理到PBO,但不涉及将像素数据从PBO传输到纹理对象的工作,而是由GPU(OpenGL驱动)来负责PBO到纹理对象的数据传输的,这也就意味着OpenGL执行DMA传输操作不会占用CPU的时钟周期。此外,OpenGL还可以安排稍后执行的异步DMA传输。所以glTexImage2D()可以立即返回,CPU也无需等待像素数据的传输了,可以继续其他工作

双pbo:
这里写图片描述
是用单个pbo可能加速并不明显,使用两个pbo效果会很明显,美图M8读取帧速度从30ms直接下降2ms,
我想了个比喻就是,比如有一个人很渴,如果只有一个人装水给他喝,那么他喝完就需要等别人去再装一杯给他,这个过程需要等待,但如果有两个人递水给他喝,那么喝完就让一个人去装水,喝另外一个人手里拿杯,等他喝完这杯,另一个已经把刚喝完那杯装满了,大概就这个意思。

代码具体流程就不讲了,主要说下这里有个坑,android Opengl30.glReadPixels()沿用的是opengl2.0的方法,这个方法并不能使用PBO,必须用ndk来调用C++的这个方法。

3.对象复用:就是将常用Bitmap设置为成员变量,每次修改里面的数据即可。

原创粉丝点击