windows支持两种位图格式,DDB(device-dependent bitmap),DIB(device-independent bitmap)。设备相关位图用于windows显示系统中,其图像格式与显卡格式兼容,因此显示速度很快。设备不相关位图定义了位图的文件格式,用于位图传输,由于其数据格式可能与显卡格式不一致,直接使用设备不相关位图显示图像时需要进行转换,因此显示速度较慢。

历史上显卡支持16色或者256色,分别使用4位或者8位表示一个像素颜色。在16色系统中,仅支持黑白灰,红绿蓝,青品红黄等基本颜色。在256色系统中,windows保留了20中基本颜色,剩余236中颜色通过颜色查找表定义。因此,基于以上色彩体系创建的位图需要设备相关的颜色查找表来解释真实颜色。

另外两种不需要查找表的颜色包括16位色与24位色。使用16位(2字节)表示红绿蓝,每个颜色分量使用5位(或者绿色使用6位),被称为 high color。使用24位(3字节)表示红绿蓝,每个颜色分量使用一个字节,被称为 true color。在没有查找表情况下,位图红绿蓝分量排列顺序可能不一致,因此DDB显示速度要优于DIB。

现代显示器一般都是32位真彩色,可以通过 ::GetDeviceCaps(hdc, BITSPIXEL) 获得每个像素上的位数,在本机测试值为32,通过 ::GetDeviceCaps(hdc, PLANES) 获得位面数,一般情况下该值为1。

windows提供了BITMAP结构体来描述DDB,定义如下:

/* Bitmap Header Definition */
typedef struct tagBITMAP
{
LONG bmType;
LONG bmWidth;
LONG bmHeight;
LONG bmWidthBytes;
WORD bmPlanes;
WORD bmBitsPixel;
LPVOID bmBits;
} BITMAP, *PBITMAP, NEAR *NPBITMAP, FAR *LPBITMAP;

其中,bmWidth, bmHeight表示DDB的尺寸,bmPlanes通常为1,bmBitsPixel表示每个像素需要多少位来表示。

有两个参数需要特别注意:

bmBits并不是指向DDB的一个指针,应用程序无法直接操作显存,多数情况下该指针为空,但可以通过特定函数获得DDB数据区的一个拷贝;

bmWidthBytes一般不需要赋值, windows会根据规则计算出正确的值,其规则是位图每行字节为2的倍数,一般计算公式为 bmWidthBytes = 2 *((bmWidth * bmBitsPixel + 15) / 16)。

windows提供了两个基本函数进行DDB绘制,BitBlt 直接将DDB一个区域拷贝到另一个区域中,StretchBlt 引入了缩放模式。

以下函数块仅在左上角绘制一条直线段,然后利用 BitBlt 将其拷贝到其他区域,关键代码如下:

::MoveToEx(hdc, 0, 0, NULL);
::LineTo(hdc, 10, 10);
for (int y = 50; y < cyClient; y += cySource)
for (int x = 50; x < cxClient; x += cxSource)
{
BitBlt(hdc, x, y, cxSource, cySource,
hdc, 0, 0, SRCCOPY);
}

得到如下显示效果:

使用 StretchBlt 替代 BitBlt ,结果如下:

代码如下:

::MoveToEx(hdc, 0, 0, NULL);
::LineTo(hdc, 10, 10);
for (int y = 50; y < cyClient; y += 30)
for (int x = 50; x < cxClient; x += 30)
{
::StretchBlt(hdc, x, y, 20, 20,
hdc, 0, 0, 10, 10, SRCCOPY);
}

StretchBlt 将10*10区域拷贝到20*20区域,当目标区域与原始区域尺寸不一致时,必然存在插值操作,使用函数 SetStretchBltMode 设置插值模式,windows 定义了如下模式:

.BLACKONWHITE:位与操作,保留插值区域中最暗的值(缩小时应用该值,放大时直接映射到原始区域中)

.WHITEONBLACK:位或操作,保留插值区域中最亮的值(缩小时应用该值,放大时直接映射到原始区域中)

.COLORONCOLOR:抽取冗余行列,即得到缩小图像(放大时同样直接映射到原始区域中)

.HALFTONE:使用均值作为目标结果(缩小时先平均再采样,放大时使用插值)

