【我的C/C++语言学习进阶之旅】介绍一下NDK开发之C的简单易用图像库stb

一、stb_image 地址

stb 的Github地址为:https://github.com/nothings/stb

在这里插入图片描述
这个库已经有19K的人starred,有6.6k的人fork。非常受欢迎。

而且在LearnOpenGL网站的demo代码里面也是使用这个库来加载图片的。

二、 为什么叫stb库呢?

因为stb作者名字的首字母,作者名叫Sean T. Barrett。这不是出于自大的选择,而是作为对文件名和源函数名称进行命名空间的一种适度理智的方式。

三、stb库的几个库

我们着重关注下下面三个库即可。

四、怎么引入这些库?

单头文件库背后的想法是它们易于分发和部署,因为所有代码都包含在单个文件中。默认情况下,此处的 .h 文件充当它们自己的头文件,即它们声明文件中包含的函数,但实际上不会导致任何代码被编译。

因此,此外,您应该准确选择一个实际实例化代码的 C/C++ 源文件,最好是您不经常编辑的文件。这个文件应该定义一个特定的宏(每个库都有文档记录)来实际启用函数定义。

4.1 引入stb_image

例如,要使用 stb_image,您应该有将 stb_image.hC/C++ 文件添加到工程中,然后在C++文件添加下面的代码

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

通过定义STB_IMAGE_IMPLEMENTATION,预处理器会修改头文件,让其只包含相关的函数定义源码,等于是将这个头文件变为一个 .cpp 文件了。现在只需要在你的程序中包含stb_image.h并编译就可以了

4.2 引入stb_image_write.h

要使用 stb_image_write,您应该有将 stb_image_write.hC/C++ 文件添加到工程中,然后在C++文件添加下面的代码

#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"

4.3 引入stb_image_resize.h

要使用 stb_image_resize,您应该有将 stb_image_resize.hC/C++ 文件添加到工程中,然后在C++文件添加下面的代码

#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "stb_image_resize.h"

4.4 注意事项

上面的几个库的引用方式介绍了,但是千万要注意,别只引用了.h文件,而不引用配套的宏定义

比如使用stb_image库,应该添加下面两行代码

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

但是如果你只添加了.h头文件,而不添加宏定义的话,则会编译失败

#include "stb_image.h"

则会报错 error: undefined reference to 'stbi_load'error: undefined reference to 'stbi_image_free'之类的错误,如下所示:
在这里插入图片描述
上面的错误是我第一次添加stb_image.h库的时候犯的错误。直到我认真读完stbREADME文件才解决这个问题。

在这里插入图片描述

通过定义STB_IMAGE_IMPLEMENTATION,预处理器会修改头文件,让其只包含相关的函数定义源码,等于是将这个头文件变为一个.cpp文件了。现在只需要在你的程序中包含stb_image.h并编译就可以了。(工程中不要放stb_image.c文件,否则会报其他错误)

五、实践一下

5.1 准备工作

我们来测试一下上面三个库
我们准备读取下面这张yangchaoyue.png图片
在这里插入图片描述
这张yangchaoyue.png图片的信息如下:

文件名: yangchaoyue.png [1/1]
图片大小: 589.4KB
修改日期: 2022/06/01 15:59:41
图片信息: 470x834 (PNG,RGBA32)

在这里插入图片描述

5.2 测试思路

然后我们分别执行下面几个步骤:

  1. 读取图片信息
  2. 改变图片尺寸
  3. 重新写入到新的图片

5.3 测试代码

5.3.1 项目结构

如下图所示,我们分别将stb_image.h,stb_image_resize.h,stb_image_write.h三个头文件添加到项目中,然后写一个stb_test.cpp用来测试。
在这里插入图片描述

在这里插入图片描述

5.3.2 测试代码

stb_test.cpp代码如下所示:

#include <iostream>

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "stb_image_resize.h"
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <vector>

using namespace std;

