关于HBITMAP

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于HBITMAP相关的知识,希望对你有一定的参考价值。

在VC中一般显示bmp要先转换为HBITMAP,为什么不能够直接显示呢?他们又是哪一个与设备有关,哪一个与设备无关呢?
谢谢各位,可是我想问的不是怎么实现,而是为什么这样实现(VC++):
程序所用的函数大致是:CreatDIBitmap,Seletjob,Bitblt; CreatDIBitmap返回值是HBITMAP吧,我就是这里不明白,为什么要这样转换?
书上说这是位图句柄,我知道什么是句柄,那位图句柄怎么理解?

VC中显示bmp要用到CBitmap类,Windows 里显示/画东西还要与device context建立关系。建bitmap GDI object 要调用CBitmap类的函数Attach
()例如:
bitmap.Attach( hBmp );
hBmp 是HBITMAP

bmp本身与设备无关。但显示时它要与设备支持的颜色种类做color mapping.

下面是我的一个子程序,用于我的程序VPIC,你可直接使用。这个子程序打开bmp文件,建bitmap GDI object,也就是得到CBitmap object。

/* =========================================================================*
* LoadBMPImage - Loads a BMP file and creates a bitmap GDI object
* also creates logical palette for it.
* Returns - TRUE for success (check success or not)
* sBMPFile - Full path of the BMP file, (input file name)
* bitmap - The bitmap object to initialize, (bring back img in it)
* pPal - Will hold the logical palette. Can be NULL (bring back)
*==========================================================================*/

BOOL CVPICDoc::LoadBMPImage(LPCTSTR sBMPFile, CBitmap &bitmap, CPalette *pPal)


BITMAPFILEHEADER bmfHeader;

// Read file header

CFile file;
if( !file.Open( sBMPFile, CFile::modeRead) ) return FALSE;

if (file.Read((LPSTR)&bmfHeader, sizeof(bmfHeader)) != sizeof(bmfHeader))
return FALSE;

// Check File type should be 'BM'
if (bmfHeader.bfType != ((WORD) ('M' << 8) | 'B'))
return FALSE;

read_bmp_o = bmfHeader.bfOffBits / 8;

// Get length of the remainder of the file and allocate memory
DWORD nPackedDIBLen = file.GetLength() - sizeof(BITMAPFILEHEADER);
HGLOBAL hDIB = ::GlobalAlloc(GMEM_FIXED, nPackedDIBLen);
if (hDIB == 0)
return FALSE;

// Read the remainder of the bitmap file.
// BITMAPINFOHEADER + possible-Color-Table + DIB-bits
if (file.ReadHuge((LPSTR)hDIB, nPackedDIBLen) != nPackedDIBLen )

::GlobalFree(hDIB);
return FALSE;


BITMAPINFOHEADER &bmiHeader = *(LPBITMAPINFOHEADER)hDIB ;
BITMAPINFO &bmInfo = *(LPBITMAPINFO)hDIB ;

read_bmp_w = bmiHeader.biWidth;
read_bmp_h = bmiHeader.biHeight;
read_bmp_c = bmiHeader.biBitCount;
read_bmp_size = bmiHeader.biSizeImage;
if (read_bmp_size == 0)
read_bmp_size = ((((read_bmp_w * read_bmp_c) + 31) & ~31) >> 3) * read_bmp_h;
;
read_bmp_l = read_bmp_size / read_bmp_h; // scan line width

// If bmiHeader.biClrUsed is zero we have to infer the number
// of colors from the number of bits used to specify it.
int nColors = bmiHeader.biClrUsed ? bmiHeader.biClrUsed :
1 << bmiHeader.biBitCount;

LPVOID lpDIBBits;
if( bmInfo.bmiHeader.biBitCount > 8 )
lpDIBBits = (LPVOID)((LPDWORD)(bmInfo.bmiColors + bmInfo.bmiHeader.biClrUsed) +
((bmInfo.bmiHeader.biCompression == BI_BITFIELDS) ? 3 : 0));
else
lpDIBBits = (LPVOID)(bmInfo.bmiColors + nColors);

