Bitmap/C++: Difference between revisions

From Rosetta Code
Content added Content deleted
(moved from Basic bitmap storage)
 
m (Fixed syntax highlighting.)
 
(2 intermediate revisions by 2 users not shown)
Line 1: Line 1:
{{collection|Basic bitmap storage}}
{{collection|Basic bitmap storage}}


<lang cpp>#include <cstddef>
<syntaxhighlight lang="cpp">#include <cstddef>
#include <stdexcept>


// CBitmap as found below is a class that represents bitmap images
// This class does not implement a copy constructor, nor
// in main memory, alternatively the images could be stored in
// does it override the = operatior. If an instance of this object
// memory in a graphics system (OpenGL, DirectX, ...), but this
// needs to survive outside the scope it was created in, allocate it
// would not be generic, nor simple.
// on the heap (e.g. pBitmap = new CBitmap(x,y) ), and pass the pointer
// around.


class CBitmap
class CBitmap
{
{
private:
// symbolic constants for the red/green/blue indices. The explicit values
enum { red_index, green_index, blue_index, num_channels };
// are given for clarity, because the program relies on them; omitting them
// would not change the values of the enumerators
enum { red_index = 0, green_index = 1, blue_index = 2, num_channels = 3 };
protected:
// This pointer will point to the region of memory that will hold the
// image data.
char* m_pImageData;


private:
// The "pitch" is the amount of memory a single horizontal line of an
char* m_pImageData;
// image takes in memory. This is NOT necessarily equal to the width
unsigned int m_width;
// of the image, and certainly not in our case, because each pixel
// takes up three bytes.
unsigned int m_pitch;

// The height will correspond to the number of rows in the image.
unsigned int m_height;
unsigned int m_height;


public:
public:
// Specify the width and height in our constructor.
// If allocation fails, "new" will throw a std::bad_alloc exception.
// For automatic variables, the result is that all accesses to the
// class' members are skipped, by leaving the scope where the object
// is created, and visible.
// For dynamic variables, created on the heap, this will not result
// in a memory leak, because the memory allocated for the CBitmap
// is freed as a result of the exception in this case. It is the
// responsability of the client not to access the class' members
// after such a failed allocation.
// The result of this is that the m_pImageData member variable
// will never be 0 in a legal access through the class' member
// functions, and thus we can be certain of the invariant (m_pImageData != 0).
CBitmap(unsigned int width, unsigned int height):
CBitmap(unsigned int width, unsigned int height):
// if allocation fails, "new" will throw a std::bad_alloc exception.
m_pImageData(new char[num_channels * width * height]),
m_width(width),
// This exception propagates out of the constructor and causes the
// object to be disposed of (without even running the destructor).
// Since this pointer will never again be changed during the lifetime
// of objects of this class, this means it will never be NULL in any
// object of this class. Tests for NULL are therefore not needed
// anywhere in this class.
m_pImageData(new char[num_channels*width*height]),

// Since we use num_channels bytes of memory for each pixel,
// then our pitch is our width * num_channels.
m_pitch(width * num_channels),

// Save our height off so we can perform sanity checks
// when SetPixel() and GetPixel() are called.
m_height(height)
m_height(height)
{
{
}

// The presence of this copy constructor enables pass-by-value,
// which is strongly discouraged, due to large amount of work
// involved in copying. Use pass-by-reference to avoid the copy.
CBitmap(CBitmap const &original):
m_pImageData(new char[num_channels * original.m_width * original.m_height]),
m_width(original.m_width),
m_height(original.m_height)
{
CopyImageDataFrom(original.m_pImageData);
}
}


Line 56: Line 55:
}
}


public:
// Using references because handling image data with function calls
// An assignment operator is defined with copy-semantics. When an
// on a per-pixel basis is slow enough. No need to make copies of all
// allocation error occurs, an exception is thrown (which should
// the arguments.
// be caught by the client, and the object destructed) and the
bool SetPixel(const unsigned int& x, const unsigned int& y, char& R, char& G, char& B)
// original data is preserved, to satisfy the invariant (m_pImageData != 0).