int main() {
    
    
	std::cout << "Hello, STB_Image" << std::endl;

	string inputPath = "./yangchaoyue.png";
	int imageWidth, imageHeight, nrComponents;

	// 加载图片获取宽、高、颜色通道信息
	unsigned char *imagedata = stbi_load(inputPath.c_str(), &imageWidth, &imageHeight, &nrComponents, 0);
    std::cout << "测试 STB_Image 读取到的信息为:imageWidth = "<< imageWidth << 
             ",imageHeight = "<<imageHeight << ", nrComponents = " << nrComponents 
             << std::endl;  

    // 输出的宽和高为原来的一半
	int outputWidth = imageWidth / 2;
	int outputHeight = imageHeight / 2;
    // 申请内存
	auto *outputImageData = (unsigned char *)malloc(outputWidth * outputHeight * nrComponents);
    std::cout << "测试 STBIR_RESIZE, 改变后的图片尺寸为:outputWidth = "<< outputWidth<<", outputHeight = "<< outputHeight << std::endl;
	// 改变图片尺寸
	stbir_resize(imagedata, imageWidth, imageHeight, 0, outputImageData, outputWidth, outputHeight, 0, STBIR_TYPE_UINT8, nrComponents, STBIR_ALPHA_CHANNEL_NONE, 0,
		STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP,
		STBIR_FILTER_BOX, STBIR_FILTER_BOX,
		STBIR_COLORSPACE_SRGB, nullptr
		);

	string outputPath = "./yangchaoyue_output.png";
    std::cout << "测试 STB_WRITE 要写入的地址为: "<< outputPath << std::endl;
	// 写入图片
	stbi_write_png(outputPath.c_str(), outputWidth, outputHeight, nrComponents, outputImageData, 0);

	stbi_image_free(imagedata);
	stbi_image_free(outputImageData);
	return 0;
}

5.3 测试结果

上面代码运行如下所示:
在这里插入图片描述

Hello, STB_Image
测试 STB_Image 读取到的信息为:imageWidth = 470,imageHeight = 834, nrComponents = 4
测试 STBIR_RESIZE, 改变后的图片尺寸为:outputWidth = 235, outputHeight = 417
测试 STB_WRITE 要写入的地址为: ./yangchaoyue_output.png

可以看得出来,我们正常读取到了原图的信息,并将原图尺寸的宽和高改成原来的一半,并输出到新的图片yangchaoyue_output.png

yangchaoyue_output.png 打开如下所示:

在这里插入图片描述

文件名: yangchaoyue_output.png [2/2]
图片大小: 251.5KB
修改日期: 2022/06/01 16:03:47
图片信息: 235x417 (PNG,RGBA32)

在这里插入图片描述

六、API介绍

上面代码运行完毕之后,我们来介绍下用法。

6.1 stb_image

在这里插入图片描述

STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp)
{
    
    
   FILE *f = stbi__fopen(filename, "rb");
   unsigned char *result;
   if (!f) return stbi__errpuc("can't fopen", "Unable to open file");
   result = stbi_load_from_file(f,x,y,comp,req_comp);
   fclose(f);
   return result;
}

STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp)
{
    
    
   unsigned char *result;
   stbi__context s;
   stbi__start_file(&s,f);
   result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp);
   if (result) {
    
    
      // need to 'unget' all the characters in the IO buffer
      fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR);
   }
   return result;
}

我们调用的代码

string inputPath = "./yangchaoyue.png";
int imageWidth, imageHeight, nrComponents;

// 加载图片获取宽、高、颜色通道信息
unsigned char *imagedata = stbi_load(inputPath.c_str(), &imageWidth, &imageHeight, &nrComponents, 0);
std::cout << "测试 STB_Image 读取到的信息为:imageWidth = "<< imageWidth << 
             ",imageHeight = "<<imageHeight << ", nrComponents = " << nrComponents 
             << std::endl;  

我们首先是调用 stbi_load 方法去加载图像数据,并获取相关信息。传入的参数除了图片文件地址,还有宽、高、颜色通道信息的引用。

变量 nrComponents 就代表图片的颜色通道值,通常有如下的情况:

  • 1 : 灰度图
  • 2 : 灰度图加透明度
  • 3 : 红绿蓝 RGB 三色图
  • 4 : 红绿蓝加透明度 RGBA 图

最后一个参数,我们传入了0,实际上就是STBI_default ,它还可以传入下面几个参数。

enum
{
    
    
   STBI_default = 0, // only used for desired_channels

   STBI_grey       = 1,
   STBI_grey_alpha = 2,
   STBI_rgb        = 3,
   STBI_rgb_alpha  = 4
};

在这里插入图片描述

返回的结果就是图片像素数据的指针了。