// Create the logical palette
if( pPal != NULL )

// Create the palette
if( nColors <= 256 )

UINT nSize = sizeof(LOGPALETTE) + (sizeof(PALETTEENTRY) * nColors);
LOGPALETTE *pLP = (LOGPALETTE *) new BYTE[nSize];

pLP->palVersion = 0x300;
pLP->palNumEntries = nColors;

for( int i=0; i < nColors; i++)

pLP->palPalEntry[i].peRed = bmInfo.bmiColors[i].rgbRed;
pLP->palPalEntry[i].peGreen = bmInfo.bmiColors[i].rgbGreen;
pLP->palPalEntry[i].peBlue = bmInfo.bmiColors[i].rgbBlue;
pLP->palPalEntry[i].peFlags = 0;


pPal->CreatePalette( pLP );

delete[] pLP;



CClientDC dc(NULL);
CPalette* pOldPalette = NULL;
if( pPal )

pOldPalette = dc.SelectPalette( pPal, FALSE );
dc.RealizePalette();


HBITMAP hBmp = CreateDIBitmap( dc.m_hDC,// handle to device context
&bmiHeader, // pointer to BITMAPINFOHEADER
CBM_INIT, // initialization flag
lpDIBBits, // pointer to initialization data
&bmInfo, // pointer to bitmap color-format data
DIB_RGB_COLORS); // color-data usage
bitmap.Attach( hBmp );

if( pOldPalette )
dc.SelectPalette( pOldPalette, FALSE );

::GlobalFree(hDIB);
return TRUE;

参考技术A 不是转换成HBITMAP呀,是要取得BMP图象的句柄的! 参考技术B 楼上的,佩服。

带有python ctypes的灰度hbitmap

【中文标题】带有python ctypes的灰度hbitmap【英文标题】:grayscale hbitmap with python ctypes 【发布时间】:2017-02-01 20:29:34 【问题描述】:

我有 PIL 图像,我正在尝试将其转换为 ctypes 中的灰度 HBitmap。我对 ctypes、C 或处理 HBITMAP 知之甚少。我拼凑了来自各种来源的代码,例如

    Drawing on 8bpp grayscale bitmap (unmanaged C++) http://d.hatena.ne.jp/chrono-meter/20090905/p3

这是我目前所拥有的。首先,我初始化了所需的标头:

import ctypes
from ctypes import wintypes

class BITMAPINFOHEADER(ctypes.Structure):
    _fields_ = [
        ('biSize', wintypes.DWORD),
        ('biWidth', wintypes.LONG),
        ('biHeight', wintypes.LONG),
        ('biPlanes', wintypes.WORD),
        ('biBitCount', wintypes.WORD),
        ('biCompression', wintypes.DWORD),
        ('biSizeImage', wintypes.DWORD),
        ('biXPelsPerMeter', wintypes.LONG),
        ('biYPelsPerMeter', wintypes.LONG),
        ('biClrUsed', wintypes.DWORD),
        ('biClrImportant', wintypes.DWORD),
        ]

class RGBQUAD(ctypes.Structure):
    _fields_ = [
        ('rgbRed', ctypes.c_byte),
        ('rgbGreen', ctypes.c_byte),
        ('rgbBlue', ctypes.c_byte),
        ('rgbReserved', ctypes.c_byte),
    ]

class BITMAPINFO(ctypes.Structure):
    _fields_ = [
        ('bmiHeader', BITMAPINFOHEADER),
        ('bmiColors', ctypes.POINTER(RGBQUAD))
    ]

w,h=image.size

bmi = BITMAPINFO()
bmi.bmiHeader.biSize = ctypes.sizeof(BITMAPINFOHEADER)
bmi.bmiHeader.biWidth = w
bmi.bmiHeader.biHeight = h
bmi.bmiHeader.biPlanes = 1
bmi.bmiHeader.biBitCount = 8
bmi.bmiHeader.biCompression = 0 
bmi.bmiHeader.biSizeImage = 0 

