0zelot12
0zelot12

Reputation: 13

Use of unique_ptr for class members

I'm implementing a C++ program to read images from a TGA file. I got a struct for the header.

struct TGA_HEADER
{
// length of id string
char    id_length;

// image storage info
char    colour_map_type;
char    image_type;

// colour Map
short   first_entry;
short   num_entries;
char    bits_per_entry;

// image description
short   x_origin;
short   y_origin;
short   width;
short   height;
char    bits_per_pixel;
char    descriptor;
};

The image class looks like this:

class image
{
private:

public:
TGA_HEADER header;
std::unique_ptr<std::vector<char>> pixel_data;

image(const std::string& image_path);   
~image();

static void save_image(const std::string& file_name, image& image);
static void load_image(const std::string& path, const std::unique_ptr<std::vector<char>>& pixel_data, TGA_HEADER& header);
}; 

The constructor for my image class:

image::image(const std::string& image_path) 
: 
pixel_data(new std::vector<char>)
{
load_image(image_path, this->pixel_data, this->header);
}

And my loading class:

void image::load_image(const std::string& path, const std::unique_ptr<std::vector<char>>& pixel_data, TGA_HEADER& header)
{
std::ifstream file_stream(path, std::ios::in | std::ios::binary | std::ios::ate);

if (file_stream.is_open())
{
    file_stream.seekg(0, std::ios::beg);

    int tgaDesc = 0;

    /* read header */
    file_stream.read(&header.id_length, sizeof(header.id_length));
    file_stream.read(&header.colour_map_type, sizeof(header.colour_map_type));
    file_stream.read(&header.image_type, sizeof(header.image_type));

    file_stream.read((char*)(&header.first_entry), sizeof(header.first_entry));
    file_stream.read((char*)(&header.num_entries), sizeof(header.num_entries));
    file_stream.read(&header.bits_per_entry, sizeof(header.bits_per_entry));

    file_stream.read((char*)(&header.x_origin), sizeof(header.x_origin));
    file_stream.read((char*)(&header.y_origin), sizeof(header.y_origin));
    file_stream.read((char*)(&header.width), sizeof(header.width));
    file_stream.read((char*)(&header.height), sizeof(header.height));
    file_stream.read(&header.bits_per_pixel, sizeof(header.bits_per_pixel));
    file_stream.read(&header.descriptor, sizeof(header.descriptor));

    // Skip the ID String
    char* skip = new char[256];
    file_stream.read(skip, header.id_length);

    // Skip the colour map if it doesn't exist
    if (!(tgaDesc & 4))
    {
        int colourMapSize = header.colour_map_type * header.num_entries;
        file_stream.read(skip, colourMapSize);
    }

    delete skip;

    int imageDataSize = header.width * header.height * (header.bits_per_pixel / 8);
    pixel_data->resize(imageDataSize);

    int originalPosition = (int)file_stream.tellg();

    /* read image data */
    file_stream.read(pixel_data->data(), imageDataSize);
}

My first question is, if I'm using the unique_ptr for my pixel_data in a correct way, especially in the constructor, and my second question would be if I initalize the TGA_HEADER object correctly respectively if I have to delete it manually or use smart pointers.

I'm quite new to C++, so feel free to comment on other issues you might come across.

Best regards

Upvotes: 0

Views: 2137

Answers (2)

Francis Cugler
Francis Cugler

Reputation: 7925

Forward

A few years back I was following a set of 3D Graphics video tutorials. The creator of the series is Marek A. Krzeminski, MASc and you can find his website here: www.marekknows.com Within his series under the download tab, there are 2 series that this code refers to, it falls under the Shader Engine and the Zing series. I can not show all parts of the code for one to copy, paste, compile, and run for several reasons. First and foremost pertains to copyright reasons. Marek has previously given me permission to use his code in guiding others to find solutions to their own works provided I give him credit for his code. So the code I'm about to show is not mine, however, the concepts and techniques that I've learned from him should apply into help solving or resolving the issues in regards to this question in particular. The other reason why I can not show all code is that it is a very large project with many parts integrated. What I can do is show you the function for loading in the TGA file and explain how he designed it, how it is integrated with other parts of the project, and how it is being used. Then I'll explain how it applies to your situation, your question.


Here is the TextureFileReader::loadTga() function

void TextureFileReader::loadTga( Texture* pTexture ) {
    if ( nullptr == pTexture ) {
        throw ExceptionHandler( __FUNCTION__ + std::string( " invalid pTexture passed in" ) );
    }

    struct TgaHeader {
        unsigned char   idLength;
        unsigned char   colorMapType;
        unsigned char   imageType;
        unsigned char   colorMapSpecifications[5];
        short           xOrigin;
        short           yOrigin;
        short           imageWidth;
        short           imageHeight;
        unsigned char   pixelDepth;
        unsigned char   imageDescriptor;
    } tgaHeader;

    enum TgaFileType {
        TGA_RGB     = 2,
        TGA_RLE_RGB = 10,
    }; // TgaFileType

    // Error Message Handling
    std::ostringstream strStream;
    strStream << __FUNCTION__ << " ";

    // Open File For Reading
    m_fileStream.open( m_strFilenameWithPath, std::ios_base::in | std::ios_base::binary );
    if ( !m_fileStream.is_open() ) {
        strStream << "can not open file for reading";
        throwError( strStream );
    }

    // Get TGA File Header
    if ( !m_fileStream.read( reinterpret_cast<char*>( &tgaHeader ), sizeof( tgaHeader ) ) ) {
        strStream << "error reading header";
        throwError( strStream );
    }

    // This TGA File Loader Can Only Load Uncompressed Or Compressed True-Color Images
    if ( (tgaHeader.imageType != TGA_RGB ) && (tgaHeader.imageType != TGA_RLE_RGB ) ) {
        strStream << "TGA loader only supports loading RGB{" << TGA_RGB << "} and RLE_RGB{" << TGA_RLE_RGB
            << "} encoded files. This file contains pixels encoded in an unsupported type{" << tgaHeader.imageType << "}";
        throwError( strStream );
    }

    // Convert Bits Per Pixel To Bytes Per Pixel
    unsigned uBytesPerPixel = tgaHeader.pixelDepth / 8;
    if ( (uBytesPerPixel != 3) && (uBytesPerPixel != 4) ) {
        strStream << "TGA loader only supports 24bpp or 32bpp images. This image uses " << tgaHeader.pixelDepth << " bits per pixel";
        throwError( strStream );
    }

    // Make Room For All Pixel Data
    if ( 0 == tgaHeader.imageWidth || 0 == tgaHeader.imageHeight ) {
        strStream << "invalid image size (" << tgaHeader.imageWidth << "," << tgaHeader.imageHeight << ")";
        throwError( strStream );
    }
    unsigned uTotalNumBytes = tgaHeader.imageWidth * tgaHeader.imageHeight * uBytesPerPixel;
    pTexture->vPixelData.resize( uTotalNumBytes );

    // Move Read Pointer To Beginning Of Image Data
    if ( tgaHeader.idLength > 0 ) {
        m_fileStream.ignore( tgaHeader.idLength );
    }

    // Used To Get And Flip Pixels Data
    std::vector<unsigned char> vTempPixel( uBytesPerPixel, 0 );

    if ( tgaHeader.imageType == TGA_RLE_RGB ) {
        // TGA Data Is Compressed

        // All Error Messages The Same If Error Occurs Below
        strStream << "file is corrupted, missing pixel data";

        unsigned char ucRepetitionCounter = 0;

        unsigned uTotalNumberPixels = tgaHeader.imageWidth * tgaHeader.imageHeight;
        unsigned uCurrentPixel = 0;
        while( uCurrentPixel < uTotalNumberPixels ) {
            // Get Repetition Count Value
            if ( !m_fileStream.read( reinterpret_cast<char*>( &ucRepetitionCounter ), sizeof( unsigned char ) ) ) {
                throwError( strStream );
            }

            if ( ucRepetitionCounter < 128 ) {
                // Raw Packet. Counter Indicates How Many Different Pixels Need To Be Read
                ++ucRepetitionCounter;

                // Get Pixel Values
                if ( !m_fileStream.read( reinterpret_cast<char*>( &pTexture->vPixelData[uCurrentPixel * uBytesPerPixel] ), uBytesPerPixel * ucRepetitionCounter ) ) {
                    throwError( strStream );
                }
            } else {
                // Run-Length Packet. Counter Indicates How Many Times The Text Pixel Needs To Repeat
                ucRepetitionCounter -= 127;

                // Get Pixel Value
                if ( !m_fileStream.read( reinterpret_cast<char*>( &vTempPixel[0] ), uBytesPerPixel ) ) {
                    throwError( strStream );
                }
                // Save Pixel Multiple Times
                for ( unsigned int u = uCurrentPixel; u < ( uCurrentPixel + ucRepetitionCounter ); ++u ) {
                    memcpy( &pTexture->vPixelData[u * uBytesPerPixel], &vTempPixel[0], uBytesPerPixel );
                }
            }

            // Increment Counter
            uCurrentPixel += ucRepetitionCounter;
        }
    } else {
        // TGA Data Is Uncompressed
        // Get Pixel Data
        if ( !m_fileStream.read( reinterpret_cast<char*>( &pTexture->vPixelData[0] ), pTexture->vPixelData.size() ) ) {
            strStream << "file is corrupted, missing pixel data";
            throwError( strStream );
        }
    }
    m_fileStream.close();

    // Convert All Pixel Data from BGR To RGB
    unsigned char ucTemp;
    for ( unsigned int u = 0; u < uTotalNumBytes; u += uBytesPerPixel ) {
        ucTemp                      = pTexture->vPixelData[u];      // Save Blue Color
        pTexture->vPixelData[u]     = pTexture->vPixelData[u + 2];  // Set Red Color
        pTexture->vPixelData[u + 2] = ucTemp;                       // Set Blue Color
    }

    // Flip Image Horizontally
    if ( tgaHeader.imageDescriptor & 0x10 ) {
        short sHalfWidth = tgaHeader.imageWidth >> 1;
        for ( short h = 0; h < tgaHeader.imageHeight; ++h ) {
            for ( short w = 0; w < sHalfWidth; ++w ) {
                unsigned uPixelLeft  = uBytesPerPixel * ( h * tgaHeader.imageWidth + w );
                unsigned uPixelRight = uBytesPerPixel * ( h * tgaHeader.imageWidth + tgaHeader.imageWidth - 1 - w );

                memcpy( &vTempPixel[0],                     &pTexture->vPixelData[uPixelLeft],  uBytesPerPixel ); // Store Left Pixel
                memcpy( &pTexture->vPixelData[uPixelLeft],  &pTexture->vPixelData[uPixelRight], uBytesPerPixel ); // Save Right Pixel @ Left
                memcpy( &pTexture->vPixelData[uPixelRight], &vTempPixel[0],                     uBytesPerPixel ); // Save Left Pixel @ Right
            }
        }
    }

    // Flip Vertically
    if ( tgaHeader.imageDescriptor & 0x20 ) {
        short sHalfHeight = tgaHeader.imageHeight >> 1;
        for ( short w = 0; w < tgaHeader.imageWidth; ++w ) {
            for ( short h = 0; h < sHalfHeight; ++h ) {
                unsigned uPixelTop    = uBytesPerPixel * ( w + tgaHeader.imageWidth * h );
                unsigned uPixelBottom = uBytesPerPixel * ( w + tgaHeader.imageWidth * ( tgaHeader.imageHeight - 1 - h ) );

                memcpy( &vTempPixel[0],                      &pTexture->vPixelData[uPixelTop],    uBytesPerPixel ); // Store Top Pixel
                memcpy( &pTexture->vPixelData[uPixelTop],    &pTexture->vPixelData[uPixelBottom], uBytesPerPixel ); // Save Bottom Pixel @ Top
                memcpy( &pTexture->vPixelData[uPixelBottom], &vTempPixel[0],                      uBytesPerPixel ); // Save Top Pixel @ Bottom
            }
        }
    }

    // Store Other Values In Texture
    pTexture->uWidth          = tgaHeader.imageWidth;
    pTexture->uHeight         = tgaHeader.imageHeight;
    pTexture->hasAlphaChannel = ( tgaHeader.pixelDepth == 32 );

}

The function is called by TextureFileReader::getOrCreateTextureInfo()

TextureInfo TextureFileReader::getOrCreateTextureInfo( TextureInfo::FilterQuality filterQuality, bool generateMipMap, bool wrapRepeat ) {
    TextureInfo textureInfo = m_pAssetStorage->getTextureInfo( m_strFilenameWithPath );
    if ( INVALID_UNSIGNED == textureInfo.uTextureId ) {
        // Need To Create The Texture
        Texture texture( filterQuality, generateMipMap, wrapRepeat );

        if ( !loadPng( &texture ) ) {
            loadTga( &texture );
        }
        textureInfo = m_pAssetStorage->add( texture, m_strFilenameWithPath );
    }

    return textureInfo;
} 

The TextureFileReader::loadTga() takes a pointer to a Texture object. This texture object is nothing more than a structure that is declared in the AssetsStorage class's header file. I will not show the AssetsStorage class here, but I will show the Texture declaration:

struct Texture {
    bool        hasAlphaChannel;
    bool        generateMipMap;
    bool        wrapRepeat;
    unsigned    uWidth;
    unsigned    uHeight;
    TextureInfo::FilterQuality filterQuality;
    std::vector<unsigned char> vPixelData;

    Texture( TextureInfo::FilterQuality filterQualityIn, bool generateMipMapIn, bool wrapRepeatIn ) :
        hasAlphaChannel( false ),
        generateMipMap( generateMipMapIn ),
        wrapRepeat( wrapRepeatIn ),
        uWidth( 0 ),
        uHeight( 0 ),
        filterQuality( filterQualityIn )
    {}
}; // Texture

Before the TextureFileReader::loadTGA() or TextureFileReader::loadPNG() are called, this function will generate a TextureInfo Object. This is another struct that is declared in "CommonStructs.h" that can be seen here:

struct TextureInfo {
    enum FilterQuality {
        FILTER_NONE = 1,
        FILTER_GOOD,
        FILTER_BETTER,
        FILTER_BEST
    }; // FilterQuality

    unsigned    uTextureId;
    bool        hasTransparency;
    glm::uvec2  size;

    TextureInfo() :
        uTextureId( INVALID_UNSIGNED ),
        hasTransparency( false ),
        size( glm::uvec2( 0, 0 ) )
    {}
};

It will try to retrieve a TextureInfo object from the AssetStorage if one already exists by lookup of its filename as it's id and if so it will return it. If the textureInfo can not be found in the AssetStorage, then it needs to create a Texture and a TextureInfo object.


If a texture doesn't exist and one needs to be created the TextureFileReader::getOrCreateTextureInfo() method will create local stack objects of a TextureInfo and a Texture object. There is no dynamic memory involved with the construction of the TextureInfo nor the Texture objects. The memory for these objects is handled by the AssetStorage class. If the construction of the Texture object is successful then this function will try to call either loadPng() or loadTga() since this engine supports both image file formats. This is where we will pass in the address to the temporary Texture object.

If the opening, reading, and parsing of the file are successful without any exceptions being thrown and all of the image data is valid, control flow will leave the scope of the load methods and return back to the getOrCreateTextureInfo member function. This will then try to store the texture object into the AssetStorage class's member container from its add(/*resource*/) member function if one does not already exist. Either way this member function will then return the textureInfo object that is referenced by the AssetStorage class's add(/*resource*/).


This is how the TextureFileReader and the AssetStorage are used to create a Texture object that is used within the Engine...

In the game.cpp file or application.cpp file within the Game's constructor here is an instance of a texture being created:

TextureFileReader titleTextureFileReader( "Assets/images/titleScreen.png" );
m_titleTextureInfo =  titleTextureFileReader.getOrCreateTextureInfo( TextureInfo::FILTER_NONE, false, false );

The AssetStorage is being used automatically by the TextureFileReader class. There is a pointer to an AssetStorage object where the AssetStorage class is a singleton object. The member variable is not seen within the TextureFileReader's header directly since this is a derived class from a FileHandler base class. There is a good amount of inheritance and polymorphic behavior within this engine!


Now to bring this full circle in regards to your question, if you read through the loadTga() function and look to see how the TgaHeader is being used, this struct only ever exists within the scope of this loadTga() function. Once the file has been read, and the data is stored, we no longer need it. After the data has been stored, then we parse it and manipulate it into a format that we want to support. As for the actual pixel data, you can see that these are clearly stored into the pTexture's vPixelData member which is declared as a std::vector<unsigned char> container. There is no place here that std::shared_ptr<T> or std::unique_ptr<T> is being used...

Now as for memory management of a Texture and TextureInfo object, these are handled by the AssetStorage class...

The AssetStorage class has a typedef for Texture support...

typedef std::unordered_map<std::string, TextureInfo>                    MapTextureInfos;

And from this typedef it has the following member variable:

MapTextureInfos m_textureInfos;

This class also has several methods for handling textures:

TextureInfo getTextureInfo( const std::string& strFilename ) const;
TextureInfo add( const Texture& texture, const std::string& strFilename );
bool        removeTextureInfo( const std::string& strFilename );
bool        removeTextureInfo( unsigned uTextureId );
void        showLoadedTextureInfo() const;

Here I will show the AssetStorage::add(/*resource*/) method that pertains to Textures, there are versions of this function for adding Audio, Fonts, Sprites, 2D & 3D Rednerables, etc...

TextureInfo AssetStorage::add( const Texture& texture, const std::string& strFilename ) {
    if ( INVALID_UNSIGNED != getTextureInfo( strFilename ).uTextureId ) {
        std::ostringstream strStream;
        strStream << __FUNCTION__ << " can not store " << strFilename << " multiple times";
        throw ExceptionHandler( strStream );
    }

    TextureInfo textureInfo;
    textureInfo.hasTransparency = texture.hasAlphaChannel;
    textureInfo.size = glm::uvec2( texture.uWidth, texture.uHeight );

    glGetError(); // Clear Errors

    glGenTextures( 1, &textureInfo.uTextureId );

    GLenum err = glGetError();
    if ( err != GL_NO_ERROR ) {
        std::ostringstream strStream;
        strStream << __FUNCTION__ << " failed glGenTextures with error code 0x" << std::hex << err;
        throw ExceptionHandler( strStream );
    }

    glBindTexture( GL_TEXTURE_2D, textureInfo.uTextureId );

    // Wrap Textures
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, ( texture.wrapRepeat ? GL_REPEAT : GL_CLAMP_TO_EDGE ) );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, ( texture.wrapRepeat ? GL_REPEAT : GL_CLAMP_TO_EDGE ) );

    const glm::uvec2& openglVersion = s_pSettings->getOpenglVersion();

    if ( texture.generateMipMap ) {
        switch ( texture.filterQuality ) {
            case TextureInfo::FILTER_NONE : {
                glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
                glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST );
                break;
            }
            case TextureInfo::FILTER_GOOD: {
                glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
                glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR );
                break;
            }
            case TextureInfo::FILTER_BEST: {
                glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
                glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
                break;
            }
            default: {
                glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
                glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST );
            }
        } // switch

        if ( openglVersion.x < 3 ) {
            // In OpenGL v3 GL_GENERATE_MIPMAP Is Deprecated, And In 3.1+ It Was Removed
            // So For Those Versions We Use glGenerateMipmap Below
            static const unsigned int GL_GENERATE_MIPMAP = 0x8191;
            glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE );
        }
    } else {
        // No MipMaps
        switch ( texture.filterQuality ) {
            case TextureInfo::FILTER_NONE:
            case TextureInfo::FILTER_GOOD: {
                glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
                glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
                break;
            }
            default: {
                glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
                glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
            }
        } // switch
    }

    // Load Texture Into Video Memory
    glPixelStorei( GL_UNPACK_ALIGNMENT, texture.hasAlphaChannel ? 4 : 1 );
    glTexImage2D( GL_TEXTURE_2D,
        0,
        ( texture.hasAlphaChannel ? GL_RGBA8 : GL_RGB8 ),
        texture.uWidth,
        texture.uHeight,
        0,
        ( texture.hasAlphaChannel ? GL_RGBA : GL_RGB ),
        GL_UNSIGNED_BYTE,
        &texture.vPixelData[0] );

    if ( texture.generateMipMap && openglVersion.x >= 3 ) {
        glGenerateMipmaps( GL_TEXTURE_2D );
    }

    // Store textureId
    BlockThread blockThread( s_criticalSection );
    m_textureInfos.insert( MapTextureInfos::value_type( strFilename, textureInfo ) );

    if ( s_pSettings->isDebugLoggingEnabled( Settings::DEBUG_MEMORY ) ) {
        Logger::log( std::string( "Created " ) + strFilename );
    }

    return textureInfo;
} // add

