Skip to content

Commit 4f78cb6

Browse files
authored
Gtk3 copilot-generated GPT-4.1
Copilot Copilot navigation Copilot Chat New conversation You said: Can you port https://github.com/HinTak/skia-python-examples/blob/main/SkiaSDLExample.py to pygtk3 please? Can you port https://github.com/HinTak/skia-python-examples/blob/main/SkiaSDLExample.py to pygtk3 please? 2/2 1 reference Copilot said: Here is a plan for porting `SkiaSDLExample.py` Here is a plan for porting SkiaSDLExample.py to use PyGTK3 (with Gtk+3 and Cairo) instead of SDL2: Key Considerations: SDL2 handles window creation, events, and OpenGL context. In PyGTK3, Gtk.Window and Gtk.DrawingArea handle windowing and drawing, and Gdk/Cairo is used for drawing (not OpenGL). Skia can render to a Cairo surface but the integration is less direct than OpenGL. Event handling is via Gtk signals (button-press-event, button-release-event, motion-notify-event, key-press-event, etc). The application logic (drawing rectangles, star, etc.) can mostly be preserved. Implementation Plan: Create a Gtk.Window with a Gtk.DrawingArea. Use Cairo for drawing. If Skia-Python supports rendering into a Cairo surface, use it; otherwise, port drawing logic to Cairo. Handle mouse and keyboard events using Gtk signals. Maintain application state similar to ApplicationState. Redraw on expose and input events. Here is the ported code: SkiaGTKExample.py #!/usr/bin/env python3 # -*- coding: utf-8 -*- # # Skia + GTK3 (PyGObject) example ported from SkiaSDLExample.py # # Copyright 2024 Hin-Tak Leung Notes: This version ports the drawing and interaction logic to Gtk/Cairo, which is the standard for PyGTK3. If you want to use Skia drawing on a Cairo surface, check if skia-python supports creating a Skia surface from a Cairo context (Surface.MakeFromCairo(cairo_context)). If so, you can adapt the drawing logic accordingly. The star is redrawn and rotated on each frame using a timer via repeated calls to queue_draw(). Rectangle drawing uses mouse drag. Pressing ESC quits. You may need python3-gi, PyGObject, and skia-python (optional) installed. Let me know if you want the Skia-specific Cairo integration as well, or if you want to keep the entire drawing logic in Skia! You said: I want to keep the entire drawing I want to keep the entire drawing logic in skia, and uses opengl Copilot said: Great! To port the SDL2+Skia+OpenGL example to Great! To port the SDL2+Skia+OpenGL example to PyGTK3 but keep all Skia drawing and OpenGL usage, the main idea is: Use PyGObject (PyGTK3) to create a window and an OpenGL-capable drawing area (Gtk.GLArea). Use PyOpenGL (for context management if needed). Use Skia’s OpenGL backend to render into the OpenGL context provided by Gtk.GLArea. Handle mouse, key, and window events via GTK signals, similar to SDL’s event loop. Call Skia drawing routines inside the Gtk.GLArea’s render callback. Manage resizing and redraw via GTK signals, not polling. Below is a direct port sketch, preserving the original Skia logic but using GTK’s GLArea for context and event handling. Some OpenGL context setup is handled by GTK, so you can skip the SDL-specific context setup. SkiaGTKGLExample.py #!/usr/bin/env python3 # -*- coding: utf-8 -*- import gi gi.require_version("Gtk", "3.0") from gi.repository import Gtk, Gdk, GObject, GLib Key Points: This uses Gtk.GLArea for an OpenGL context and delivers OpenGL events to Skia, just like SDL2 did. All drawing is handled by Skia, via its OpenGL backend. Mouse, motion, and key events are handled using GTK signals mapped to Skia logic. Rotating star and rectangle drawing logic is preserved from your original. This is the closest GTK/OpenGL/Skia analog to your original SDL2 code. Dependencies: PyGObject (python3-gi) PyOpenGL skia-python (must be built/installed with OpenGL support) GTK3 development libraries at build time Let me know if you want further tweaks or explanations!
1 parent 08b0b49 commit 4f78cb6

File tree

2 files changed

+321
-0
lines changed

2 files changed

+321
-0
lines changed

SkiaGTKExample.py