elems=(RGBQUAD*256)()
bmi.bmiColors=ctypes.cast(elems,ctypes.POINTER(RGBQUAD))


for i in range(256):
    bmi.bmiColors[i].rgbRed=i
    bmi.bmiColors[i].rgbGreen=i
    bmi.bmiColors[i].rgbBlue=i
    bmi.bmiColors[i].rgbReserved=0

然后,我创建了我的 hbitmap:

ctypes.windll.LoadLibrary('C:\Windows\System32\gdi32.dll')
gdi=ctypes.WinDLL('C:\Windows\System32\gdi32.dll')

hDC = gdi.CreateCompatibleDC(0)

try:
    dataptr = ctypes.c_void_p()
    result = gdi.CreateDIBSection(hDC, ctypes.byref(bmi), 0,
                                  ctypes.byref(dataptr), None, 0)

    hOldBitmap = gdi.SelectObject(hDC, result)
    try:
        buf = imagebytes
        wintypes.memmove(dataptr, buf, len(buf))
    finally:
        gdi.SelectObject(hDC, hOldBitmap)

finally:
    gdi.DeleteDC(hDC)

hbitmap = result

我正在通过 Python 中的单独代码行将这些 HBITMAP 上传到某些投影仪。我创建的 HBITMAP 似乎部分起作用,因为我可以成功定义要投影的空间模式。我有问题,而不是获得分级像素强度。具体来说,如果我将值设置为 0-127,像素将显示为黑色,如果我将值设置为 128-255,则像素显示为白色,没有渐变。这些让我怀疑这是设置 RGB 调色板的问题。

我已将 PIL 图像文件直接保存为 .bmp 并验证它们具有分级强度值。如果我有办法将最后的 HBITMAP 输出保存到 .bmp,也许会更容易排除故障,但在这个阶段,我只是通过直接上传到我的投影仪来检查这些 HBITMAP。

我也尝试过修改定义调色板的代码,例如:

bmi.bmiColors[i].rgbRed=9999

或:

bmi.bmiColors[i].rgbsRed=i

但这些似乎都对我的投影仪的输出没有任何影响。我仍然可以准确地设置图像,只是没有分级像素强度。

【问题讨论】:

HBITMAP 是一种结构数据类型,它不是一种文件格式。你能举一些例子或链接吗? 抱歉,我只想说 HBITMAP 句柄,我通过 python 将它传递给我的投影仪系统。 “我无法分配调色板值” - 具体如何?它在运行时失败了吗?它成功了,但没有产生预期的结果吗?代码存在一些问题:您没有将 biCompression 成员设置为 BI_RGB(这是必需的)。您将 biSizeImage 设置为非零值,但它是错误的值,因为它没有考虑到扫描线与DWORD 边界的对齐。然后,BITMAPINFO 的大小是可变的(请参阅Why do some structures end with an array of size 1? 以获得解释)。 @IInspectable 我现在已将 biCompressionbiSizeImage 都设置为 0。但是,我对 C++ 的了解太少,不知道如何继续:BITMAPINFO。我知道 BITMAPINFO 由于 bmiColors 的大小可变(正确吗?)。这是否意味着我必须为 BITMAPINFO 正确分配内存?我不知道如何在 ctypes 中执行此操作,我找不到与 alloca() 或 offsetof() 对应的函数,正如我在您在 ***.com/questions/3142349/… 上的回答中看到的那样 【参考方案1】:

@OP:破坏你的 python 代码的是这行:

('bmiColors', ctypes.POINTER(RGBQUAD))

改用:

('bmiColors', RGBQUAD * 256)

像这样初始化:

bmi = BITMAPINFO(BITMAPINFOHEADER(sizeof(BITMAPINFOHEADER), 0, 0, 1, 8, 0, 0, 0, 0, 0, 0),
                 (RGBQUAD * 256)(*[RGBQUAD(i,i,i,0) for i in range(256)]))

