在C++/OpenGL中加载tga/bmp文件

10

我正在尝试加载一个tga/bmp文件。这样可以正常工作,但结果看起来像这样:

enter image description here

我正在尝试加载的图像看起来是这样的:

enter image description here

我正在使用的一些代码:

GLuint texture;
const char* filename = "/Users/Admin/Documents/Visual Studio 2013/Projects/OpenGL/OpenGL/image.tga";

unsigned char* data;
data = (unsigned char *) malloc(128 * 128 * 3);
FILE* f;
fopen_s(&f, filename, "rb");
fread(data, 128 * 128 * 3, 1, f);

glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 128, 128, 0, GL_RGB, GL_UNSIGNED_BYTE, data);

有人知道我做错了什么吗?


4
TGA文件是有头部的,对吧?看起来你期望它是一个128x128像素的原始文件。我不知道为什么你会期望它能正常工作。 - Tim Seguine
只是好奇,如果你正在使用C++,那么为什么要使用malloc() - vallentin
我在另一个教程上看到过。我尝试了不同的方法,例如使用malloc(),但结果并没有改变。 - user3075425
补充@Tim所说的:TGA文件不仅仅有一个头部,还支持不同的数据格式(通道数、调色板),图像数据可以开始的偏移量,RLE压缩,不同的起始位置设置等。上述代码永远无法正确加载任何图像格式,它只是假定某种格式的原始转储。 - derhass
2个回答

21

您可以使用以下方法加载位图和tga文件...

#include <vector>
#include <fstream>

#ifdef __APPLE__
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#endif


#ifdef _WIN32
#include <GL/gl.h>
#include <GL/glu.h>
#endif


typedef union PixelInfo
{
    std::uint32_t Colour;
    struct
    {
        std::uint8_t B, G, R, A;
    };
} *PPixelInfo;


class BMP
{
private:
    std::uint32_t width, height;
    std::uint16_t BitsPerPixel;
    std::vector<std::uint8_t> Pixels;

public:
    BMP(const char* FilePath);
    std::vector<std::uint8_t> GetPixels() const {return this->Pixels;}
    std::uint32_t GetWidth() const {return this->width;}
    std::uint32_t GetHeight() const {return this->height;}
    bool HasAlphaChannel() {return BitsPerPixel == 32;}
};

BMP::BMP(const char* FilePath)
{
    std::fstream hFile(FilePath, std::ios::in | std::ios::binary);
    if (!hFile.is_open()) throw std::invalid_argument("Error: File Not Found.");

    hFile.seekg(0, std::ios::end);
    std::size_t Length = hFile.tellg();
    hFile.seekg(0, std::ios::beg);
    std::vector<std::uint8_t> FileInfo(Length);
    hFile.read(reinterpret_cast<char*>(FileInfo.data()), 54);

    if(FileInfo[0] != 'B' && FileInfo[1] != 'M')
    {
        hFile.close();
        throw std::invalid_argument("Error: Invalid File Format. Bitmap Required.");
    }

    if (FileInfo[28] != 24 && FileInfo[28] != 32)
    {
        hFile.close();
        throw std::invalid_argument("Error: Invalid File Format. 24 or 32 bit Image Required.");
    }

    BitsPerPixel = FileInfo[28];
    width = FileInfo[18] + (FileInfo[19] << 8);
    height = FileInfo[22] + (FileInfo[23] << 8);
    std::uint32_t PixelsOffset = FileInfo[10] + (FileInfo[11] << 8);
    std::uint32_t size = ((width * BitsPerPixel + 31) / 32) * 4 * height;
    Pixels.resize(size);

    hFile.seekg (PixelsOffset, std::ios::beg);
    hFile.read(reinterpret_cast<char*>(Pixels.data()), size);
    hFile.close();
}

int main()
{
    BMP info = BMP("C:/Users/....../Desktop/SomeBmp.bmp");

    GLuint texture = 0;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, info.HasAlphaChannel() ? GL_RGBA : GL_RGB, info.GetWidth(), info.GetWidth(), 0, info.HasAlphaChannel() ? GL_BGRA : GL_BGR, GL_UNSIGNED_BYTE, info.GetPixels().data());
}
TGA的:
#include <vector>
#include <fstream>

#ifdef __APPLE__
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#endif


#ifdef _WIN32
#include <GL/gl.h>
#include <GL/glu.h>
#endif

typedef union PixelInfo
{
    std::uint32_t Colour;
    struct
    {
        std::uint8_t R, G, B, A;
    };
} *PPixelInfo;

class Tga
{
private:
    std::vector<std::uint8_t> Pixels;
    bool ImageCompressed;
    std::uint32_t width, height, size, BitsPerPixel;

public:
    Tga(const char* FilePath);
    std::vector<std::uint8_t> GetPixels() {return this->Pixels;}
    std::uint32_t GetWidth() const {return this->width;}
    std::uint32_t GetHeight() const {return this->height;}
    bool HasAlphaChannel() {return BitsPerPixel == 32;}
};

