我有PIL图像,我试图在ctypes中转换为灰度HBitmap。我对ctypes,C或处理HBITMAP的知识很少。我拼凑了各种来源的代码,例如
这是我到目前为止所拥有的。首先,我初始化了所需的标题:
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
但这些似乎都没有对我的投影机输出产生任何影响。我仍然可以准确地设置图像,只是没有渐变的像素强度。
答案 0 :(得分:0)
@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 的注意事项:
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;
}
应用理念:这可能对人们在像素化工具(如硅基液晶 Spatial Light Modulators (LCOS SLM))上绘制灰度图像很有价值。像这样的 SLM 驱动程序将不需要额外的线程/进程来在 SLM 上运行窗口。在 SLM 上的单独进程(多处理)中与 PyQt5 窗口的速度比较产生的延迟时间降低了大约10 毫秒,可能是因为不需要跨进程通信。我是为搜索引擎提这个的。