2

I know I am missing something but I don't know what. This piece of code helps me for saving images into BMP files. But when I use it, I get a row of black pixels at the top of the image, and the image is shifted to the right. Any ideas ? Thanks !

struct CVImporter::BITMAPFILEHEADER
{
    ushort bfType;
    uint   bfSize;
    uint   bfReserved;
    uint   bfOffBits;
};

struct CVImporter::BITMAPINFOHEADER
{
    uint  biSize;
    int   biWidth;
    int   biHeight;
    short biPlanes;
    short biBitCount;
    uint  biCompression;
    uint  biSizeImage;
    int   biXPelsPerMeter;
    int   biYPelsPerMeter;
    uint  biClrUsed;
    uint  biClrImportant;
};

struct CVImporter::RGBQUAD
{
    uchar rgbBlue;
    uchar rgbGreen;
    uchar rgbRed;
    uchar rgbReserved;
};

struct CVImporter::BITMAPINFO
{
    TBITMAPINFOHEADER bmiHeader;
    TRGBQUAD          bmiColors[256];
};

//-----------------------------------------------------------------------------
//
void
CVImporter::WriteBitmapFileHeader( std::ofstream& stream,
                                   const BITMAPFILEHEADER& header )
{
    WriteToStream( stream, header.bfType );
    WriteToStream( stream, header.bfSize );
    WriteToStream( stream, header.bfReserved );
    WriteToStream( stream, header.bfOffBits );
}

//-----------------------------------------------------------------------------
//
void
CVImporter::WriteBitmapInfoHeader( std::ofstream& stream,
                                   const BITMAPINFOHEADER& infoHeader )
{
    WriteToStream( stream, infoHeader.biSize );
    WriteToStream( stream, infoHeader.biWidth );
    WriteToStream( stream, infoHeader.biHeight );
    WriteToStream( stream, infoHeader.biPlanes );
    WriteToStream( stream, infoHeader.biBitCount );
    WriteToStream( stream, infoHeader.biCompression );
    WriteToStream( stream, infoHeader.biSizeImage );
    WriteToStream( stream, infoHeader.biXPelsPerMeter );
    WriteToStream( stream, infoHeader.biYPelsPerMeter );
    WriteToStream( stream, infoHeader.biClrUsed );
    WriteToStream( stream, infoHeader.biClrImportant );
}

//-----------------------------------------------------------------------------
//
void
CVImporter::WriteBitmapRGBQuad( std::ofstream& stream, const RGBQUAD& quad )
{
    WriteToStream( stream, quad.rgbBlue );
    WriteToStream( stream, quad.rgbGreen );
    WriteToStream( stream, quad.rgbRed );
    WriteToStream( stream, quad.rgbReserved );
}


//-----------------------------------------------------------------------------
//
void
CVImporter::WriteBitmapInfo( std::ofstream& stream,
                             const BITMAPINFO& info )
{
    WriteBitmapInfoHeader( stream, info.bmiHeader );
    for( uint i = 0; i < 256; ++i )
        WriteBitmapRGBQuad( stream, info.bmiColors[i] );
}

