前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >WPF开发-扫描仪Twain协议图片解析

WPF开发-扫描仪Twain协议图片解析

作者头像
码客说
发布2024-06-09 13:56:53
1010
发布2024-06-09 13:56:53
举报
文章被收录于专栏:码客码客

前言

Twain协议扫描图片的时候,图片是以Bitmap的格式存储在内存中,我们需要从内存中把图片给复制出来。

小知识:

1字节 = 8位

首先我们要了解Bitmap的结构

Bitmap结构

BMP文件由文件头、位图信息头、颜色信息和图形数据四部分组成。

文件头

位图文件头BITMAPFILEHEADER,是一个结构,其定义如下:

代码语言:javascript
复制
typedef struct tagBITMAPFILEHEADER{
    WORD bfType;
    DWORD bfSize;
    WORD bfReserved1;
    WORD bfReserved2;
    DWORD bfOffBits;
} BITMAPFILEHEADER;

位图信息头

其中位图的信息头对应的结构体如下

代码语言:javascript
复制
[StructLayout(LayoutKind.Sequential, Pack = 2)]
public class Bitmapinfoheader
{
    public uint biSize;
    public int biWidth;
    public int biHeight;
    public ushort biPlanes;
    public ushort biBitCount;
    public uint biCompression;
    public uint biSizeImage;
    public int biXPelsPerMeter;
    public int biYPelsPerMeter;
    public uint biClrUsed;
    public uint biClrImportant;
}

字段说明

  • 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。

代码示例

代码语言:javascript
复制
int colorNum = 0;
if (bi.biClrUsed != 0)
{
    colorNum = (int)bi.biClrUsed;
}
else
{
    switch (bi.biBitCount)
    {
        case 1:
            colorNum = 2;
            break;
        case 4:
            colorNum = 16;
            break;
        case 8:
            colorNum = 256;
            break;
        case 24:
            {
                colorNum = 0; //对于真彩色图,没用到调色板
                break;
            }
    }
}
int paletteSize = colorNum * 4;

图形数据

字节数为 biSizeImage 的值。

Windows规定一个扫描行所占的字节数必须是4的倍数(即以long为单位),不足的以0填充,

所以图片的尺寸计算公式为

代码语言:javascript
复制
biSizeImage = ((((bi.biWidth * bi.biBitCount) + 31) & ~31) / 8) * bi.biHeight;

其中

代码语言:javascript
复制
((bi.biWidth * bi.biBitCount) + 31) & ~31

这行代码的含义是将一个图像的每行像素数据的字节数补齐至32位对齐。也就是4字节对齐。

在这段代码中,bi.biWidth 表示图像的宽度,bi.biBitCount 表示每个像素所占的位数。

首先,将每行像素数据的字节数计算为 (bi.biWidth * bi.biBitCount)。然后,加上31,并将结果与31进行与运算,相当于向上取整至32的倍数,以确保每行像素数据结束时是32位对齐的。

TWain扫描的图片

TWain协议保存在内存的Bitmap是不包含文件头的。

只包含位图信息头颜色信息图形数据

内存操作

获取图像句柄的内存指针

代码语言:javascript
复制
[DllImport("kernel32.dll", ExactSpelling = true)]
public static extern IntPtr GlobalLock(IntPtr handle);

[DllImport("kernel32.dll", ExactSpelling = true)]
public static extern IntPtr GlobalUnlock(IntPtr handle);

上面的两个方法分别是使用 P/Invoke 调用 Windows API 中的 GlobalLock 和 GlobalUnlock 函数。

  1. GlobalLock 方法:
    • GlobalLock 函数的作用是将内存对象的句柄转换为指向相应内存块的指针。
    • 在这段代码中,通过使用 P/Invoke 调用 GlobalLock 函数,将传入的句柄(handle)转换为指向全局内存块的指针,并返回该指针的 IntPtr 类型对象。
    • 这样可以访问和操作全局内存中的数据。
  2. GlobalUnlock 方法:
    • GlobalUnlock 函数的作用是减小指定的内存对象的锁定计数,并将它解锁。
    • 在这段代码中,通过使用 P/Invoke 调用 GlobalUnlock 函数,解锁之前通过 GlobalLock 锁定的全局内存块。
    • 当不再需要访问该全局内存块时,可以使用 GlobalUnlock 来解锁,以释放内存资源并允许其他进程访问该内存块。

