对BMP文件进行增高并写字时总是失败

red-fly 2010-11-12 05:56:31
目的:
对BMP图片的底部增加高度,然后在新增加的区域写字,然后再保存成新的BMP图片。
做法:
根据原始的图片数据创建位图句柄,然后把它显示到内存的DC上,在适当的位置写字(TextOut),然后再获取此DC上的图像数据,接着把此数据保存成为新的BMP图片
限制:
由于这些图片的数据是通过解码视频数据得到的,所以没有所谓的BMP文件头等信息
条件:
纯粹的24位RGB数据

先看代码如下:

BOOL Save2BMPFile(char* FileName, char *RGBData, int RGBDataSize, int width, int height)
{
char *pszTmp = NULL;
char *pTmp = NULL;
HDC dc = CreateDC( TEXT("DISPLAY"), NULL, NULL, NULL );
HDC dc2 = CreateCompatibleDC( dc );

int iCountLine = 1;
long lLineHeight = 20;

long lWidthBytes, lNewHeight;
if(width%4 == 0)
lWidthBytes = width * 3;
else
lWidthBytes = (width+(4-width%4)) * 3;
lNewHeight = height+(iCountLine*lLineHeight);

// 创建新大小的图片内存
pszTmp = new char[lWidthBytes * lNewHeight];
memset( pszTmp, 120,lWidthBytes * lNewHeight);
memcpy(pszTmp + (lWidthBytes * iCountLine*lLineHeight),RGBData,lWidthBytes * height);
pTmp = new char[lWidthBytes];
int l;
for ( l = 0; l < lNewHeight / 2; l++ )
{
memcpy( pTmp, pszTmp + lWidthBytes * l, lWidthBytes );
memcpy( pszTmp + lWidthBytes * l, pszTmp + lWidthBytes * (lNewHeight - 1 - l), lWidthBytes );
memcpy( pszTmp + lWidthBytes * (lNewHeight - 1 - l), pTmp, lWidthBytes );
}

HBITMAP hBitmap = CreateBitmap( width, lNewHeight, 1, 24, pszTmp );
SelectObject( dc2, hBitmap );
l = 0;
TextOut( dc2,5,height, "abc", 3 ); // 在新增加的调试上写个

HBITMAP retBitMap = (HBITMAP)GetCurrentObject(dc2,OBJ_BITMAP);
long lGet = GetBitmapBits(retBitMap,lWidthBytes * lNewHeight,pszTmp);
for ( l = 0; l < lNewHeight / 2; l++ )
{
memcpy( pTmp, pszTmp + lWidthBytes * l, lWidthBytes );
memcpy( pszTmp + lWidthBytes * l, pszTmp + lWidthBytes * (lNewHeight - 1 - l), lWidthBytes );
memcpy( pszTmp + lWidthBytes * (lNewHeight - 1 - l), pTmp, lWidthBytes );
}

// 保存为文件
BITMAPFILEHEADER bmfHdr;
BITMAPINFOHEADER bmiHdr;
ZeroMemory( &bmfHdr, sizeof( bmfHdr ) );
ZeroMemory( &bmiHdr, sizeof( bmiHdr ) );

bmiHdr.biSize = sizeof(BITMAPINFOHEADER);
bmiHdr.biWidth = width;
bmiHdr.biHeight = lNewHeight;
bmiHdr.biPlanes = 1;
bmiHdr.biBitCount = 24;
bmiHdr.biCompression = BI_RGB;
bmiHdr.biSizeImage = 0;
bmiHdr.biXPelsPerMeter = 0;
bmiHdr.biYPelsPerMeter = 0;
bmiHdr.biClrUsed = 0;
bmiHdr.biClrImportant = 0;

bmfHdr.bfType = ((WORD) ('M' << 8) | 'B');
bmfHdr.bfSize=(DWORD)(sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER) + lWidthBytes*lNewHeight);
bmfHdr.bfReserved1 = 0;
bmfHdr.bfReserved2 = 0;
bmfHdr.bfOffBits=(DWORD)(sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER));

FILE *fp;
fp = fopen(FileName,"w+b");
if(fp != NULL)
{
fwrite((LPSTR)&bmfHdr, sizeof(BITMAPFILEHEADER), 1, fp);
fwrite((LPSTR)&bmiHdr,sizeof(BITMAPINFOHEADER), 1, fp);

int size = lWidthBytes*lNewHeight;
fwrite( pszTmp, lWidthBytes*lNewHeight,1, fp );
fclose(fp);
return TRUE;
}

return FALSE;
}

此函数的几个参数介绍:
FileName:[in],修改后的BMP图片要保存成的文件名
RGBData:[in],原始的BMP图片数据,24位的,不包括文件头等信息,只是纯粹的颜色数据
RGBDataSize:[in],原始数据的长度,单位为字节
width,height:[in],原始图片的宽度和高度,它们的成绩再乘以3应该和RGBDataSize相等

