Reputation: 13
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
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
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