复制代码
import io
import time
import zlib
import struct
import ctypes
gdi32 = ctypes.windll.gdi32
user32 = ctypes.windll.user32
SM_CXSCREEN = 0
SM_CYSCREEN = 1
zoom = 1
screenWidth = int(user32.GetSystemMetrics(SM_CXSCREEN) * zoom)
screenHeight = int(user32.GetSystemMetrics(SM_CYSCREEN) * zoom)
# ---------- 像素转换:逐行优化版 ----------
def bgr_to_rgb_rows(mv, width, height):
"""纯Python最优实现:循环行数,每行内整块交换"""
row_bytes = width * 3
out = bytearray()
for i in range(0, len(mv), row_bytes):
# 提取一行,复制为bytearray(C级memcpy)
row = bytearray(mv[i:i+row_bytes])
# 行内交换R/B(同类型切片赋值,高速路径)
row[0::3], row[2::3] = row[2::3], row[0::3]
out.append(0) # PNG 无过滤标志(可去掉)
out.extend(row)
return out
# 屏幕截图
def capture_screen(x, y, width, height):
# 1. 获取DC
hwnd = user32.GetDesktopWindow()
hdc_src = user32.GetDC(hwnd)
hdc_dest = gdi32.CreateCompatibleDC(hdc_src)
# 2. 创建兼容位图
bmp = gdi32.CreateCompatibleBitmap(hdc_src, width, height)
old_bmp = gdi32.SelectObject(hdc_dest, bmp)
# 3. BitBlt 拷贝屏幕
SRCCOPY = 0x00CC0020
gdi32.BitBlt(hdc_dest, 0, 0, width, height, hdc_src, x, y, SRCCOPY)
# 4. 计算 DIB 每行实际字节数(强制4字节对齐)
bits_per_pixel = 24
stride = ((width * bits_per_pixel + 31) // 32) * 4
buffer_size = stride * height
class BITMAPINFOHEADER(ctypes.Structure):
_fields_ = [
("biSize", ctypes.c_uint),
("biWidth", ctypes.c_int),
("biHeight", ctypes.c_int),
("biPlanes", ctypes.c_ushort),
("biBitCount", ctypes.c_ushort),
("biCompression", ctypes.c_uint),
("biSizeImage", ctypes.c_uint),
("biXPelsPerMeter", ctypes.c_int),
("biYPelsPerMeter", ctypes.c_int),
("biClrUsed", ctypes.c_uint),
("biClrImportant", ctypes.c_uint)
]
class BITMAPINFO(ctypes.Structure):
_fields_ = [
("bmiHeader", BITMAPINFOHEADER),
("bmiColors", ctypes.c_byte * 3)
]
bmi = BITMAPINFO()
bmi.bmiHeader.biSize = ctypes.sizeof(BITMAPINFOHEADER)
bmi.bmiHeader.biWidth = width
bmi.bmiHeader.biHeight = -height # 自顶向下
bmi.bmiHeader.biPlanes = 1
bmi.bmiHeader.biBitCount = bits_per_pixel
bmi.bmiHeader.biCompression = 0 # BI_RGB
bmi.bmiHeader.biSizeImage = 0
# 分配对齐后的缓冲区
pixel_data = (ctypes.c_ubyte * buffer_size)()
ret = gdi32.GetDIBits(hdc_src, bmp, 0, height, pixel_data,
ctypes.byref(bmi), 0) # DIB_RGB_COLORS=0
if ret == 0:
raise RuntimeError("GetDIBits failed")
# 5. 转为 memoryview,便于切片
mv = memoryview(pixel_data).cast('B')
# 注意:mv 包含 stride 填充字节,但我们只取有效宽度 * 3
# 需要逐行拷贝到无填充的缓冲区
clean = bytearray()
for row in range(height):
start = row * stride
clean.extend(mv[start:start + width * 3])
# 此时 clean 是紧凑的 BGR 数据,无填充
# 6. 像素转换(使用逐行优化)
t0 = time.perf_counter()
b_stream = bgr_to_rgb_rows(memoryview(clean), width, height)
t1 = time.perf_counter()
print(f"像素转换耗时: {(t1-t0)*1000:.2f} ms")
# 7. 构建PNG
def crc32_b(data):
return zlib.crc32(data).to_bytes(4, 'big')
png_signature = b"\x89PNG\r\n\x1a\n"
ihdr = struct.pack("!IIBBBBB", width, height, 8, 2, 0, 0, 0)
mid = crc32_b(b"IHDR" + ihdr)
bd = zlib.compress(b_stream)
crc_bytes = crc32_b(b"IDAT" + bd)
idat_chunk = b"IDAT" + bd + crc_bytes
iend = b"\x00\x00\x00\x00IEND" + crc32_b(b"IEND")
png_io = io.BytesIO()
png_io.write(png_signature + b"\x00\x00\x00\rIHDR" + ihdr + mid)
idat_header = struct.pack("!I", len(bd))
png_io.write(idat_header + idat_chunk)
png_io.write(iend)
with open("photo.png", "wb") as f:
f.write(png_io.getvalue())
# 8. 释放资源
gdi32.SelectObject(hdc_dest, old_bmp)
gdi32.DeleteDC(hdc_dest)
user32.ReleaseDC(hwnd, hdc_src)
gdi32.DeleteObject(bmp)
# ---------- 执行 ----------
if __name__ == "__main__":
s = time.perf_counter()
capture_screen(0, 0, screenWidth, screenHeight)
e = time.perf_counter()
print(f"总耗时: {(e-s)*1000:.2f} ms")
python
复制代码
import io
import time
import zlib
import struct
import ctypes
gdi32 = ctypes.windll.gdi32
user32 = ctypes.windll.user32
SM_CXSCREEN = 0
SM_CYSCREEN = 1
zoom = 1
screenWidth = int(user32.GetSystemMetrics(SM_CXSCREEN) * zoom)
screenHeight = int(user32.GetSystemMetrics(SM_CYSCREEN) * zoom)
# ---------- 像素转换:逐行优化版 ----------
def bgr_to_rgb_rows(mv, width, height):
"""纯Python最优实现:循环行数,每行内整块交换"""
row_bytes = width * 3
out = bytearray()
for i in range(0, len(mv), row_bytes):
# 提取一行,复制为bytearray(C级memcpy)
row = bytearray(mv[i:i+row_bytes])
# 行内交换R/B(同类型切片赋值,高速路径)
row[0::3], row[2::3] = row[2::3], row[0::3]
out.append(0) # PNG 无过滤标志(可去掉)
out.extend(row)
return out
# 屏幕截图
def capture_screen(x, y, width, height):
# 1. 获取DC
hwnd = user32.GetDesktopWindow()
hdc_src = user32.GetDC(hwnd)
hdc_dest = gdi32.CreateCompatibleDC(hdc_src)
# 2. 创建兼容位图
bmp = gdi32.CreateCompatibleBitmap(hdc_src, width, height)
old_bmp = gdi32.SelectObject(hdc_dest, bmp)
# 3. BitBlt 拷贝屏幕
SRCCOPY = 0x00CC0020
gdi32.BitBlt(hdc_dest, 0, 0, width, height, hdc_src, x, y, SRCCOPY)
# 4. 计算 DIB 每行实际字节数(强制4字节对齐)
bits_per_pixel = 24
stride = ((width * bits_per_pixel + 31) // 32) * 4
buffer_size = stride * height
class BITMAPINFOHEADER(ctypes.Structure):
_fields_ = [
("biSize", ctypes.c_uint),
("biWidth", ctypes.c_int),
("biHeight", ctypes.c_int),
("biPlanes", ctypes.c_ushort),
("biBitCount", ctypes.c_ushort),
("biCompression", ctypes.c_uint),
("biSizeImage", ctypes.c_uint),
("biXPelsPerMeter", ctypes.c_int),
("biYPelsPerMeter", ctypes.c_int),
("biClrUsed", ctypes.c_uint),
("biClrImportant", ctypes.c_uint)
]
class BITMAPINFO(ctypes.Structure):
_fields_ = [
("bmiHeader", BITMAPINFOHEADER),
("bmiColors", ctypes.c_byte * 3)
]
bmi = BITMAPINFO()
bmi.bmiHeader.biSize = ctypes.sizeof(BITMAPINFOHEADER)
bmi.bmiHeader.biWidth = width
bmi.bmiHeader.biHeight = -height # 自顶向下
bmi.bmiHeader.biPlanes = 1
bmi.bmiHeader.biBitCount = bits_per_pixel
bmi.bmiHeader.biCompression = 0 # BI_RGB
bmi.bmiHeader.biSizeImage = 0
# 分配对齐后的缓冲区
pixel_data = (ctypes.c_ubyte * buffer_size)()
ret = gdi32.GetDIBits(hdc_src, bmp, 0, height, pixel_data,
ctypes.byref(bmi), 0) # DIB_RGB_COLORS=0
if ret == 0:
raise RuntimeError("GetDIBits failed")
# 5. 转为 memoryview,便于切片
mv = memoryview(pixel_data).cast('B')
# 注意:mv 包含 stride 填充字节,但我们只取有效宽度 * 3
# 需要逐行拷贝到无填充的缓冲区
clean = bytearray()
for row in range(height):
start = row * stride
clean.extend(mv[start:start + width * 3])
# 此时 clean 是紧凑的 BGR 数据,无填充
# 6. 像素转换(使用逐行优化)
t0 = time.perf_counter()
b_stream = bgr_to_rgb_rows(memoryview(clean), width, height)
t1 = time.perf_counter()
print(f"像素转换耗时: {(t1-t0)*1000:.2f} ms")
# 7. 构建PNG
def crc32_b(data):
return zlib.crc32(data).to_bytes(4, 'big')
png_signature = b"\x89PNG\r\n\x1a\n"
ihdr = struct.pack("!IIBBBBB", width, height, 8, 2, 0, 0, 0)
mid = crc32_b(b"IHDR" + ihdr)
bd = zlib.compress(b_stream)
crc_bytes = crc32_b(b"IDAT" + bd)
idat_chunk = b"IDAT" + bd + crc_bytes
iend = b"\x00\x00\x00\x00IEND" + crc32_b(b"IEND")
png_io = io.BytesIO()
png_io.write(png_signature + b"\x00\x00\x00\rIHDR" + ihdr + mid)
idat_header = struct.pack("!I", len(bd))
png_io.write(idat_header + idat_chunk)
png_io.write(iend)
with open("photo.png", "wb") as f:
f.write(png_io.getvalue())
# 8. 释放资源
gdi32.SelectObject(hdc_dest, old_bmp)
gdi32.DeleteDC(hdc_dest)
user32.ReleaseDC(hwnd, hdc_src)
gdi32.DeleteObject(bmp)
# ---------- 执行 ----------
if __name__ == "__main__":
s = time.perf_counter()
capture_screen(0, 0, screenWidth, screenHeight)
e = time.perf_counter()
print(f"总耗时: {(e-s)*1000:.2f} ms")