android使用opengles渲染一張圖片


一、概述

  案例:使用opengles+egl渲染一張圖片

  關鍵類介紹:

    1.新建一個DrawPictureActivity.java用於充當顯示容器:初始化SurfaceView並設置SurfaceView的callback回調函數。並在其onSurfaceCreated函數中對DrawPicture對象進行初始化。

    2.新建DrawPicture.java用於java層和native層進行通訊

    3.新建picture_controller.cpp用於和DrawPicture.java溝通java傳遞給jni層的數據。並在其init方法中創建ANativeWindow、提取Bitmap中的像素數組,初始化C++類Picture類

    4.新建picture.cpp類,此類為整個渲染過程提供:OpenGL ES上下文環境、顯示設備EGLSurface、GPU程序(shader)、OpenGL ES程序

  渲染步驟:

    1.DrawPictureActivity.java中在其SurfaceHolder.addCallback的回調函數中初始化DrawPicture.java類,並調用DrawPicture對象的initBitmap方法將客戶端的bitmap傳遞給jni層

    2.在jni層的(picture_controller.cpp)initBitmap方法中,根據傳遞過來的Surface對ANativeWindow進行創建,根據傳遞過來的Bitmap對其中的像素進行提取。

    3.實例化C++的Picture並調用其init方法,在init方法中會初始化OpenGL ES上下文環境以及顯示設備EGLSurface,並調用eglMakeCurrent來為當前線程綁定上下文及顯示設備。

    3.調用C++的Picture實例中的renderPicture方法,通過此方法,a.創建一個紋理id、綁定紋理id、設置紋理的過濾方式及平鋪方式、並將從第二步中提取出來的像素數組上傳到這個紋理上。b.創建shader 。c.創建opengles程序 。d.給opengles着色器傳遞坐標數據(頂點坐標、紋理坐標)。e.調用glDrawArray方法開始繪制紋理 f.調用eglSwapBuffers方法將back frame buffer 和front frame buffer交互,將圖像顯示到用戶屏幕上。

      

二、關鍵代碼

  1.DrawPictureActivity.java:初始化SurfaceView並得到SurfaceHolder,然后給SurfaceHolder設置回調函數

    surfaceView = findViewById(R.id.pictureSurfaceView);

        holder = surfaceView.getHolder();
        holder.addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(@NonNull SurfaceHolder holder) {
                drawPicture = new DrawPicture();
                parseBitmap();
                Log.e("surfaceCreated", "" + pixels.length);
//                drawPicture.initByte(bitmapWidth, bitmapHeight, bitmap2RGB(bitmap), holder.getSurface());
                drawPicture.initBitmap(bitmap,holder.getSurface());
            }

            @Override
            public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
                drawPicture.onSurfaceChanged(width,height);

            }

            @Override
            public void surfaceDestroyed(@NonNull SurfaceHolder holder) {

            }
        });

  2.DrawPicture.java:和jni溝通的類

public native void initBitmap(Bitmap bitmap,Surface surface);
    /**
     * 當surface發生改變的時候更新width、height
     *
     * @param mWidth  寬度
     * @param mHeight 高度
     */
    public native void onSurfaceChanged(int mWidth, int mHeight);

  3.picture_controller.cpp :提取bitmap像素數組、初始化ANativeWindow、初始化Picture C++類

  

extern "C"
JNIEXPORT void JNICALL
Java_com_yw_ywmediaplayer_activity_nativeinterface_DrawPicture_initBitmap(JNIEnv *env, jobject thiz,
                                                                          jobject bitmap,
                                                                          jobject surface) {
    void* pixels  =NULL;
    AndroidBitmapInfo bitmapInfo;
    int ret = AndroidBitmap_getInfo(env,bitmap,&bitmapInfo);
    if(ret<0){
        LOGE("get bitmap info error");
        return;
    }
    LOGE("bitmapInfo:width=[%d],height=[%d]",bitmapInfo.width,bitmapInfo.height);
    if(bitmapInfo.format!=ANDROID_BITMAP_FORMAT_RGBA_8888){
        LOGE("not support RGBA8888");
        return;
    }

    ret = AndroidBitmap_lockPixels(env,bitmap,&pixels);
    if(ret<0){
        LOGE("get bitmap pixels fail");
        return;
    }

    _window = ANativeWindow_fromSurface(env, surface);
    LOGE("實例化Picture");
    picture = new Picture(bitmapInfo.width, bitmapInfo.height, pixels, _window);
    LOGE("picture->init()");
    picture->init();



    AndroidBitmap_unlockPixels(env,bitmap);

}

  3.Picture C++類

    a.初始化OpenGL ES上下文環境及顯示設備   

/**
 * 初始化egl並創建eglsurface
 */
void Picture::init() {
    eglCore = new EGLCore();
    eglCore->init();
    eglSurface = eglCore->createWindowSurface(_window);
    //給當前線程綁定上下文環境及顯示設備
    eglCore->makeCurrent(eglSurface);
}

  b.設置窗口大小及清除各種緩沖區

