如何在 pysdl2 中嵌入 skia python 表面
How to embed skia python surface inside pysdl2
我正在尝试使用文档中的以下代码嵌入 skia-python
's surface inside a window rather than output to a image file. I am using pysdl2
以创建 window:
import sys
import sdl2.ext
RESOURCES = sdl2.ext.Resources(__file__, "resources")
sdl2.ext.init()
window = sdl2.ext.Window("Hello World!", size=(640, 480))
window.show()
factory = sdl2.ext.SpriteFactory(sdl2.ext.SOFTWARE)
sprite = factory.from_image(RESOURCES.get_path("hello.bmp"))
spriterenderer = factory.create_sprite_render_system(window)
spriterenderer.render(sprite)
processor = sdl2.ext.TestEventProcessor()
processor.run(window)
sdl2.ext.quit()
以及从 skia 的文档中创建表面的代码:
import skia
surface = skia.Surface(128, 128)
with surface as canvas:
rect = skia.Rect(32, 32, 96, 96)
paint = skia.Paint(
Color=skia.ColorBLUE,
Style=skia.Paint.kFill_Style)
canvas.drawRect(rect, paint)
image = surface.makeImageSnapshot()
image.save('output.png', skia.kPNG)
现在我想要实现的是从 skia 部分获取 image
(或 surface
适用的)对象并将其插入 pysdl2 以便我可以使用 skia 进行绘制但处理 window 与 pysdl2 的事件循环,我现在想避免使用 ctypes,因为我不太熟悉它。
我放弃了在没有 ctypes
的情况下创建它,因为我们只需要 ctypes.byref
并且我现在正在导入 sdl2
而不是 sdl2.ext
这更像 pythonic , 但也限制了此处所需的一些功能。
现在回答这个问题,我遵循了这个指南here(如果你不是在构建浏览器,它可能对你来说有点偏离主题)
所以我还从上面的指南中实现了一个足够通用的版本,你可以绘制到 Window.skia_surface
然后调用 Window.update
将 skia 表面复制到 window 屏幕:
import skia
import sdl2 as sdl
from ctypes import byref
def sdl_event_loop():
event = sdl.SDL_Event()
running = True
while running:
pending_events = sdl.SDL_PollEvent(byref(event))
while pending_events != 0:
# QUIT HANDLER
if event.type == sdl.SDL_QUIT:
running = False
sdl.SDL_Quit()
break
# UPDATE PENDING EVENTS
pending_events = sdl.SDL_PollEvent(byref(event))
class Window:
DEFAULT_FLAGS = sdl.SDL_WINDOW_SHOWN
BYTE_ORDER = {
# ---------- -> RED GREEN BLUE ALPHA
"BIG_ENDIAN": (0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff),
"LIL_ENDIAN": (0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000)
}
PIXEL_DEPTH = 32 # BITS PER PIXEL
PIXEL_PITCH_FACTOR = 4 # Multiplied by Width to get BYTES PER ROW
def __init__(self, title, width, height, x=None, y=None, flags=None):
self.title = bytes(title, "utf8")
self.width = width
self.height = height
# Center Window By default
self.x, self.y = x, y
if x is None:
self.x = sdl.SDL_WINDOWPOS_CENTERED
if y is None:
self.y = sdl.SDL_WINDOWPOS_CENTERED
# Override flags
self.flags = flags
if flags is None:
self.flags = self.DEFAULT_FLAGS
# SET RGBA MASKS BASED ON BYTE_ORDER
is_big_endian = sdl.SDL_BYTEORDER == sdl.SDL_BIG_ENDIAN
if is_big_endian:
self.RGBA_MASKS = self.BYTE_ORDER["BIG_ENDIAN"]
else:
self.RGBA_MASKS = self.BYTE_ORDER["LIL_ENDIAN"]
# CALCULATE PIXEL PITCH
self.PIXEL_PITCH = self.PIXEL_PITCH_FACTOR * self.width
# SKIA INIT
self.skia_surface = self.__create_skia_surface()
# SDL INIT
sdl.SDL_Init(sdl.SDL_INIT_EVENTS) # INITIALIZE SDL EVENTS
self.sdl_window = self.__create_SDL_Window()
sdl_event_loop()
def __create_SDL_Window(self):
window = sdl.SDL_CreateWindow(
self.title,
self.x, self.y,
self.width, self.height,
self.flags
)
return window
def __create_skia_surface(self):
surface_blueprint = skia.ImageInfo.Make(
self.width, self.height,
ct=skia.kRGBA_8888_ColorType,
at=skia.kUnpremul_AlphaType
)
surface = skia.Surface.MakeRaster(surface_blueprint)
return surface
def __pixels_from_skia_surface(self):
image = self.skia_surface.makeImageSnapshot()
pixels = image.tobytes()
return pixels
def __transform_skia_surface_to_SDL_surface(self):
pixels = self.__pixels_from_skia_surface()
sdl_surface = sdl.SDL_CreateRGBSurfaceFrom(
pixels,
self.width, self.height,
self.PIXEL_DEPTH, self.PIXEL_PITCH,
*self.RGBA_MASKS
)
return sdl_surface
def update(self):
rect = sdl.SDL_Rect(0, 0, self.width, self.height)
window_surface = sdl.SDL_GetWindowSurface(self.sdl_window) # the SDL surface associated with the window
transformed_skia_surface = self.__transform_skia_surface_to_SDL_surface()
# Transfer skia surface to SDL window's surface
sdl.SDL_BlitSurface(
transformed_skia_surface, rect,
window_surface, rect
)
# Update window with new copied data
sdl.SDL_UpdateWindowSurface(self.sdl_window)
if __name__ == "__main__":
window = Window("Browser Test", 500, 500, flags=sdl.SDL_WINDOW_SHOWN | sdl.SDL_WINDOW_RESIZABLE)
说明:上面的代码主要做了四件事:创建一个您将在其上绘制的 skia 表面,创建一个 SDL window,将 skia 表面转换为 SDL 表面,最后将数据复制到新的创建表面到与 window 关联的 SDL 表面并更新它。如需更多解释,我建议您查看上述指南并查看 skia-python docs and SDL2's API Reference.
我正在尝试使用文档中的以下代码嵌入 skia-python
's surface inside a window rather than output to a image file. I am using pysdl2
以创建 window:
import sys
import sdl2.ext
RESOURCES = sdl2.ext.Resources(__file__, "resources")
sdl2.ext.init()
window = sdl2.ext.Window("Hello World!", size=(640, 480))
window.show()
factory = sdl2.ext.SpriteFactory(sdl2.ext.SOFTWARE)
sprite = factory.from_image(RESOURCES.get_path("hello.bmp"))
spriterenderer = factory.create_sprite_render_system(window)
spriterenderer.render(sprite)
processor = sdl2.ext.TestEventProcessor()
processor.run(window)
sdl2.ext.quit()
以及从 skia 的文档中创建表面的代码:
import skia
surface = skia.Surface(128, 128)
with surface as canvas:
rect = skia.Rect(32, 32, 96, 96)
paint = skia.Paint(
Color=skia.ColorBLUE,
Style=skia.Paint.kFill_Style)
canvas.drawRect(rect, paint)
image = surface.makeImageSnapshot()
image.save('output.png', skia.kPNG)
现在我想要实现的是从 skia 部分获取 image
(或 surface
适用的)对象并将其插入 pysdl2 以便我可以使用 skia 进行绘制但处理 window 与 pysdl2 的事件循环,我现在想避免使用 ctypes,因为我不太熟悉它。
我放弃了在没有 ctypes
的情况下创建它,因为我们只需要 ctypes.byref
并且我现在正在导入 sdl2
而不是 sdl2.ext
这更像 pythonic , 但也限制了此处所需的一些功能。
现在回答这个问题,我遵循了这个指南here(如果你不是在构建浏览器,它可能对你来说有点偏离主题)
所以我还从上面的指南中实现了一个足够通用的版本,你可以绘制到 Window.skia_surface
然后调用 Window.update
将 skia 表面复制到 window 屏幕:
import skia
import sdl2 as sdl
from ctypes import byref
def sdl_event_loop():
event = sdl.SDL_Event()
running = True
while running:
pending_events = sdl.SDL_PollEvent(byref(event))
while pending_events != 0:
# QUIT HANDLER
if event.type == sdl.SDL_QUIT:
running = False
sdl.SDL_Quit()
break
# UPDATE PENDING EVENTS
pending_events = sdl.SDL_PollEvent(byref(event))
class Window:
DEFAULT_FLAGS = sdl.SDL_WINDOW_SHOWN
BYTE_ORDER = {
# ---------- -> RED GREEN BLUE ALPHA
"BIG_ENDIAN": (0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff),
"LIL_ENDIAN": (0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000)
}
PIXEL_DEPTH = 32 # BITS PER PIXEL
PIXEL_PITCH_FACTOR = 4 # Multiplied by Width to get BYTES PER ROW
def __init__(self, title, width, height, x=None, y=None, flags=None):
self.title = bytes(title, "utf8")
self.width = width
self.height = height
# Center Window By default
self.x, self.y = x, y
if x is None:
self.x = sdl.SDL_WINDOWPOS_CENTERED
if y is None:
self.y = sdl.SDL_WINDOWPOS_CENTERED
# Override flags
self.flags = flags
if flags is None:
self.flags = self.DEFAULT_FLAGS
# SET RGBA MASKS BASED ON BYTE_ORDER
is_big_endian = sdl.SDL_BYTEORDER == sdl.SDL_BIG_ENDIAN
if is_big_endian:
self.RGBA_MASKS = self.BYTE_ORDER["BIG_ENDIAN"]
else:
self.RGBA_MASKS = self.BYTE_ORDER["LIL_ENDIAN"]
# CALCULATE PIXEL PITCH
self.PIXEL_PITCH = self.PIXEL_PITCH_FACTOR * self.width
# SKIA INIT
self.skia_surface = self.__create_skia_surface()
# SDL INIT
sdl.SDL_Init(sdl.SDL_INIT_EVENTS) # INITIALIZE SDL EVENTS
self.sdl_window = self.__create_SDL_Window()
sdl_event_loop()
def __create_SDL_Window(self):
window = sdl.SDL_CreateWindow(
self.title,
self.x, self.y,
self.width, self.height,
self.flags
)
return window
def __create_skia_surface(self):
surface_blueprint = skia.ImageInfo.Make(
self.width, self.height,
ct=skia.kRGBA_8888_ColorType,
at=skia.kUnpremul_AlphaType
)
surface = skia.Surface.MakeRaster(surface_blueprint)
return surface
def __pixels_from_skia_surface(self):
image = self.skia_surface.makeImageSnapshot()
pixels = image.tobytes()
return pixels
def __transform_skia_surface_to_SDL_surface(self):
pixels = self.__pixels_from_skia_surface()
sdl_surface = sdl.SDL_CreateRGBSurfaceFrom(
pixels,
self.width, self.height,
self.PIXEL_DEPTH, self.PIXEL_PITCH,
*self.RGBA_MASKS
)
return sdl_surface
def update(self):
rect = sdl.SDL_Rect(0, 0, self.width, self.height)
window_surface = sdl.SDL_GetWindowSurface(self.sdl_window) # the SDL surface associated with the window
transformed_skia_surface = self.__transform_skia_surface_to_SDL_surface()
# Transfer skia surface to SDL window's surface
sdl.SDL_BlitSurface(
transformed_skia_surface, rect,
window_surface, rect
)
# Update window with new copied data
sdl.SDL_UpdateWindowSurface(self.sdl_window)
if __name__ == "__main__":
window = Window("Browser Test", 500, 500, flags=sdl.SDL_WINDOW_SHOWN | sdl.SDL_WINDOW_RESIZABLE)
说明:上面的代码主要做了四件事:创建一个您将在其上绘制的 skia 表面,创建一个 SDL window,将 skia 表面转换为 SDL 表面,最后将数据复制到新的创建表面到与 window 关联的 SDL 表面并更新它。如需更多解释,我建议您查看上述指南并查看 skia-python docs and SDL2's API Reference.