以上插值模式中,COLORONCOLOR速度最快,HALFTONE速度最慢,BLACKONWHITE与WHITEONBLACK应用在一些特殊场景中,一般使用COLORONCOLOR。

注意参数 SRCCOPY 定义了拷贝操作运算,该运算针对每个像素做同样的操作,这是一个三元运算,其操作数包括源像素,目标像素以及模式像素(画刷),该三元运算包括256中组合,下面仅给出部分重要组合:

.SRCCOPY: dest = src

.SRCPAINT: dest = source OR dest

.SRCAND: dest = source AND dest

.SRCINVERT: dest = source XOR dest

.SRCERASE: dest = source AND (NOT dest )

.NOTSRCCOPY: dest = (NOT source)

有了以上拷贝模式,很自然会想到是否可以使用某些运算组合实现椭圆图像绘制,这里所谓椭圆图像是指对矩形图像绘制椭圆区域,典型应用如界面上圆角按钮。

// load image
hBitmapImag = (HBITMAP)LoadImage(hInstance,
TEXT("img.bmp"),
IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE |
LR_CREATEDIBSECTION); // obtain image size
GetObject(hBitmapImag, sizeof(BITMAP), &bitmap);
cxBitmap = bitmap.bmWidth;
cyBitmap = bitmap.bmHeight; // Select the original image into a memory DC
hdcMemImag = CreateCompatibleDC(NULL);
SelectObject(hdcMemImag, hBitmapImag); // Create the monochrome mask bitmap and memory DC
hBitmapMask = CreateBitmap(cxBitmap, cyBitmap, 1, 1, NULL);
hdcMemMask = CreateCompatibleDC(NULL);
SelectObject(hdcMemMask, hBitmapMask); // Color the mask bitmap black with a white ellipse
SelectObject(hdcMemMask, GetStockObject(BLACK_BRUSH));
Rectangle(hdcMemMask, 0, 0, cxBitmap, cyBitmap);
SelectObject(hdcMemMask, GetStockObject(WHITE_BRUSH));
Ellipse(hdcMemMask, 0, 0, cxBitmap, cyBitmap); // Mask the original image
BitBlt(hdcMemImag, 0, 0, cxBitmap, cyBitmap,
hdcMemMask, 0, 0, SRCAND); // Center image
x = (cxClient - cxBitmap) / 2;
y = (cyClient - cyBitmap) / 2; // Do the bitblts
SelectObject(hdc, GetStockObject(GRAY_BRUSH));
Rectangle(hdc, 0, 0, cxClient, cyClient);
BitBlt(hdc, x, y, cxBitmap, cyBitmap, hdcMemMask, 0, 0, 0x220326);
BitBlt(hdc, x, y, cxBitmap, cyBitmap, hdcMemImag, 0, 0, SRCPAINT); DeleteDC(hdcMemImag);
DeleteDC(hdcMemMask);

以上代码实现了绘制图像椭圆区域,下面详细解读实现过程:

1)首先加载DDB图像,并获取图像尺寸

2)创建内存兼容DC,并将DDB图像加载到DC

3)创建与原始图像尺寸一致的模板图像,模板图像为二值图像,并加载到内存DC中

4)对模板图像椭圆区域内设置为1,椭圆区域外设置为0

5)使用 SRCAND 操作将模板图像绘制到原图像中,此时原图像保留图像区域内像素值,椭圆区域外被设置为0

6)为了使图像居中显示,计算绘制起点x,y

7)将整个区域填充为中灰色

8)使用 0x220326 操作得到一个椭圆中心为黑色,椭圆外围保持不变的DC,该操作没有命名,具体执行逻辑为 Dest = Dest AND (Not Source)

9)使用 SRCPAINT 操作得到最终结果,该操作执行 Dest = Source OR Dest

windows 同样提供了一个更加简便的函数来实现以上功能,使用 PlgBlt 函数可以实现任意自定义区域绘制,同时该函数还可以做图像变换功能。

