我知道我错过了什么,但我不知道是什么。这段代码帮助我将图像保存到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) );
}
答案 0 :(得分:4)
您的代码存在两个直接问题。
首先,结构成员通常在4字节内存地址(SO:structure padding and structure packing)上对齐。这意味着所有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());
- 请注意-1
之后的GetHeight()
- 让它发挥作用。