SetDIBitsToDevice函数的理解

int SetDIBitsToDevice(
  HDC hdc,                 // handle to DC
  int XDest,               // x-coord of destination upper-left corner
  int YDest,               // y-coord of destination upper-left corner
  DWORD dwWidth,           // source rectangle width
  DWORD dwHeight,          // source rectangle height
  int XSrc,                // x-coord of source lower-left corner
  int YSrc,                // y-coord of source lower-left corner
  UINT uStartScan,         // first scan line in array
  UINT cScanLines,         // number of scan lines
  CONST VOID *lpvBits,     // array of DIB bits
  CONST BITMAPINFO *lpbmi, // bitmap information
  UINT fuColorUse          // RGB or palette indexes
);

个人对SetDIBitsToDevice函数的理解,说句实话这个函数的msdn说的让人很晦涩难懂,我把自己的理

解和大家分享一下,不知道正确与否。首先不要去管什么源左下角坐标和目标左上角的说法。这个左下

和右上的概念是相对于笛卡尔坐标系(屏幕左下角为坐标原点)而言。而我们函数使用时参数msdn指定

的是逻辑坐标系,如果采用默认的映射方式(MM_TEXT)的映射方式,映射到设备DC上的坐标系的坐标

原点是屏幕左上角,正方向为向右和向下。换句话说对于从下而上的位图而言,msdn的说法是有问题的

,因为存储在内存中的从下而上的dib位图文件相对于逻辑坐标系而言,(xSrc, ySrc)实际上是dib位图

文件的左上角,而(xDst, yDst)是左下角。

下面就我的理解解释一下这些参数到底做了些什么,如果照搬msdn当然也就不用写了,我是用我的通俗

的理解方法,希望给大家有点帮助。
1.XSrc, YSrc,dwWidth, dwHeight这四个参数决定了一个源矩形,现在假设在内存中有这样一块源矩形

大小的空白画板。现在只是有这样一块空白画板,至于画什么,由第2步的参数决定。

2. 现在有这样一块空白画板了。lpvBits,cScanLines这两个参数最为重要,他们联合决定在空画板上

画什么内容。lpvBits指向的是你要显示的字节类型的位图数组,cScanLines表示的是该位图数组中的

扫描线数目。通俗的说就是:lpvBits指定了你要从整个位图的哪一行开始扫描,cScanLines指定了你

要扫描多少行。这两个参数一决定,实际上就在你从内存中加载的位图中相当于截取了图形的某一块。

3.uStartScan这个参数不得不提,这个参数很容易出错,不要将这个参数理解成用来选定位图数据的起

始行,其实这个参数指定的是在第1步所决定的空白画板中的什么位置开始显示由第2步的两个参数截取

的图形(这个图形可能是整个位图,也可能只是图形的一部分)。

4.最后一步根据映射关系,将由源矩形确定的画板上画好的内容(第1,2,3步已经指明在画板什么位置

,画上从内存中加载的位图的哪一部分)映射到目标矩形,也就是我们真实的在目标设备dc中看到的图

像。如果是默认的映射方式,查看源坐标和目标坐标的映射表会发现,就是将源矩形进行了上下翻转,

就得到真实的在屏幕上看到目标图像。

本人对SetDIBitsToDevice函数的理解 - jixiang1119 - jixiang

 
参考《windows程序设计》,修改程序15-3 的APOLLO11程序中的参数,我们来看看效果。具体的程序较

长,我没有贴出来。我们就看SetDIBitsToDevice函数的修改参数后的效果。


               // Bottom-up DIB full size

          SetDIBitsToDevice (hdc,
                             0,                   // xDst
                             0,                   // yDst
                             cxDib[0],            // cxSrc
                             cyDib[0],            // cySrc
                             0 ,                   // xSrc
                             0,                    // ySrc
                             0,                    // 从什么位置开始显示dib
                             cyDib[0]/2,            // 扫描多少行
                             pBits[0],              //指定从dib的什么位置开始扫描
                             pbmi[0],              
                             DIB_RGB_COLORS) ;
注意本程序中 pBits[0] = (BYTE *) pbmfh[0] + pbmfh[0]->bfOffBits ;指向的是内存中dib的第一行

那么函数实现的效果就是。将dib位图的上半部分截取下来,放到起始位置为0的大小为源矩形大小的

画板中。然后上下翻转图形,即可看到真实显示在屏幕上的图形效果。

本人对SetDIBitsToDevice函数的理解 - jixiang1119 - jixiang

 

 

从程序运行结果来看,得到了宇航员的身体的下半部分。如果要得到上半部分即看到宇航员的头怎么做

呢,千万别告诉我修改uStartScan参数啊,不然我说半天白说了。正确的方法是修改lpvBits指针,让

它指向dib的下半部分,让它扫描dib的下半部分,这样就可以看到头了。具体操作就是先要计算dib位

图的每一行的宽度。然后乘以高度的1/2,就可以使lpvBits指针指向dii位图的中间。然后往下扫描就可以了。
     int dibWidth = cxDib[0];    //计算dib每一行的字节数,注意默认是4字节对齐的,加个判断


    if(dibWidth % 4 == 0)
    {
     dibWidth = cxDib[0];
    }else
     dibWidth = cxDib[0] + 4 - cxDib[0] / 4;

