纯python 最快png转换RGB截图方案 ——deepseek

复制代码
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")
相关推荐
weixin_458580121 分钟前
CSS如何控制列表间距_使用padding-left与盒模型
jvm·数据库·python
m0_617881422 分钟前
Tailwind CSS如何实现固定定位布局_使用fixed与z-index控制CSS层级
jvm·数据库·python
iiiiyu14 分钟前
常用API(SimpleDateFormat类 & Calendar类 & JDK8日期 时间 日期时间 & JDK8日期(时区) )
java·大数据·开发语言·数据结构·编程语言
故事和你9116 分钟前
洛谷-数据结构1-4-图的基本应用2
开发语言·数据结构·算法·深度优先·动态规划·图论
m0_6742946417 分钟前
Cgo 中正确处理 const char- 类型回调参数的实践方法
jvm·数据库·python
justjinji25 分钟前
Chart.js 4 中实现基于数据极值的垂直线性渐变填充
jvm·数据库·python
qq_120840937125 分钟前
Three.js 工程向:Clock、deltaTime 与固定步长主循环
开发语言·javascript·ecmascript
小菜同学爱学习27 分钟前
夯实基础!MySQL数据类型进阶、约束详解与报错排查
开发语言·数据库·sql·mysql
迷藏49428 分钟前
# 发散创新:基于Selenium的自动化测试框架重构与实战优化在当今快速迭代的软件开
java·python·selenium·测试工具·重构
天选之子12331 分钟前
Django基本概念入门(一)
python·django·sqlite