//-----------------------------------------------------------------------------
//
void
CVImporter::LoadBitmapFile( const CLString& fileName,
                            CVBitmap::Ptr bm ) throw(IOException)
{
    if( bm.IsNull() ) throw( IOException("Pointer should not be null", CL_ORIGIN) );

    // Verify the extension of the file
    CLString ext("");
    CLFileSystem::GetExtension( fileName, ext );
    if( ext != "bmp" )
        throw( IOException("Bad file extension (should be bmp)", CL_ORIGIN) );

    uint bytesPerPixel {3};
    uint imgDataSize = (bm->GetWidth()*bytesPerPixel)*bm->GetHeight();

    BITMAPFILEHEADER bmFile;
    BITMAPINFO bmInfo;

    bmFile.bfType = (ushort)0x4D42;
    bmFile.bfSize = imgDataSize + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFO);
    bmFile.bfReserved = 0;
    bmFile.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFO);

    bmInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmInfo.bmiHeader.biWidth = (ulong)bm->GetWidth();
    bmInfo.bmiHeader.biHeight = (ulong)bm->GetHeight();
    bmInfo.bmiHeader.biPlanes = 1;
    bmInfo.bmiHeader.biBitCount = (ushort) 8*bytesPerPixel;
    bmInfo.bmiHeader.biCompression = 0; //BI_RGB;
    bmInfo.bmiHeader.biSizeImage = imgDataSize;
    bmInfo.bmiHeader.biXPelsPerMeter = 0;
    bmInfo.bmiHeader.biYPelsPerMeter = 0;
    bmInfo.bmiHeader.biClrUsed = 256;
    bmInfo.bmiHeader.biClrImportant = 256;

    for( uint i = 0; i < 256; ++i )
    {
        bmInfo.bmiColors[i].rgbBlue = i;
        bmInfo.bmiColors[i].rgbGreen = i;
        bmInfo.bmiColors[i].rgbRed = i;
        bmInfo.bmiColors[i].rgbReserved = 0;
    }

    std::ofstream stream( fileName.c_str(), std::ios::binary );
    WriteBitmapFileHeader( stream, bmFile );
    WriteBitmapInfo( stream, bmInfo );

    uint padding = (4 - ((3 * bm->GetWidth()) % 4)) % 4;
    char padding_data[4] {0, 0, 0, 0};

    for( uint i = 0; i < bm->GetHeight(); ++i )
    {
        uchar* data_ptr = bm->GetBits() + ((bm->GetHeight()-i)*bm->GetWidthStep());
        stream.write( reinterpret_cast<char*>(data_ptr), sizeof(char) *
                      bm->GetByteSize() * bm->GetWidth() );

        stream.write( padding_data, padding );
    }

    stream.close();
}


//-----------------------------------------------------------------------------
//
template<typename T>
inline void
CVImporter::WriteToStream( std::ofstream& stream, const T& t )
{
    stream.write( reinterpret_cast<const char*>(&t), sizeof(T) );
}

enter image description here

