最近比較忙屹堰,還花時間寫了幾篇Xposed的文章,所以O(shè)penGL 更新有點慢憋槐,不過還是要堅持呀~
上一篇文章《OpenGL從入門到放棄08》相機預(yù)覽双藕,這樣講就好理解了詳細介紹了相機預(yù)覽,并且封裝了一個 Camera2BaseFilter
類阳仔,
主要是為了方便實現(xiàn)不同濾鏡效果忧陪。
跟濾鏡有關(guān)的內(nèi)容還有濾鏡預(yù)覽、濾鏡拍照近范、開濾鏡錄視頻嘶摊,這些都會講,這篇文章主要學(xué)習(xí)一下濾鏡預(yù)覽评矩、濾鏡拍照叶堆,而濾鏡錄視頻比較麻煩一些,所以后面單獨寫一篇吧~
如果各位有讀完上一篇文章斥杜,這篇文章理解起來就很簡單了虱颗。
一沥匈、濾鏡預(yù)覽
先增加各種不同濾鏡對應(yīng)的片元著色器,然后在 ShaderManager
中預(yù)加載著色器代碼忘渔, ShaderManager
在 《OpenGL從入門到放棄07》濾鏡高帖,一定要看,不虧 這一篇文章里有說明了畦粮,本次就新增著色器散址,照貓畫虎
先看下最終實現(xiàn)的濾鏡預(yù)覽效果,gif壓縮了不清晰宣赔,效果還是可以看出來的预麸,大家可以自己去實踐一下~
1、黑白濾鏡著色器
頂點著色器
camera2_vertex_shader_base.glsl
attribute vec4 aPosition;
uniform mat4 uMVPMatrix;
attribute vec4 aTexCoord;
varying vec2 vTextureCoord; //傳給片元著色器
varying vec4 vPosition; //傳給片元著色器
void main()
{
vTextureCoord = (uMVPMatrix * aTexCoord).xy;
gl_Position = aPosition;
vPosition = aPosition;
}
片元著色器
camera2_fragment_shader_gray.glsl
#extension GL_OES_EGL_image_external : require
precision mediump float;
uniform samplerExternalOES uTextureSampler;
varying vec2 vTextureCoord;
void main()
{
vec4 vCameraColor = texture2D(uTextureSampler, vTextureCoord);
float fGrayColor = (0.3*vCameraColor.r + 0.59*vCameraColor.g + 0.11*vCameraColor.b);
gl_FragColor = vec4(fGrayColor, fGrayColor, fGrayColor, 1.0);
}
看到這里儒将,是不是很熟悉
《OpenGL從入門到放棄07》濾鏡吏祸,一定要看,不虧
這一篇文章介紹了好幾種濾鏡實現(xiàn)椅棺,都是關(guān)于圖片的犁罩,這里相機預(yù)覽同樣適用,只不過紋理不一樣了两疚,相機預(yù)覽床估,用到的紋理是擴展紋理samplerExternalOES
,替換一下诱渤,然后其它基本一樣丐巫。
2、黑白濾鏡實現(xiàn)類
public class Camera2FilterGray extends Camera2BaseFilter {
public Camera2FilterGray(Context context, int textureId) {
super(context, textureId);
}
@Override
protected ShaderManager.Param getProgram() {
return ShaderManager.getParam(ShaderManager.CAMERA_GRAY_SHADER);
}
}
黑白著色器的加載在ShaderManager
中
//黑白
insertParam(CAMERA_GRAY_SHADER, GLUtil.loadFromAssetsFile(context, "shader/camera2/camera2_vertex_shader_base.glsl")
, GLUtil.loadFromAssetsFile(context, "shader/camera2/camera2_fragment_shader_gray.glsl"));
添加一個濾鏡就只需要以上步驟勺美,然后在切換濾鏡的點擊事件中替換濾鏡即可递胧。
其它濾鏡就不再講了,如果前面文章都看懂赡茸,那么這個對你來說應(yīng)該是so easy了缎脾。
二、拍照
這里思考一個問題:
相機返回的預(yù)覽數(shù)據(jù)流其實我們并沒有更改占卧,只是在渲染到GLSurfaceView的時候我們增加濾鏡顯示遗菠,所以我們拍照保存肯定不能用Camera2的那套,而是要另辟蹊徑华蜒。
onDrawFrame
這個方法是會一直調(diào)用的辙纬,每調(diào)用一次,就渲染一幀圖片叭喜,所以我們就可以從onDrawFrame
這個方法入手贺拣,當(dāng)點擊拍照的時候,獲取渲染的這一幀數(shù)據(jù),然后保存到本地譬涡。同理錄制視頻就應(yīng)該是不斷的保存每一幀圖片闪幽,這個后面會介紹。
2.1 如何獲取紋理圖片數(shù)據(jù)?
我們現(xiàn)在是要獲取GLSurfaceView中渲染的紋理圖片數(shù)據(jù)涡匀,然后保存到本地沟使,
GLES20.glReadPixels(...)
上場
public static native void glReadPixels(
int x, //0 左下角的坐標
int y, //0
int width, //輸出的寬度
int height, //輸出的高度
int format, //GLES20.GL_RGBA
int type, //GLES20.GL_UNSIGNED_BYTE
java.nio.Buffer pixels //接收輸出Buffer
);
這個方法可以獲取渲染在GLSurfaceView中的圖片數(shù)據(jù),
各個參數(shù)的意思:
x渊跋, y:
圖像的左下角的坐標,如果想截取整個opengl顯示窗口着倾,取0,0即可
width拾酝,height:
截屏范圍
format:指的是opengl中像素存儲的格式,一般有:
GL_ALPHA
GL_RGB
GL_RGBA
type:這個值要和format匹配上卡者,
GL_UNSIGNED_BYTE蒿囤,(0-255)
GL_UNSIGNED_SHORT_5_6_5,
GL_UNSIGNED_SHORT_4_4_4_4,
oGL_UNSIGNED_SHORT_5_5_5_1,
這個每一個通道的范圍在0-2n次方的范圍內(nèi)
pixels:
接收輸出Buffer
2.2 上代碼
2.2.1 onDrawFrame
boolean mTakePicture = false;
public void takePicture() {
mTakePicture = true;
}
public void onDrawFrame(GL10 gl) {
...
mCamera2BaseFilter.draw(mTransformMatrix);
if (mTakePicture) {
doTakePicture();
}
}
在 onDrawFrame
中,用一個boolean
變量標識是否要拍照崇决,在點擊拍照的時候設(shè)置true材诽,然后在doTakePicture
中設(shè)置為false
2.2.3 doTakePicture
//回調(diào)數(shù)據(jù)的寬,init 賦值
public int mFrameCallbackWidth;
public int mFrameCallbackHeight;
//用于存儲拍照回調(diào)數(shù)據(jù)的buffer恒傻,大小是寬乘以高
private IntBuffer mOutPutIntBuffer;
private void doTakePicture() {
mTakePicture = false;
if (mOutPutIntBuffer == null) {
mOutPutIntBuffer = IntBuffer.allocate(mFrameCallbackWidth *
mFrameCallbackHeight);
}
//todo 這里可以用離屏渲染的方式脸侥,增加水印,后面再加
// mCamera2BaseFilter.draw(mTransformMatrix);
//通過這個api獲取畫的這一幀數(shù)據(jù)
GLES20.glReadPixels(0, 0, mFrameCallbackWidth, mFrameCallbackHeight,
GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mOutPutIntBuffer);
//獲取到圖片數(shù)據(jù)了
int[] origin = mOutPutIntBuffer.array();
int[] result = new int[mFrameCallbackWidth * mFrameCallbackHeight];
//解決方向顛倒問題
for (int i = 0; i < mFrameCallbackHeight; i++) {
for (int j = 0; j < mFrameCallbackWidth; j++) {
int pix = origin[i * mFrameCallbackWidth + j];
int pb = (pix >> 16) & 0xff;
int pr = (pix << 16) & 0x00ff0000;
int pix1 = (pix & 0xff00ff00) | pr | pb;
result[(mFrameCallbackHeight - i - 1) * mFrameCallbackWidth + j] = pix1;
}
}
//幀數(shù)據(jù)回調(diào), Activity中做保存圖片操作
if (mCameraCallback != null) {
mCameraCallback.onFrameCallBack(result);
}
}
基本都注釋清楚了盈厘,這里再整理一下這幾個步驟:
- 初始化要輸出的 IntBuffer mOutPutIntBuffer睁枕,大小是寬乘以高
- 調(diào)用
GLES20.glReadPixels
,傳寬高和接收圖片的 mOutPutIntBuffer - 得到mOutPutIntBuffer之后沸手,這時候如果直接保存圖片外遇,會發(fā)現(xiàn)圖片上下顛倒,所以要先解決圖片上下顛倒問題(for循環(huán)自己去理解理解)
- 最終將數(shù)據(jù)回調(diào)到Activity中
2.2.4 onFrameCallBack
回調(diào)到Activity中契吉,大概看一下
public void onFrameCallBack(final int[] bytes) {
final int width = mCameraV2Renderer.mFrameCallbackWidth;
final int height = mCameraV2Renderer.mFrameCallbackHeight;
Log.d(TAG, "onFrameCallBack: bitmap width = "+ width);
Log.d(TAG, "onFrameCallBack: bitmap height = "+ height);
new Thread(new Runnable() {
@Override
public void run() {
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
IntBuffer byteBuffer = IntBuffer.wrap(bytes);
bitmap.copyPixelsFromBuffer(byteBuffer);
saveBitmap(bitmap);
bitmap.recycle();
}
}).start();
}
int數(shù)組轉(zhuǎn)換成IntBuffer跳仿,然后通過Bitmap的copyPixelsFromBuffer方法,將數(shù)據(jù)拷貝到Bitmap中捐晶,然后就常規(guī)保存Bitmap即可
2.2.5 保存Bitmap
//圖片保存
public void saveBitmap(Bitmap bitmap) {
String path = getSD() + "/OpenGLDemo/photo/";
File folder = new File(path);
if (!folder.exists() && !folder.mkdirs()) {
mHandler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(Camera2Demo_SurfaceView_Activity.this, "無法保存照片", Toast.LENGTH_SHORT).show();
}
});
return;
}
long dataTake = System.currentTimeMillis();
final String jpegName = path + "_" + dataTake + ".jpg";
try {
FileOutputStream fos = new FileOutputStream(jpegName);
BufferedOutputStream bos = new BufferedOutputStream(fos);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);
bos.flush();
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
Log.w(TAG, "saveBitmap: 保存成功,耗時:" + (System.currentTimeMillis() - dataTake));
mHandler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(Camera2Demo_SurfaceView_Activity.this, "保存成功,路徑:" + jpegName, Toast.LENGTH_SHORT).show();
}
});
}
protected String getSD() {
return Environment.getExternalStorageDirectory().getAbsolutePath();
}
6.0 動態(tài)權(quán)限就不說了菲语,我提前申請的
接下來就是見證效果的時候了,點擊拍照租悄,打開圖片
方向?qū)α私骶浚贿^圖片不太對,右邊黑了一塊泣棋?
看下日志打印的大小
D/Camera2Demo_SurfaceView: onFrameCallBack: bitmap width = 1670
D/Camera2Demo_SurfaceView: onFrameCallBack: bitmap height = 1252
圖片的大薪赫堋:
這個是個bug,這個大小是Camer2獲取到的大小潭辈,而實際上拍照應(yīng)該是取GLSurfaceView的大小
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
glViewport(0, 0, width, height);
Log.i(TAG, "onSurfaceChanged: " + width + ", " + height);
mFrameCallbackWidth = width;
mFrameCallbackHeight = height;
}
在 onSurfaceChanged
中獲取這個大小鸯屿,然后重新拍照看下
嗯澈吨,這就對了,
最終幾個濾鏡拍照效果:
gif有點模糊寄摆,原圖是很清晰的~
拍照到這里就結(jié)束了谅辣,后面還有拍照添加貼紙、拍視頻等功能要講婶恼,敬請期待吧~
源碼學(xué)習(xí)
之前的文章
《OpenGL從入門到放棄01 》一些基本概念
《OpenGL從入門到放棄02 》GLSurfaceView和Renderer
《OpenGL從入門到放棄03 》相機和視圖
《OpenGL從入門到放棄04 》畫一個長方形
《OpenGL從入門到放棄05 》著色器語言
《OpenGL從入門到放棄06 》紋理圖片顯示
《OpenGL從入門到放棄07》濾鏡桑阶,一定要看,不虧
《OpenGL從入門到放棄08》相機預(yù)覽勾邦,這樣講就好理解了