在测试的时候发现总是不对,之前有试过有文件头的就没有问题(只是在拷贝RGBData数据时把文件头去掉,另外在写图片信息时只是更改了和高度有关的信息),但现在总是不对!
请各位大虾帮助
...全文
173 21 打赏 收藏 转发到动态 举报
写回复
用AI写文章
21 条回复
切换为时间正序
请发表友善的回复…
发表回复
手机写程序 2010-11-18
  • 打赏
  • 举报
回复
[Quote=引用 18 楼 jszj 的回复:]
它们的参数是不一样,但我觉是这些参数所提供的信息都是一样的。
可以确定,位图本身确是一个DIB,24位的纯图像数据。

后来我采取了一个新的策略,就是原始数据我不动,我只是生成新增加的区域,然后把这个区域的图像数据拿出来再加入到原始的图片数据中,然后生成文件头等其它部分的内容,这样就不用管原来的数据了(其前提是,它们的位数都是一样的,要不然,就没有办法合并起来)。这只是一个操作方法,但还是少……
[/Quote]
和你显卡显示的位深度有关吗?你到桌面修改成16,24,32位分别试试.
red-fly 2010-11-18
  • 打赏
  • 举报
回复
嗯,没错。
不过,在这个特定的环境下,我对DIB的理解就是不管以什么样的格式来表现这个图片,都可以通过当前的这些图像数据达到目的。因为这里的图像数据已经是全部的数据了
superarhow 2010-11-17
  • 打赏
  • 举报
回复
位图是不是DIB,并不是由颜色深度决定的.在MSDN上有讲. 一个8位位图,只要有调色板,也可以是DIB,反之,24BIT的位图也可能是DDB.
red-fly 2010-11-17
  • 打赏
  • 举报
回复
它们的参数是不一样,但我觉是这些参数所提供的信息都是一样的。
可以确定,位图本身确是一个DIB,24位的纯图像数据。

后来我采取了一个新的策略,就是原始数据我不动,我只是生成新增加的区域,然后把这个区域的图像数据拿出来再加入到原始的图片数据中,然后生成文件头等其它部分的内容,这样就不用管原来的数据了(其前提是,它们的位数都是一样的,要不然,就没有办法合并起来)。这只是一个操作方法,但还是少不了从DC上获取最终的图像数据这一步,直接采用桌面的就没有办法成功,后来没有办法,只好重新创建一个窗口来代替桌面,结果成功了,唉,觉的还是不可理解
red-fly 2010-11-16
  • 打赏
  • 举报
回复
这个问题总算是基本解决了。方法如下:
1. 创建一个窗口CreateDialog()(要事先在资源中增加一个对话框,这样方便些)
2. 获取其DC
3. 创建兼容的内存DC
4. 创建一个空的BITMAP,但大小合适

5. 选此BITMAP到内存DC中,并保存返回的旧BITMAP句柄
6. 在内存DC上画图、写字
7. 把旧的BITMAP选进内存DC,并保存返回的BITMAP句柄hb

8. 采用GetDIBits()来获取hb上的图像数据(指定24位)

9. 生成BMP文件头,主要是修改高度
10. 写BMP文件头
11. 写第8步获取的图像数据
12. 写原始的BMP图片的图像数据
完成

这样是基本上没啥大问题,但是有一个小问题,就是在第6步中,总是黑白色!其体现在最终生成的BMP图片上
red-fly 2010-11-16
  • 打赏
  • 举报
回复
从msdn上看,CreateDIBSection()和SetDIBits()应该是可以达到同样的效果的。
另外,在获取这些图片信息时,采用GetDIBits()应该可以获取自己想要的格式,但结果还是不对,一片空白
superarhow 2010-11-16
  • 打赏
  • 举报
回复
CreateDIBSection和SetDIBits应该效果不一样.关键问题在于位图本身是否是一个DIB,不是它的色彩深度决定的. CreateDIBSection或者CreateDIBitmap参数很多,但实际上大部份可以传NULL. 一般大家都用它.
fishion 2010-11-15
  • 打赏
  • 举报
回复
GetLastError是多少了
或者CreateBitmap创建一个空位图
再用SetBitmapBits来设置数据
superarhow 2010-11-15
  • 打赏
  • 举报
回复
[Quote=引用 9 楼 jszj 的回复:]
HBITMAP hBitmap = CreateBitmap( width, lNewHeight, 1, 24, pszTmp );
的返回是有值的,而SelectObject( dc2, hBitmap );的返回值是0
[/Quote]

