前言
Twain协议扫描图片的时候,图片是以Bitmap的格式存储在内存中,我们需要从内存中把图片给复制出来。
小知识:
1字节 = 8位
首先我们要了解Bitmap的结构
Bitmap结构
BMP文件由文件头、位图信息头、颜色信息和图形数据四部分组成。
文件头
位图文件头BITMAPFILEHEADER,是一个结构,其定义如下:
1 | typedef struct tagBITMAPFILEHEADER{ |
位图信息头
其中位图的信息头对应的结构体如下
1 | [ ] |
字段说明
- biSize:指定这个结构的长度,为40,单位字节
- biWidth:指定图象的宽度,单位是像素
- biHeight:指定图象的高度,单位是像素
- biPlanes:必须是1,不用考虑
- biBitCount:指定表示颜色时要用到的位数,常用的值为1(黑白二色图),4(16色图),8(256色图),24(真彩色图),新的.bmp格式支持32位图
- biCompression:指定位图是否压缩,有效的值为BI_RGB,BI_RLE8,BI_RLE4,BI_BITFIELDS(都是一些Windows定义好的常量)。要说明的是,Windows位图可以采用RLE4,和RLE8的压缩格式,但用的不多。我们今后所讨论的只有第一种不压缩的情况,即BI_RGB。
- biSizeImage:指定实际的位图数据占用的字节数,如果biCompression为BI_RGB,则该项可能为零
- biXPelsPerMeter:指定目标设备的水平分辨率,单位是每米的象素个数,关于分辨率的概念,我们将在打印部分详细介绍。
- biYPelsPerMeter:指定目标设备的垂直分辨率,单位同上。
- biClrUsed:指定本图像实际用到的颜色数,如果该值为0,则用到的颜色数为2的biBitCount次方
- biClrImportant:指定本图象中重要的颜色数,如果该值为零,则认为所有的颜色都是重要的。
颜色信息
所占字节 = 颜色数 * 4
调色板实际上是一个数组,共有biClrUsed个元素,每个元素占4字节,如果该值为零,则有2的biBitCount次方个元素。
真彩色图,是不需要调色板的,颜色数为0。
代码示例
1 | int colorNum = 0; |
图形数据
字节数为 biSizeImage 的值。
Windows规定一个扫描行所占的字节数必须是4的倍数(即以long为单位),不足的以0填充,
所以图片的尺寸计算公式为
1 | biSizeImage = ((((bi.biWidth * bi.biBitCount) + 31) & ~31) / 8) * bi.biHeight; |
其中
1 | ((bi.biWidth * bi.biBitCount) + 31) & ~31 |
这行代码的含义是将一个图像的每行像素数据的字节数补齐至32位对齐。也就是4字节对齐。
在这段代码中,bi.biWidth 表示图像的宽度,bi.biBitCount 表示每个像素所占的位数。
首先,将每行像素数据的字节数计算为 (bi.biWidth * bi.biBitCount)。然后,加上31,并将结果与31进行与运算,相当于向上取整至32的倍数,以确保每行像素数据结束时是32位对齐的。
TWain扫描的图片
TWain协议保存在内存的Bitmap是不包含文件头的。
只包含位图信息头
、颜色信息
和图形数据
。
内存操作
获取图像句柄的内存指针
1 | [ ] |
上面的两个方法分别是使用 P/Invoke 调用 Windows API 中的 GlobalLock 和 GlobalUnlock 函数。
传入的参数都是句柄。
GlobalLock 方法:
- GlobalLock 函数的作用是将内存对象的句柄转换为指向相应内存块的指针,并增加指定的内存对象的锁定计数。
- 调用 GlobalLock 函数,将传入的句柄(handle)转换为指向全局内存块的指针,并返回该指针的 IntPtr 类型对象。
- 这样可以访问和操作全局内存中的数据。
GlobalUnlock 方法:
- GlobalUnlock 函数的作用是减小指定的内存对象的锁定计数,并将它解锁。
- 调用 GlobalUnlock 函数,解锁之前通过 GlobalLock 锁定的全局内存块,以释放内存资源并允许其他进程访问该内存块。
这两个函数配合使用,可以在操作全局内存块时进行锁定和解锁操作,确保内存访问的正确性和资源释放的准确性。
如下代码所示
1 | IntPtr bmpPtr = TwainWin32.GlobalLock(dibHandle); |
第一行是把内存对象的句柄转换为内存块指针。
解析位图信息头
Marshal.PtrToStructure(bmpPtr, bmi)
方法将内存中的数据按照指定的结构体类型进行解析,并将其转换为.NET中的结构体对象。
这里之所以不用传内存的长度,是因为他会自动根据结构体中属性的类型所占字节自动计算。
所以使用Marshal.PtrToStructure
获取对象的时候结构体是不能删除属性的也不能修改字段名,会造成解析错误。
图形数据指针
1 | IntPtr pixptr = (IntPtr)((int)bmpPtr + bi.biSize + paletteSize); |
整个图片的内存指针+位图信息头偏移+颜色信息偏移就是图形数据所在的开始的指针了。
复制图形数据
这种方式复制的图形才是正常的
1 | int width = bi.biWidth; |
下面这种方式获取的是翻转的。
1 | byte[] imageBytes = new byte[bi.biSizeImage]; |
创建BitmapSource
1 | public static BitmapSource Create( |
参数说明:
pixelWidth: 位图的宽度,以像素为单位。
pixelHeight: 位图的高度,以像素为单位。
dpiX: 位图的水平分辨率,即每英寸水平包含的像素数。
dpiY: 位图的垂直分辨率,即每英寸垂直包含的像素数。
pixelFormat: 位图的像素格式,指定像素的布局和颜色信息的存储方式。
palette: 调色板,如果不使用调色板,则传入 null。
pixels: 包含位图像素数据的字节数组。
stride: 位图的扫描行宽度,即每行像素数据所占的字节数。
小知识
句柄和指针
内存对象的句柄(handle)和内存对象的指针(pointer)是用于访问内存中对象的两种不同机制。
指针(Pointer):
- 指针是一个直接指向内存地址的值。它们直接包含要访问的内存地址,因此可以直接用来访问对象或数据。
- 指针提供了直接的、实时的内存访问。
句柄(Handle):
- 句柄是一个间接引用的值,它本身不直接指向内存对象,而是用作访问内存对象的标识符或引用。
- 句柄通常是一个在内存中固定位置的值,可以用来查找实际的内存地址或对象。
关系:
- 间接性:句柄用于间接访问内存对象,而指针直接指向内存对象。
- 安全性:句柄可以提供额外的安全性,因为它们可以隐藏实际的内存地址或对象,并且可以进行权限检查。
- 管理:句柄有助于管理内存对象的生命周期,可以用来实现资源管理和自动化清理。
在实际应用中,句柄和指针通常根据需要和设计选择使用。
例如,Windows操作系统中使用句柄来管理GUI对象,而C语言中使用指针直接访问内存。