并在必要时设置 bmi.bmiHeader.biWidth 和 bmi.bmiHeader.biHeight。

关于在 python 中使用 ctypes 的注意事项:

尽可能为您导入的每个 C 函数设置 .argtypes。不这样做可能会引发异常,即使一切看起来都井井有条。 使用类并初始化 BITMAPINFO,如下所示:(代码不完整!!!)
import ctypes
from ctypes import c_ubyte, c_int, c_uint, c_void_p, POINTER, byref, sizeof
from ctypes.wintypes import WORD, DWORD, LONG, HDC

class RGBQUAD(ctypes.Structure):
    _fields_ = [
        ('rgbRed', c_ubyte),
        ('rgbGreen', c_ubyte),
        ('rgbBlue', c_ubyte),
        ('rgbReserved', c_ubyte)
    ]
class BITMAPINFOHEADER(ctypes.Structure):
    _fields_ = [
        ('biSize', DWORD),
        ('biWidth', LONG),
        ('biHeight', LONG),
        ('biPlanes', WORD), # 1
        ('biBitCount', WORD), # 8
        ('biCompression', DWORD), # BI_RGB = 0 for uncompressed format
        ('biSizeImage', DWORD), # 0
        ('biXPelsPerMeter', LONG), # 0
        ('biYPelsPerMeter', LONG), # 0
        ('biClrUsed', DWORD), # 0
        ('biClrImportant', DWORD) # 0
    ]
class BITMAPINFO(ctypes.Structure):
    _fields_ = [
        ('bmiHeader', BITMAPINFOHEADER),
        ('bmiColors', RGBQUAD * 256)
    ]

SetDIBitsToDevice = ctypes.windll.Gdi32.SetDIBitsToDevice
SetDIBitsToDevice.restype = BOOL # 0 if failed
SetDIBitsToDevice.argtypes = [HDC, c_int, c_int, DWORD, DWORD, c_int, c_int, c_uint, c_uint, c_void_p, POINTER(BITMAPINFO), c_uint]

bmi = BITMAPINFO(BITMAPINFOHEADER(sizeof(BITMAPINFOHEADER), 0, 0, 1, 8, 0, 0, 0, 0, 0, 0),
                 (RGBQUAD * 256)(*[RGBQUAD(i,i,i,0) for i in range(256)]))

SLM_HDC = CreateDC(None, monitor.info.szDevice, None, None)
data = np.array(...).astype(np.uint8)
data_p = data.ctypes.data_as(c_void_p)
SetDIBitsToDevice(SLM_HDC,
                  0, 0,
                  monitor.width(), monitor.height(),
                  0, 0,
                  0, monitor.height(),
                  data_p, byref(bmi), 0)

至于在 C++ 中执行此操作的完整方法,这里有一个代码示例,它创建一个 8 位灰度 DIB 并将其绘制在主监视器上。只需将其编译为 .exe 并运行它,您就会在主显示器上看到对角线灰度图案。说明如下。

#include <cstdlib>
#include <iostream>
#include <malloc.h>
#include <windows.h>

// tell linker where to resolve external dependencies
#pragma comment(lib, "User32.lib")
#pragma comment(lib, "Gdi32.lib")

BITMAPINFO* CreateGreyscaleBITMAPINFO_P(int width, int height) 
    BITMAPINFO* pbmi = (BITMAPINFO*) std::malloc(offsetof(BITMAPINFO, bmiColors[256]));
    pbmi->bmiHeader.biSize = sizeof(pbmi->bmiHeader);
    pbmi->bmiHeader.biWidth = width;
    pbmi->bmiHeader.biHeight = height;
    pbmi->bmiHeader.biPlanes = 1;
    pbmi->bmiHeader.biBitCount = 8;
    pbmi->bmiHeader.biCompression = BI_RGB;
    pbmi->bmiHeader.biSizeImage = 0;
    pbmi->bmiHeader.biXPelsPerMeter = 0;
    pbmi->bmiHeader.biYPelsPerMeter = 0;
    pbmi->bmiHeader.biClrUsed = 0;
    pbmi->bmiHeader.biClrImportant = 0;
    for(int i=0; i<256; i++) 
        pbmi->bmiColors[i].rgbRed = (BYTE)i;
        pbmi->bmiColors[i].rgbGreen = (BYTE)i;
        pbmi->bmiColors[i].rgbBlue = (BYTE)i;
        pbmi->bmiColors[i].rgbReserved = (BYTE)0;
    
    return pbmi;