stbi_load 不仅仅支持 png 格式,把上面例子中的图片改成 jpg 格式后缀的依旧可行。

它支持的所有格式如下:

  • png
  • jpg
  • tga
  • bmp
  • psd
  • gif
  • hdr
  • pic

格式虽多,不过一般用到 png 和 jpg 就好了。

6.2 sbt_image_resize

加载完图片像素数据之后,就可以通过 stbir_resize 方法改变图片的尺寸。
在这里插入图片描述

STBIRDEF int stbir_resize(         const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
                                         void *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
                                   stbir_datatype datatype,
                                   int num_channels, int alpha_channel, int flags,
                                   stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical,
                                   stbir_filter filter_horizontal,  stbir_filter filter_vertical,
                                   stbir_colorspace space, void *alloc_context)
{
    
    
    return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes,
        output_pixels, output_w, output_h, output_stride_in_bytes,
        0,0,1,1,NULL,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical,
        edge_mode_horizontal, edge_mode_vertical, space);
}

stbir_edgestbir_filter 类型的参数,stb_image_resize 提供了多种类型:

在这里插入图片描述

typedef enum
{
    
    
    STBIR_EDGE_CLAMP   = 1,
    STBIR_EDGE_REFLECT = 2,
    STBIR_EDGE_WRAP    = 3,
    STBIR_EDGE_ZERO    = 4,
} stbir_edge;

在这里插入图片描述

typedef enum
{
    
    
    STBIR_FILTER_DEFAULT      = 0,  // use same filter type that easy-to-use API chooses
    STBIR_FILTER_BOX          = 1,  // A trapezoid w/1-pixel wide ramps, same result as box for integer scale ratios
    STBIR_FILTER_TRIANGLE     = 2,  // On upsampling, produces same results as bilinear texture filtering
    STBIR_FILTER_CUBICBSPLINE = 3,  // The cubic b-spline (aka Mitchell-Netrevalli with B=1,C=0), gaussian-esque
    STBIR_FILTER_CATMULLROM   = 4,  // An interpolating cubic spline
    STBIR_FILTER_MITCHELL     = 5,  // Mitchell-Netrevalli filter with B=1/3, C=1/3
} stbir_filter;

6.3 stb_image_write

最后就是调用 stbi_write_png 方法将像素数据写入文件中

在这里插入图片描述

#ifndef STBI_WRITE_NO_STDIO
STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes)
{
    
    
   FILE *f;
   int len;
   unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len);
   if (png == NULL) return 0;

   f = stbiw__fopen(filename, "wb");
   if (!f) {
    
     STBIW_FREE(png); return 0; }
   fwrite(png, 1, len, f);
   fclose(f);
   STBIW_FREE(png);
   return 1;
}
#endif

除此之外,stb_image_write 还提供了

  • stbi_write_jpg 方法来保存 jpg 格式图片。
  • stbi_write_bmp 方法来保存 bmp 格式图片。
  • stbi_write_tga 方法来保存 tga 格式图片。
  • stbi_write_hdr 方法来保存 hdr 格式图片。

根据格式的不同,方法调用的参数也是不一样的。

#ifndef STBI_WRITE_NO_STDIO
STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void  *data, int stride_in_bytes);
STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void  *data);
STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void  *data);
STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data);
STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void  *data, int quality);

在这里插入图片描述

七、总结

stb库还是很简单的就可以使用了。

读者可以通过本项目源代码 https://github.com/ouyangpeng/stb_test 来测试学习。

八、结合OpenGL

8.1 LearnOpenGL官网的例子

可以参考下面的链接的内容

使用纹理之前要做的第一件事是把它们加载到我们的应用中。纹理图像可能被储存为各种各样的格式,每种都有自己的数据结构和排列,所以我们如何才能把这些图像加载到应用中呢?一个解决方案是选一个需要的文件格式,比如.PNG,然后自己写一个图像加载器,把图像转化为字节序列。写自己的图像加载器虽然不难,但仍然挺麻烦的,而且如果要支持更多文件格式呢?你就不得不为每种你希望支持的格式写加载器了。

另一个解决方案也许是一种更好的选择,使用一个支持多种流行格式的图像加载库来为我们解决这个问题。比如说我们要用的stb_image.h库。

stb_image.h