BOOL PlgBlt( HDC hdcDest, CONST POINT * lpPoint, HDC hdcSrc,  int xSrc, int ySrc, int width,

int height,  HBITMAP hbmMask,  int xMask,  int yMask);

    参数 hbmMask 为一个二值图像,作为原始图像的绘制模板

    参数 hdcSrc 为原始图像,同时定义了绘制区间

    参数 hdcDest 为目标DC,图像将安装指定规则绘制到目标DC上

    参数 lpPoint 包含了三个点,在目标区域上构成了一个平行四边形,三个点分别对应左上,右上,左下,第四个点(右下)可以根据平行四边形规则计算得出,

    将原始图像矩形映射到目标平行四边形上即构成了一个变换,该变换包括平移,旋转,缩放,放射变换。

下面给出 PlgBlt 函数的使用效果:

左边图像仅包括平移与缩放,右边图像为任意仿射变换,主要代码如下:

POINT pt[3];
pt[0].x = 50;
pt[0].y = 50;
pt[1].x = 300;
pt[1].y = 50;
pt[2].x = 50;
pt[2].y = 300;
PlgBlt(hdc, pt, hdcMemImag, 0, 0, cxBitmap, cyBitmap,
hBitmapMask, 0, 0); pt[0].x = 350;
pt[0].y = 50;
pt[1].x = 600;
pt[1].y = 0;
pt[2].x = 500;
pt[2].y = 400;
PlgBlt(hdc, pt, hdcMemImag, 0, 0, cxBitmap, cyBitmap, hBitmapMask, 0, 0);

除了对DDB图像进行透明与仿射变换操作外,是否可以可以对DDB图像做融合操作呢?msimg32.dll 提供了更懂的图像操作函数。

TransparentBlt 函数可以对指定像素做透明处理,函数定义如下:

BOOL TransparentBlt(_In_ HDC hdcDest, _In_ int xoriginDest, _In_ int yoriginDest, _In_ int wDest, _In_ int hDest, 

                                     _In_ HDC hdcSrc, _In_ int xoriginSrc, _In_ int yoriginSrc, _In_ int wSrc, _In_ int hSrc, _In_ UINT crTransparent);

   该函数对指定像素透明处理,但指定像素不能为RGB(0,0,0),不论DC缩放模式设置为什么值,该函数永远执行 COLORONCOLOR 模式。

AlphaBlend 将图像与背景融合,函数定义如下:

BOOL AlphaBlend (_In_ HDC hdcDest, _In_ int xoriginDest, _In_ int yoriginDest, _In_ int wDest, _In_ int hDest, 

                                     _In_ HDC hdcSrc, _In_ int xoriginSrc, _In_ int yoriginSrc, _In_ int wSrc, _In_ int hSrc, _In_ BLENDFUNCTION ftn);

该函数关键参数为 BLENDFUNCTION 结构体,当 AlphaFormat 取值为 AC_SRC_ALPHA 时,原图像必须具有 alpha 通道。

当 SourceConstantAlpha 取值小于255时,整个图像应用SourceConstantAlpha;当 SourceConstantAlpha 取值为255时,应用每个像素的alpha值。

图像融合计算公式为 dest = source * alpha / 255 + dest * (1 - alpah / 255)。

一个注意点:source * alpha / 255 在调用显示函数前可以提前计算出来,为了提升显示速度,AlphaBlend 函数应用了公式 dest = source  + dest * (1 - alpah / 255),

即 source 不再是原始图像,而是 source = source * alpha / 255。

下面给出一个 AlphaBlend 函数应用示例,我创建了一个32位图像,其上半部分图像的alpha值被设置为128,下半部分图像的alpha值设置为255,

同时上半部分图像的每个像素进行了预计算(source = source * alpha / 255),以下为部分显示代码及效果:

hdcMemImag = CreateCompatibleDC(hdc);
SelectObject(hdcMemImag, hBitmapImag);
SelectObject(hdc, GetStockObject(GRAY_BRUSH));
Rectangle(hdc, 0, 0, cxClient, cyClient);
BLENDFUNCTION bn;
bn.AlphaFormat = AC_SRC_ALPHA;
bn.BlendFlags = 0;
bn.BlendOp = AC_SRC_OVER;
bn.SourceConstantAlpha = 255; // 应用每个像素的alpha值
AlphaBlend(hdc, 0, 0, cxBitmap, cyBitmap,
hdcMemImag, 0, 0, cxBitmap, cyBitmap, bn);
DeleteDC(hdcMemImag); 