这两个函数配合使用,可以在操作全局内存块时进行锁定和解锁操作,确保内存访问的正确性和资源释放的准确性。

如下代码所示

代码语言:javascript
复制
IntPtr bmpPtr = TwainWin32.GlobalLock(dibHandle);
TwainWin32.Bitmapinfoheader bmi = new TwainWin32.Bitmapinfoheader();
Marshal.PtrToStructure(bmpPtr, bmi);

第一行是把内存对象的句柄转换为内存块指针。

解析位图信息头

Marshal.PtrToStructure(bmpPtr, bmi)方法将内存中的数据按照指定的结构体类型进行解析,并将其转换为.NET中的结构体对象。

这里之所以不用传内存的长度,是因为他会自动根据结构体中属性的类型所占字节自动计算。

所以使用Marshal.PtrToStructure获取对象的时候结构体是不能删除属性的也不能修改字段名,会造成解析错误。

图形数据指针

代码语言:javascript
复制
IntPtr pixptr = (IntPtr)((int)bmpPtr + bi.biSize + paletteSize);

整个图片的内存指针+位图信息头偏移+颜色信息偏移就是图形数据所在的开始的指针了。

复制图形数据

这种方式复制的图形才是正常的

代码语言:javascript
复制
int width = bi.biWidth;
int height = bi.biHeight;
int stride = (int)(bi.biSizeImage / height);
byte[] imageBytes = new byte[stride * height];
for (int i = 0, j = 0, k = (height - 1) * stride; i < height; i++, j += stride, k -= stride)
{
    Marshal.Copy(
        ((IntPtr)((int)pixptr + j)),
        imageBytes,
        k,
        stride
    );
}

下面这种方式获取的是翻转的。

代码语言:javascript
复制
byte[] imageBytes = new byte[bi.biSizeImage];
Marshal.Copy(
    pixptr,
    imageBytes,
    0,
    (int)bi.biSizeImage
);

创建BitmapSource

代码语言:javascript
复制
public static BitmapSource Create(
  int pixelWidth,
  int pixelHeight,
  double dpiX,
  double dpiY,
  PixelFormat pixelFormat,
  BitmapPalette palette,
  Array pixels,
  int stride)
{
  return (BitmapSource) new CachedBitmap(pixelWidth, pixelHeight, dpiX, dpiY, pixelFormat, palette, pixels, stride);
}

参数说明:

  • pixelWidth: 位图的宽度,以像素为单位。
  • pixelHeight: 位图的高度,以像素为单位。
  • dpiX: 位图的水平分辨率,即每英寸水平包含的像素数。
  • dpiY: 位图的垂直分辨率,即每英寸垂直包含的像素数。
  • pixelFormat: 位图的像素格式,指定像素的布局和颜色信息的存储方式。
  • palette: 调色板,如果不使用调色板,则传入 null。
  • pixels: 包含位图像素数据的字节数组。
  • stride: 位图的扫描行宽度,即每行像素数据所占的字节数。
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-06-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • Bitmap结构
    • 文件头
      • 位图信息头
        • 颜色信息
          • 图形数据
          • TWain扫描的图片
          • 内存操作
            • 获取图像句柄的内存指针
              • 解析位图信息头
                • 图形数据指针
                  • 复制图形数据
                    • 创建BitmapSource
                    相关产品与服务
                    图数据库 KonisGraph
                    图数据库 KonisGraph(TencentDB for KonisGraph)是一种云端图数据库服务,基于腾讯在海量图数据上的实践经验,提供一站式海量图数据存储、管理、实时查询、计算、可视化分析能力;KonisGraph 支持属性图模型和 TinkerPop Gremlin 查询语言,能够帮助用户快速完成对图数据的建模、查询和可视化分析。
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档