python:使用Windows API渲染TTF字体文本
有没有一个完整的例子,能从头到尾展示如何打开一个特定的.ttf
字体文件,并用这个字体渲染一些文字,最后在内存中得到一个位图,使用的是原生的Windows API?我现在正在努力学习Windows API,所以我和StackOverflow上的其他人之间的竞争很激烈。
2 个回答
2
这是针对Python 3的更新版本,来自被接受的答案(完全归功于@Claudiu)。我用“Segoe UI”作为例子,来展示我们如何完美匹配Windows的原生用户界面:
import ctypes, struct, win32con, win32gui, win32ui, PIL.Image
def native_bmp_to_pil(hdc, bitmap_handle, width, height):
bmpheader = struct.pack("LHHHH", struct.calcsize("LHHHH"), width, height, 1, 24)
c_bmpheader = ctypes.c_buffer(bmpheader)
c_bits = ctypes.c_buffer(b" " * (height * ((width*3 + 3) & -4)))
res = ctypes.windll.gdi32.GetDIBits(hdc, bitmap_handle, 0, height, c_bits, c_bmpheader, win32con.DIB_RGB_COLORS)
if not res:
raise IOError("native_bmp_to_pil failed: GetDIBits")
im = PIL.Image.frombuffer("RGB", (width, height), c_bits, "raw", "BGR", (width*3 + 3) & -4, -1)
return im
class Win32Font:
def __init__(self, name, height, weight=win32con.FW_NORMAL, italic=False, underline=False):
self.font = win32ui.CreateFont({'name': name, 'height': height, 'weight': weight, 'italic': italic, 'underline': underline})
self.desktopHwnd = win32gui.GetDesktopWindow()
self.desktopDC = win32gui.GetWindowDC(self.desktopHwnd)
self.mfcDC = win32ui.CreateDCFromHandle(self.desktopDC)
self.drawDC = self.mfcDC.CreateCompatibleDC()
self.drawDC.SelectObject(self.font)
def renderText(self, text):
self.drawDC.SetTextColor(0)
w,h = self.drawDC.GetTextExtent(text)
saveBitMap = win32ui.CreateBitmap()
saveBitMap.CreateCompatibleBitmap(self.mfcDC, w, h)
self.drawDC.SelectObject(saveBitMap)
self.drawDC.DrawText(text, (0, 0, w, h), win32con.DT_LEFT)
im = native_bmp_to_pil(self.drawDC.GetSafeHdc(), saveBitMap.GetHandle(), w, h)
win32gui.DeleteObject(saveBitMap.GetHandle())
return im
def __del__(self):
self.mfcDC.DeleteDC()
self.drawDC.DeleteDC()
win32gui.ReleaseDC(self.desktopHwnd, self.desktopDC)
win32gui.DeleteObject(self.font.GetSafeHandle())
f = Win32Font("Segoe UI", 15)
im = f.renderText("Hello World")
im.save("test.png")
9
完成了字体渲染的工作(需要用到PyWin32库):
import ctypes
import struct
import win32con
import win32gui
import win32ui
from PIL import Image
def RGB(r, g, b):
return r | (g << 8) | (b << 16)
def native_bmp_to_pil(hdc, bitmap_handle, width, height):
bmpheader = struct.pack("LHHHH", struct.calcsize("LHHHH"),
width, height, 1, 24) #w,h, planes=1, bitcount)
c_bmpheader = ctypes.c_buffer(bmpheader)
#3 bytes per pixel, pad lines to 4 bytes
c_bits = ctypes.c_buffer(" " * (height * ((width*3 + 3) & -4)))
res = ctypes.windll.gdi32.GetDIBits(
hdc, bitmap_handle, 0, height,
c_bits, c_bmpheader,
win32con.DIB_RGB_COLORS)
if not res:
raise IOError("native_bmp_to_pil failed: GetDIBits")
im = Image.frombuffer(
"RGB", (width, height), c_bits,
"raw", "BGR", (width*3 + 3) & -4, -1)
return im
class Win32Font:
def __init__(self, name, height, weight=win32con.FW_NORMAL,
italic=False, underline=False):
self.font = win32ui.CreateFont({
'name': name, 'height': height,
'weight': weight, 'italic': italic, 'underline': underline})
#create a compatible DC we can use to draw:
self.desktopHwnd = win32gui.GetDesktopWindow()
self.desktopDC = win32gui.GetWindowDC(self.desktopHwnd)
self.mfcDC = win32ui.CreateDCFromHandle(self.desktopDC)
self.drawDC = self.mfcDC.CreateCompatibleDC()
#initialize it
self.drawDC.SelectObject(self.font)
def renderText(self, text):
"""render text to a PIL image using the windows API."""
self.drawDC.SetTextColor(RGB(255,0,0))
#create the compatible bitmap:
w,h = self.drawDC.GetTextExtent(text)
saveBitMap = win32ui.CreateBitmap()
saveBitMap.CreateCompatibleBitmap(self.mfcDC, w, h)
self.drawDC.SelectObject(saveBitMap)
#draw it
self.drawDC.DrawText(text, (0, 0, w, h), win32con.DT_LEFT)
#convert to PIL image
im = native_bmp_to_pil(self.drawDC.GetSafeHdc(), saveBitMap.GetHandle(), w, h)
#clean-up
win32gui.DeleteObject(saveBitMap.GetHandle())
return im
def __del__(self):
self.mfcDC.DeleteDC()
self.drawDC.DeleteDC()
win32gui.ReleaseDC(self.desktopHwnd, self.desktopDC)
win32gui.DeleteObject(self.font.GetSafeHandle())
def __del__(self):
win32gui.DeleteObject(self.font.GetSafeHandle())
使用方法:
>>> f = Win32Font("Arial", 15)
>>> im = f.renderText("this is just a test")
>>> im.save("c:/hope.png")
结果:
太棒了!!!
要渲染一个特定的.ttf
文件,我还需要进一步探索。
更新:更新了计算位图大小的功能: