OpenGL ES 3.0(8) 워터마크가 있는 카메라 미리보기

9706 단어
착색기 코드
이번에는 안드로이드에서 워터마크가 있는 카메라 미리보기 기능을 구현하기 위해 두 개의 무늬가 필요합니다. 하나는 카메라 미리보기, 하나는 워터마크를 표시하는 데 사용되고 정점 착색기는 다음과 같습니다.
#version 300 es

layout(location=0) in vec4 aPosition;
layout(location=1) in vec4 aCameraTexCoord;
layout(location=2) in vec4 aWatermarkTexCoord;

uniform mat4 mCameraMatrix;
uniform mat4 mWatermarkMatrix;

out vec2 vCameraTexCoord;
out vec2 vWatermarkTexCoord;

void main() {
    vCameraTexCoord = (mCameraMatrix * aCameraTexCoord).xy;
    vWatermarkTexCoord = (mWatermarkMatrix * aWatermarkTexCoord).xy;
    gl_Position = aPosition;
}


세그먼트 셰이더는 두 텍스쳐의 중첩 방법을 염두에 두고 알파 값으로 판단하면 됩니다.
#version 300 es
#extension GL_OES_EGL_image_external_essl3 : require

precision highp float;

uniform samplerExternalOES sCameraTexture;
uniform sampler2D sWatermarkTexture;

in vec2 vCameraTexCoord;
in vec2 vWatermarkTexCoord;

layout(location=0) out vec4 fragColor;

void main() {
    vec4 camera = texture(sCameraTexture, vCameraTexCoord);
    vec4 watermark = texture(sWatermarkTexture, vWatermarkTexCoord);
    //                
    float r = watermark.r + (1.0 - watermark.a) * camera.r;
    float g = watermark.g + (1.0 - watermark.a) * camera.g;
    float b = watermark.b + (1.0 - watermark.a) * camera.b;
    fragColor = vec4(r, g, b, 1.0);
}

OpenGL 코드
먼저 교점 좌표, 텍스쳐 좌표 등을 설정합니다.
const static GLfloat VERTICES[] = {
        -1.0f, -1.0f,
        -1.0f, 1.0f,
        1.0f, 1.0f,
        1.0f, -1.0f
};

const static GLfloat CAMERA_COORDS[] = {
        0.0f, 0.0f,
        0.0f, 1.0f,
        1.0f, 1.0f,
        1.0f, 0.0f,
};

const static GLfloat WATERMARK_COORD[] = {
        0.0f, 1.0f,
        0.0f, 0.0f,
        1.0f, 0.0f,
        1.0f, 1.0f,
};

const static GLushort INDICES[] = {
        0, 1, 2,
        0, 2, 3
};

const static GLuint ATTRIB_POSITION = 0;
const static GLuint ATTRIB_CAMERA_COORD = 1;
const static GLuint ATTRIB_WATERMARK_COORD = 2;
const static GLuint VERTEX_POS_SIZE = 2;
const static GLuint CAMERA_COORD_SIZE = 2;
const static GLuint WATERMARK_COORD_SIZE = 2;
const static GLuint INDEX_NUMBER = 6;

다음으로 OpenGL 환경을 초기화하고 Camera 미리 보기를 열기 위해 sCameraTexture의 id를 자바 층에 되돌려줍니다.
int Watermark::init() {
    if (!mEGLCore->buildContext(mWindow)) {
        return -1;
    }

    std::string *vShader = readShaderFromAsset(mAssetManager, "watermark.vert");
    std::string *fShader = readShaderFromAsset(mAssetManager, "watermark.frag");
    mProgram = loadProgram(vShader->c_str(), fShader->c_str());

    glGenTextures(1, &mTextureOes);
    glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureOes);
    glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0);

    glGenTextures(1, &mTexture2D);
    glBindTexture(GL_TEXTURE_2D, mTexture2D);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, mWatermarkWidth, mWatermarkHeight, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, mWatermarkPixel);

    mCameraMatrixLoc = glGetUniformLocation(mProgram, "mCameraMatrix");
    mWatermarkMatrixLoc = glGetUniformLocation(mProgram, "mWatermarkMatrix");
    mCameraTextureLoc = glGetUniformLocation(mProgram, "sCameraTexture");
    mWatermarkTextureLoc = glGetUniformLocation(mProgram, "sWatermarkTexture");

    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
    delete vShader;
    delete fShader;

    return mTextureOes;
}

마지막으로 그리면 됩니다.
void Watermark::draw(GLfloat *cameraMatrix, GLfloat *watermarkMatrix) {
    glViewport(0, 0, mWidth, mHeight);
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(mProgram);

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureOes);
    glUniform1i(mCameraTextureLoc, 0);

    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, mTexture2D);
    glUniform1i(mWatermarkTextureLoc, 1);

    glUniformMatrix4fv(mCameraMatrixLoc, 1, GL_FALSE, cameraMatrix);
    glUniformMatrix4fv(mWatermarkMatrixLoc, 1, GL_FALSE, watermarkMatrix);

    glEnableVertexAttribArray(ATTRIB_POSITION);
    glVertexAttribPointer(ATTRIB_POSITION, VERTEX_POS_SIZE, GL_FLOAT, GL_FALSE, 0, VERTICES);

    glEnableVertexAttribArray(ATTRIB_CAMERA_COORD);
    glVertexAttribPointer(ATTRIB_CAMERA_COORD, CAMERA_COORD_SIZE, GL_FLOAT, GL_FALSE, 0,
                          CAMERA_COORDS);

    glEnableVertexAttribArray(ATTRIB_WATERMARK_COORD);
    glVertexAttribPointer(ATTRIB_WATERMARK_COORD, WATERMARK_COORD_SIZE, GL_FLOAT, GL_FALSE, 0,
                          WATERMARK_COORD);