stb_image.hSean Barrett的一个非常流行的单头文件图像加载库,它能够加载大部分流行的文件格式,并且能够很简单得整合到你的工程之中。stb_image.h可以在这里下载。下载这一个头文件,将它以stb_image.h的名字加入你的工程,并另创建一个新的C++文件,输入以下代码:

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

通过定义STB_IMAGE_IMPLEMENTATION,预处理器会修改头文件,让其只包含相关的函数定义源码,等于是将这个头文件变为一个 .cpp 文件了。现在只需要在你的程序中包含stb_image.h并编译就可以了。

下面的教程中,我们会使用一张木箱的图片。要使用stb_image.h加载图片,我们需要使用它的stbi_load函数:

int width, height, nrChannels;
unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);

这个函数首先接受一个图像文件的位置作为输入。接下来它需要三个int作为它的第二、第三和第四个参数,stb_image.h将会用图像的宽度高度颜色通道的个数填充这三个变量。我们之后生成纹理的时候会用到的图像的宽度和高度的。

生成纹理

和之前生成的OpenGL对象一样,纹理也是使用ID引用的。让我们来创建一个:

unsigned int texture;
glGenTextures(1, &texture);

glGenTextures函数首先需要输入生成纹理的数量,然后把它们储存在第二个参数的unsigned int数组中(我们的例子中只是单独的一个unsigned int),就像其他对象一样,我们需要绑定它,让之后任何的纹理指令都可以配置当前绑定的纹理:

glBindTexture(GL_TEXTURE_2D, texture);

现在纹理已经绑定了,我们可以使用前面载入的图片数据生成一个纹理了。纹理可以通过glTexImage2D来生成:

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);

函数很长,参数也不少,所以我们一个一个地讲解:

  • 第一个参数指定了纹理目标(Target)。设置为GL_TEXTURE_2D意味着会生成与当前绑定的纹理对象在同一个目标上的纹理(任何绑定到GL_TEXTURE_1DGL_TEXTURE_3D的纹理不会受到影响)。
  • 第二个参数为纹理指定多级渐远纹理的级别,如果你希望单独手动设置每个多级渐远纹理的级别的话。这里我们填0,也就是基本级别。
  • 第三个参数告诉OpenGL我们希望把纹理储存为何种格式。我们的图像只有RGB值,因此我们也把纹理储存为RGB值。
  • 第四个和第五个参数设置最终的纹理的宽度和高度。我们之前加载图像的时候储存了它们,所以我们使用对应的变量。
  • 下个参数应该总是被设为0(历史遗留的问题)。
  • 第七第八个参数定义了源图的格式和数据类型。我们使用RGB值加载这个图像,并把它们储存为char(byte)数组,我们将会传入对应值。
  • 最后一个参数是真正的图像数据。

当调用glTexImage2D时,当前绑定的纹理对象就会被附加上纹理图像。然而,目前只有基本级别(Base-level)的纹理图像被加载了,如果要使用多级渐远纹理,我们必须手动设置所有不同的图像(不断递增第二个参数)。或者,直接在生成纹理之后调用glGenerateMipmap。这会为当前绑定的纹理自动生成所有需要的多级渐远纹理。

生成了纹理和相应的多级渐远纹理后,释放图像的内存是一个很好的习惯。

stbi_image_free(data);

生成一个纹理的过程应该看起来像这样:

unsigned int texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
// 为当前绑定的纹理对象设置环绕、过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);   
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 加载并生成纹理
int width, height, nrChannels;
unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);
if (data)
{
    
    
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
    glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
    
    
    std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data);

应用纹理

后面的这部分我们会使用glDrawElements绘制[「你好,三角形」](…/04 Hello Triangle/)教程最后一部分的矩形。我们需要告知OpenGL如何采样纹理,所以我们必须使用纹理坐标更新顶点数据:

float vertices[] = {
    
    
//     ---- 位置 ----       ---- 颜色 ----     - 纹理坐标 -
     0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f,   // 右上
     0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f,   // 右下
    -0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f,   // 左下
    -0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f    // 左上
};

由于我们添加了一个额外的顶点属性,我们必须告诉OpenGL我们新的顶点格式:

在这里插入图片描述

glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);

注意,我们同样需要调整前面两个顶点属性的步长参数为8 * sizeof(float)

接着我们需要调整顶点着色器使其能够接受顶点坐标为一个顶点属性,并把坐标传给片段着色器:

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aTexCoord;