pBits[0] = (BYTE *) pbmfh[0] + pbmfh[0]->bfOffBits + dibWidth * cyDib [0] / 2;

再次调用

          SetDIBitsToDevice (hdc,
                             0,                   // xDst
                             0,                   // yDst
                             cxDib[0],            // cxSrc
                             cyDib[0],            // cySrc
                             0 ,                   // xSrc
                             0,                    // ySrc
                             0,                    // 从什么位置开始显示dib
                             cyDib[0]/2,            // 扫描多少行
                             pBits[0],              //指定从dib的什么位置开始扫描
                             pbmi[0],              
                             DIB_RGB_COLORS) ;

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
为了实现这个功能,我们将使用以下库: - OpenCV:用于读取视频流和处理图像数据 - Windows GDI:用于创建离屏表面和将图像渲染到窗口 - Qt:用于调用动态库和管理窗口 首先,我们需要创建一个 Windows 动态库项目。在 Visual Studio 中,选择“新建项目”->“Win32 控制台应用程序”->“DLL”。我们将在该项目中编写我们的函数。 我们将使用以下函数签名: ```cpp void render_video(HWND hwnd, const char* filename); ``` 这个函数将接受一个窗口句柄和一个视频文件名作为参数。它将在离屏表面上读取视频帧,并使用 Windows GDI 将它们渲染到窗口。 我们将首先使用 OpenCV 打开视频文件并读取第一帧: ```cpp cv::VideoCapture cap(filename); cv::Mat frame; cap.read(frame); ``` 接下来,我们将创建一个离屏表面并将其与窗口绑定。我们还需要创建一个设备上下文(DC),以便将图像渲染到表面: ```cpp HDC hdc = GetDC(hwnd); HDC memdc = CreateCompatibleDC(hdc); HBITMAP hbitmap = CreateCompatibleBitmap(hdc, frame.cols, frame.rows); SelectObject(memdc, hbitmap); ``` 现在,我们可以将第一帧图像复制到表面上: ```cpp cv::Mat flipped; cv::flip(frame, flipped, 0); BITMAPINFOHEADER bmih = { sizeof(BITMAPINFOHEADER), flipped.cols, -flipped.rows, 1, 24, BI_RGB, 0, 0, 0, 0, 0 }; SetDIBitsToDevice(memdc, 0, 0, flipped.cols, flipped.rows, 0, 0, 0, flipped.rows, flipped.data, (BITMAPINFO*)&bmih, DIB_RGB_COLORS); ``` 我们将图像翻转并使用 GDI 函数 `SetDIBitsToDevice` 将其复制到表面上。我们还需要释放我们创建的对象: ```cpp DeleteObject(hbitmap); DeleteDC(memdc); ReleaseDC(hwnd, hdc); ``` 现在,我们需要使用 Qt 创建一个窗口并调用我们的函数。在 Qt 中,我们可以使用 `QWindow::fromWinId` 函数从窗口句柄创建一个 `QWindow` 对象。我们可以将这个对象传递给我们的函数: ```cpp void MainWindow::on_pushButton_clicked() { HWND hwnd = (HWND)centralWidget()->winId(); render_video(hwnd, "test.mp4"); } ``` 最后,我们将完整的源代码放在一起: ```cpp #include <Windows.h> #include <opencv2/opencv.hpp> #include <QtWidgets/QMainWindow> #include <QtWidgets/QPushButton> void render_video(HWND hwnd, const char* filename) { cv::VideoCapture cap(filename); cv::Mat frame; cap.read(frame); HDC hdc = GetDC(hwnd); HDC memdc = CreateCompatibleDC(hdc); HBITMAP hbitmap = CreateCompatibleBitmap(hdc, frame.cols, frame.rows); SelectObject(memdc, hbitmap); cv::Mat flipped; cv::flip(frame, flipped, 0); BITMAPINFOHEADER bmih = { sizeof(BITMAPINFOHEADER), flipped.cols, -flipped.rows, 1, 24, BI_RGB, 0, 0, 0, 0, 0 }; SetDIBitsToDevice(memdc, 0, 0, flipped.cols, flipped.rows, 0, 0, 0, flipped.rows, flipped.data, (BITMAPINFO*)&bmih, DIB_RGB_COLORS); DeleteObject(hbitmap); DeleteDC(memdc); ReleaseDC(hwnd, hdc); } class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = Q_NULLPTR) : QMainWindow(parent) { QPushButton* button = new QPushButton("Render video", this); setCentralWidget(button); connect(button, &QPushButton::clicked, this, &MainWindow::on_pushButton_clicked); } private slots: void on_pushButton_clicked() { HWND hwnd = (HWND)centralWidget()->winId(); render_video(hwnd, "test.mp4"); } }; int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); } ``` 这个例子演示了如何在 Windows 动态库中读取视频流并将其渲染到窗口中。我们使用 OpenCV 读取视频帧,使用 Windows GDI 创建离屏表面并将图像渲染到表面上。在 Qt 中,我们使用 `QWindow::fromWinId` 函数从窗口句柄创建一个 `QWindow` 对象,并将其传递给我们的函数

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值