Athanase
  • 933
  • 9
  • 25
  • Nothing jumps out to me. How are the `TBITMAPINFOHEADER` and `TRGBQUAD` referenced in `CVImporter::BITMAPINFO` defined? – Jonathan Potter Aug 05 '13 at 20:41
  • You're writing your header *twice*. You call `WriteBitmapInfoHeader()`, then call `WriteBitmapInfo()` which also calls `WriteBitmapInfoHeader()`. I see the 'file' and 'info' separation, is that intentional? If so, I'll keep looking. (edit: nm, i see the file hdr vs. info hdr. i'll keep looking). – WhozCraig Aug 05 '13 at 20:41
  • @WhozCraig no I don't think the OP is? the "file header" and "bitmap header" are separate things. – Jonathan Potter Aug 05 '13 at 20:42
  • @Athanase I concur, i missed the 'File' part of that call. Its somewhere else. – WhozCraig Aug 05 '13 at 20:48
  • I tried using the sizes of the members of the structures instead of the sizes of the structures itself but it pushes the black pixel row at the end... – Athanase Aug 05 '13 at 20:51
  • __What year__ is this? Use [FreeImage](http://freeimage.sourceforge.net/) to load PNG, JPG, or even [DeVIL](http://openil.sourceforge.net/) [(example)](http://bobobobo.wordpress.com/2009/03/02/how-to-load-a-png-image-in-c/), but ffs don't use .bmp. _As a rule_. – bobobobo Aug 05 '13 at 21:04
  • @bobobobo thanks for the tips but it does not really answer the question. PS: I need something to save raw images (640*480) in C++ without using any compression process: what format do would you recommend ? – Athanase Aug 05 '13 at 21:13
  • 1
    [TGA](http://paulbourke.net/dataformats/tga/) is less stupid (it doesn't have padding, and the images aren't stored upside down), but I still would recommend using an image library for import/export and to (almost never) roll your own. – bobobobo Aug 05 '13 at 21:17
  • __If you're on Windows__ (which I presume you are), you can use [GDI+](https://sites.google.com/site/cgmeltscode/Home/windows/load-image-using-gdi) to load (any type of image) more easily. – bobobobo Aug 05 '13 at 21:18
  • Sorry I did not mention that I am using linux. – Athanase Aug 05 '13 at 21:19
  • 1
    You are using `sizeof` to get the correct size for your headers. Are you aware these structures should be packed? They may be aligned on double words in memory. The black line on top, by the way, is because you are writing from (height..1) instead of (height-1..0). – Jongware Aug 05 '13 at 21:28
  • What do you mean by packed ? And what would be your correction (I always get a black row at the top or at the bottom...) – Athanase Aug 05 '13 at 21:44
  • I'm going to write this up as a Full Answer -- too long for comment, and it addresses *both* issues. – Jongware Aug 05 '13 at 22:05

1 Answers1

4

There are two immediate problems with your code.

First off, structure members are usually aligned on 4-byte memory addresses (SO: structure padding and structure packing). That means that all of a char, short, and int will occupy 4 bytes, and for the first two there are simply a few unused bytes in memory. This is usually a good thing, because memory access -- read and write -- is usually faster when the processor can read from aligned memory. However, if your structure consists of different-sized members, you have to be careful when reading or writing a file. On reading, some of your data may disappear into the 'unused' bytes, and on writing, you will save these unused bytes into your file.

You say you already tried the reported size values instead of sizeof, but that only partially resolves it. The correct size will be written to your file, but it will still be the wrong data -- because you are still writing the padding bytes.

The solution is to tell your compiler you do not want to automatically add padding between your structure members. There are different ways for different compilers, and you don't mention yours, but the SO Question I pointed to above contains a few examples. If all else fails, look it up in the manual for your compiler.

Another strategy is to manually read and write each structure member instead of the structure in its entirety, but that has hardly any advantages in your situation.

This should solve the problem that a few columns of the right are wrapped over to the left. Your BMP reader seems to be quite forgiving as lots of values end up 'wrong', but in the end it still starts displaying the image from the wrong starting position.


Problem #2 is, fortunately, easier. From your code:

for( uint i = 0; i < bm->GetHeight(); ++i )
{
    uchar* data_ptr = bm->GetBits() + ((bm->GetHeight()-i)*bm->GetWidthStep());
    ...

(You forgot to include the function GetWidthStep but I'm guessing it returns the length of a single bitmap line.) You mean to grab a pointer to the start of the last line first, then the line above it, and so on, until line #0. However, you are off by one line!

With i=0, you calculate the pointer as start + (height-i) * width, so it's start + height * width. That points to the 'line' immediately after your image, at height * width! You can see that if you mentally fill in real values for 'height'.

So you are grabbing pointers to lines 1 to height, while you should have used 0 to height-1. Use this instead:

uchar* data_ptr = bm->GetBits() + ((bm->GetHeight()-1-i)*bm->GetWidthStep());

-- note the -1 after GetHeight() -- to get it to work.

Community
  • 1
  • 1
Jongware
  • 22,200
  • 8
  • 54
  • 100
  • Thanks for that useful answer. I added the "-1" already since I understood your comment earlier. But I still have one black row at the top, so I guess this is also a result from the first problem (padding/packing). I am going to look deeper into packing since my first attempt (`__attribute__((__packed__))`) does not work (colors are inverted). – Athanase Aug 05 '13 at 22:47
  • This is really interesting ... using this attribute DOES remove the black line but invert the colors. I am gonna keep looking into this. – Athanase Aug 05 '13 at 22:54
  • What about just `size_t structSize(){ sizeof(bfType)+sizeof(bfSize)+sizeof(bfReserved)+sizeof(bfOffBits);}` in the stuctures since I am writing the members one by one using the templated `WriteToStream` ? – Athanase Aug 05 '13 at 23:09
  • Ah, yes you are. But `sizeof` still could return the wrong size if you don't specify the right packing. Try using the literal values. – Jongware Aug 05 '13 at 23:36
  • By the way, are you sure you checked every occurrence of `sizeof`? – Jongware Aug 06 '13 at 08:15
  • Yes. Ok so it works now but let me explain first. I use `#pragma pack(1)` for the structures and I checked all sizes. It still would not work... so I downloaded another bitmap and it worked. Note that the bitmap I was using at the beginning was a JPEG exported into the bitmap format using GIMP... – Athanase Aug 06 '13 at 08:42