SelectObject返回NULL是失败的.即使是空DC它也应该返回一个黑白图片的对象.应该就是这个问题.你用CreateBitmap虽然指出创建真彩色位图,但它不一定与你的dc2是兼容的.
你应该使用CreateCompatibleBitmap创建位图才能Select成功,不过这样位数就不一定是你想要的了.你也可以使用CreateDIBSection来创建一个设备无关位图.DIB一般都是能Select成功的.
red-fly 2010-11-15
  • 打赏
  • 举报
回复
HBITMAP hBitmap = CreateBitmap( width, lNewHeight, 1, 24, pszTmp );
的返回是有值的,而SelectObject( dc2, hBitmap );的返回值是0
superarhow 2010-11-15
  • 打赏
  • 举报
回复
LZ检查一下:
HBITMAP hBitmap = CreateBitmap( width, lNewHeight, 1, 24, pszTmp );
SelectObject( dc2, hBitmap ); <<<<---- 这句成功了么?返回句柄是多少?
red-fly 2010-11-15
  • 打赏
  • 举报
回复
生成新的像素矩阵没有问题,但是在新增加的区域上面要写字,这是问题的关键,那如何写字?

当然可以使用字库来自己写字,但那有个问题,一个是麻烦,一个是要找字库,如果有多种语言的话,那非常麻烦了,所以为了简单,就使用DC来写字,然后把此DC上的图片再重新保存为BMP文件,这就是目的。
现在的问题了,DC上是可以写字的,确实可以看到,但就是无法保存为图片,即
long lGet = GetBitmapBits(retBitMap,lWidthBytes * lNewHeight,pszTmp);
的结果总是不对
leodream 2010-11-15
  • 打赏
  • 举报
回复
这个很容易实现啊,虽然你没有头文件,但我想你总能得到每个像素RGB的大小吧?那么最土的方法你将原图片的所有像素值读出,再加上新加部分的像素,重新组成一个像素矩阵,再用这些像素生成一幅新位图,保存、画到DC上就OK了。
red-fly 2010-11-15
  • 打赏
  • 举报
回复
晕,上面的图片样子的中间空格竟然全被删除了!
red-fly 2010-11-15
  • 打赏
  • 举报
回复
1. 上下对调2次,是因为图片的显示和BMP文件的保存是相反的顺序,开始的对调是为了正确地显示,在完成画图之后,要再对调一次用来保存
2. 没有调用DeleteObject()或者相关的删除操作,是因为这里的代码是一个demo,只是要调试出来那种效果,弄好了,在正式的程序里是要加上相关的删除操作的
3. 这里已经确定是24位的,所以其它的都可以不予考虑
4. lWidthBytes的计算在这里也没有问题,因为图片的宽度确实是4的整数倍。这些计算都是小问题,最起码在当前的状态下这个计算是不会影响最终的结果的,只要是最终调出效果了,其它的部分如果有问题可以再进行修正都不迟。

要达到的目的,比如原始的图片如下:
-----------
| |
| 原始图片 |
| |
| |
-----------

在改变之后如下:
-----------
| |
| 原始图片 |
| |
| |
-----------
| |
| 新增高部分|
| |
-----------

如果是不写字,增高是可以做到的,但现在是为了写字,就使用了DC,在这种情况下,就完全不对了,比如
long lGet = GetBitmapBits(retBitMap,lWidthBytes * lNewHeight,pszTmp);
的结果,lGet肯定应该是 长x高x3,结果完全不是这回事,是一个很小的值
red-fly 2010-11-15
  • 打赏
  • 举报
回复
明天 试一下,唉...
superarhow 2010-11-15
  • 打赏
  • 举报
回复
这个跟桌面设置有关,所以也不要直接使用32位,最好用CreateDIBSection.
red-fly 2010-11-15
  • 打赏
  • 举报
回复
确实,我使用24位的时候,SelectObject( dc2, hBitmap );的返回值是0,而我使用32位的时候(直接把里面几个地方进行修改)返回的是一个值。当没有返回值的时候就是没有任何图像,否则是有图像,只是不正确,而且变成了黑白色
副组长 2010-11-12
  • 打赏
  • 举报
回复
4字节对齐不对,楼上已经说了。
为什么要把图像数据上下掉头2次呢?别的没看出什么明显错误,说说你出错的症状帮你分析一下。
qsycn 2010-11-12
  • 打赏
  • 举报
回复
lWidthBytes = (width+(4-width%4)) * 3;
-->
lWidthBytes = ((width* 3)+3)/4*4;//仅限于24位,如果知道位,尽量使用WIDTHBYTES
#define WIDTHBYTES(bits) (((bits) + 31) / 32 * 4)


还有你的gdi对象创建了后都没有DeleteObject

加载更多回复(1)

15,979

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 界面
社区管理员
  • 界面
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