Android OpenGLES如何給相機添加濾鏡詳解
濾鏡介紹
目前市面上的濾鏡有很多,但整體歸類也就幾樣,都是在fragment shader中進行處理。目前濾鏡最常用的就是 lut濾鏡以及調(diào)整RGB曲線的濾鏡了。其他的類型變更大同小異。
動態(tài)濾鏡的構(gòu)建
為了實現(xiàn)動態(tài)下載的濾鏡,我們接下來實現(xiàn)一套濾鏡的json參數(shù),主要包括濾鏡類型、濾鏡名稱、vertex shader、fragment shader 文件、統(tǒng)一變量列表、與統(tǒng)一變量綁定的紋理圖片、默認濾鏡強度、是否帶紋理寬高偏移量、音樂路徑、音樂是否循環(huán)播放等參數(shù)。
json 以及各個字段的介紹如下:
{
"filterList": [{
"type": "filter", // 表明濾鏡類型,目前filter是只普通濾鏡,后續(xù)還會加入其它類型的濾鏡
"name": "amaro", // 濾鏡名稱
"vertexShader": "", // vertex shader 文件名
"fragmentShader": "fragment.glsl", // fragment shader 文件名
"uniformList":["blowoutTexture", "overlayTexture", "mapTexture"], // 統(tǒng)一變量
"uniformData": { // 與統(tǒng)一變量綁定的紋理圖片
"blowoutTexture": "blowout.png",
"overlayTexture": "overlay.png",
"mapTexture": "map.png"
},
"strength": 1.0, // 默認濾鏡強度 0.0 ~ 1.0之間
"texelOffset": 0, // 是否需要支持寬高偏移值,即需要傳遞 1.0f/width, 1.0f/height到shader中
"audioPath": "", // 音樂路徑
"audioLooping": 1 // 是否循環(huán)播放音樂
}]
}
有了json 之后,我們需要解碼得到濾鏡參數(shù)對象,解碼如下:
/**
* 解碼濾鏡數(shù)據(jù)
* @param folderPath
* @return
*/
public static DynamicColor decodeFilterData(String folderPath)
throws IOException, JSONException {
File file = new File(folderPath, "json");
String filterJson = FileUtils.convertToString(new FileInputStream(file));
JSONObject jsonObject = new JSONObject(filterJson);
DynamicColor dynamicColor = new DynamicColor();
dynamicColor.unzipPath = folderPath;
if (dynamicColor.filterList == null) {
dynamicColor.filterList = new ArrayList<>();
}
JSONArray filterList = jsonObject.getJSONArray("filterList");
for (int filterIndex = 0; filterIndex < filterList.length(); filterIndex++) {
DynamicColorData filterData = new DynamicColorData();
JSONObject jsonData = filterList.getJSONObject(filterIndex);
String type = jsonData.getString("type");
// TODO 目前濾鏡只做普通的filter,其他復(fù)雜的濾鏡類型后續(xù)在做處理
if ("filter".equals(type)) {
filterData.name = jsonData.getString("name");
filterData.vertexShader = jsonData.getString("vertexShader");
filterData.fragmentShader = jsonData.getString("fragmentShader");
// 獲取統(tǒng)一變量字段
JSONArray uniformList = jsonData.getJSONArray("uniformList");
for (int uniformIndex = 0; uniformIndex < uniformList.length(); uniformIndex++) {
String uniform = uniformList.getString(uniformIndex);
filterData.uniformList.add(uniform);
}
// 獲取統(tǒng)一變量字段綁定的圖片資源
JSONObject uniformData = jsonData.getJSONObject("uniformData");
if (uniformData != null) {
Iterator<String> dataIterator = uniformData.keys();
while (dataIterator.hasNext()) {
String key = dataIterator.next();
String value = uniformData.getString(key);
filterData.uniformDataList.add(new DynamicColorData.UniformData(key, value));
}
}
filterData.strength = (float) jsonData.getDouble("strength");
filterData.texelOffset = (jsonData.getInt("texelOffset") == 1);
filterData.audioPath = jsonData.getString("audioPath");
filterData.audioLooping = (jsonData.getInt("audioLooping") == 1);
}
dynamicColor.filterList.add(filterData);
}
return dynamicColor;
}
濾鏡的實現(xiàn)
在解碼得到濾鏡參數(shù)之后,我們接下來實現(xiàn)動態(tài)濾鏡渲染過程。為了方便構(gòu)建濾鏡,我們創(chuàng)建一個濾鏡資源加載器,代碼如下:
/**
* 濾鏡資源加載器
*/
public class DynamicColorLoader {
private static final String TAG = "DynamicColorLoader";
// 濾鏡所在的文件夾
private String mFolderPath;
// 動態(tài)濾鏡數(shù)據(jù)
private DynamicColorData mColorData;
// 資源索引加載器
private ResourceDataCodec mResourceCodec;
// 動態(tài)濾鏡
private final WeakReference<DynamicColorBaseFilter> mWeakFilter;
// 統(tǒng)一變量列表
private HashMap<String, Integer> mUniformHandleList = new HashMap<>();
// 紋理列表
private int[] mTextureList;
// 句柄
private int mTexelWidthOffsetHandle = OpenGLUtils.GL_NOT_INIT;
private int mTexelHeightOffsetHandle = OpenGLUtils.GL_NOT_INIT;
private int mStrengthHandle = OpenGLUtils.GL_NOT_INIT;
private float mStrength = 1.0f;
private float mTexelWidthOffset = 1.0f;
private float mTexelHeightOffset = 1.0f;
public DynamicColorLoader(DynamicColorBaseFilter filter, DynamicColorData colorData, String folderPath) {
mWeakFilter = new WeakReference<>(filter);
mFolderPath = folderPath.startsWith("file://") ? folderPath.substring("file://".length()) : folderPath;
mColorData = colorData;
mStrength = (colorData == null) ? 1.0f : colorData.strength;
Pair pair = ResourceCodec.getResourceFile(mFolderPath);
if (pair != null) {
mResourceCodec = new ResourceDataCodec(mFolderPath + "/" + (String) pair.first, mFolderPath + "/" + pair.second);
}
if (mResourceCodec != null) {
try {
mResourceCodec.init();
} catch (IOException e) {
Log.e(TAG, "DynamicColorLoader: ", e);
mResourceCodec = null;
}
}
if (!TextUtils.isEmpty(mColorData.audioPath)) {
if (mWeakFilter.get() != null) {
mWeakFilter.get().setAudioPath(Uri.parse(mFolderPath + "/" + mColorData.audioPath));
mWeakFilter.get().setLooping(mColorData.audioLooping);
}
}
loadColorTexture();
}
/**
* 加載紋理
*/
private void loadColorTexture() {
if (mColorData.uniformDataList == null || mColorData.uniformDataList.size() <= 0) {
return;
}
mTextureList = new int[mColorData.uniformDataList.size()];
for (int dataIndex = 0; dataIndex < mColorData.uniformDataList.size(); dataIndex++) {
Bitmap bitmap = null;
if (mResourceCodec != null) {
bitmap = mResourceCodec.loadBitmap(mColorData.uniformDataList.get(dataIndex).value);
}
if (bitmap == null) {
bitmap = BitmapUtils.getBitmapFromFile(mFolderPath + "/" + String.format(mColorData.uniformDataList.get(dataIndex).value));
}
if (bitmap != null) {
mTextureList[dataIndex] = OpenGLUtils.createTexture(bitmap);
bitmap.recycle();
} else {
mTextureList[dataIndex] = OpenGLUtils.GL_NOT_TEXTURE;
}
}
}
/**
* 綁定統(tǒng)一變量句柄
* @param programHandle
*/
public void onBindUniformHandle(int programHandle) {
if (programHandle == OpenGLUtils.GL_NOT_INIT || mColorData == null) {
return;
}
mStrengthHandle = GLES30.glGetUniformLocation(programHandle, "strength");
if (mColorData.texelOffset) {
mTexelWidthOffsetHandle = GLES30.glGetUniformLocation(programHandle, "texelWidthOffset");
mTexelHeightOffsetHandle = GLES30.glGetUniformLocation(programHandle, "texelHeightOffset");
} else {
mTexelWidthOffsetHandle = OpenGLUtils.GL_NOT_INIT;
mTexelHeightOffsetHandle = OpenGLUtils.GL_NOT_INIT;
}
for (int uniformIndex = 0; uniformIndex < mColorData.uniformList.size(); uniformIndex++) {
String uniformString = mColorData.uniformList.get(uniformIndex);
int handle = GLES30.glGetUniformLocation(programHandle, uniformString);
mUniformHandleList.put(uniformString, handle);
}
}
/**
* 輸入紋理大小
* @param width
* @param height
*/
public void onInputSizeChange(int width, int height) {
mTexelWidthOffset = 1.0f / width;
mTexelHeightOffset = 1.0f / height;
}
/**
* 綁定濾鏡紋理,只需要綁定一次就行,不用重復(fù)綁定,減少開銷
*/
public void onDrawFrameBegin() {
if (mStrengthHandle != OpenGLUtils.GL_NOT_INIT) {
GLES30.glUniform1f(mStrengthHandle, mStrength);
}
if (mTexelWidthOffsetHandle != OpenGLUtils.GL_NOT_INIT) {
GLES30.glUniform1f(mTexelWidthOffsetHandle, mTexelWidthOffset);
}
if (mTexelHeightOffsetHandle != OpenGLUtils.GL_NOT_INIT) {
GLES30.glUniform1f(mTexelHeightOffsetHandle, mTexelHeightOffset);
}
if (mTextureList == null || mColorData == null) {
return;
}
// 逐個綁定紋理
for (int dataIndex = 0; dataIndex < mColorData.uniformDataList.size(); dataIndex++) {
for (int uniformIndex = 0; uniformIndex < mUniformHandleList.size(); uniformIndex++) {
// 如果統(tǒng)一變量存在,則直接綁定紋理
Integer handle = mUniformHandleList.get(mColorData.uniformDataList.get(dataIndex).uniform);
if (handle != null && mTextureList[dataIndex] != OpenGLUtils.GL_NOT_TEXTURE) {
OpenGLUtils.bindTexture(handle, mTextureList[dataIndex], dataIndex + 1);
}
}
}
}
/**
* 釋放資源
*/
public void release() {
if (mTextureList != null && mTextureList.length > 0) {
GLES30.glDeleteTextures(mTextureList.length, mTextureList, 0);
mTextureList = null;
}
if (mWeakFilter.get() != null) {
mWeakFilter.clear();
}
}
/**
* 設(shè)置強度
* @param strength
*/
public void setStrength(float strength) {
mStrength = strength;
}
}
然后我們構(gòu)建一個DynamicColorFilter的基類,方便后續(xù)添加其他類型的濾鏡,代碼如下:
public class DynamicColorBaseFilter extends GLImageAudioFilter {
// 顏色濾鏡參數(shù)
protected DynamicColorData mDynamicColorData;
protected DynamicColorLoader mDynamicColorLoader;
public DynamicColorBaseFilter(Context context, DynamicColorData dynamicColorData, String unzipPath) {
super(context, (dynamicColorData == null || TextUtils.isEmpty(dynamicColorData.vertexShader)) ? VERTEX_SHADER
: getShaderString(context, unzipPath, dynamicColorData.vertexShader),
(dynamicColorData == null || TextUtils.isEmpty(dynamicColorData.fragmentShader)) ? FRAGMENT_SHADER_2D
: getShaderString(context, unzipPath, dynamicColorData.fragmentShader));
mDynamicColorData = dynamicColorData;
mDynamicColorLoader = new DynamicColorLoader(this, mDynamicColorData, unzipPath);
mDynamicColorLoader.onBindUniformHandle(mProgramHandle);
}
@Override
public void onInputSizeChanged(int width, int height) {
super.onInputSizeChanged(width, height);
if (mDynamicColorLoader != null) {
mDynamicColorLoader.onInputSizeChange(width, height);
}
}
@Override
public void onDrawFrameBegin() {
super.onDrawFrameBegin();
if (mDynamicColorLoader != null) {
mDynamicColorLoader.onDrawFrameBegin();
}
}
@Override
public void release() {
super.release();
if (mDynamicColorLoader != null) {
mDynamicColorLoader.release();
}
}
/**
* 設(shè)置強度,調(diào)節(jié)濾鏡的輕重程度
* @param strength
*/
public void setStrength(float strength) {
if (mDynamicColorLoader != null) {
mDynamicColorLoader.setStrength(strength);
}
}
/**
* 根據(jù)解壓路徑和shader名稱讀取shader的字符串內(nèi)容
* @param unzipPath
* @param shaderName
* @return
*/
protected static String getShaderString(Context context, String unzipPath, String shaderName) {
if (TextUtils.isEmpty(unzipPath) || TextUtils.isEmpty(shaderName)) {
throw new IllegalArgumentException("shader is empty!");
}
String path = unzipPath + "/" + shaderName;
if (path.startsWith("assets://")) {
return OpenGLUtils.getShaderFromAssets(context, path.substring("assets://".length()));
} else if (path.startsWith("file://")) {
return OpenGLUtils.getShaderFromFile(path.substring("file://".length()));
}
return OpenGLUtils.getShaderFromFile(path);
}
}
接下來我們構(gòu)建動態(tài)濾鏡組,因為動態(tài)濾鏡有可能有多個濾鏡組合而成。代碼如下:
public class GLImageDynamicColorFilter extends GLImageGroupFilter {
public GLImageDynamicColorFilter(Context context, DynamicColor dynamicColor) {
super(context);
// 判斷數(shù)據(jù)是否存在
if (dynamicColor == null || dynamicColor.filterList == null
|| TextUtils.isEmpty(dynamicColor.unzipPath)) {
return;
}
// 添加濾鏡
for (int i = 0; i < dynamicColor.filterList.size(); i++) {
mFilters.add(new DynamicColorFilter(context, dynamicColor.filterList.get(i), dynamicColor.unzipPath));
}
}
/**
* 設(shè)置濾鏡強度
* @param strength
*/
public void setStrength(float strength) {
for (int i = 0; i < mFilters.size(); i++) {
if (mFilters.get(i) != null && mFilters.get(i) instanceof DynamicColorBaseFilter) {
((DynamicColorBaseFilter) mFilters.get(i)).setStrength(strength);
}
}
}
}
總結(jié)
基本的動態(tài)濾鏡實現(xiàn)起來比較簡單,總的來說就是簡單的json參數(shù)、shader、統(tǒng)一變量和紋理綁定需要做成動態(tài)構(gòu)建的過程而已。
效果如下:

