做者:蒼王 時間:2019.1.28java
上一節介紹了攝像頭的幀採集,這一節將要介紹採集回來的攝像頭數據如何顯示到屏幕,以及對數據進行濾鏡添加。android
如今demo中提供了大概有三十幾種濾鏡,其原來MagicCamera大體同樣的。 首先說一下采坑遇到的問題 git
####1.圖片格式不一樣的問題 濾鏡效果比較簡單的理解就是原來的圖像的基礎上,混合上紋理顯示出來的效果,而Opengl中紋理能夠圖片,也能夠數據的形式來加載。通常是有png,jpg,bmp三種。 移動端不多使用bmp圖,由於圖片比較壓縮度低,通常bmp圖片容量對比png,jpg大不少。png圖和jpg圖最大的不一樣是,png是RGBA的編碼,而jpg是RGB編碼,就是透明通道的區別,若是填寫錯誤,我遇到過現實的濾鏡會有黑條的問題(不是顯示不出來,是黑條)。而Opengl不少都須要填寫通道的格式和通道長度的,這點尤其重要。 我一開始複寫這個框架的時候就是由於對圖片解碼等問題不夠深刻,耽誤了很長的時間(大概兩週,纔在這個坑口爬出來,因此入門的時候必定要認清這些圖片格式確切的區別)github
###2.圖片加載的問題 (1)若是你單純用Android的OpenGL來讀取圖片紋理,很是簡單,Android的OpenGL直接支持bitmap的輸入做爲紋理轉換。 (2)若是你單純使用jni來讀取圖片,Android獨有的android/bitmap.h框架能夠直接從java層傳遞二進制圖片數據到native層,並且能讀取到圖片的一些基本信息。 (3)若是你想單純經過native層來完成圖片紋理讀取解碼以及native 的opengl加載,這是最高自由度的,能夠根本上考慮代碼的複用性,以及圖片加載機制的瞭解。固然,我就選擇了這種,途中遇到複雜的問題拆分爲: ####如何讓native讀取到Android內置圖片的地址? 在native讀取到內置圖片的地址,這裏要藉助到android種asset這個文件夾,用來存放紋理圖片,以及shader文件。(固然你也能夠將shader文件也複製到代碼中,這樣更加壓縮會更加好,可是維護比較麻煩就是了)。經過asset來讀取到圖片文件信息,這樣作的好處在於圖片的處理都在native層,當圖片釋放,能夠馬上釋放資源,不會想java那張圖片可能還在緩存中等待回收。asset_manager_jni.h中提供了一套完整的讀取asset文件夾的方法。緩存
//打開asset文件夾中的文件夾
AAssetDir *dir = AAssetManager_openDir(manager,"filter");
//初始化文件名指針
const char *file = nullptr;
//循環遍歷
while ((file =AAssetDir_getNextFileName(dir))!= nullptr) {
//對比文件名
if (strcmp(file, fileName) == 0) {
//拼接文件路徑,以asset文件夾爲起始
std::string *name = new std::string("filter/");
name->append(file);
//以流的方式打開文件
AAsset *asset = AAssetManager_open(manager, name->c_str(), AASSET_MODE_STREAMING);
複製代碼
####若是在native中讀取到圖片的信息? 讀取圖片文件的信息,包括長寬,圖片大小,通道大小,這裏固然只能使用C的框架來讀取圖像數據了,這裏使用了一個比較簡便,且可接入性較高的圖像解析庫stb。bash
//引用stb庫
#define STB_IMAGE_IMPLEMENTATION
#include "src/main/cpp/utils/stb_image.h"
int len = AAsset_getLength(asset);
int width=0,height=0,n=0;
//讀取資源文件流
unsigned char* buff = (unsigned char *) AAsset_getBuffer(asset);
//讀取圖片長寬以及通道數據
unsigned char* data = stbi_load_from_memory(buff, len, &width, &height, &n, 0);
ALOGV("loadTextureFromAssets fileName = %s,width = %d,height=%d,n=%d,size = %d",fileName,width,height,n,len);
//關閉資源
AAsset_close(asset);
//關閉asset文件夾
AAssetDir_close(dir);
複製代碼
這裏必定要注意,引入stb_image.h的方式,請必定要遵循我這種引用方式,否則將會出現某些圖像沒法解析的可能。我就是在這裏卡了幾天,一度懷疑人生,想要換庫操做。 app
####讀取完成後怎麼加載圖片的紋理信息? 這裏其實很明確使用glTexImage2D來將圖片的數據生成一個2D紋理,可是有人會考慮使用mipmaps貼圖,可是濾鏡通常都不須要使用mimpas貼圖的。千萬別誤用了mipmaps貼圖,mipmaps貼圖是2的次冪方,並且長和寬大小是同樣的正方形,然而我真的是調試的時候本身傻傻的添加了,只怪我仍是太年輕了。框架
if(data!=NULL) {
GLint format = GL_RGB;
if (n==3) { //RGB三通道,例如jpg格式
format = GL_RGB;
} else if (n==4) { //RGBA四通道,例如png格式
format = GL_RGBA;
}
//將圖片數據生成一個2D紋理
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
} else{
LOGE("load texture from assets is null,fileName = %s",fileName);
}
複製代碼
作完這些後能夠從Native直接讀取到圖片紋理,那麼接下來就是將攝像頭採集過來的幀圖和濾鏡紋理疊加,而後將其顯示到屏幕當中,如何獲取到幀緩衝圖,請查看上一節的內容。 ui
濾鏡添加的流程圖 編碼
這裏是繪製的公共代碼。
if (cameraInputFilter != nullptr){
// cameraInputFilter->onDrawFrame(mTextureId,matrix,VERTICES,TEX_COORDS);
//獲取幀緩衝id
GLuint id = cameraInputFilter->onDrawToTexture(mTextureId,matrix);
if (filter != nullptr)
//經過濾鏡filter繪製
filter->onDrawFrame(id,matrix);
//緩衝區交換
glFlush();
mEGLCore->swapBuffer();
}
int GPUImageFilter::onDrawFrame(const GLuint textureId, GLfloat *matrix,const float *cubeBuffer,
const float *textureBuffer) {
onDrawPrepare();
glUseProgram(mGLProgId);
if (!mIsInitialized) {
ALOGE("NOT_INIT");
return NOT_INIT;
}
//加載矩陣
// glUniformMatrix4fv(mMatrixLoc,1,GL_FALSE,matrix);
glVertexAttribPointer(mGLAttribPosition, 2, GL_FLOAT, GL_FALSE, 0, cubeBuffer);
glEnableVertexAttribArray(mGLAttribPosition);
glVertexAttribPointer(mGLAttribTextureCoordinate, 2, GL_FLOAT, GL_FALSE, 0, textureBuffer);
glEnableVertexAttribArray(mGLAttribTextureCoordinate);
if(textureId !=NO_TEXTURE){
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D,textureId);
//加載紋理
glUniform1i(mGLUniformTexture,0);
}
//濾鏡參數加載
onDrawArraysPre();
glDrawArrays(GL_TRIANGLE_STRIP,0,4);
//濾鏡參數釋放
onDrawArraysAfter();
//釋放頂點綁定
glDisableVertexAttribArray(mGLAttribPosition);
glDisableVertexAttribArray(mGLAttribTextureCoordinate);
if(textureId !=NO_TEXTURE) //激活回到默認紋理
glBindTexture(GL_TEXTURE_2D,0);
return ON_DRAWN;
}
複製代碼
用一個簡單的Amaro濾鏡來講明一下使用。
//構造時加載program,以及shader
MagicAmaroFilter::MagicAmaroFilter(AAssetManager *assetManager)
: GPUImageFilter(assetManager,readShaderFromAsset(assetManager,"nofilter_v.glsl"), readShaderFromAsset(assetManager,"amaro.glsl")){
}
//初始化shader頂點和紋理參數
void MagicAmaroFilter::onInit() {
GPUImageFilter::onInit();
inputTextureUniformLocations[0] = glGetUniformLocation(mGLProgId,"inputImageTexture2");
inputTextureUniformLocations[1] = glGetUniformLocation(mGLProgId,"inputImageTexture3");
inputTextureUniformLocations[2] = glGetUniformLocation(mGLProgId,"inputImageTexture4");
mGLStrengthLocation = glGetUniformLocation(mGLProgId,"strength");
}
//從多個圖中生成紋理
void MagicAmaroFilter::onInitialized() {
GPUImageFilter::onInitialized();
inputTextureHandles[0] = loadTextureFromAssets(mAssetManager,"brannan_blowout.png");
inputTextureHandles[1] = loadTextureFromAssets(mAssetManager,"overlaymap.png");
inputTextureHandles[2] = loadTextureFromAssets(mAssetManager,"amaromap.png");
}
複製代碼
在繪製的時候onDrawArraysPre()加入綁定紋理,以及onDrawArraysAfter()取消綁定
void MagicAmaroFilter::onDrawArraysPre() {
glUniform1f(mGLStrengthLocation, 1.0f);
if (inputTextureHandles[0] != 0) {
glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_2D, inputTextureHandles[0]);
glUniform1i(inputTextureUniformLocations[0], 3);
}
……
}
void MagicAmaroFilter::onDrawArraysAfter() {
if (inputTextureHandles[0] != 0) {
glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_2D, inputTextureHandles[0]);
glActiveTexture(GL_TEXTURE0);
}
……
}
複製代碼
這裏關鍵效果的疊加是Fragment Shader中計算繪製來完成。
void main()
{
//從採樣器中進程紋理採樣
vec4 originColor = texture(inputImageTexture, textureCoordinate);
vec4 texel = texture(inputImageTexture, textureCoordinate);
vec3 bbTexel = texture(inputImageTexture2, textureCoordinate).rgb;
texel.r = texture(inputImageTexture3, vec2(bbTexel.r, texel.r)).r;
texel.g = texture(inputImageTexture3, vec2(bbTexel.g, texel.g)).g;
texel.b = texture(inputImageTexture3, vec2(bbTexel.b, texel.b)).b;
//按比例分別混合RGB
vec4 mapped;
mapped.r = texture(inputImageTexture4, vec2(texel.r, .16666)).r;
mapped.g = texture(inputImageTexture4, vec2(texel.g, .5)).g;
mapped.b = texture(inputImageTexture4, vec2(texel.b, .83333)).b;
mapped.a = 1.0;
//mix(x, y, a): x, y的線性混疊, x(1-a) + y*a;
mapped.rgb = mix(originColor.rgb, mapped.rgb, strength);
gl_FragColor = mapped;
}
複製代碼
基本的加載濾鏡的模板代碼就到這裏了。須要注意的是,若是你看原版的MagicCamera不少代碼使用android的消息傳輸以及線程的簡便,拋到到繪製onDraw的時候執行。而C++編寫的時候,仍是好好的嚴格遵循繪製的順序來編寫和初始化。