[OpenGL]將來視覺6-靜態圖片紋理加載

你們好,我係蒼王。java

如下是我這個系列的相關文章,有興趣能夠參考一下,能夠給個喜歡或者關注個人文章。canvas

OpenGL和音視頻相關的文章,將會在 [OpenGL]將來視覺-MagicCamera3實用開源庫 當中給你們呈現 裏面會記錄我編寫這個庫的一些經歷和經驗。bash

這一章是寫圖片加載。app

你用Android上開發,若是想直接加載一個圖片會怎麼作?函數

1.直接使用一個ImageView加載圖片?源碼分析

2.使用一個view的onDraw來繪製一個Bitmap圖片數據?性能

這裏ImageView其實在源碼裏都是經過onDraw方法使用canvas畫圖片(Drawable對象,並非bitmap) 而Canvas對象實如今底層中使用了skcanvas庫,用於將圖片轉換到底層硬件能夠顯示的數據。測試

下面一個簡單的ImageView源碼分析ImageView核心源碼分析優化

你須要知道幾點ui

1.ImageView的canvas是經過繪製Drawable對象繪製,並非bitmap

2.scaleType等轉換,是經過java內置Matrix矩陣函數作轉換的,最終經過canvas設置matrix矩陣

爲何你的canvas那麼慢?淺析Android的canvas性能

這是canvas繪製的原理的一些分析

1.canvas是實用skia庫來繪製的,實用cpu計算繪製

2.Android普通的view都是繼承於GLES20RecordingCanvas,這個類繪製圖都帶有硬件加速

3.SurfaceView TextureView裏面的Canvas並非 GLES20RecordingCanvas,因此要特別注意,可是其實用紋理渲染是實用GPU的。

4.從Android繪製效率上說,硬件加速繪製>opengl繪製>canvas繪製
自定義view筆記-之關於硬件加速

這篇是關於硬件加速的一些淺析

1.view沒法強制開啓硬件加速,只能強制關閉。

2.並非view的繪製操做都支持硬件加速

那還有其餘方式繪製圖片嗎?若是我要在圖片中加入一些濾鏡效果應該怎麼作? 咱們可使用SurfaceView、TextureView或者GLSurfaceView來繪製圖片,他們都會持有canvas對象,並且這些對象都是非硬件加速的。同時他們均可以自定義使用opengles來繪製。

咱們上面說過普通的view的canvas都是使用GLES20RecordingCanvas來繪製,從名字上來看就知道是用opengles2.0版原本作硬件加速的轉換編寫了。

那麼咱們也是能夠經過這些view來自定義加載Opengles的。以前已經介紹過MagicCamera3中相機是使用SurfaceView+Opengles的紋理加載方式來編寫的。

GLSurfaceView其自己就自帶了GLThread並初始化了EGL環境,系統默認mode==RENDERMODE_CONTINUOUSLY,這樣系統會自動重繪;mode==RENDERMODE_WHEN_DIRTY時,只有surfaceCreate的時候會繪製一次,而後就須要經過requestRender()方法主動請求重繪。同時也提到,若是你的界面不須要頻繁的刷新最好是設置成RENDERMODE_WHEN_DIRTY,這樣能夠下降CPU和GPU的活動,能夠省電。

而SurfaceView你只會在觸發的時候繪製一次,沒有模式能夠切換。GLSurfaceView是繼承於SurfaceView。

下面就說一下怎麼使用SurfaceView來繪製一個紋理圖片

首先是要初始化Opengles,和以前介紹的攝像頭的opengl的初始化相似,可是要傳入surface對象,assets對象,圖片的地址,以及圖片的角度。

private fun initOpenGL(surface: Surface){
        mExecutor.execute {
            //傳入surface對象,assets對象,圖片地址和圖片角度
            val textureId = OpenGLJniLib.magicImageFilterCreate(surface,BaseApplication.context.assets,imagePath,ExifUtil.getExifOrientation(imagePath))
            if (textureId < 0){
                Log.e(TAG, "surfaceCreated init OpenGL ES failed!")
                return@execute
            }
            mSurfaceTexture = SurfaceTexture(textureId)
            //若是幀圖有改變就畫圖
            mSurfaceTexture?.setOnFrameAvailableListener {
                //畫圖
                drawOpenGL()
            }
        }
    }
複製代碼