As for the instance of this AssetStorage class that is a member of the FileHandler class which is a base class to TextureFileReader it is declared as a:

static AssetStorage* m_pAssetStorage;

and is initialized by the FileHandler's constructor as such:

if ( bSaveExceptionInLog && nullptr == m_pAssetStorage ) {
    m_pAssetStorage = AssetStorage::get();
}

As you can see this Engine has many integrated parts, and there is no "manual" dynamic memory being used, nor any smart pointers within this context for image files directly. All of this is handled with the dynamic memory of the containers that are used. Now, there are other parts of this engine where some objects are shared_ptr and others are unique_ptr objects where it depends on the lifetime of the object and the ownership of the object. The reason you do not see any use of a shared_ptr or unique_ptr in this context is due to the fact of how the overall engine was designed. Here, the AssetStorage object that is used by the Game class within the TextureFileReader is a referenced object to the Engine's class's member variable:

std::unique_ptr<AssetStorage>  m_pAssetStorage;

The Engine class resides in a library and the Game class resides in the main application project. The Game class is inherited from the Engine class.

Here the Engine is responsible for the lifetime and owns all of the unique pointers to all of the singleton objects such as the ShaderManager, AudioManager, BatchManager, FontManager and the AnimationManager. All of these objects are created within the Engine's constructor which is called when the Game class or Application class which inherits from it is called. They all have a lifetime as that of the Engine class or until they are explicitly released. Now as for shared_ptr objects these would be found within the AudioManager, ShaderManger, AnimationManager, FontManger etc. as these would be shared resources that can be used by multiple objects.