動態(tài)濾鏡效果
該效果是通過解壓asset目錄下的壓縮包資源來實現(xiàn)的。你只需要提供包含shader 、紋理資源、以及json的壓縮包即可更改濾鏡。
詳細實現(xiàn)過程,可參考本人的開源項目:
CainCamera
好了,以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,謝謝大家對腳本之家的支持。
相關(guān)文章
Android多線程+單線程+斷點續(xù)傳+進度條顯示下載功能
這篇文章主要介紹了Android多線程+單線程+斷點續(xù)傳+進度條顯示下載功能,需要的朋友可以參考下2017-06-06
Android簡單實現(xiàn) 緩存數(shù)據(jù)
這篇文章主要介紹了Android簡單實現(xiàn) 緩存數(shù)據(jù),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-11-11
Android 優(yōu)雅的實現(xiàn)通用格式化編輯
這篇文章主要介紹了Android 優(yōu)雅的實現(xiàn)通用格式化編輯,幫助大家更好的理解和學(xué)習(xí)使用Android,感興趣的朋友可以了解下2021-03-03
深入解析Android App開發(fā)中Context的用法
這篇文章主要介紹了深入解析Android App開發(fā)中Context的用法,包括Context的創(chuàng)建場景和Context對資源的訪問等內(nèi)容,需要的朋友可以參考下2016-02-02
Android倒計時控件 Splash界面5秒自動跳轉(zhuǎn)
這篇文章主要為大家詳細介紹了Android倒計時控件,Splash界面5秒自動跳轉(zhuǎn),具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-09-09
Android Presentation雙屏異顯開發(fā)流程詳細講解
最近開發(fā)的一個項目,有兩個屏幕,需要將第二個頁面投屏到副屏上,這就需要用到Android的雙屏異顯(Presentation)技術(shù)了,研究了一下,這里做下筆記2023-01-01

