Android OpenGL 做了一个修图(P 图)功能

P 图功能与 OpenGL

玩过 P 图软件的朋友一定对这个功能有所了解,P 图我们可以简单地看做把一个区域的像素按照某一方向进行移动,产生一定形变效果,基于这个原理,我们可以手动实现瘦脸、长腿、瘦腰、大眼、丰胸等等一系列效果,从而达到美颜、美型的目的。

我们将一个区域的像素移走以后,那么用什么来填充这个被"掏空"的区域呢?答案是, OpenGL 自带插值功能会使用周围的像素对被"掏空"的区域进行插值填充。

回想下 OpenGL 纹理贴图,将图像贴到相对大的区域,就会产生拉伸的效果,贴到相对更小的区域就会产生挤压的效果,这都是借助于 OpenGL 的双线性插值算法实现。

对纹理贴图不了解的同学可以移步:Android OpenGL ES 系统性学习教程

所以,当我们选中一块图像区域进行移动时,OpenGL 纹理贴图时会在移动的方向上产生挤压的效果,而反方向便会产生拉伸效果,从而可以实现对人体部位形变效果。

OpenGL 实现 P 图功能

根据上节讨论的原理,我们把选定位图像区域看成一个圆形,圆形之外的区域不进行偏移形变(不受影响),圆内的区域的像素则是越靠近圆心移动位移相对越大。

如上图所示,BC 表示偏移方向和偏移程度的向量,将圆内的所有像素按照向量 BC 的方向进行一定程度的偏移,像素偏移的强度,和像素与圆心的距离相关,越靠近圆心强度越大。

再回想下纹理贴图(纹理映射)那篇文章,我们只是将图像映射到一个网格(2个三角形组成),这是我们只能对整图做形变,无法做到对如脸部等一小块具体的区域做形变。

以此类比,这个时候我们就需要更多的网格,当网格足够密集,就可以覆盖整张图的全部区域,这个时候我们通过调整网格,可以实现对任意区域的形变,从而手动实现瘦脸、长腿、瘦腰、大眼、丰胸等等效果不在话下。

生成更多的网格实际上是为了能控制一小块网格区域图像的形变,也就是一定范围内网格区域图像的形变,不对这个范围外的图像产生影响。

所以,剩下来的问题就是生成很多网格,然后控制网格结点的偏移,通过简单纹理映射实现 P 图功能。

生成网格及对应的顶点坐标和纹理坐标:

/**
 *
 * @param verticalNum  垂直方向网格数
 * @param horizonNum   水平方向网格数
 * @param ppVertices   顶点坐标集合
 * @param ppTexCoor    纹理坐标集合
 */
void GLRender::GenVertexMesh(int verticalNum, int horizonNum, float **ppVertices, float **ppTexCoor)
{
    *ppVertices = static_cast<float *>(malloc(verticalNum * horizonNum * 18 * sizeof(float)));
    *ppTexCoor = static_cast<float *>(malloc(verticalNum * horizonNum * 12 * sizeof(float)));
    m_pStatusTexCoords = static_cast<float *>(malloc(verticalNum * horizonNum * 12 * sizeof(float)));

    float dS = 1.0f / horizonNum;
    float dT = 1.0f / verticalNum;

    for (int i = 0; i < horizonNum; ++i) //S
    {
        float s0 = i * dS;
        float s1 = (1 + i) * dS;
        for (int j = 0; j < verticalNum; ++j) //T
        {
            float t0 = j * dT;
            float t1 = (1 + j) * dT;
            int meshIndex = j * horizonNum + i;
            (*ppTexCoor)[meshIndex * 12 + 0] = s0;
            (*ppTexCoor)[meshIndex * 12 + 1] = t0;

            (*ppTexCoor)[meshIndex * 12 + 2] = s0;
            (*ppTexCoor)[meshIndex * 12 + 3] = t1;


            (*ppTexCoor)[meshIndex * 12 + 4] = s1;
            (*ppTexCoor)[meshIndex * 12 + 5] = t0;

            (*ppTexCoor)[meshIndex * 12 + 6] = s1;
            (*ppTexCoor)[meshIndex * 12 + 7] = t0;


            (*ppTexCoor)[meshIndex * 12 + 8] = s0;
            (*ppTexCoor)[meshIndex * 12 + 9] = t1;

            (*ppTexCoor)[meshIndex * 12 + 10] = s1;
            (*ppTexCoor)[meshIndex * 12 + 11] = t1;

            // vertex coordinate
            (*ppVertices)[meshIndex * 18 + 0] = 2 * s0 - 1;
            (*ppVertices)[meshIndex * 18 + 1] = 1 - 2 * t0;
            (*ppVertices)[meshIndex * 18 + 2] = 0;

            (*ppVertices)[meshIndex * 18 + 3] = 2 * s0 - 1;
            (*ppVertices)[meshIndex * 18 + 4] = 1 - 2 * t1;
            (*ppVertices)[meshIndex * 18 + 5] = 0;

            (*ppVertices)[meshIndex * 18 + 6] = 2 * s1 - 1;
            (*ppVertices)[meshIndex * 18 + 7] = 1 - 2 * t0;
            (*ppVertices)[meshIndex * 18 + 8] = 0;

            (*ppVertices)[meshIndex * 18 + 9] = 2 * s1 - 1;
            (*ppVertices)[meshIndex * 18 + 10] = 1 - 2 * t0;
            (*ppVertices)[meshIndex * 18 + 11] = 0;

            (*ppVertices)[meshIndex * 18 + 12] = 2 * s0 - 1;
            (*ppVertices)[meshIndex * 18 + 13] = 1 - 2 * t1;
            (*ppVertices)[meshIndex * 18 + 14] = 0;

            (*ppVertices)[meshIndex * 18 + 15] = 2 * s1 - 1;
            (*ppVertices)[meshIndex * 18 + 16] = 1 - 2 * t1;
            (*ppVertices)[meshIndex * 18 + 17] = 0;

        }
    }

    memcpy(m_pStatusTexCoords, (*ppTexCoor), verticalNum * horizonNum * 12 * sizeof(float));
}