+154
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
#
4+
# Skia + GTK3 (PyGObject) example ported from SkiaSDLExample.py
5+
#
6+
# Copyright 2024 Hin-Tak Leung
7+
#
8+
# Ported to PyGTK3 by Copilot
9+
10+
import gi
11+
gi.require_version("Gtk", "3.0")
12+
from gi.repository import Gtk, Gdk
13+
import cairo
14+
from collections import namedtuple
15+
import math
16+
import sys
17+
18+
try:
19+
from skia import ColorWHITE, ColorBLACK, Surface, Paint, Font, Rect, Path, Point
20+
HAS_SKIA = True
21+
except ImportError:
22+
HAS_SKIA = False
23+
24+
RectState = namedtuple('RectState', ['left', 'top', 'right', 'bottom'])
25+
26+
class ApplicationState:
27+
def __init__(self, width=800, height=600):
28+
self.fQuit = False
29+
self.window_width = width
30+
self.window_height = height
31+
self.fRects = []
32+
self.dragging = False
33+
self.current_rect = None
34+
self.rotation = 0
35+
36+
def create_star_path(center_x, center_y, radius):
37+
# Create a 5-pointed star path in Cairo (or Skia)
38+
path = []
39+
num_points = 5
40+
for i in range(num_points * 2):
41+
angle = i * math.pi / num_points
42+
r = radius if i % 2 == 0 else radius * 0.5
43+
x = center_x + r * math.sin(angle)
44+
y = center_y - r * math.cos(angle)
45+
path.append((x, y))
46+
return path
47+
48+
class SkiaGTKExample(Gtk.Window):
49+
def __init__(self):
50+
super().__init__(title="Skia GTK Example")
51+
self.set_default_size(800, 600)
52+
self.state = ApplicationState(800, 600)
53+
54+
self.drawing_area = Gtk.DrawingArea()
55+
self.add(self.drawing_area)
56+
57+
# Connect signals
58+
self.connect("destroy", Gtk.main_quit)
59+
self.drawing_area.connect("draw", self.on_draw)
60+
self.drawing_area.add_events(Gdk.EventMask.BUTTON_PRESS_MASK |
61+
Gdk.EventMask.BUTTON_RELEASE_MASK |
62+
Gdk.EventMask.POINTER_MOTION_MASK |
63+
Gdk.EventMask.KEY_PRESS_MASK |
64+
Gdk.EventMask.STRUCTURE_MASK)
65+
self.drawing_area.set_can_focus(True)
66+
self.drawing_area.grab_focus()
67+
self.drawing_area.connect("button-press-event", self.on_button_press)
68+
self.drawing_area.connect("button-release-event", self.on_button_release)
69+
self.drawing_area.connect("motion-notify-event", self.on_motion_notify)
70+
self.connect("key-press-event", self.on_key_press)
71+
self.drawing_area.connect("configure-event", self.on_configure)
72+
73+
def on_draw(self, widget, cr):
74+
width = widget.get_allocated_width()
75+
height = widget.get_allocated_height()
76+
self.state.window_width = width
77+
self.state.window_height = height
78+
79+
# White background
80+
cr.set_source_rgb(1, 1, 1)
81+
cr.rectangle(0, 0, width, height)
82+
cr.fill()
83+
84+
# Draw help message
85+
cr.set_source_rgb(0, 0, 0)
86+
cr.select_font_face("Sans", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
87+
cr.set_font_size(18)
88+
cr.move_to(10, 24)
89+
cr.show_text("Click and drag to create rects. Press esc to quit.")
90+
91+
# Draw random-colored rectangles
92+
import random
93+
random.seed(0)
94+
for rect in self.state.fRects:
95+
color = ((random.randint(0,255)/255, random.randint(0,255)/255, random.randint(0,255)/255, 0.27))
96+
cr.set_source_rgba(*color)
97+
l, t, r, b = rect.left, rect.top, rect.right, rect.bottom
98+
cr.rectangle(l, t, r-l, b-t)
99+
cr.fill()
100+
101+
# Draw rotating star in the center
102+
cr.save()
103+
cx = width / 2
104+
cy = height / 2
105+
cr.translate(cx, cy)
106+
cr.rotate(math.radians(self.state.rotation))
107+
self.state.rotation = (self.state.rotation + 1) % 360
108+
cr.set_source_rgb(0, 0, 0)
109+
star_path = create_star_path(0, 0, 50)
110+
cr.move_to(*star_path[0])
111+
for pt in star_path[1:]:
112+
cr.line_to(*pt)
113+
cr.close_path()
114+
cr.stroke()
115+
cr.restore()
116+
117+
# Schedule next redraw for animation
118+
self.drawing_area.queue_draw()
119+
120+
def on_button_press(self, widget, event):
121+
if event.button == 1:
122+
self.state.dragging = True
123+
self.state.current_rect = RectState(event.x, event.y, event.x, event.y)
124+
125+
def on_motion_notify(self, widget, event):
126+
if self.state.dragging and self.state.current_rect:
127+
self.state.current_rect = self.state.current_rect._replace(right=event.x, bottom=event.y)
128+
self.queue_draw()
129+
130+
def on_button_release(self, widget, event):
131+
if event.button == 1 and self.state.dragging:
132+
rect = self.state.current_rect
133+
# Normalize rectangle
134+
left, right = sorted([rect.left, rect.right])
135+
top, bottom = sorted([rect.top, rect.bottom])
136+
self.state.fRects.append(RectState(left, top, right, bottom))
137+
self.state.dragging = False
138+
self.state.current_rect = None
139+
self.queue_draw()
140+
141+
def on_key_press(self, widget, event):
142+
if event.keyval == Gdk.KEY_Escape:
143+
Gtk.main_quit()
144+
145+
def on_configure(self, widget, event):
146+
self.queue_draw()
147+
148+
def queue_draw(self):
149+
self.drawing_area.queue_draw()
150+
151+
if __name__ == "__main__":
152+
win = SkiaGTKExample()
153+
win.show_all()
154+
Gtk.main()

SkiaGTKGLExample.py

+167
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
4+
import gi
5+
gi.require_version("Gtk", "3.0")
6+
from gi.repository import Gtk, Gdk, GObject, GLib
7+
from OpenGL.GL import *
8+
import sys
9+
10+
from skia import *
11+
12+
class ApplicationState:
13+
def __init__(self, width, height):
14+
self.fQuit = False
15+
self.window_width = width
16+
self.window_height = height
17+
self.fRects = []
18+
19+
def create_star():
20+
kNumPoints = 5
21+
concavePath = Path()
22+
points = [Point(0, -50)]
23+
rot = Matrix()
24+
rot.setRotate(360.0 / kNumPoints)
25+
for i in range(kNumPoints - 1): # skip 0
26+
points.append(rot.mapPoints(points[i:i+1])[0])
27+
concavePath.moveTo(points[0])
28+
for i in range(kNumPoints):
29+
concavePath.lineTo(points[(2 * i) % kNumPoints])
30+
concavePath.setFillType(PathFillType.kEvenOdd)
31+
assert not concavePath.isConvex()
32+
concavePath.close()
33+
return concavePath
34+
35+
class SkiaGLArea(Gtk.GLArea):
36+
def __init__(self):
37+
super().__init__()
38+
self.set_has_depth_buffer(False)
39+
self.set_has_stencil_buffer(True)
40+
self.set_auto_render(True)
41+
self.set_required_version(3, 0)
42+
self.connect("realize", self.on_realize)
43+
self.connect("render", self.on_render)
44+
self.connect("resize", self.on_resize)
45+
self.add_events(Gdk.EventMask.BUTTON_PRESS_MASK |
46+
Gdk.EventMask.BUTTON_RELEASE_MASK |
47+
Gdk.EventMask.POINTER_MOTION_MASK |
48+
Gdk.EventMask.KEY_PRESS_MASK)
49+
self.set_can_focus(True)
50+
self.grab_focus()
51+
self.state = ApplicationState(800, 600)
52+
self.rotation = 0
53+
self.star_image = None
54+
self.helpMessage = "Click and drag to create rects. Press esc to quit."
55+
self.drag_rect = None
56+
57+
def on_realize(self, area):
58+
self.make_current()
59+
glClearColor(1, 1, 1, 1)
60+
glClearStencil(0)
61+
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
62+
self.grContext = GrDirectContext.MakeGL()
63+
self.star_image = self.make_offscreen_star()
64+
GLib.timeout_add(16, self.queue_draw) # ~60fps
65+
66+
def on_resize(self, area, width, height):
67+
self.state.window_width = width
68+
self.state.window_height = height
69+
70+
def make_offscreen_star(self):
71+
# Draw star to an image so we can rotate/translate it efficiently
72+
surface = Surface.MakeRasterN32Premul(100, 100)
73+
canvas = surface.getCanvas()
74+
canvas.clear(ColorWHITE)
75+
paint = Paint()
76+
canvas.save()
77+
canvas.translate(50, 50)
78+
canvas.drawPath(create_star(), paint)
79+
canvas.restore()
80+
return surface.makeImageSnapshot()
81+
82+
def on_render(self, area, glctx):
83+
width = self.get_allocated_width()
84+
height = self.get_allocated_height()
85+
glViewport(0, 0, width, height)
86+
glClearColor(1, 1, 1, 1)
87+
glClearStencil(0)
88+
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
89+
90+
# Setup Skia backend target for this frame
91+
fb = glGetIntegerv(GL_FRAMEBUFFER_BINDING)
92+
info = GrGLFramebufferInfo(fb, GL_RGBA8)
93+
kStencilBits = 8
94+
kMsaaSampleCount = 0
95+
target = GrBackendRenderTarget(width, height, kMsaaSampleCount, kStencilBits, info)
96+
surface = Surface.MakeFromBackendRenderTarget(
97+
self.grContext, target, kBottomLeft_GrSurfaceOrigin, kRGBA_8888_ColorType, None, None
98+
)
99+
canvas = surface.getCanvas()
100+
canvas.clear(ColorWHITE)
101+
font = Font()
102+
paint = Paint()
103+
104+
# Draw help message in the top left
105+
paint.setColor(ColorBLACK)
106+
canvas.drawString(self.helpMessage, 0, font.getSize(), font, paint)
107+
108+
# Draw rectangles
109+
import random
110+
random.seed(0)
111+
for rect in self.state.fRects:
112+
paint.setColor(random.randint(0, 0xFFFFFFFF) | 0x44808080)
113+
canvas.drawRect(rect, paint)
114+
if self.drag_rect:
115+
paint.setColor(0x44808080)
116+
canvas.drawRect(self.drag_rect, paint)
117+
118+
# Draw rotating star in the center
119+
canvas.save()
120+
canvas.translate(width / 2.0, height / 2.0)
121+
canvas.rotate(self.rotation)
122+
self.rotation = (self.rotation + 1) % 360
123+
canvas.drawImage(self.star_image, -50.0, -50.0)
124+
canvas.restore()
125+
126+
canvas.flush()
127+
self.grContext.flush()
128+
return True
129+
130+
def do_button_press_event(self, event):
131+
if event.button == 1:
132+
self.drag_start = (event.x, event.y)
133+
self.drag_rect = Rect.MakeLTRB(event.x, event.y, event.x, event.y)
134+
self.queue_draw()
135+
136+
def do_motion_notify_event(self, event):
137+
if self.drag_rect:
138+
x0, y0 = self.drag_start
139+
self.drag_rect = Rect.MakeLTRB(x0, y0, event.x, event.y)
140+
self.queue_draw()
141+
142+
def do_button_release_event(self, event):
143+
if event.button == 1 and self.drag_rect:
144+
self.state.fRects.append(self.drag_rect)
145+
self.drag_rect = None
146+
self.queue_draw()
147+
148+
def do_key_press_event(self, event):
149+
if event.keyval == Gdk.KEY_Escape:
150+
Gtk.main_quit()
151+
152+
def queue_draw(self, *args):
153+
super().queue_draw()
154+
return True # GLib.timeout_add expects a bool
155+
156+
class SkiaGTKGLWindow(Gtk.Window):
157+
def __init__(self):
158+
super().__init__(title="Skia + GTK3 + OpenGL")
159+
self.set_default_size(800, 600)
160+
self.area = SkiaGLArea()
161+
self.add(self.area)
162+
self.connect("destroy", Gtk.main_quit)
163+
164+
if __name__ == "__main__":
165+
win = SkiaGTKGLWindow()
166+
win.show_all()
167+
Gtk.main()

0 commit comments

Comments
 (0)