這裏若是你有辦法使用C++讀取到圖片數據頭Exif數據,最好仍是使用C++來作,這邊由於網上找了好久都沒有能簡單使用C++讀取exif數據的方法,故在討巧的使用java層解析讀取好,而後再傳入native,至於角度有什麼做用,就看後面的解析吧。

//圖片濾鏡surfaceView初始化的時候建立
JNIEXPORT jint JNICALL
Java_com_cangwang_magic_util_OpenGLJniLib_magicImageFilterCreate(JNIEnv *env, jobject obj,
                                                            jobject surface,jobject assetManager,jstring imgPath,jint degree) {
    std::unique_lock<std::mutex> lock(gMutex);
    if(glImageFilter){ //中止攝像頭採集並銷燬
        glImageFilter->stop();
        delete glImageFilter;
        glImageFilter = nullptr;
    }

    //初始化native window
    ANativeWindow *window = ANativeWindow_fromSurface(env,surface);
    //初始化app內獲取數據管理
    aAssetManager= AAssetManager_fromJava(env,assetManager);
    //初始化圖片 jstring轉爲std::string
    const char* addressStr = env->GetStringUTFChars(imgPath,0);
    std::string nativeAddress = addressStr;
    
    glImageFilter = new ImageFilter(window,aAssetManager,nativeAddress,degree);
    env->ReleaseStringUTFChars(imgPath, addressStr);
    //建立
    return glImageFilter->create();
}
複製代碼

初始化時還須要設置圖片角度

void ImageFilter::setFilter(AAssetManager* assetManager) {
    if(filter != nullptr){
        filter->destroy();
    }
    filter = new MagicNoneFilter(assetManager);
    filter->setPool(pool);
    //調整濾鏡中的圖片的方向問題
    filter->setOrientation(degree);
    ALOGD("set filter success");
}
void GPUImageFilter::setOrientation(int degree) {
    this->degree = degree;
    //獲取繪製時須要的角度變換,這裏只是兼容圖片0,90,180,270度
    mGLTextureBuffer = getRotation(degree, false, false);
}
複製代碼

兼容角度計算,這個是在shader加載的時候須要調整角度的

//獲取角度
float* getRotation(int degree, const bool flipHorizontal, const bool flipVertical){
    const float* rotateTex;
    //調整角度
    switch (degree){
        case 90:
            rotateTex = TEXTURE_ROTATED_90;
            break;
        case 180:
            rotateTex = TEXTURE_ROTATED_180;
            break;
        case 270:
            rotateTex = TEXTURE_ROTATED_270;
            break;
        case 0:
        default:
            rotateTex = TEXTURE_NO_ROTATION;
            break;
    }
    //垂直翻轉
    if (flipHorizontal){
        const static float flipTran[]={
                flip(rotateTex[0]),rotateTex[1],
                flip(rotateTex[2]),rotateTex[3],
                flip(rotateTex[4]),rotateTex[5],
                flip(rotateTex[6]),rotateTex[7]
        };
        return const_cast<float *>(flipTran);
    }

    //水平翻轉
    if (flipVertical){
        const static float flipTran[]={
                rotateTex[0],flip(rotateTex[1]),
                rotateTex[2],flip(rotateTex[3]),
                rotateTex[4],flip(rotateTex[5]),
                rotateTex[6],flip(rotateTex[7])
        };
        return const_cast<float *>(flipTran);
    }

    return const_cast<float *>(rotateTex);
}

複製代碼

建立紋理

int ImageFilter::create() {
    //初始化,清空視口顏色
    glDisable(GL_DITHER);
    glClearColor(0,0,0,0);
    glEnable(GL_CULL_FACE);
    glEnable(GL_DEPTH_TEST);

    //建立EGL環境
    if (!mEGLCore->buildContext(mWindow,EGL_NO_CONTEXT)){
        return -1;
    }

    //圖片初始化
    if (imageInput!= nullptr){
        imageInput->init();
    }

    //濾鏡初始化
    if (filter!= nullptr)
        filter->init();
    //獲取紋理id
    mTextureId = get2DTextureID();
    ALOGD("get textureId success");

    return mTextureId;
}
複製代碼

輸入視口大小,這裏須要設置顯示圖片顯示尺寸,以及屏幕尺寸