//    glDrawArrays(GL_TRIANGLE_STRIP, 0, VERTEX_NUM);
    glDrawElements(GL_TRIANGLES, INDEX_NUMBER, GL_UNSIGNED_SHORT, INDICES);

    glDisableVertexAttribArray(ATTRIB_POSITION);
    glDisableVertexAttribArray(ATTRIB_CAMERA_COORD);
    glDisableVertexAttribArray(ATTRIB_WATERMARK_COORD);

    glFlush();
    mEGLCore->swapBuffer();
}

Java 코드
Java 코드는 간단합니다. UI는 SurfaceView 하나만 있으면 됩니다. Camera를 열면 OpenGL에서 되돌아오는 무늬 id를 Camera에 설정하면 미리 보기가 가능합니다.
        private void initOpenGL(Surface surface, int width, int height, byte[] watermark,
                                int watermarkWidth, int watermarkHeight) {
            mExecutor.execute(() -> {
                int textureId = _init(surface, width, height, watermark, watermark.length,
                        watermarkWidth, watermarkHeight, getAssets());
                if (textureId < 0) {
                    Log.e(TAG, "surfaceCreated init OpenGL ES failed!");
                    return;
                }
                mSurfaceTexture = new SurfaceTexture(textureId);
                mSurfaceTexture.setOnFrameAvailableListener(surfaceTexture -> drawOpenGL());
                try {
                    mCamera.setPreviewTexture(mSurfaceTexture);
                    mCamera.startPreview();
                } catch (IOException e) {
                    Log.e(TAG, "onSurfaceCreated exception: " + e.getLocalizedMessage());
                }
            });
        }

        private void drawOpenGL() {
            mExecutor.execute(() -> {
                if (mSurfaceTexture != null) {
                    mSurfaceTexture.updateTexImage(); //       OpenGL      
                    mSurfaceTexture.getTransformMatrix(mCameraMatrix);
                    _draw(mCameraMatrix, mWatermarkMatrix);
                }
            });
        }

        private void releaseOpenGL() {
            mExecutor.execute(() -> {
                if (mSurfaceTexture != null) {
                    mSurfaceTexture.release();
                    mSurfaceTexture = null;
                }
                _release();
            });
        }

여기서 ""처음에 native 방법으로 시작해서 물자국에 대해서는 그림이 될 수 있다.
        private void makeImageWatermark() {
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inPreferredConfig = Bitmap.Config.ARGB_8888;
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher, options);
            int byteCount = bitmap.getByteCount();
            ByteBuffer buffer = ByteBuffer.allocate(byteCount);
            bitmap.copyPixelsToBuffer(buffer);
            buffer.position(0);
            mWatermark = buffer.array();
            mWatermarkWidth = bitmap.getWidth();
            mWatermarkHeight = bitmap.getHeight();
            bitmap.recycle();

            Matrix.scaleM(mWatermarkMatrix, 0, 2.5f, 2.5f, 2.5f);
        }

또는 문자일 수 있습니다.
        private void makeTextWatermark(int width, int height) {
            Bitmap textBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            Canvas canvas = new Canvas(textBitmap);
            Paint paint = new Paint();
            paint.setColor(Color.argb(255, 255, 0, 0));
            paint.setTextSize(28);
            paint.setAntiAlias(true);
            paint.setTextAlign(Paint.Align.CENTER);
            Rect rect = new Rect(0, 0, width, height);
            Paint.FontMetricsInt fontMetrics = paint.getFontMetricsInt();
            //               
            int baseline = (rect.bottom + rect.top - fontMetrics.bottom - fontMetrics.top) / 2;
            canvas.drawText("  : zouzhiheng", rect.centerX(), baseline, paint);

            int capacity = width * height * 4;
            ByteBuffer buffer = ByteBuffer.allocate(capacity);
            textBitmap.copyPixelsToBuffer(buffer);
            buffer.position(0);
            mWatermark = buffer.array();
            mWatermarkWidth = textBitmap.getWidth();
            mWatermarkHeight = textBitmap.getHeight();
            textBitmap.recycle();

            Matrix.scaleM(mWatermarkMatrix, 0, 2f, 2f, 2f);
        }

의문
마지막으로 몇 가지 이해하지 못한 문제가 있었다.
  • CAMERA_COORDS、WATERMARK_COORD의 좌표 벡터 방향은 반대이다. 이렇게 해야만 휴대전화에 표시될 때 거꾸로 돌지 않는다
  • 워터마크를 만들 때 Matrix를 사용했습니다.scalem, 워터마크를 줄이려면 확대된 값을 보내야 한다(2f)
  • Matrix가 호출된 경우rotateM, 물자국은 90°든 180°
  • 든 사라집니다.
    소스가 GitHub에 업로드되었으므로 이에 대한 자세한 지침이 필요합니다.

    좋은 웹페이지 즐겨찾기