out vec3 ourColor;
out vec2 TexCoord;

void main()
{
    
    
    gl_Position = vec4(aPos, 1.0);
    ourColor = aColor;
    TexCoord = aTexCoord;
}

片段着色器应该接下来会把输出变量TexCoord作为输入变量。

片段着色器也应该能访问纹理对象,但是我们怎样能把纹理对象传给片段着色器呢?GLSL有一个供纹理对象使用的内建数据类型,叫做采样器(Sampler),它以纹理类型作为后缀,比如sampler1Dsampler3D,或在我们的例子中的sampler2D。我们可以简单声明一个uniform sampler2D把一个纹理添加到片段着色器中,稍后我们会把纹理赋值给这个uniform。

   #version 330 core
    out vec4 FragColor;

    in vec3 ourColor;
    in vec2 TexCoord;

    uniform sampler2D ourTexture;

    void main()
    {
    
    
        FragColor = texture(ourTexture, TexCoord);
    }

我们使用GLSL内建的texture函数来采样纹理的颜色,它第一个参数是纹理采样器,第二个参数是对应的纹理坐标。texture函数会使用之前设置的纹理参数对相应的颜色值进行采样。这个片段着色器的输出就是纹理的(插值)纹理坐标上的(过滤后的)颜色。

现在只剩下在调用glDrawElements之前绑定纹理了,它会自动把纹理赋值给片段着色器的采样器:

   glBindTexture(GL_TEXTURE_2D, texture);
    glBindVertexArray(VAO);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

如果你跟着这个教程正确地做完了,你会看到下面的图像:

在这里插入图片描述

如果你的矩形是全黑或全白的你可能在哪儿做错了什么。检查你的着色器日志,并尝试对比一下源码

如果你的纹理代码不能正常工作或者显示是全黑,请继续阅读,并一直跟进我们的代码到最后的例子,它是应该能够工作的。在一些驱动中,必须要对每个采样器uniform都附加上纹理单元才可以,这个会在下面介绍。

我们还可以把得到的纹理颜色与顶点颜色混合,来获得更有趣的效果。我们只需把纹理颜色与顶点颜色在片段着色器中相乘来混合二者的颜色:

FragColor = texture(ourTexture, TexCoord) * vec4(ourColor, 1.0);

最终的效果应该是顶点颜色和纹理颜色的混合色:

在这里插入图片描述

我猜你会说我们的箱子喜欢跳70年代的迪斯科。

8.2 工具类

我写了一个用于加载Android指定sdcard路径上贴纸并加载到纹理中。

每次渲染的时候调用updateStickerTexture() 来根据时间更新纹理

部分实现代码如下所示:

//
// Created by OuyangPeng on 2022/5/28.
//
#include "FaceStickerLoader.h"
#include "../utils/TimeUtil.h"

#define STB_IMAGE_IMPLEMENTATION
#include "../include/stb/stb_image.h"

#include "../utils/GLUtils.h"

