P graph function with OpenGL

Friends played P chart software for this function to understand somewhat, P figure we can simply as an area of the moving pixels according to a certain direction, produce certain deformation effect, based on this principle, we can manually thin face, long legs, waist, big eye, breast enhancement, and so on a series of effect, so as to achieve the purpose of beauty, beauty.

Once we remove the pixels from an area, what will fill the hollowed out area? And the answer is, OpenGL has built-in interpolation that uses the surrounding pixels to interpolate and fill the empty area.

Think back toOpenGL texture mapping, sticking the image to a relatively large area will produce the effect of stretching, sticking to a relatively small area will produce the effect of extrusion, which is achieved by using the Bilinear interpolation algorithm of OpenGL.

For those who do not know about texture mapping, you can take the following steps: Android OpenGL ES Systematic learning tutorial

So, when we select an area of the image to move, OpenGL texture maps will squeeze in the direction of the move and stretch in the opposite direction, which can achieve the deformation effect of human body parts.

OpenGL implements P graph function

According to the principle discussed in the previous section, we regard the selected image area as a circle. The area outside the circle is not deformed (not affected), and the pixels in the area inside the circle move more closely to the center of the circle.

As shown in the figure above, BC represents the vector of offset direction and offset degree. All pixels in the circle are offset to a certain extent according to the direction of vector BC. The intensity of pixel offset is related to the distance between pixels and the center of the circle.

Recall from the texture mapping article, we only mapped the image to a grid (2 triangles), so we can only deform the whole image, not a small specific area such as a face.

By analogy, we need more grids at this time. When the grids are dense enough, they can cover the whole area of the whole picture. At this time, we can realize the deformation of any region by adjusting the grids, so as to manually achieve the effect of thin face, long legs, thin waist, big eyes, breast enlargement and so on.

In fact, the purpose of generating more meshes is to control the deformation of images in a small area of meshes, that is, the deformation of images in a certain area of meshes does not affect the images outside this area.

Therefore, the remaining problem is to generate many meshes, and then control the offset of the mesh nodes to achieve the p-graph function through simple texture mapping.

Generate mesh and corresponding vertex coordinates and texture coordinates:

/** ** @param verticalNum Number of vertical grids * @param horizonNum Number of horizontal grids * @param ppVertices vertex coordinates * @param ppTexCoor texture coordinates */
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.0 f / horizonNum;
	float dT = 1.0 f / 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));
}
Copy the code

The offset of grid nodes is controlled within the circle, and the area outside the circle is not deformed (not affected). The pixels in the area inside the circle move more closely to the center of the circle.

//prePoint center, 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;

	// Convert to image coordinates
	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);
		// Check if it is within the circle
		if (r < imgRadius)
		{
            // The offset increases as you get closer to the center of the circle
			float alpha = 1.0 f - r / imgRadius;
			// Make it smooth
			alpha = smoothstep(0.f.1.f, alpha);

			// Move direction
			PointF dVec = (imgCurPoint - imgPrePoint) * pow(alpha, 2.0 f); 
            // Multiply by a coefficient, otherwise the offset is too large
			dVec = dVec * 0.08 f;
			// Move the grid node
			PointF newImgTexPoint = imgTexPoint + dVec;
			/ / normalization
			newImgTexPoint = newImgTexPoint / imgSize;

			// Update corresponding texture coordinates and vertex coordinates
			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; }}}Copy the code

Texture mapping uses the Shader, a regular texture sampler.

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"
        "}";
Copy the code

Recommended reference items: github.com/githubhaoha…