void ImageFilter::change(int width, int height) {
    //設置視口
    glViewport(0,0,width,height);
    this->mScreenWidth = width;
    this->mScreenHeight = height;
    if (imageInput!= nullptr){
        //觸發輸入大小更新
        imageInput->onInputSizeChanged(width, height);

        //初始化圖片幀緩衝
        imageInput->initFrameBuffer(imageInput->mImageWidth,imageInput->mImageHeight);

        if (filter != nullptr){
            //設置濾鏡寬高
            filter->onInputSizeChanged(width,height);
            //設置圖片的寬高
            filter->onInputDisplaySizeChanged(imageInput->mImageWidth,imageInput->mImageHeight);
            //設置矩陣
            setMatrix(width,height);
            //初始化濾鏡幀緩衝
            filter->initFrameBuffer(imageInput->mImageWidth,imageInput->mImageHeight);
        } else{
            //銷燬圖片幀緩衝
            imageInput->destroyFrameBuffers();
        }
    }
}
複製代碼

這個網上找的時候,網上圖片是以0度爲標準,能夠用如下代碼來顯示。經過正交投影很簡單就能完成。

public void onSurfaceChanged(GL10 glUnused, int width, int height) {
        // Set the OpenGL viewport to fill the entire surface.
        glViewport(0, 0, width, height);

        final float aspectRatio = width > height ? 
            (float) width / (float) height : 
            (float) height / (float) width;

        if (width > height) {
            // Landscape
            orthoM(projectionMatrix, 0, -aspectRatio, aspectRatio, -1f, 1f, -1f, 1f);
        } else {
            // Portrait or square
            orthoM(projectionMatrix, 0, -1f, 1f, -aspectRatio, aspectRatio, -1f, 1f);
        }   
    }
複製代碼

可是圖片的角度會對圖片大小顯示比例會有影響,若是調整不正確,顯示會問題很是凸顯,這裏就只區分90和270,還得經過屏幕尺寸、圖片角度、圖片尺寸來計算出正交矩陣,有些相機拍照後保存的圖片是偏移這兩種角度的。這裏屏幕一直是豎屏方向,還沒測試過橫屏。

void ImageFilter::setMatrix(int width,int height){
    memcpy(mvpMatrix,NONE_MATRIX,16);
   
    if (degree == 90 || degree == 270){  //先判斷角度
        float x;
        if(imageInput->mImageHeight>imageInput->mImageWidth){  //圖片寬比高要大 ,屏幕寬/屏幕高 * 屏幕高/屏幕寬
            x = width / (float) height *
                (float) imageInput->mImageHeight / imageInput->mImageWidth;
        } else{ //圖片寬比高要大 ,屏幕高/屏幕寬 * 屏幕高/屏幕寬
            x = height / (float) width
                    * (float) imageInput->mImageHeight / imageInput->mImageWidth;
        }
        ALOGD("x=%f",x);
        orthoM(mvpMatrix, 0, -1, 1, -x, x, -1, 1);
    } else{  //圖片高比寬要大 ,屏幕寬/屏幕高 * 屏幕高/屏幕寬
        float y;
        if(imageInput->mImageHeight>imageInput->mImageWidth){
            y = width / (float) height *
                (float) imageInput->mImageHeight / imageInput->mImageWidth;
        } else{ //圖片高比寬要大 ,屏幕高/屏幕寬 * 屏幕寬/屏幕高
            y = height / (float) width
                * ((float) imageInput->mImageWidth / imageInput->mImageHeight);
        }
        ALOGD("y=%f",y);
        orthoM(mvpMatrix, 0, -1, 1, -y, y, -1, 1);
    }
    filter->setMvpMatrix(mvpMatrix);
}
複製代碼

這裏計算後顯示到屏幕的尺寸是正常的。經過正交矩陣來作縮放比例,視口仍是屏幕尺寸。

這種加載比屏幕大不少的圖片的時候,會須要必定的延遲,由於解析成紋理也是須要時間的。

通過計算使用stb_image來加載3840*2160的圖片,小米6上耗時700毫秒以上,那麼首次顯示到屏幕上會黑屏一下。

若是你們有優化的方法能夠告訴我這邊,我也繼續試驗完善。

新建一個專欄羣,但願有興趣的同窗多多討論。

客戶端音視頻Opengles羣
相關文章
相關標籤/搜索