Tga::Tga(const char* FilePath)
{
    std::fstream hFile(FilePath, std::ios::in | std::ios::binary);
    if (!hFile.is_open()){throw std::invalid_argument("File Not Found.");}

    std::uint8_t Header[18] = {0};
    std::vector<std::uint8_t> ImageData;
    static std::uint8_t DeCompressed[12] = {0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0};
    static std::uint8_t IsCompressed[12] = {0x0, 0x0, 0xA, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0};

    hFile.read(reinterpret_cast<char*>(&Header), sizeof(Header));

    if (!std::memcmp(DeCompressed, &Header, sizeof(DeCompressed)))
    {
        BitsPerPixel = Header[16];
        width  = Header[13] * 256 + Header[12];
        height = Header[15] * 256 + Header[14];
        size  = ((width * BitsPerPixel + 31) / 32) * 4 * height;

        if ((BitsPerPixel != 24) && (BitsPerPixel != 32))
        {
            hFile.close();
            throw std::invalid_argument("Invalid File Format. Required: 24 or 32 Bit Image.");
        }

        ImageData.resize(size);
        ImageCompressed = false;
        hFile.read(reinterpret_cast<char*>(ImageData.data()), size);
    }
    else if (!std::memcmp(IsCompressed, &Header, sizeof(IsCompressed)))
    {
        BitsPerPixel = Header[16];
        width  = Header[13] * 256 + Header[12];
        height = Header[15] * 256 + Header[14];
        size  = ((width * BitsPerPixel + 31) / 32) * 4 * height;

        if ((BitsPerPixel != 24) && (BitsPerPixel != 32))
        {
            hFile.close();
            throw std::invalid_argument("Invalid File Format. Required: 24 or 32 Bit Image.");
        }

        PixelInfo Pixel = {0};
        int CurrentByte = 0;
        std::size_t CurrentPixel = 0;
        ImageCompressed = true;
        std::uint8_t ChunkHeader = {0};
        int BytesPerPixel = (BitsPerPixel / 8);
        ImageData.resize(width * height * sizeof(PixelInfo));

        do
        {
            hFile.read(reinterpret_cast<char*>(&ChunkHeader), sizeof(ChunkHeader));

            if(ChunkHeader < 128)
            {
                ++ChunkHeader;
                for(int I = 0; I < ChunkHeader; ++I, ++CurrentPixel)
                {
                    hFile.read(reinterpret_cast<char*>(&Pixel), BytesPerPixel);

                    ImageData[CurrentByte++] = Pixel.B;
                    ImageData[CurrentByte++] = Pixel.G;
                    ImageData[CurrentByte++] = Pixel.R;
                    if (BitsPerPixel > 24) ImageData[CurrentByte++] = Pixel.A;
                }
            }
            else
            {
                ChunkHeader -= 127;
                hFile.read(reinterpret_cast<char*>(&Pixel), BytesPerPixel);

                for(int I = 0; I < ChunkHeader; ++I, ++CurrentPixel)
                {
                    ImageData[CurrentByte++] = Pixel.B;
                    ImageData[CurrentByte++] = Pixel.G;
                    ImageData[CurrentByte++] = Pixel.R;
                    if (BitsPerPixel > 24) ImageData[CurrentByte++] = Pixel.A;
                }
            }
        } while(CurrentPixel < (width * height));
    }
    else
    {
        hFile.close();
        throw std::invalid_argument("Invalid File Format. Required: 24 or 32 Bit TGA File.");
    }

    hFile.close();
    this->Pixels = ImageData;
}


int main()
{
    Tga info = Tga("C:/Users/...../Desktop/SomeTGA.tga");

    GLuint texture = 0;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, info.HasAlphaChannel() ? GL_RGBA : GL_RGB, info.GetWidth(), info.GetWidth(), 0, info.HasAlphaChannel() ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, info.GetPixels().data());

}

谢谢你的代码,但是在 RGB Pixel = {0}; 这一行中,RGB 是什么意思?你是不是想用结构体 RGBA?你能否提供整个类文件在 GitHub 上以帮助更多人? - wangdq
@wangdq 已修复。同时添加了标题。 - Brandon
1
TGA 的宽度和高度计算有误。应该乘以 256 而不是 255(0xFF)。 宽度 = Header[13] * 256 + Header[12]; 高度 = Header[15] * 256 + Header[14]; - GambitSunob
如何从BMP的Pixels.data()中获取每个像素的RGB颜色(分别)? - rjalfa
@Rounaq_intel; 你可以这样做:reinterpret_cast<PixelInfo*>(Pixels.data()) 注意:BMP是BGRA格式,而TGA是RGBA格式。 - Brandon
glGenTextures已经过时。 - Nicholas Jela

6
我将这个问题理解为:“如何加载TGA文件?我尝试的方法没有成功。”99%的情况下,正确的答案不会是“复制并粘贴我编写的加载TGA文件的函数”。这是被接受的回答所采取的方法。但如果您想要一个不仅可以工作,而且更易于维护和更容易修复错误的解决方案,则最好使用库。自己编写解决方案是学习的好方法,应该受到鼓励,但通常不是完成任务的最佳或最简单的方法。使用真正的图像库,而不是尝试编写自己的读取函数。您对数据格式的假设是不正确的,因此您得到的数据是无效的。您似乎认为数据是128x128像素的原始数据。http://tgalib.sourceforge.net/是一个用于读取TGA文件的开源库。例如,使用它。

https://github.com/nothings/stb是另一个选择,它有一个更自由(公共领域)的许可证,并且除了加载TGA文件外,还有很多其他OpenGL程序中会用到的东西。

这两个库都比从网上复制粘贴代码更好的选择。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接