Search code examples
c++openglopengl-3stb-image

I have an error loading an image with STB image


my problem is that while loading any type of image with the library STB image I get the next error: BAD PNG SIG, even if It's not even a PNG, all the code worked before I tried making an OBJECT class as I'm learning OpenGL, now the results that I get are the next:

enter image description here

First I get no apparent errors so far apart to the fact that I should be seeing two textures and not only the crate one,

and the other thing that happens if i run the program but with a different texture is that i get a debugBreak:
enter image description here

The codes for the different parts of the texture Generation/Loading are the next:

Texture creating/adding:

TEXTURE tex1("res/textures/container.jpg", FILTER_LINEAR, GL_RGB, GL_TEXTURE_2D);

TEXTURE tex2("res/textures/dog.jpg", FILTER_NEAREST, GL_RGBA, GL_TEXTURE_2D);

box1.AddTextures(tex1);

box1.AddTextures(tex2);

This is the texture class:

class TEXTURE{

private:

    unsigned int m_TextureID;
    std::string m_filepath;
    int m_width, m_height, m_BPP;
    unsigned int m_TextureType;

public:

    TEXTURE(std::string filepath, FILTERING_OPTIONS opt, unsigned int RGB_CHANNEL, unsigned int TEXTURE_TYPE, bool FLIP_IMAGE = 0, bool MIPMAP = 1);
    ~TEXTURE();


    void Bind(unsigned int slot = 0);

    void Unbind();


    inline int GetWidth() const { return m_width; }
    inline int GetHeight() const { return m_height; }

};

And its constructor is the next:

TEXTURE::TEXTURE(std::string filepath, FILTERING_OPTIONS opt, unsigned int RGB_CHANNEL, unsigned int TEXTURE_TYPE, bool FLIP_IMAGE, bool MIPMAP) : m_filepath(filepath), m_TextureType(TEXTURE_TYPE){

    //stbi_set_flip_vertically_on_load(FLIP_IMAGE);
    stbi_set_flip_vertically_on_load(1);
    glCall(glTexParameteri(TEXTURE_TYPE, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT));
    glCall(glTexParameteri(TEXTURE_TYPE, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT));

    float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
    glCall(glTexParameterfv(TEXTURE_TYPE, GL_TEXTURE_BORDER_COLOR, borderColor));


    if (MIPMAP) {

        switch (opt)
        {
        
        case FILTER_NEAREST:
            glCall(glTexParameteri(TEXTURE_TYPE, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR));
            glCall(glTexParameteri(TEXTURE_TYPE, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
            break;
        
        case FILTER_LINEAR:
            glCall(glTexParameteri(TEXTURE_TYPE, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR));
            glCall(glTexParameteri(TEXTURE_TYPE, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
            break;
        
        case BOTH:
            glCall(glTexParameteri(TEXTURE_TYPE, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR));
            glCall(glTexParameteri(TEXTURE_TYPE, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
            break;
        
        default:

            std::cerr << "[Error]: Wrong option parameter" << std::endl;

            return;
            break;
        }

    }
    else {

        switch (opt)
        {

        case FILTER_NEAREST:
            glCall(glTexParameteri(TEXTURE_TYPE, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
            glCall(glTexParameteri(TEXTURE_TYPE, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
            break;

        case FILTER_LINEAR:
            glCall(glTexParameteri(TEXTURE_TYPE, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
            glCall(glTexParameteri(TEXTURE_TYPE, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
            break;

        case BOTH:
            glCall(glTexParameteri(TEXTURE_TYPE, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
            glCall(glTexParameteri(TEXTURE_TYPE, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
            break;

        default:

            std::cerr << "[Error]: Wrong option parameter" << std::endl;

            return;
            break;
        }

    }

    
    unsigned char* data = stbi_load(filepath.c_str(), &m_width, &m_height, &m_BPP, 0);

    glCall(glGenTextures(1, &m_TextureID));

    glCall(glBindTexture(m_TextureType, m_TextureID));

    std::cerr << stbi_failure_reason() << std::endl;

    if (data)
    {
        glCall(glTexImage2D(TEXTURE_TYPE, 0, GL_RGB, m_width, m_height, 0, RGB_CHANNEL, GL_UNSIGNED_BYTE, data));
        glCall(if (MIPMAP) glGenerateMipmap(TEXTURE_TYPE));
    }
    else
    {
        std::cout << "Failed to load texture" << std::endl;
    };
    stbi_image_free(data);



}

This is the texture bind function:

void TEXTURE::Bind(unsigned int slot){

    glCall(glActiveTexture(GL_TEXTURE0 + slot));
    glCall(glBindTexture(m_TextureType, m_TextureID));


}

And the loading is beeing made by the OBJECT class:

This adds the texture to a vector:

void OBJECT::AddTextures(TEXTURE texture){
    textures.push_back(texture);

}

and this loads the OBJECT(and the textures):

void OBJECT::Show(bool INDEXED, callbackFunction preRenderFunction){

    vb.Bind();
    va.Bind();
    shader.Bind();
    shader.Use();

    for (unsigned int i = 0; i < textures.size(); i++) {

        textures[i].Bind(i);

    }

    preRenderFunction(this);

    if (INDEXED) {
        ib.Bind();
        glCall(glDrawElements(GL_TRIANGLES, ib.GetLength() , GL_UNSIGNED_INT, 0));
        
    }else glDrawArrays(GL_TRIANGLES, 0, vb.GetLength());

}

This is the shader bind and use functions

void SHADER::Bind(){
    glCall(glUseProgram(m_ShaderProgramID));

}

void SHADER::Use(){

    glCall(glUseProgram(m_ShaderProgramID));

}

This is the vertex shader:

#version 330 core
layout (location = 0) in vec3 aPos; // the position variable has attribute position 0
layout (location = 1) in vec2 aTexCoord;

out vec3 vertexColor; // specify a color output to the fragment shader
out vec2 TexCoord;


uniform mat4 transform;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
uniform mat4 MVP;

void main()
{
    gl_Position = projection * view * model * transform * vec4(aPos, 1.0); // see how we directly give a vec3 to vec4's constructor
    //gl_Position = MVP* vec4(aPos, 1.0);
    //gl_Position = projection * view * model * vec4(aPos, 1.0);
    TexCoord = aTexCoord;
}

This is the fragment shader:

#version 330 core
out vec4 FragColor;
  
in vec3 vertexColor; // the input variable from the vertex shader (same name and same type)  
in vec2 TexCoord;

uniform sampler2D texture1;
uniform sampler2D texture2;
uniform float blend;

void main()
{
    //FragColor = texture(texture2, vec2(TexCoord.x, TexCoord.y * -1 ));
    FragColor = mix(texture(texture1, TexCoord), texture(texture2, vec2(TexCoord.x, TexCoord.y * -1 )) / 10, blend);
}

That all the code i think its relevant, also, here is the file tree of my project(i'm using Visual Studio btw):

C:.
├───res
│   ├───shaders
│   └───textures
├───src
│   ├───include
│   └───vendor
│       ├───glm
│       │   ├───detail
│       │   ├───ext
│       │   ├───gtc
│       │   ├───gtx
│       │   └───simd
│       ├───imgui
│       └───stb_image
└───x64
    ├───Debug
    │   └───OpenGL-PLSDFMTT.tlog
    └───Release
        └───OpenGL-PLSDFMTT.tlog

I tried using the debugger of VS for watching what was happening, in the case that it didn't crash the data pointer to the image data was empty (the data was empty), and when it had data it crashed, I tried reviewing my logic, but it all seemed to be ok, that's all I tried (all this in a lapse of 4 hours).


Solution

  • Before you include the stbi_image header file in one C or C++ source file to create the implementation, do

    #define STB_IMAGE_IMPLEMENTATION
    

    alternatively, add

    #define STBI_FAILURE_USERMSG //generate user friendly error messages
    

    afterwards

    #include "stb_image.h"
    

    Once the stbi_image implementation has been included, to load an image, do

    unsigned char *pixels = stbi_load(filename, &width, &height, &channels, 0);
    

    but before doing anything else, one should do the mandatory error checking

    if (!pixels) {
        std::cout
        << "unable to load image: "
        << stbi_failure_reason() 
        << "\n";
        //throw;
    }
    

    From now on, one can safely assume that the image has been loaded and the values in the fields pixels, width, height and channels are safe to use.

    Additional checks, e.g. if the dimensions are power of two (important for pixel unpack alignment), the number of channels (pixel format), and so on, are required for a successful (desired) result.


    To set the pixel unpack alignment before uploading the pixel data, do

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1); //default is 4
    

    Furthermore

    TEXTURE tex2("res/textures/dog.jpg", FILTER_NEAREST, GL_RGBA, GL_TEXTURE_2D);
    

    what if the image file does not have an alpha channel (what happens to be the case with jpeg encoded images), then the call to glTexImage2D will fail, because you're passing an invalid format argument (which does not match the pixel data). Therefore, check how many channels your image has and depending on that, use the proper format argument.

    #1 -> GL_RED
    #2 -> GL_RG
    #3 -> GL_RGB
    #4 -> GL_RGBA
    

    It is possible to force stbi to add additional channels, for that you'll have to set the last parameter of stbi_load to the desired number of channels, 0 means to take the number of channels stored in the image file.

    Instead of loading the whole image, one can do

    stbi_info(filename, &width, &height, &channels);
    

    which loads only the header of the image file (most image formats do contain a header with all the relevant information).


    Once the data has been successfully loaded and uploaded to the GPU, its time to bind the textures to specific texture image units.

    The shader has also to be notified, via setting the according uniform variables, e.g.

    //bind tex to unit 0
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(target, tex);
    
    //set the value of the uniform located at sampler_loc to the
    //unit where the texture is bound to, here 0
    glProgramUniform1i(prog, sampler_loc, 0);