Load 24 bit TGA

2019-07-15 12:37发布

问题:

I wrote a TGA loader to load TGA files. It loads and saves 32-bit TGA files just fine but when it comes to loading and saving 24-bit, it messed up.

Example TGA 24-bit file from Photoshop:

My output:

Any idea what is wrong with it? I did the padding the same way as my bitmap loader and it works but the TGA doesn't.. :S The code below can compile and load TGA's just in case anyone is wanting to test it.

#include <iostream>
#include <vector>
#include <stdexcept>
#include <fstream>
#include <cstring>

typedef union RGB
{
    std::uint32_t Color;
    struct
    {
        std::uint8_t B, G, R, A;
    } RGBA;
} *PRGB;

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

    public:
        Tga(const char* FilePath);
        void Save(const char* FilePath);
};

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] * 0xFF + Header[12];
        height = Header[15] * 0xFF + 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] * 0xFF + Header[12];
        height = Header[15] * 0xFF + 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.");
        }

        RGB 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(RGB));

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

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

    hFile.close();
    std::uint8_t* BuffPos = ImageData.data();
    Pixels.resize(width * height);

    //Flip the pixels and store them in my vector..

    for (std::size_t I = 0; I < height; ++I)
    {
        for (std::size_t J = 0; J < width; ++J)
        {
            Pixels[(height - 1 - I) * width + J].RGBA.B = *(BuffPos++);
            Pixels[(height - 1 - I) * width + J].RGBA.G = *(BuffPos++);
            Pixels[(height - 1 - I) * width + J].RGBA.R = *(BuffPos++);
            Pixels[(height - 1 - I) * width + J].RGBA.A = (BitsPerPixel > 24 ? *(BuffPos++) : 0xFF);
        }
        if(BitsPerPixel == 24)
            BuffPos += (-width * 3) & 3;
    }
}

void Tga::Save(const char* FilePath)
{
    std::fstream hFile(FilePath, std::ios::out | std::ios::binary);
    if (!hFile.is_open()) {throw std::invalid_argument("Cannot open file for writing.");}

    std::vector<std::uint8_t> ImageData(size);
    std::uint8_t* BuffPos = ImageData.data();


    //Flip it back to how it was when we loaded it.. 
    for (std::size_t I = 0; I < height; ++I)
    {
        for (std::size_t J = 0; J < width; ++J)
        {                                                                   //Flip The ScanLines/Rows back to normal.
            *(BuffPos++) = Pixels[(height - 1 - I) * width + J].RGBA.B;
            *(BuffPos++) = Pixels[(height - 1 - I) * width + J].RGBA.G;
            *(BuffPos++) = Pixels[(height - 1 - I) * width + J].RGBA.R;

            if (BitsPerPixel > 24)
                *(BuffPos++) = Pixels[(height - 1 - I) * width + J].RGBA.A;
        }
        if(BitsPerPixel == 24)
            BuffPos += (-width * 3) & 3;
    }

    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};

    if (!ImageCompressed)
    {
        hFile.write(reinterpret_cast<char*>(&DeCompressed), sizeof(DeCompressed));
        hFile.put((width & 0xFF));
        hFile.put((width & 0xFF) / 0xFF);
        hFile.put((height & 0xFF));
        hFile.put(((height & 0xFF) / 0xFF));
        hFile.put(BitsPerPixel);
        hFile.put(0x0);
        hFile.write(reinterpret_cast<char*>(ImageData.data()), ImageData.size());
        hFile.close();
    }
    else
    {
        hFile.write(reinterpret_cast<char*>(&IsCompressed), sizeof(IsCompressed));
        hFile.put((width & 0xFF));
        hFile.put((width & 0xFF) / 0xFF);
        hFile.put((height & 0xFF));
        hFile.put(((height & 0xFF) / 0xFF));
        hFile.put(BitsPerPixel);
        hFile.put(0x0);
    }
    hFile.close();
}



int main()
{
}

回答1:

I think the problem you have is that you're assuming a TGA file is padded, and it isn't.

So your buffers are the wrong size, and you index them wrongly. That you do so symmetrically for input and output means that it almost works, but the padding byte ends up in the image, which (as it's out by one byte per line) results in a diagonal stripe up the image, alternating through the colour channels.

You reads will be returning less bytes than you expected, but you're not checking.

(Though 6502 is completely correct about you handling 2-byte fields incorrectly - however this particular image is less than 255 pixels wide/high, so doesn't suffer).



回答2:

Your code has for sure at least a couple of problems:

width  = Header[13] * 0xFF + Header[12];

this is not the correct way to read a two-bytes value... 0xFF is 255 and not 256: the correct way is

width  = (Header[13] << 8) + Header[12];

Your code has also a different problem in the same area when writing:

hFile.put((width & 0xFF));
hFile.put((width & 0xFF) / 0xFF);

the code is wrong (note that for example you only consider the low 8 bits of width). A correct version would be instead

hFile.put(width & 0xFF);
hFile.put((width >> 8) & 0xFF);


标签: c++ tga