在圆的范围内控制网格结点的偏移,圆形之外的区域不进行偏移形变(不受影响),圆内的区域的像素则是越靠近圆心移动位移相对越大。

//prePoint 圆心,radius 半径
void GLRender::UpdateVertexMeshWithLinear(PointF prePoint, PointF curPoint, float radius, float *pVertices)
{

    int pointNum = m_MeshNum * 6;
    float textureWidth = m_RenderImg.width;
    float textureHeight = m_RenderImg.height;

    // 转化为图片坐标
    float imgRadius = radius * textureWidth;
    PointF imgSize = {textureWidth, textureHeight};
    PointF imgPrePoint = prePoint * imgSize;
    PointF imgCurPoint = curPoint * imgSize;
    PointF texPoint;
    for (int i = 0; i < pointNum; ++i)
    {
        texPoint.x = m_TexCoords[i * 2 + 0];
        texPoint.y = m_TexCoords[i * 2 + 1];

        PointF imgTexPoint = texPoint * imgSize;
        float r = distance(imgTexPoint, imgPrePoint);
        //判断是否在圆的范围内
        if (r < imgRadius)
        {
            //越靠近圆心偏移越大
            float alpha = 1.0f - r / imgRadius;
            //做个平滑
            alpha = smoothstep(0.f, 1.f, alpha);

            //移动方向
            PointF dVec = (imgCurPoint - imgPrePoint) * pow(alpha, 2.0f); 
            //乘以一个系数,不然偏移太大了
            dVec = dVec * 0.08f;
            //移动后的网格结点
            PointF newImgTexPoint = imgTexPoint + dVec;
            //归一化
            newImgTexPoint = newImgTexPoint / imgSize;

            //更新对应的纹理坐标和顶点坐标
            m_TexCoords[i * 2 + 0] = newImgTexPoint.x;
            m_TexCoords[i * 2 + 1] = newImgTexPoint.y;

            pVertices[i * 3 + 0] = 2 * newImgTexPoint.x - 1;
            pVertices[i * 3 + 1] = 1 - 2 * newImgTexPoint.y;
        }
    }

}

纹理贴图使用的 shader,一个常规的纹理采样。

const char vShaderStr[] =
        "#version 300 es                            \n"
        "layout(location = 0) in vec4 a_position;   \n"
        "layout(location = 1) in vec2 a_texCoord;   \n"
        "out vec2 v_texCoord;                       \n"
        "uniform mat4 u_MVPMatrix;                  \n"
        "void main()                                \n"
        "{                                          \n"
        "   gl_Position = u_MVPMatrix * a_position; \n"
        "   v_texCoord = a_texCoord;                \n"
        "}                                          \n";

const char fShaderStr[] =
        "#version 300 es                            \n"
        "precision mediump float;                   \n"
        "in vec2 v_texCoord;                        \n"
        "layout(location = 0) out vec4 outColor;    \n"
        "uniform sampler2D s_TextureMap;            \n"
        "void main()                                \n"
        "{                                          \n"
        "    outColor = texture(s_TextureMap, v_texCoord);\n"
        "}";

推荐参考项目:https://github.com/githubhaohao/NDK_OpenGLES_3_0

猜你喜欢

转载自blog.csdn.net/qq_21743659/article/details/123819228