[英]Bitmap loader C++
我知道我錯過了什么,但我不知道是什么。 這段代碼幫助我將圖像保存到BMP文件中。 但是當我使用它時,我在圖像頂部得到一排黑色像素,圖像向右移動。 有任何想法嗎 ? 謝謝 !
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) );
}
您的代碼有兩個直接問題。
首先,結構成員通常在4字節存儲器地址上對齊(SO: 結構填充和結構填充 )。 這意味着所有char
, short
和int
將占用4個字節,而前兩個字節在內存中只有幾個未使用的字節。 這通常是一件好事,因為當處理器可以從對齊的內存中讀取時,內存訪問 - 讀取和寫入 - 通常更快。 但是,如果您的結構由不同大小的成員組成,則在讀取或寫入文件時必須小心。 在讀取時,您的一些數據可能會消失在“未使用”的字節中,在寫入時,您會將這些未使用的字節保存到文件中。
你說你已經嘗試過報告的大小值而不是sizeof
,但這只能部分解決它。 正確的大小將寫入您的文件,但它仍然是錯誤的數據 - 因為您仍在編寫填充字節。
解決方案是告訴編譯器您不希望在結構成員之間自動添加填充。 不同的編譯器有不同的方法,你沒有提到你的,但我上面提到的SO問題包含了一些例子。 如果所有其他方法都失敗了,請在編譯器的手冊中查找。
另一個策略是手動讀取和寫入每個結構成員而不是整個結構,但這在您的情況下幾乎沒有任何優勢。
這應該解決了右邊幾列被包裹在左邊的問題。 你的BMP閱讀器似乎很寬容,因為很多值最終都是“錯誤的”,但最終它仍然開始從錯誤的起始位置顯示圖像。
幸運的是,問題#2更容易。 從您的代碼:
for( uint i = 0; i < bm->GetHeight(); ++i )
{
uchar* data_ptr = bm->GetBits() + ((bm->GetHeight()-i)*bm->GetWidthStep());
...
(你忘了包含函數GetWidthStep
但我猜它會返回單個位圖行的長度。)你的意思是首先抓住指向最后一行開頭的指針,然后抓住它上面的行,依此類推,直到第0行。 但是,你走了一條線!
如果i=0
,則將指針計算為start + (height-i) * width
,因此它是start + height * width
。 這指向圖像后面的“線”, height * width
! 你可以看到,如果你在精神上填寫'身高'的實際值。
所以你要抓住第1
行到height
指針,而你應該使用0
到height-1
。 請改用:
uchar* data_ptr = bm->GetBits() + ((bm->GetHeight()-1-i)*bm->GetWidthStep());
- 注意GetHeight()
之后的-1
- 讓它工作。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.