// post-condition: this bitmap becomes a uniform copy of the original.
// exception: failed allocation will cause the image data to be unchanged.
CBitmap& operator=(CBitmap const &original)
{
{
// Trying to access a pixel outside the image data's dimensions
if ( this == &original )
// may cause an access violations on architectures with memory
{
// protection. ( Such as x86 and PPC.) On systems without
return *this;
// memory protection, or where it's disabled, or in situations
}
// where the program happens to have legal access to the
// calculated memory address, you'll get garbage data at best.
// At worst, you may be returning values that represent
// sensitive data.
if( (y >= m_height) || (x * num_channels >= m_pitch) )
// Indicate that we were not successful.
return false;


// Calculate the start of the pixel in our image buffer, so we
try
// don't perform a multiply and add for each subpixel.
{
char *new_image_data = new char[num_channels * original.m_width * original.m_height];
// Some optimizing compilers will store off this value in
// advance. Some might have a better way of doing things.
delete [] m_pImageData;
// But this is both fast without optimizations enabled, and
m_pImageData = new_image_data;
// easy to debug.
m_width = original.m_width;
unsigned int pixel_index = (y * m_pitch + x) * num_channels;
m_height = original.m_height;
CopyImageDataFrom(original.m_pImageData);
}
catch ( ... )
{
throw std::runtime_error( "assignment failed, original data conserved" );
}


// The red byte sits at the beginning of the pixel.
return *this;
}
R = m_pImageData[pixel_index + red_index];


// The green byte sits at the byte following the red byte.
G = m_pImageData[pixel_index + green_index];


public:
// The blue byte sits at the byte following the green byte.
bool SetPixel(unsigned int x, unsigned int y, char R, char G, char B)
B = m_pImageData[pixel_index + blue_index];
{
if ( ! IsWithinBitmap(x, y) )
{
return false;
}

unsigned int pixel_index = ImageCoordinateToPixelIndex(x, y);
SetColorValueAtIndex( pixel_index, R, G, B );


// Indicate that we were successful.
return true;
return true;
}
}


bool GetPixel(unsigned int x, unsigned int y, char& R, char& G, char& B)
// Using references because handling image data with function calls
// on a per-pixel basis is slow enough already. No need to make copies
// of all of the arguments.
bool GetPixel(const unsigned int& x, const unsigned int& y, char& R, char& G, char& B)
{
{
// As with SetPixel(), trying to access a pixel outside the data's
if ( ! IsWithinBitmap(x, y) )
{
// dimensions will cause problems. However, when you're writing
// data, as opposed to reading it, if memory protections fail or
// aren't available, you can cause memory corruption, which can
// be a particularly difficult type of bug to track down.
if( (y >= m_height) || (x * num_channels >= m_pitch) )
// Indicate that we were not successful.
return false;
return false;
}


// Calculate the start of the pixel in our image buffer, for the same
unsigned int pixel_index = ImageCoordinateToPixelIndex(x, y);
// reasons as in SetPixel()
GetColorValueAtIndex( pixel_index, R, G, B );
unsigned int pixel_index = (y * m_pitch + x) * num_channels;


// The red byte sits at the beginning of the pixel.
return true;
}
R = m_pImageData[pixel_index + red_index];


void Fill(char R, char G, char B)
// The green byte sits at the byte following the red byte.
{
G = m_pImageData[pixel_index + green_index];
for(unsigned int pixel_index = 0;
pixel_index < m_height * m_width * num_channels;
pixel_index += num_channels)
SetColorValueAtIndex( pixel_index, R, G, B );
}


private:
// The blue byte sits at the byte following the green byte.
// An alternative to status flags is the use of exceptions.
B = m_pImageData[pixel_index + blue_index];
bool IsWithinBitmap(unsigned int x, unsigned int y)
{
return y < m_height && x < m_width;
}


unsigned int ImageCoordinateToPixelIndex(unsigned int x, unsigned int y)
// Indicate that we were successful.
return true;
{
return (y * m_width + x) * num_channels;
}
}


bool Fill(const char& R, const char& G, const char& B)
void SetColorValueAtIndex(unsigned int pixel_index, char R, char G, char B)
{
{
m_pImageData[pixel_index + red_index] = R;
// Since we fill the whole image with the same color, we don't care about the actual image dimensions.
m_pImageData[pixel_index + green_index] = G;
// All we need to know is that the pixels are densely packed, and where to stop.
m_pImageData[pixel_index + blue_index] = B;
for(unsigned int pixel_index = 0; pixel_index < m_height * m_pitch; pixel_index += num_channels)
{
}
m_pImageData[pixel_index + red_index] = R;
m_pImageData[pixel_index + green_index] = G;
m_pImageData[pixel_index + blue_index] = B;
}


void GetColorValueAtIndex(unsigned int pixel_index, char R, char G, char B)
return true;
{
R = m_pImageData[pixel_index + red_index];
G = m_pImageData[pixel_index + green_index];
B = m_pImageData[pixel_index + blue_index];
}
}

private:
void CopyImageDataFrom(char *source)
// Prevent the compiler from creating implicit copy constructor and copy assignment
{
// operators. Those defauls would have incorrect semantics for this class.
// An alternative implementation using memcpy would be more efficient
CBitmap(CBitmap const&); // not implemented
CBitmap& operator=(CBitmap const&); // not implemented
// on almost all platforms.
for (unsigned int i = 0; i < num_channels * m_width * m_height; ++i)
};</lang>
m_pImageData[i] = source[i];
}

};</syntaxhighlight>

Latest revision as of 15:55, 1 September 2022