This wasn't just to answer this question directly about the use of unique_ptr or the implementation of writing a texture or image loader, but also as an illustration into the thought and design process of such a minimally complex and robust, yet flexible, dynamic, and generically integrated system.

If you would like to know more about this code, it's design structure and the planning and designing methods that are used within the engineering of this software I would highly suggest visiting Marek's website. Albeit his content is not free, it is not real expensive either. The knowledge that I've gained over the years from purchasing and viewing his content I would say is well worth it. I'm not saying that his methods, techniques or implementation designs are the best, but his explanation and demonstration of how and why he does what he does is a great resource and tool to have in your collection. I would say that this is a very good investment.

Upvotes: 0

Miles Budnek
Miles Budnek

Reputation: 30694

There is nothing technically wrong with your use of std::unique_ptr here, but it's totally unnecessary in this situation. std::vector is already essentially a unique pointer to an underlying array. Instead of having your class have a std::unique_ptr<std::vector<char>> member it should likely just have a std::vector<char> member.


I would also question why load_image and save_image are static if they need references/pointers to the class's members (or the object itself, in the case of save_image). It seems like it would make much more sense for them to be non-static member functions. That way they would implicitly have access to the members of the object on which they're called.

There's also a potential memory leak in your load_image function. You dynamically allocate the array pointed to by skip. If any of the read operations between there and when you delete skip were to throw an exception you would leak that array. 256 bytes may also not be enough to read the entire color map. You should probably use a std::vector<char>. Better yet, rather than reading the values you don't want, you could just seek past them:

// Skip the ID String
file_stream.seekg(header.id_length, std::ios::cur);

// Skip the colour map if it doesn't exist
if (!(tgaDesc & 4)) {
    int colourMapSize = header.colour_map_type * header.num_entries;
    file_stream.seekg(colourMapSize, std::ios::cur);
}

Writing that example made me notice that tgaDesc is always 0, so that if block will always run. Did you mean to check header.colour_map_type here? Of course, if there's no color map then header.num_entries should be 0, so I'm not sure the if is even needed at all.

While we're in load_image, you pass the std::ios::ate flag when opening file_stream but then immediately seekg back to the beginning of the file. If you remove the std::ios::ate flag then the stream will initially be positioned at the beginning of the file and the extra seekg could be eliminated.

The way you read the file header is mostly fine. Byte ordering (AKA endianness) could be a possible issue, but both TGA and most modern CPUs use little-endian byte order so you're probably fine unless you want to support some esoteric platform.

Putting that all together would give you an image class that looks something like this:

class image {
public:
    TGA_HEADER header;
    std::vector<char> pixel_data;

    image(const std::string& image_path);
    ~image();

    void save_image(const std::string& file_name);
    void load_image(const std::string& path);
};

image::image(const std::string& image_path)
{
    load_image(image_path);
}