以上是DDB的一些主要内容,windows同样提供了DIB,其目的是为了图像文件传输与存储,因为DDB高度依赖设备。

windows DIB 结构集成自 OS/2,包括以下几个部分:

1)文件头,定义为 BITMAPFILEHEADER,包括文件识别及尺寸等基本信息;

2)信息头,OS/2定义为BITMAPCOREHEADER, windows 对其进行扩展,定义为 BITMAPINFOHEADER,在windows中两者均可使用;

3)查找表,高彩色或者真彩色不需要查找表;

4)图像数据区;

这里不详细解释所有结构体成员,仅对一些自认为很重要的地方进行说明。

1)BITMAPCOREHEADER 与 BITMAPINFOHEADER 的成员变量 bcPlanes 与 biPlanes 永远为1;

2)BITMAPCOREHEADER 的成员变量 bcBitCount可取值为 1,2,4,8,24,而windows扩展下 BITMAPINFOHEADER的成员变量biBitCount 可取值1,2,4,8,16,24,32;

3)BITMAPCOREINFO 使用查找表 RGBTRIPLE,而 BITMAPINFO 使用查找表 RGBQUAD;

4)由于 OS/2 风格定义也被 windows 支持,当拿到一个信息头结构体后,可以通过 bcSize/biSize 字段来判断到底是哪种风格位图,因为两个结构体尺寸分别为 12,40字节;

5)由于结构体在内存中的对齐方式由编译器确定,不同编译器可能得到不同大小的结构体,当将图像结构写入文件时,会导致无法正确读取结构体(对齐方式未知),因此需要使用 packed 模式存储结构体变量;

6)图像数据区原点位于左下角(满足笛卡尔坐标系),而显示系统原点一般位于左上角,这会导致显示图像时上下翻转;

7)位图数据行一般是4字节整数倍,而DDB要求为两字节整数倍,所以当DIB转换为DDB时自然满足对齐要求;

8)biCompression 可取值 BI_RGB,BI_RLE8, BI_RLE4,BI_BITFIELDS,具体解释如下:

.1位位图时,biCompression 取值只能为 BI_RGB;

.4位位图时,biCompression  取值可以为 BI_RGB 或者 BI_RLE4, BI_RLE4为行程码压缩数据,该压缩算法直观简单,因此编解码速度会很快,但压缩率有限;

.8位位图时,biCompression  取值可以为 BI_RGB 或者 BI_RLE8, BI_RLE8为行程码压缩数据;

.24位位图时,biCompression 取值只能为 BI_RGB;

.16位或者32位位图时,biCompression 取值可以为 BI_RGB 或者 BI_BITFIELDS

.只要 biCompression 取值为 BI_RGB,不管16位,24位或者32位位图,其排列顺序均为blue->green->red->alpha(可能没有),只是16位位图时每个颜色占5位,24位与32位位图时每个颜色占8位;

.当 biCompression 取值为 BI_BITFIELDS 时,在 BITMAPCOREHEADER 后紧跟3个DWORD数据作为颜色位标记符,程序根据这3个DWORD数据提取色彩;

与DDB类似,windows同样提供了两个函数用于显示DIB, SetDIBitsToDevice直接将DIB显示在DC上, StretchDIBits 提供了缩放显示,定义如下:

int SetDIBitsToDevice(_In_ HDC hdc, _In_ int xDest, _In_ int yDest, _In_ DWORD w, _In_ DWORD h, _In_ int xSrc,

                                      _In_ int ySrc, _In_ UINT StartScan, _In_ UINT cLines, _In_ CONST VOID * lpvBits, _In_ CONST BITMAPINFO * lpbmi, _In_ UINT ColorUse);

    int StretchDIBits(_In_ HDC hdc, _In_ int xDest, _In_ int yDest, _In_ int DestWidth, _In_ int DestHeight, _In_ int xSrc, _In_ int ySrc, _In_ int SrcWidth, _In_ int SrcHeight,

                             _In_opt_ CONST VOID * lpBits, _In_ CONST BITMAPINFO * lpbmi, _In_ UINT iUsage, _In_ DWORD rop);

    是否可以对使用 StretchDIBits  函数完成图像仿射变换及椭圆显示呢?可以采用如下方法实现:

1)直接对DIB图像数据应用仿射变换;

2)使用类似位逻辑操作(如SRCPAINT等 )实现指定区域透明显示功能;

要实现DIB图像融合,目前没有看到windows提供更多的方法,需要自己对数据直接处理。

在实际应用中,DDB与DIB一般会交互使用,以下流程给出了DIB与DDB之间的转换方法:

1)从文件中加载一张DIB图像;

2)使用 CreateCompatibleBitmap 创建与DIB尺寸一致且与设备兼容的位图(DDB);

3)使用 CreateCompatibleDC 创建与设备兼容的内存DC,并将兼容位图选人兼容内存DC;

4)使用 SetDIBitsToDevice 将DIB绘制到兼容内存DC上;

5)使用 BitBlt 将内存DC显示到物理DC上,实现了DIB到DDB转换并显示;

6)使用 GetDIBits 并将第五个参数(数据指针)设置为 NULL,可获得内存DC数据相关参数;

7)根据 6)获得参数分配所需内存,再次调用 GetDIBits 可获得数据,返回值表示成功拷贝图像行数;

8)将获得图像保存到文件,对比与原始图像一致,实现了DDB到DIB转换并保存。

以下给出部分代码:

// 打开图像
... // DIB转换到DDB
HBITMAP hBitmap = ::CreateCompatibleBitmap(hdc, w, h);
HDC hMemDC = ::CreateCompatibleDC(hdc);
::SelectObject(hMemDC, hBitmap);
::SetDIBitsToDevice(hMemDC, 0, 0, w, h, 0, 0, 0, h,
imgs[0].data, (LPBITMAPINFO)lpBmpInfoHead,
DIB_RGB_COLORS); // 显示DDB
CRect rcClient;
this->GetClientRect(&rcClient);
BitBlt(hdc, rcClient.left, rcClient.top, rcClient.Width(),
rcClient.Height(), hMemDC, 0, 0, SRCCOPY); // DIB信息头,在第一次调用GetDIBits时被填充
BITMAPINFO bmi = { 0 };
int sz = sizeof(BITMAPINFO);
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); // 获取DIB尺寸等参数
::GetDIBits(hMemDC, hBitmap, 0, h, NULL, &bmi,
DIB_RGB_COLORS); // 分配DIB数据所需内存
unsigned char *bitmapBits = new unsigned
char[bmi.bmiHeader.biSizeImage];
memset(bitmapBits, 0, bmi.bmiHeader.biSizeImage); // 从DDB中获取DIB数据
int lines = ::GetDIBits(hMemDC, hBitmap, 0, h,
bitmapBits, &bmi, DIB_RGB_COLORS); // 拷贝数据并保存文件
... // 删除内存
delete []bitmapBits ;

参考资料:Programming Windows by Charles Petzold

Wingdi.h header - Win32 apps | Microsoft Docs

