美文网首页
使用C++将tga格式的图片读入内存

使用C++将tga格式的图片读入内存

作者: an0nym0us | 来源:发表于2017-07-09 15:02 被阅读282次

    最近在用blackmagic design (BMD) 的相机做开发,用MediaExpress保存的图片是tga格式的。Matlab和OpenCV都不支持。

    搜了一下,stackoverflow上有个帖子[1]贴了段代码:

    #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());
    
    }
    

    这段功能完整的代码可以直接运行,代码写的也通畅。

    但是我将从BMD相机得到tga作为输入,代码却抛出异常:"Invalid File Format. Required: 24 or 32 Bit TGA File.

    循着源代码排查了一下,在tga的格式检查步骤中,代码认为给出了我输入的tga格式的图片不符合它的要求。

    这就有点奇怪,于是查一下tga的标准的一个实现描述[2], 发现stack overflow给出的代码是符合这个tga的实现的。

    既然如此,那可能是我的TGA文件有点不一样。于是只好用二进制的方式打开我的tga图片。
    发现第一个字节就是0xe,跟标准给出的0x0不一样。另外,header的长度为32个字节,不是18个字节。
    这两个地方改了一下,tga文件就可以被读入内存了。

    能跑的Tga类如下:

    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[32] = {0};
        std::vector<std::uint8_t> ImageData;
        static std::uint8_t DeCompressed[12] = {0xe, 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;
    }
    

    另外,如果要把内存里面的图片信息放进OpenCV里面,用下面的代码就可以:

    Tga tgaImg = Tga("文件的路径,比如/home/userA/tmp/test.tga");
            
    Mat img(tgaImg.GetHeight(), tgaImg.GetWidth(), CV_8UC4);
    
    memcpy(img.data, tgaImg.GetPixels().data(), tgaImg.GetHeight() * tgaImg.GetWidth() * 4);
    

    [1]https://stackoverflow.com/questions/20595340/loading-a-tga-bmp-file-in-c-opengl
    [2]http://www.paulbourke.net/dataformats/tga/

    相关文章

      网友评论

          本文标题:使用C++将tga格式的图片读入内存

          本文链接:https://www.haomeiwen.com/subject/dqftcxtx.html