void image::load_image(const std::string& path)
{
    std::ifstream file_stream(path, std::ios::in | std::ios::binary);

    if (file_stream.is_open()) {
        int tgaDesc = 0;

        /* read header */
        file_stream.read(&header.id_length, sizeof(header.id_length));
        file_stream.read(&header.colour_map_type, sizeof(header.colour_map_type));
        file_stream.read(&header.image_type, sizeof(header.image_type));

        file_stream.read((char*)(&header.first_entry), sizeof(header.first_entry));
        file_stream.read((char*)(&header.num_entries), sizeof(header.num_entries));
        file_stream.read(&header.bits_per_entry, sizeof(header.bits_per_entry));

        file_stream.read((char*)(&header.x_origin), sizeof(header.x_origin));
        file_stream.read((char*)(&header.y_origin), sizeof(header.y_origin));
        file_stream.read((char*)(&header.width), sizeof(header.width));
        file_stream.read((char*)(&header.height), sizeof(header.height));
        file_stream.read(&header.bits_per_pixel, sizeof(header.bits_per_pixel));
        file_stream.read(&header.descriptor, sizeof(header.descriptor));

        // Skip the ID String
        file_stream.seekg(header.id_length, std::ios::cur);

        // Skip the colour map if it doesn't exist
        if (!(tgaDesc & 4)) {
            int colourMapSize = header.colour_map_type * header.num_entries;
            file_stream.seekg(colourMapSize, std::ios::cur);
        }


        int imageDataSize = header.width * header.height * (header.bits_per_pixel / 8);
        pixel_data.resize(imageDataSize);

        /* read image data */
        file_stream.read(pixel_data.data(), imageDataSize);
    }
}

Upvotes: 2

Related Questions