Skip to content

Commit 6ac3c62

Browse files
committed
1 parent 4c04147 commit 6ac3c62

File tree

1 file changed

+190
-0
lines changed

1 file changed

+190
-0
lines changed

SKIA-WX-GPU.py

+190
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
# python imports
2+
import math
3+
import ctypes
4+
# pip imports
5+
import wx
6+
from wx import glcanvas
7+
import skia
8+
import moderngl
9+
10+
GL_RGBA8 = 0x8058
11+
12+
13+
"""Enable high-res displays."""
14+
try:
15+
ctypes.windll.shcore.SetProcessDpiAwareness(1)
16+
ctypes.windll.shcore.SetProcessDpiAwareness(2)
17+
except Exception:
18+
pass # fail on non-windows
19+
20+
21+
class SkiaWxGPUCanvas(glcanvas.GLCanvas):
22+
"""A skia based GlCanvas"""
23+
24+
def __init__(self, parent, size):
25+
glcanvas.GLCanvas.__init__(self, parent, -1, size=size)
26+
self.glctx = glcanvas.GLContext(self) # ✅ Correct GLContext
27+
self.size = size
28+
self.ctx = None
29+
self.canvas = None
30+
self.surface = None
31+
self.is_dragging = False
32+
self.last_mouse_pos = (0.0, 0.0)
33+
self.offset_x = 0.0
34+
self.offset_y = 0.0
35+
self.zoom = 1.0
36+
37+
self.Bind(wx.EVT_LEFT_DOWN, self.on_mouse_left_down)
38+
self.Bind(wx.EVT_LEFT_UP, self.on_mouse_left_up)
39+
self.Bind(wx.EVT_MOTION, self.on_mouse_move)
40+
self.Bind(wx.EVT_MOUSEWHEEL, self.on_mouse_wheel)
41+
self.Bind(wx.EVT_PAINT, self.on_paint)
42+
self.Bind(wx.EVT_SIZE, self.on_size)
43+
# Do nothing, to avoid flashing on MSW.
44+
self.Bind(wx.EVT_ERASE_BACKGROUND, lambda x: None)
45+
46+
def init_gl(self):
47+
"""Initialize Skia GPU context and surface."""
48+
if self.ctx is None:
49+
self.ctx = moderngl.create_context()
50+
context = skia.GrDirectContext.MakeGL()
51+
backend_render_target = skia.GrBackendRenderTarget(
52+
self.size[0], self.size[1], 0, 0, skia.GrGLFramebufferInfo(
53+
0, GL_RGBA8)
54+
)
55+
self.surface = skia.Surface.MakeFromBackendRenderTarget(
56+
context, backend_render_target, skia.kBottomLeft_GrSurfaceOrigin,
57+
skia.kRGBA_8888_ColorType, skia.ColorSpace.MakeSRGB()
58+
)
59+
self.canvas = self.surface.getCanvas()
60+
61+
def on_paint(self, event):
62+
"""Handle drawing."""
63+
64+
self.SetCurrent(self.glctx)
65+
66+
if self.canvas is None:
67+
self.init_gl()
68+
69+
# This is your actual skia based drawing function
70+
self.on_draw()
71+
72+
self.SwapBuffers()
73+
74+
def on_mouse_left_down(self, event):
75+
self.is_dragging = True
76+
self.last_mouse_pos = (event.GetX(), event.GetY())
77+
event.Skip()
78+
79+
def on_mouse_left_up(self, event):
80+
self.is_dragging = False
81+
event.Skip()
82+
83+
def on_mouse_move(self, event):
84+
if self.is_dragging:
85+
xpos, ypos = event.GetX(), event.GetY()
86+
lx, ly = self.last_mouse_pos
87+
dx, dy = xpos - lx, ypos - ly
88+
self.offset_x += dx / self.zoom
89+
self.offset_y += dy / self.zoom
90+
self.last_mouse_pos = (xpos, ypos)
91+
self.Refresh()
92+
event.Skip()
93+
94+
def on_mouse_wheel(self, event):
95+
"""Implement zoom to point"""
96+
x, y = event.GetX(), event.GetY()
97+
rotation = event.GetWheelRotation()
98+
99+
if rotation > 1:
100+
zoom_factor = 1.1
101+
elif rotation < -1:
102+
zoom_factor = 0.9
103+
else:
104+
return
105+
106+
old_zoom = self.zoom
107+
new_zoom = self.zoom * zoom_factor
108+
109+
# Convert screen coords to centered canvas coords
110+
cx, cy = self.size[0] / 2, self.size[1] / 2
111+
dx = x - cx
112+
dy = y - cy
113+
114+
# World coords under mouse before zoom
115+
world_x = (dx / old_zoom) - self.offset_x
116+
world_y = (dy / old_zoom) - self.offset_y
117+
118+
# Apply zoom
119+
self.zoom = new_zoom
120+
121+
# Adjust offset so world point stays under cursor
122+
self.offset_x = (dx / self.zoom) - world_x
123+
self.offset_y = (dy / self.zoom) - world_y
124+
125+
self.Refresh()
126+
127+
def on_draw(self):
128+
"""Draw on Skia canvas."""
129+
w, h = self.GetSize()
130+
if w == 0 or h == 0:
131+
return
132+
self.ctx.viewport = (0, 0, self.size.width, self.size.height)
133+
134+
self.canvas.clear(skia.ColorWHITE)
135+
self.canvas.save()
136+
137+
# Set up the translation and zoom (pan/zoom)
138+
self.canvas.translate(w / 2, h / 2)
139+
self.canvas.scale(self.zoom, self.zoom)
140+
self.canvas.translate(self.offset_x, self.offset_y)
141+
142+
# Create a solid paint (Blue color, but explicitly defining RGBA)
143+
paint = skia.Paint(AntiAlias=True, Color=skia.Color(255, 0, 0),)
144+
145+
# Draw a series of circles in a spiral pattern
146+
for i in range(150):
147+
angle = i * math.pi * 0.1
148+
x = math.cos(angle) * i * 3
149+
y = math.sin(angle) * i * 3
150+
# Draw solid circles
151+
self.canvas.drawCircle(x, y, 4 + (i % 4), paint)
152+
153+
self.canvas.restore()
154+
self.surface.flushAndSubmit()
155+
156+
def on_size(self, event):
157+
"""Handle resizing of the canvas."""
158+
wx.CallAfter(self.set_viewport)
159+
event.Skip()
160+
161+
def set_viewport(self):
162+
# Actual drawing area (without borders) is GetClientSize
163+
size = self.GetClientSize()
164+
# On HiDPI screens, this could be 1.25, 1.5, 2.0, etc.
165+
scale = self.GetContentScaleFactor()
166+
width = int(size.width * scale)
167+
height = int(size.height * scale)
168+
self.size = wx.Size(width, height)
169+
if self.ctx:
170+
self.SetCurrent(self.glctx)
171+
self.ctx.viewport = (0, 0, width, height)
172+
self.init_gl()
173+
174+
175+
class MainFrame(wx.Frame):
176+
def __init__(self):
177+
super().__init__(None, title="Skia WxPython GPU Canvas", size=(800, 600))
178+
panel = wx.Panel(self)
179+
sizer = wx.BoxSizer(wx.VERTICAL)
180+
self.canvas = SkiaWxGPUCanvas(panel, (800, 600))
181+
sizer.Add(self.canvas, 1, wx.EXPAND)
182+
panel.SetSizer(sizer)
183+
self.Show()
184+
185+
186+
if __name__ == "__main__":
187+
app = wx.App(False)
188+
frame = MainFrame()
189+
frame.Show()
190+
app.MainLoop()

0 commit comments

Comments
 (0)