int main(int argc, char** argv) 
    // to identify screen resolution correctly
    SetProcessDPIAware();

    // get HWND of full primary monitor to retrieve screen resolution and get HDC for drawing
    HWND desktop_HWND = GetDesktopWindow();
    LPRECT desktop_RECT = new RECT();
    if(GetWindowRect(desktop_HWND, desktop_RECT) == 0)  return 0; 
    int width = std::abs(desktop_RECT -> right - desktop_RECT -> left);
    int height = std::abs(desktop_RECT -> bottom - desktop_RECT -> top);
    HDC desktop_DC = GetDC(desktop_HWND);

    // define array with linearly increasing pixel value along the diagonal x=y
    // pixels have 8bit grayscale values from 0 (black) to 255 (white)
    BYTE* array = (BYTE*) std::malloc(sizeof(BYTE) * width * height);
    for(int i=0; i<height; i++) 
        for(int j=0; j<width; j++) 
            array[i*width + j] = ((j + i) % 256);
        
    

    // initialize a BITMAPINFO instance and draw on desktop with SetDIBitsToDevice()
    const BITMAPINFO* bmip = CreateGreyscaleBITMAPINFO_P(width, height);
    int result = SetDIBitsToDevice(
        desktop_DC,
        0, 0, width, height,
        0, 0, 0, height,
        array, bmip, DIB_RGB_COLORS
        );

    // print out for debugging
    std::cout << "primary monitor resolution: " << width << " (width) x " << height << " (height)" << std::endl;
    std::cout << "naive BITMAPINFO length (BYTES): " << sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD)*256
    << " vs. with windows macro offsetof(): " << offsetof(BITMAPINFO, bmiColors[256]) << std::endl;
    std::cout << "bmiHeader.biSize: " << bmip->bmiHeader.biSize << std::endl;
    std::cout << "number of lines drawn on monitor: " << result << std::endl;

使用 malloc() 分配 BITMAPINFO。其他示例使用了 alloca(),这会导致在屏幕上绘制 DIB 之前对 BITMAPINFO 进行垃圾收集。如果您像我一样是物理学家,并且不关心编程细节,请始终使用 malloc() 并记住之后手动 free() 内存。 我不知道 HDC 句柄在这里和一般情况下发生了什么。 设置 bmiColors 时,将整数计数器 i 转换为 (BYTE) 看起来不干净,但对于 i offsetof(BITMAPINFO, bmiColors[256]) 和 sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD)*256 对于每像素 8 位给出相同的结果。

应用理念:这可能对人们在像素化工具上绘制灰度图像很有价值,例如硅上液晶Spatial Light Modulators (LCOS SLM)。像这样的 SLM 驱动程序将消除在 SLM 上运行窗口的额外线程/进程的需要。与 SLM 上单独进程(多处理)中的 PyQt5 窗口的速度比较产生的延迟时间降低了大约 100 倍。 10 毫秒,可能是因为不需要跨进程通信。我是为搜索引擎提到的。

【讨论】:

以上是关于关于HBITMAP的主要内容,如果未能解决你的问题,请参考以下文章

再聊面试,这次关于钱,关于培训,关于内卷

关于数学,关于年少的感动,关于初心

scrapy 关于 rule, 关于多页

JS学习笔记关于选项卡,关于this,关于innerHTML

关于唯典冰淇淋新闻页的布局

关于prototype.js的一些技术说明