Bitmap/C++ is part of Basic bitmap storage. You may find other members of Basic bitmap storage at Category:Basic bitmap storage.
#include <cstddef>
#include <stdexcept>

// CBitmap as found below is a class that represents bitmap images
// in main memory, alternatively the images could be stored in 
// memory in a graphics system (OpenGL, DirectX, ...), but this
// would not be generic, nor simple.

class CBitmap
{
private:
        enum { red_index, green_index, blue_index, num_channels };

private:
        char* m_pImageData;
        unsigned int m_width;
        unsigned int m_height;

public:
        // If allocation fails, "new" will throw a std::bad_alloc exception.
        // For automatic variables, the result is that all accesses to the
        // class' members are skipped, by leaving the scope where the object
        // is created, and visible.
        // For dynamic variables, created on the heap, this will not result
        // in a memory leak, because the memory allocated for the CBitmap
        // is freed as a result of the exception in this case.  It is the
        // responsability of the client not to access the class' members
        // after such a failed allocation.
        // The result of this is that the m_pImageData member variable
        // will never be 0 in a legal access through the class' member
        // functions, and thus we can be certain of the invariant (m_pImageData != 0).
        CBitmap(unsigned int width, unsigned int height):
                m_pImageData(new char[num_channels * width * height]),
                m_width(width),
                m_height(height)
        {
        }

        // The presence of this copy constructor enables pass-by-value,
        // which is strongly discouraged, due to large amount of work
        // involved in copying.  Use pass-by-reference to avoid the copy.
        CBitmap(CBitmap const &original):
                m_pImageData(new char[num_channels * original.m_width * original.m_height]),
                m_width(original.m_width),
                m_height(original.m_height)
        {
                CopyImageDataFrom(original.m_pImageData);
        }

        ~CBitmap()
        {
                delete [] m_pImageData;
        }

public:
        // An assignment operator is defined with copy-semantics. When an
        // allocation error occurs, an exception is thrown (which should
        // be caught by the client, and the object destructed) and the
        // original data is preserved, to satisfy the invariant (m_pImageData != 0).

        // post-condition: this bitmap becomes a uniform copy of the original.
        // exception: failed allocation will cause the image data to be unchanged.
        CBitmap& operator=(CBitmap const &original)
        {
                if ( this == &original )
                {
                        return *this;
                }

                try
                {
                        char *new_image_data = new char[num_channels * original.m_width * original.m_height];
                        delete [] m_pImageData;
                        m_pImageData = new_image_data;
                        m_width = original.m_width;
                        m_height = original.m_height;
                        CopyImageDataFrom(original.m_pImageData);
                }
                catch ( ... )
                {
                        throw std::runtime_error( "assignment failed, original data conserved" );
                }

                return *this;
        }


public:
        bool SetPixel(unsigned int x, unsigned int y, char R, char G, char B)
        {
                if ( ! IsWithinBitmap(x, y) )
                {
                        return false;
                }

                unsigned int pixel_index = ImageCoordinateToPixelIndex(x, y);
                SetColorValueAtIndex( pixel_index, R, G, B );

                return true;
        }

        bool GetPixel(unsigned int x, unsigned int y, char& R, char& G, char& B)
        {
                if ( ! IsWithinBitmap(x, y) )
                {
                        return false;
                }

                unsigned int pixel_index = ImageCoordinateToPixelIndex(x, y);
                GetColorValueAtIndex( pixel_index, R, G, B );

                return true;
        }

        void Fill(char R, char G, char B)
        {
                for(unsigned int pixel_index = 0;
                                pixel_index < m_height * m_width * num_channels;
                                pixel_index += num_channels)
                        SetColorValueAtIndex( pixel_index, R, G, B );
        }

private:
        // An alternative to status flags is the use of exceptions.
        bool IsWithinBitmap(unsigned int x, unsigned int y)
        {
                return y < m_height && x < m_width;
        }

        unsigned int ImageCoordinateToPixelIndex(unsigned int x, unsigned int y)
        {
                return (y * m_width + x) * num_channels;
        }

        void SetColorValueAtIndex(unsigned int pixel_index, char R, char G, char B)
        {
                m_pImageData[pixel_index + red_index] = R;
                m_pImageData[pixel_index + green_index] = G;
                m_pImageData[pixel_index + blue_index] = B;
        }

        void GetColorValueAtIndex(unsigned int pixel_index, char R, char G, char B)
        {
                R = m_pImageData[pixel_index + red_index];
                G = m_pImageData[pixel_index + green_index];
                B = m_pImageData[pixel_index + blue_index];
        }

        void CopyImageDataFrom(char *source)
        {
                // An alternative implementation using memcpy would be more efficient
                // on almost all platforms.
                for (unsigned int i = 0; i < num_channels * m_width * m_height; ++i)
                        m_pImageData[i] = source[i];
        }

};