windows设备相关位图与设备无关位图的更多相关文章

  1. 第15章 设备无关位图_15.3 DIB和DDB的结合

    第15章 设备相关位图_15.3 DIB和DDB的结合 15.3.1 从DIB创建DDB (1)hBitmap =CreateDIBitmap(…)——注意这名称会误导,实际上创建的是DDB 参数 说 ...

  2. 第15章 设备无关位图_15.1 DIB文件格式

    15.1 DIB文件格式(一种文件格式,扩展名为BMP) 15.1.1 OS/2风格的DIB 文件格式 字段 说明 文件头 (BITMAPFILEHEADER) 1.共14个字节 2.缩写建议用bmf ...

  3. device-independent bitmap (DIB) 设备无关位图

    设备无关位图即独立于设备的位图(DIB)与"Device-Dependent Bitmaps (DDB) 设备相关位图"相比,它不再依赖于具体的设备,从而更适合在不同的计算机之间传 ...

  4. Device-Dependent Bitmaps (DDB) 设备相关位图

    设备相关的位图(DDB)使用单一结构BITMAP结构描述.该结构的成员指定矩形区域的宽度和高度,以像素为单位;将条目从设备调色板映射到像素的数组的宽度;以及器件的颜色格式,在每个像素的颜色平面和位数方 ...

  5. Xamarin设备相关图片尺寸要求

    Xamarin设备相关图片尺寸要求   Xamarin跨平台开发,要兼顾iOS.Android.尤其是图片方面,各个平台有对应的不同要求.在iOS中,需要提供没有后缀(设备无关单位尺寸).@2x(双倍 ...

  6. 与众不同 windows phone (18) - Device(设备)之加速度传感器, 数字罗盘传感器

    原文:与众不同 windows phone (18) - Device(设备)之加速度传感器, 数字罗盘传感器 [索引页][源码下载] 与众不同 windows phone (18) - Device ...

  7. 与众不同 windows phone (23) - Device(设备)之硬件状态, 系统状态, 网络状态

    原文:与众不同 windows phone (23) - Device(设备)之硬件状态, 系统状态, 网络状态 [索引页][源码下载] 与众不同 windows phone (23) - Devic ...

  8. 与众不同 windows phone (22) - Device(设备)之摄像头(硬件快门, 自动对焦, 实时修改捕获视频)

    原文:与众不同 windows phone (22) - Device(设备)之摄像头(硬件快门, 自动对焦, 实时修改捕获视频) [索引页][源码下载] 与众不同 windows phone (22 ...

  9. 与众不同 windows phone (21) - Device(设备)之摄像头(拍摄照片, 录制视频)

    原文:与众不同 windows phone (21) - Device(设备)之摄像头(拍摄照片, 录制视频) [索引页][源码下载] 与众不同 windows phone (21) - Device ...

随机推荐

  1. 使用 arguments 对象

    arguments 对象表示参数集合,它是一个伪类数组,拥有与数组相似的结构,可以通过数组下标的形式访问函数实参值,但是没有基础 Array 的原型方法. //函数没有定义形参,但是在函数体内通过 a ...

  2. Go语言系列之RabbitMQ消息队列

    1. RabbitMQ是什么?   MQ 是什么?队列是什么,MQ 我们可以理解为消息队列,队列我们可以理解为管道.以管道的方式做消息传递. 生活场景: 1.其实我们在双11的时候,当我们凌晨大量的秒 ...

  3. Elasticsearch基础知识学习

    概要 ElasticSearch是一个基于Lucene的搜索服务器.它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口.Elasticsearch是用Java开发的,并作为Ap ...

  4. Pop Sequeue

    题目描述 Given a stack which can keep M numbers at most. Push N numbers in the order of 1,2,3...,N and p ...

  5. 读《疯狂Java讲义》笔记总结一

    最近在读<疯狂Java讲义>,现把其中遇到的一些自己以前没掌握的点儿记录下来. 1.字符串相关 字符串不是基本类型,字符串是一个类,也就是一个引用类型. 字符串转int类型String a ...

  6. ctfshow web2 web3

    ctfshow web2 1.手动注入题.先用万能密码admin' or 1=1%23,有回显 2.union select注入,2处有回显 3.依次查找数据库.表.字段 得到flag ctfshow ...

  7. 刷机错误ERROR:STATUS_BROM_CMD__FAIL

    ERROR:STATUS_BROM_CMD_STARTCMD_FAIL window驱动没有安装好,几乎所有安装包都有问题,很难成功,成功了也很慢.这是因为之前检测到且烧写错误,然后上一次的驱动错误连 ...

  8. Centos7下,Docker的安装与使用

    一.Docker Install 1.卸载旧的版本 yum remove docker \ docker-client \ docker-client-latest \ docker-common \ ...

  9. 推荐一个github国内访问加速神器GitHub520

    一.介绍 对 GitHub 说"爱"太难了:访问慢.图片加载不出来. 注: 本项目还处于测试阶段,仅在本机测试通过,如有问题欢迎提 issues 本项目无需安装任何程序,通过修改本 ...

  10. Nginx代理常用参数

    目录 一:Nginx代理常用参数 1.添加发往后端服务器的请求头信息 二:参数案例 1.lb01配置文件 2.web01 web02 web服务器 3.测试 4.重启 5.DNS域名解析 6.网址测试 ...