void FaceStickerLoader::updateStickerTexture() {
    
    
    // 根据时间,来计算当前贴纸
    if (mCurrentTime == 100) {
    
    
        mCurrentTime = GetCurrentTimeMsec();
        LOGD("FaceStickerLoader::updateStickerTexture() set mCurrentTime = %llu , this is %p", mCurrentTime,this)
    }
    unsigned long long nowTime =  GetCurrentTimeMsec();
    long long frameIndex = (int) (( nowTime - mCurrentTime) / mStickerData.duration);

    LOGD("FaceStickerLoader::updateStickerTexture() mCurrentTime = %llu , nowTime = %llu , frameIndex = %lld, duration = %d , mStickerData.stickerLooping = %d ",
         mCurrentTime ,nowTime, frameIndex, mStickerData.duration, mStickerData.stickerLooping)

    if (frameIndex >= mStickerData.frames) {
    
    
        LOGD("FaceStickerLoader::updateStickerTexture() frameIndex is %lld ",frameIndex)
        if (!mStickerData.stickerLooping) {
    
    
            mCurrentTime = 0L;
            mRestoreTexture = mStickerTexture;
            mStickerTexture = Constant::GL_NOT_TEXTURE;
            mFrameIndex = -1;
            return;
        }
        frameIndex = 0;
        mCurrentTime = GetCurrentTimeMsec();
    }
    if (frameIndex < 0) {
    
    
        frameIndex = 0;
    }
    if (mFrameIndex == frameIndex) {
    
    
        return;
    }

    std::string format(mFolderPath + mStickerData.stickerName + "_%03d.png");
    // 计算贴纸完整路径
    char stickerItemNamePath[100];
    // 格式化,并获取最终需要的字符串
    snprintf(stickerItemNamePath, sizeof(stickerItemNamePath), format.c_str(), frameIndex);

    // 使用stbi_image库去加载图片
    int imageWidth, imageHeight, nrComponents;
    unsigned char *imageData = stbi_load(stickerItemNamePath, &imageWidth, &imageHeight, &nrComponents, STBI_default);
    if (imageData) {
    
    
        LOGD("FaceStickerFilter::loadAndBindTexture() Load texture success at path: [%s] , imageWidth = %d , imageHeight = %d, nrComponents = %d",
             stickerItemNamePath, imageWidth, imageHeight, nrComponents)
        GLenum imageFormat;
        if (nrComponents == 1)
            imageFormat = GL_RED;
        else if (nrComponents == 3)
            imageFormat = GL_RGB;
        else if (nrComponents == 4)
            imageFormat = GL_RGBA;

        if (mStickerTexture == Constant::GL_NOT_TEXTURE
            && mRestoreTexture != Constant::GL_NOT_TEXTURE) {
    
    
            mStickerTexture = mRestoreTexture;
        }
        if (mStickerTexture == Constant::GL_NOT_TEXTURE) {
    
    
            mStickerTexture = createTextureAndBindTexture(imageData, imageFormat , imageWidth, imageHeight);
        } else {
    
    
            mStickerTexture = createTextureAndBindTexture(imageData, imageFormat, imageWidth, imageHeight,
                                                          mStickerTexture);
        }
        mRestoreTexture = mStickerTexture;
        mFrameIndex = frameIndex;


        // 释放
        stbi_image_free(imageData);
    } else {
    
    
        LOGD("FaceStickerFilter::loadAndBindTexture() Load texture failed at path: [%s] ", stickerItemNamePath)
        // 释放
        stbi_image_free(imageData);
        mRestoreTexture = mStickerTexture;
        mStickerTexture = Constant::GL_NOT_TEXTURE;
        mFrameIndex = -1;
    }
}

GLuint FaceStickerLoader::createTextureAndBindTexture(unsigned char *imageData, GLint imageFormat , int imageWidth, int imageHeight) {
    
    
    if (imageData) {
    
    
        GLuint m_StickerTextureId;
        glGenTextures(1, &m_StickerTextureId);
        // 将纹理绑定到当前活动单元
        glBindTexture(GL_TEXTURE_2D, m_StickerTextureId);
        //    为当前绑定的纹理对象设置环绕、过滤方式
        //    将纹理包装设置为GL_REPEAT(默认包装方法)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
        //    设置纹理过滤参数
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        //加载 RGBA 格式的图像数据
        glTexImage2D(GL_TEXTURE_2D, 0, imageFormat, imageWidth, imageHeight,
                     0, imageFormat, GL_UNSIGNED_BYTE, imageData);
        glBindTexture(GL_TEXTURE_2D, GL_NONE);
        return m_StickerTextureId;
    }
    return 0;
}


GLuint FaceStickerLoader::createTextureAndBindTexture(unsigned char *imageData, GLint imageFormat,
                                          int imageWidth, int imageHeight,
                                          GLuint m_StickerTextureId) {
    
    
    if (m_StickerTextureId == Constant::GL_NOT_TEXTURE) {
    
    
        m_StickerTextureId = createTextureAndBindTexture(imageData, imageFormat , imageWidth, imageHeight);
    } else {
    
    
        if (imageData) {
    
    
            glActiveTexture(GL_TEXTURE0);
            // 将纹理绑定到当前活动单元
            glBindTexture(GL_TEXTURE_2D, m_StickerTextureId);
            //加载 RGBA 格式的图像数据
            glTexImage2D(GL_TEXTURE_2D, 0, imageFormat, imageWidth, imageHeight,
                         0, imageFormat, GL_UNSIGNED_BYTE, imageData);
            glBindTexture(GL_TEXTURE_2D, GL_NONE);
        }
    }
    return m_StickerTextureId;
}

猜你喜欢

转载自blog.csdn.net/qq446282412/article/details/125083512