void Picture::initViewPort() {
    //設置窗口大小
    glViewport(0, 0, screenWidth, screenHeight);
    LOGE("screenWidth=[%d],screenHeight=[%d]",screenWidth,screenHeight);
    glClearColor(0.0f, 0.0f, 1.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    //reset xoffset yoffset
    resizeTexture();
}

  c.創建紋理id、綁定紋理id、設置紋理過濾方式及紋理的重復映射和簡約映射方式、將像素數組上傳到紋理上

 /**創建紋理*/
    //創建並綁定紋理
    LOGE("glGenTextures");
    GLuint textureId = 0;
    glGenTextures(1, &textureId);
    LOGE("glBindTexture");
    //此處創建紋理以后需要綁定紋理id,並給紋理設置過濾方式及映射方式,不然會黑屏
    glBindTexture(GL_TEXTURE_2D,
                  textureId);//在opengles操作過程中必須告訴opengles操作的是哪個紋理,所以要調用操作opengles提供的綁定紋理的方法綁定紋理

    /**設置紋理參數*/
    //設置紋理的過濾方式(雙線性過濾):當紋理對象(可以理解為一張圖片)被渲染到物體表面上的時候(實際上是OpenGL繪制管線將紋理的元素映射到OpenGL生成的片段上的時候),有可能要被放大或者縮小,而當其放大或者縮小的時候,具體應該如何確定每個像素是如何被填充的,就由開發者配置的紋理對象的紋理過濾器來指明
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    //設置紋理映射過程中用到的重復映射和簡約映射規則:將該紋理的s軸和t軸的坐標設置為GL_CLAMP_TO_EDGE類型,因為紋理坐標可以超出(0,1)的范圍,而按照上述設置規則,所有大於1的紋理值都要設置為1,所有小於0的值都要置為0。
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    /**將RGBA表示的橡樹數組上傳到紋理上*/
    //將RGBA數組表示的像素內容上傳到紋理上
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmapWidth, bitmapHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 (uint8_t *) pixels);

  d.創建opengles可執行程序及着色器

/**創建opengles顯卡可執行程序*/
    //創建opengles程序
    LOGE("glCreateProgram");
    GLuint programId = glCreateProgram();
    GLuint vertexShader = createShader(GL_VERTEX_SHADER, VERTEX_SHADER_SOURCE);
    GLuint framgentShader = createShader(GL_FRAGMENT_SHADER, FRAGMENT_SHADER_SOURCE);
    LOGE("glAttachShader");
    glAttachShader(programId, vertexShader);
    glAttachShader(programId, framgentShader);
    LOGE("glLinkProgram");
    glLinkProgram(programId);
    GLint status;
    glGetProgramiv(programId, GL_LINK_STATUS, &status);
    if (status == GL_FALSE) {
        GLint bufLength = 0;
        glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &bufLength);
        if (bufLength) {
            char *buf = (char *) malloc(bufLength);
            if (buf) {
                glGetProgramInfoLog(programId, bufLength, NULL, buf);
                LOGI("Could not link program:\n%s\n", buf);
                free(buf);
            }
        }
        glDeleteProgram(programId);
        programId = 0;
        return -1;
    }
    GLint mPositionHandle = glGetAttribLocation(programId, "position");
    GLint mTextureHandle = glGetAttribLocation(programId, "texcoord");
    GLint mUniformSampler = glGetUniformLocation(programId, "yuvTexSampler");
    LOGE("glUseProgram");
    glUseProgram(programId);
/**
 * 創建shader
 * @param shaderType shader類型
 * @param source shader程序字符串
 * @return shader
 */
GLuint Picture::createShader(GLenum shaderType, const char *source) {
    GLint status;
    GLuint shader = glCreateShader(shaderType);
    glShaderSource(shader, 1, &source, NULL);
    glCompileShader(shader);
    glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
    if (!status) {
        GLint infoLen = 0;
        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
        if (infoLen) {
            char *buf = (char *) malloc(infoLen);
            if (buf) {
                glGetShaderInfoLog(shader, infoLen, NULL, buf);
                LOGI("Could not compile shader %d:\n%s\n", shaderType, buf);
                free(buf);
            }
        } else {
            LOGI("Guessing at GL_INFO_LOG_LENGTH size\n");
            char *buf = (char *) malloc(0x1000);
            if (buf) {
                glGetShaderInfoLog(shader, 0x1000, NULL, buf);
                LOGI("Could not compile shader %d:\n%s\n", shaderType, buf);
                free(buf);
            }
        }
        glDeleteShader(shader);
        shader = 0;
    }
    return shader;
}

  e.設置頂點坐標、紋理坐標、激活紋理並使用紋理

LOGE("glGetAttribLocation");
    //設置頂點坐標
    glVertexAttribPointer(mPositionHandle, 2, GL_FLOAT, 0, 0, VERTEXS_POINTS);
    glEnableVertexAttribArray(mPositionHandle);


    //設置紋理坐標
    LOGE("glGetAttribLocation");
    GLfloat TEXTURE_POINTS[] = {
            xOffset, yOffset,
            xOffset, 1.0f - yOffset,
            1.0f - xOffset, yOffset,
            1.0f - xOffset, 1.0f - yOffset
    };
    glVertexAttribPointer(mTextureHandle, 2, GL_FLOAT, 0, 0, TEXTURE_POINTS);
    glEnableVertexAttribArray(mTextureHandle);

    glActiveTexture(GL_TEXTURE0);
//    glBindTexture(GL_TEXTURE_2D, textureId);
    glUniform1i(mUniformSampler, 0);
    checkGlError("glUniform1i");

  f.開始繪制

//開始繪制
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

  g.將back frame buffer和front frame buffer進行交換,並將圖像顯示到屏幕上

LOGE("將front frame buffer 與back frame buffer交換並顯示到屏幕上");
    eglCore->swapBuffers(eglSurface);

 

  h.到此結束,運行后就能顯示出圖片了。

  

三、示例圖片

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM